Browse Source

:technologist: Add a kernel API `/api/filetree/moveDocsByID` https://github.com/siyuan-note/siyuan/issues/13247

Daniel 7 months ago
parent
commit
f21f0ea60b
4 changed files with 145 additions and 32 deletions
  1. 55 20
      API.md
  2. 34 10
      API_zh_CN.md
  3. 55 2
      kernel/api/filetree.go
  4. 1 0
      kernel/api/router.go

+ 55 - 20
API.md

@@ -73,7 +73,8 @@
 
 
 * Endpoint: `http://127.0.0.1:6806`
 * Endpoint: `http://127.0.0.1:6806`
 * Both are POST methods
 * Both are POST methods
-* An interface with parameters is required, the parameter is a JSON string, placed in the body, and the header Content-Type is `application/json`
+* An interface with parameters is required, the parameter is a JSON string, placed in the body, and the header
+  Content-Type is `application/json`
 * Return value
 * Return value
 
 
    ````json
    ````json
@@ -328,7 +329,8 @@ View API token in <kbd>Settings - About</kbd>, request header: `Authorization: T
   ```
   ```
 
 
     * `notebook`: Notebook ID
     * `notebook`: Notebook ID
-    * `path`: Document path, which needs to start with / and separate levels with / (path here corresponds to the database hpath field)
+    * `path`: Document path, which needs to start with / and separate levels with / (path here corresponds to the
+      database hpath field)
     * `markdown`: GFM Markdown content
     * `markdown`: GFM Markdown content
 * Return value
 * Return value
 
 
@@ -381,8 +383,8 @@ Rename a document by `id`:
   }
   }
   ```
   ```
 
 
-  * `id`: Document ID
-  * `title`: New document title
+    * `id`: Document ID
+    * `title`: New document title
 * Return value
 * Return value
 
 
   ```json
   ```json
@@ -416,7 +418,7 @@ Rename a document by `id`:
     "data": null
     "data": null
   }
   }
   ```
   ```
-  
+
 Remove a document by `id`:
 Remove a document by `id`:
 
 
 * `/api/filetree/removeDocByID`
 * `/api/filetree/removeDocByID`
@@ -428,7 +430,7 @@ Remove a document by `id`:
   }
   }
   ```
   ```
 
 
-  * `id`: Document ID
+    * `id`: Document ID
 * Return value
 * Return value
 
 
   ```json
   ```json
@@ -465,6 +467,30 @@ Remove a document by `id`:
   }
   }
   ```
   ```
 
 
+Move documents by `id`:
+
+* `/api/filetree/moveDocsByID`
+* Parameters
+
+  ```json
+  {
+    "fromIDs": ["20210917220056-yxtyl7i"],
+    "toID": "20210817205410-2kvfpfn"
+  }
+  ```
+
+    * `fromIDs`: Source docs' IDs
+    * `toID`: Target parent ID
+* Return value
+
+  ```json
+  {
+    "code": 0,
+    "msg": "",
+    "data": null
+  }
+  ```
+
 ### Get human-readable path based on path
 ### Get human-readable path based on path
 
 
 * `/api/filetree/getHPathByPath`
 * `/api/filetree/getHPathByPath`
@@ -510,7 +536,7 @@ Remove a document by `id`:
     "data": "/foo/bar"
     "data": "/foo/bar"
   }
   }
   ```
   ```
-  
+
 ### Get storage path based on ID
 ### Get storage path based on ID
 
 
 * `/api/filetree/getPathByID`
 * `/api/filetree/getPathByID`
@@ -522,7 +548,7 @@ Remove a document by `id`:
   }
   }
   ```
   ```
 
 
-  * `id`: Block ID
+    * `id`: Block ID
 * Return value
 * Return value
 
 
   ```json
   ```json
@@ -545,8 +571,8 @@ Remove a document by `id`:
   }
   }
   ```
   ```
 
 
-  * `path`: Human-readable path
-  * `notebook`: Notebook ID
+    * `path`: Human-readable path
+    * `notebook`: Notebook ID
 * Return value
 * Return value
 
 
   ```json
   ```json
@@ -570,7 +596,8 @@ Remove a document by `id`:
         * `"/assets/"`: workspace/data/assets/ folder
         * `"/assets/"`: workspace/data/assets/ folder
         * `"/assets/sub/"`: workspace/data/assets/sub/ folder
         * `"/assets/sub/"`: workspace/data/assets/sub/ folder
 
 
-      Under normal circumstances, it is recommended to use the first method, which is stored in the assets folder of the workspace, putting in a subdirectory has some side effects, please refer to the assets chapter of the user guide.
+      Under normal circumstances, it is recommended to use the first method, which is stored in the assets folder of the
+      workspace, putting in a subdirectory has some side effects, please refer to the assets chapter of the user guide.
     * `file[]`: Uploaded file list
     * `file[]`: Uploaded file list
 * Return value
 * Return value
 
 
@@ -588,7 +615,9 @@ Remove a document by `id`:
   ```
   ```
 
 
     * `errFiles`: List of filenames with errors in upload processing
     * `errFiles`: List of filenames with errors in upload processing
-    * `succMap`: For successfully processed files, the key is the file name when uploading, and the value is assets/foo-id.png, which is used to replace the asset link address in the existing Markdown content with the uploaded address
+    * `succMap`: For successfully processed files, the key is the file name when uploading, and the value is
+      assets/foo-id.png, which is used to replace the asset link address in the existing Markdown content with the
+      uploaded address
 
 
 ## Blocks
 ## Blocks
 
 
@@ -613,7 +642,8 @@ Remove a document by `id`:
     * `previousID`: The ID of the previous block, used to anchor the insertion position
     * `previousID`: The ID of the previous block, used to anchor the insertion position
     * `parentID`: The ID of the parent block, used to anchor the insertion position
     * `parentID`: The ID of the parent block, used to anchor the insertion position
 
 
-  `nextID`, `previousID`, and `parentID` must have at least one value, using priority: `nextID` > `previousID` > `parentID`
+  `nextID`, `previousID`, and `parentID` must have at least one value, using priority: `nextID` > `previousID` >
+  `parentID`
 * Return value
 * Return value
 
 
   ```json
   ```json
@@ -820,7 +850,8 @@ Remove a document by `id`:
 
 
     * `id`: Block ID to move
     * `id`: Block ID to move
     * `previousID`: The ID of the previous block, used to anchor the insertion position
     * `previousID`: The ID of the previous block, used to anchor the insertion position
-    * `parentID`: The ID of the parent block, used to anchor the insertion position, `previousID` and `parentID` cannot be empty at the same time, if they exist at the same time, `previousID` will be used first
+    * `parentID`: The ID of the parent block, used to anchor the insertion position, `previousID` and `parentID` cannot
+      be empty at the same time, if they exist at the same time, `previousID` will be used first
 * Return value
 * Return value
 
 
   ```json
   ```json
@@ -860,7 +891,7 @@ Remove a document by `id`:
   }
   }
   ```
   ```
 
 
-  * `id`: Block ID to fold
+    * `id`: Block ID to fold
 * Return value
 * Return value
 
 
   ```json
   ```json
@@ -882,7 +913,7 @@ Remove a document by `id`:
   }
   }
   ```
   ```
 
 
-  * `id`: Block ID to unfold
+    * `id`: Block ID to unfold
 * Return value
 * Return value
 
 
   ```json
   ```json
@@ -1380,7 +1411,8 @@ Remove a document by `id`:
     "timeout": 7000
     "timeout": 7000
   }
   }
   ```
   ```
-    * `timeout`: The duration of the message display in milliseconds. This field can be omitted, the default is 7000 milliseconds
+    * `timeout`: The duration of the message display in milliseconds. This field can be omitted, the default is 7000
+      milliseconds
 * Return value
 * Return value
 
 
   ```json
   ```json
@@ -1405,7 +1437,8 @@ Remove a document by `id`:
     "timeout": 7000
     "timeout": 7000
   }
   }
   ```
   ```
-    * `timeout`: The duration of the message display in milliseconds. This field can be omitted, the default is 7000 milliseconds
+    * `timeout`: The duration of the message display in milliseconds. This field can be omitted, the default is 7000
+      milliseconds
 * Return value
 * Return value
 
 
   ```json
   ```json
@@ -1457,7 +1490,8 @@ Remove a document by `id`:
         * `base32` | `base32-std`
         * `base32` | `base32-std`
         * `base32-hex`
         * `base32-hex`
         * `hex`
         * `hex`
-    * `responseEncoding`: The encoding scheme used by `body` in response data, default is `text`, optional values are as follows
+    * `responseEncoding`: The encoding scheme used by `body` in response data, default is `text`, optional values are as
+      follows
 
 
         * `text`
         * `text`
         * `base64` | `base64-std`
         * `base64` | `base64-std`
@@ -1484,7 +1518,8 @@ Remove a document by `id`:
   }
   }
   ```
   ```
 
 
-    * `bodyEncoding`: The encoding scheme used by `body`, is consistent with field `responseEncoding` in request, default is `text`, optional values are as follows
+    * `bodyEncoding`: The encoding scheme used by `body`, is consistent with field `responseEncoding` in request,
+      default is `text`, optional values are as follows
 
 
         * `text`
         * `text`
         * `base64` | `base64-std`
         * `base64` | `base64-std`

+ 34 - 10
API_zh_CN.md

@@ -381,8 +381,8 @@
   }
   }
   ```
   ```
 
 
-  * `id`:文档 ID
-  * `title`:新标题
+    * `id`:文档 ID
+    * `title`:新标题
 * 返回值
 * 返回值
 
 
   ```json
   ```json
@@ -416,7 +416,7 @@
     "data": null
     "data": null
   }
   }
   ```
   ```
-  
+
 通过 `id` 删除文档:
 通过 `id` 删除文档:
 
 
 * `/api/filetree/removeDocByID`
 * `/api/filetree/removeDocByID`
@@ -428,7 +428,7 @@
   }
   }
   ```
   ```
 
 
-  * `id`:文档 ID
+    * `id`:文档 ID
 * 返回值
 * 返回值
 
 
   ```json
   ```json
@@ -465,6 +465,30 @@
   }
   }
   ```
   ```
 
 
+通过 `id` 移动文档:
+
+* `/api/filetree/moveDocsByID`
+* 参数
+
+  ```json
+  {
+    "fromIDs": ["20210917220056-yxtyl7i"],
+    "toID": "20210817205410-2kvfpfn"
+  }
+  ```
+
+    * `fromIDs`:源文档 ID
+    * `toID`:目标父文档 ID
+* 返回值
+
+  ```json
+  {
+    "code": 0,
+    "msg": "",
+    "data": null
+  }
+  ```
+
 ### 根据路径获取人类可读路径
 ### 根据路径获取人类可读路径
 
 
 * `/api/filetree/getHPathByPath`
 * `/api/filetree/getHPathByPath`
@@ -522,7 +546,7 @@
   }
   }
   ```
   ```
 
 
-  * `id`:块 ID
+    * `id`:块 ID
 * 返回值
 * 返回值
 
 
   ```json
   ```json
@@ -545,8 +569,8 @@
   }
   }
   ```
   ```
 
 
-  * `path`:人类可读路径
-  * `notebook`:笔记本 ID
+    * `path`:人类可读路径
+    * `notebook`:笔记本 ID
 * 返回值
 * 返回值
 
 
   ```json
   ```json
@@ -860,7 +884,7 @@
   }
   }
   ```
   ```
 
 
-  * `id`:待折叠块的 ID
+    * `id`:待折叠块的 ID
 * 返回值
 * 返回值
 
 
   ```json
   ```json
@@ -882,7 +906,7 @@
   }
   }
   ```
   ```
 
 
-  * `id`:待展开块的 ID
+    * `id`:待展开块的 ID
 * 返回值
 * 返回值
 
 
   ```json
   ```json
@@ -1063,7 +1087,7 @@
     ]
     ]
   }
   }
   ```
   ```
-  
+
 ### 提交事务
 ### 提交事务
 
 
 * `/api/sqlite/flushTransaction`
 * `/api/sqlite/flushTransaction`

+ 55 - 2
kernel/api/filetree.go

@@ -472,9 +472,7 @@ func moveDocs(c *gin.Context) {
 	if util.InvalidIDPattern(toNotebook, ret) {
 	if util.InvalidIDPattern(toNotebook, ret) {
 		return
 		return
 	}
 	}
-
 	callback := arg["callback"]
 	callback := arg["callback"]
-
 	err := model.MoveDocs(fromPaths, toNotebook, toPath, callback)
 	err := model.MoveDocs(fromPaths, toNotebook, toPath, callback)
 	if err != nil {
 	if err != nil {
 		ret.Code = -1
 		ret.Code = -1
@@ -484,6 +482,61 @@ func moveDocs(c *gin.Context) {
 	}
 	}
 }
 }
 
 
+func moveDocsByID(c *gin.Context) {
+	ret := gulu.Ret.NewResult()
+	defer c.JSON(http.StatusOK, ret)
+
+	arg, ok := util.JsonArg(c, ret)
+	if !ok {
+		return
+	}
+
+	fromIDsArg := arg["fromIDs"].([]any)
+	var fromIDs []string
+	for _, fromIDArg := range fromIDsArg {
+		fromID := fromIDArg.(string)
+		if util.InvalidIDPattern(fromID, ret) {
+			return
+		}
+		fromIDs = append(fromIDs, fromID)
+	}
+	toID := arg["toID"].(string)
+	if util.InvalidIDPattern(toID, ret) {
+		return
+	}
+
+	var fromPaths []string
+	for _, fromID := range fromIDs {
+		tree, err := model.LoadTreeByBlockID(fromID)
+		if err != nil {
+			ret.Code = -1
+			ret.Msg = err.Error()
+			ret.Data = map[string]interface{}{"closeTimeout": 7000}
+			return
+		}
+		fromPaths = append(fromPaths, tree.Path)
+	}
+	fromPaths = gulu.Str.RemoveDuplicatedElem(fromPaths)
+
+	toTree, err := model.LoadTreeByBlockID(toID)
+	if err != nil {
+		ret.Code = -1
+		ret.Msg = err.Error()
+		ret.Data = map[string]interface{}{"closeTimeout": 7000}
+		return
+	}
+	toNotebook := toTree.Box
+	toPath := toTree.Path
+	callback := arg["callback"]
+	err = model.MoveDocs(fromPaths, toNotebook, toPath, callback)
+	if err != nil {
+		ret.Code = -1
+		ret.Msg = err.Error()
+		ret.Data = map[string]interface{}{"closeTimeout": 7000}
+		return
+	}
+}
+
 func removeDoc(c *gin.Context) {
 func removeDoc(c *gin.Context) {
 	ret := gulu.Ret.NewResult()
 	ret := gulu.Ret.NewResult()
 	defer c.JSON(http.StatusOK, ret)
 	defer c.JSON(http.StatusOK, ret)

+ 1 - 0
kernel/api/router.go

@@ -109,6 +109,7 @@ func ServeAPI(ginServer *gin.Engine) {
 	ginServer.Handle("POST", "/api/filetree/removeDocByID", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, removeDocByID)
 	ginServer.Handle("POST", "/api/filetree/removeDocByID", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, removeDocByID)
 	ginServer.Handle("POST", "/api/filetree/removeDocs", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, removeDocs)
 	ginServer.Handle("POST", "/api/filetree/removeDocs", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, removeDocs)
 	ginServer.Handle("POST", "/api/filetree/moveDocs", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, moveDocs)
 	ginServer.Handle("POST", "/api/filetree/moveDocs", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, moveDocs)
+	ginServer.Handle("POST", "/api/filetree/moveDocsByID", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, moveDocsByID)
 	ginServer.Handle("POST", "/api/filetree/duplicateDoc", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, duplicateDoc)
 	ginServer.Handle("POST", "/api/filetree/duplicateDoc", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, duplicateDoc)
 	ginServer.Handle("POST", "/api/filetree/getHPathByPath", model.CheckAuth, getHPathByPath)
 	ginServer.Handle("POST", "/api/filetree/getHPathByPath", model.CheckAuth, getHPathByPath)
 	ginServer.Handle("POST", "/api/filetree/getHPathsByPaths", model.CheckAuth, getHPathsByPaths)
 	ginServer.Handle("POST", "/api/filetree/getHPathsByPaths", model.CheckAuth, getHPathsByPaths)