思源笔记插件丨自定义排序模式下,进行一次性自动化排序

背景

思源笔记的文档树可以选择多种排序方式,因为根据文档名进行自动排序,往往不够自由灵活,所以我一般选择自定义排序。

但是在自定义排序模式下,有时候如果想要根据文件名排序,自己一个个拖实在太麻烦了,要是能在自定义排序的基础上,加上排序功能该多好。可以先随意无序创建文档,沉静于创作,之后再根据文档名进行排序,得到有序的笔记层级,非常的自由灵活。

image

提了一个 issue,自定义排序下,给笔记本和父文档右键菜单添加排序功能 · Issue #13297 · siyuan-note/siyuan

但是迅速被否了

于是只好自己先写一个简单的插件,先用用。

插件功能

Achuan-2/siyuan-plugin-doctree-autosort: Custom Sort + Automation Sort = Ideal DocTree Sort

当文档树排序模式为自定义排序时,插件会有文档树中的父文档和笔记本右键菜单添加排序按钮,点击即可根据名称字母进行自动化排序,解放双手。
目前只支持根据名称字母进行自然排序。

自定义排序 2

开发笔记

获取当前排序规则

window.siyuan.config.fileTree.sort =6 为自定义排序

PixPin_2024-11-28_11-45-26

调用 API 的方法

async function fetchSyncPost(url, data, returnType = 'json') { const init = { method: "POST", }; if (data) { if (data instanceof FormData) { init.body = data; } else { init.body = JSON.stringify(data); } } try { const res = await fetch(url, init); const res2 = returnType === 'json' ? await res.json() : await res.text(); return res2; } catch (e) { console.log(e); return returnType === 'json' ? { code: e.code || 1, msg: e.message || "", data: null } : ""; } }

获取当前文档信息

/api/filetree/getDoc​:获取 doc 信息,发现还能获取到 DOM 内容!

let parent_doc_id ="20241128153524-e8m8fzb"; let parent_doc_info = (await fetchSyncPost("/api/filetree/getDoc", { id: parent_doc_id // 默认最大加载块数 102400 })).data; console.log(parent_doc_info)

PixPin_2024-11-28_15-13-44

/api/block/getDocInfo​:获取块信息,对于文档来说,只是基本的文档属性信息

// 查询文档信息 let parent_doc_id ="20241128153524-e8m8fzb"; let parent_doc_info = (await fetchSyncPost("/api/block/getDocInfo", { id: parent_doc_id })).data; console.log(parent_doc_info)

PixPin_2024-11-28_15-43-16

如何获取当前文档的子文档

/api/filetree/listDocTree

let query = { "notebook": parent_doc_info.box, "path": parent_doc_info.path.replace(".sy","") } let child_doc_ids = (await fetchSyncPost("/api/filetree/listDocTree", query)).data.tree; console.log(child_doc_ids);

获取得到 48 个 array,每个 array 的结构为 {id: '20220821202911-z8t1vmo', children: Array(11)}

PixPin_2024-11-28_15-28-48

根据 id 获取文档名,并进行排序

async function fetchNameById(id) { // Fetch the name for a given id const response = await fetchSyncPost("/api/filetree/getHPathByID", {id: id }); return response.data; // Assuming the API returns the name in this structure } // Fetch names for each id let idNamePairs = await Promise.all(child_doc_ids.map(async (doc) => { let name = await fetchNameById(doc.id); return { id: doc.id, name: name }; })); // Sort the pairs by name idNamePairs.sort((a, b) => a.name.localeCompare(b.name)); // Create a sorted result object let sortedResult = {}; idNamePairs.forEach((pair, index) => { sortedResult[pair.id] = index; // Assuming you want the order to start from 1 }); console.log(sortedResult)

确认能否用 api 获取 sort.json 自定义排序文件

let query = { "path" : `/data/${parent_doc_info.box}/.siyuan/sort.json` } const sort_json = await fetchSyncPost('/api/file/getFile',query)

如何把修改过的排序值写入 sort.json 文件

// 保存文件的辅助函数 async function putFile(path, json, isDir = false, modTime = Date.now()) { let file; if (typeof json === "object") { file = new File( [new Blob([JSON.stringify(json)], { type: "application/json" })], path.split("/").pop() ); } else { file = new File([new Blob([json])], path.split("/").pop()); } let formdata = new FormData(); formdata.append("path", path); formdata.append("file", file); formdata.append("isDir", isDir); formdata.append("modTime", modTime); const response = await fetch("/api/file/putFile", { body: formdata, method: "POST", headers: { Authorization: `Token `, }, }); if (response.ok) return await response.json(); else return null; } // 保存更新后的sort.json文件 await putFile(`/data/${parentDocInfo.box}/.siyuan/sort.json`, sortJson);

如何刷新文档树

/api/filetree/refreshFiletree​重建索引,太过麻烦,没有必要

✅ 点击折叠再展开即可刷新排序后的文档

在开发者工具里实现排序功能

用法:修改 parentDocId,直接粘贴到开发者工具里(对,没有写成代码片段)。

// 调用函数进行排序 const parentDocId = "20241128153524-e8m8fzb"; const result = await sortChildDocsByID(parentDocId); if (result) { console.log('排序成功'); } else { console.log('排序失败'); } async function sortChildDocsByID(parentDocId) { // 判断是否是自定义排序模式 if (window.siyuan.config.fileTree.sort != 6){ return } // 通用的fetch函数 async function fetchSyncPost(url, data, returnType = 'json') { const init = { method: "POST", }; if (data) { if (data instanceof FormData) { init.body = data; } else { init.body = JSON.stringify(data); } } try { const res = await fetch(url, init); const res2 = returnType === 'json' ? await res.json() : await res.text(); return res2; } catch (e) { console.log(e); return returnType === 'json' ? { code: e.code || 1, msg: e.message || "", data: null } : ""; } } // 获取文档名称的辅助函数 async function fetchNameById(id) { const response = await fetchSyncPost("/api/filetree/getHPathByID", { id: id }); return response.data; } // 保存文件的辅助函数 async function putFile(path, json, isDir = false, modTime = Date.now()) { let file; if (typeof json === "object") { file = new File( [new Blob([JSON.stringify(json)], { type: "application/json" })], path.split("/").pop() ); } else { file = new File([new Blob([json])], path.split("/").pop()); } let formdata = new FormData(); formdata.append("path", path); formdata.append("file", file); formdata.append("isDir", isDir); formdata.append("modTime", modTime); const response = await fetch("/api/file/putFile", { body: formdata, method: "POST", headers: { Authorization: `Token `, }, }); if (response.ok) return await response.json(); else return null; } try { // 1. 获取父文档信息 const parentDocInfo = (await fetchSyncPost("/api/filetree/getDoc", { id: parentDocId })).data; // 2. 获取子文档列表 const listDocTreeQuery = { "notebook": parentDocInfo.box, "path": parentDocInfo.path.replace(".sy", "") }; const childDocIds = (await fetchSyncPost("/api/filetree/listDocTree", listDocTreeQuery)).data.tree; // 3. 获取所有文档名称并排序 const idNamePairs = await Promise.all(childDocIds.map(async (doc) => { const name = await fetchNameById(doc.id); return { id: doc.id, name: name }; })); idNamePairs.sort((a, b) => a.name.localeCompare(b.name)); // 4. 创建排序结果对象 const sortedResult = {}; idNamePairs.forEach((pair, index) => { sortedResult[pair.id] = index; }); // 5. 获取现有的sort.json文件 const getFileQuery = { "path": `/data/${parentDocInfo.box}/.siyuan/sort.json` }; const sortJson = await fetchSyncPost('/api/file/getFile', getFileQuery); // 6. 更新排序值 for (let id in sortedResult) { if (sortJson.hasOwnProperty(id)) { sortJson[id] = sortedResult[id]; } } // 7. 保存更新后的sort.json文件 await putFile(`/data/${parentDocInfo.box}/.siyuan/sort.json`, sortJson); // 8. 刷新文档树 const element = document.querySelector(`.file-tree li[data-node-id="${parentDocId}"] > .b3-list-item__toggle--hl`); if (element) { element.click(); element.click(); } return true; } catch (error) { console.error('排序过程中出现错误:', error); return false; } }

如何获取文档树右键点击事件,并添加菜单

监听文档树右键菜单

this.eventBus.on("open-menu-doctree", this.addSortButton.bind(this));

添加菜单

文档树可以选中多个文件,所以是 elements 而不是 element,暂时设置只选中一个文档时且这个文档是父文档或者笔记本,右键才添加菜单.

判断是否是笔记本,是 elements 有 data-type="navigation-root"属性,父节点有 data-url,为笔记本 id,给 sortDocuments 传递笔记本 id 或者父文档 id,并添加一个参数,isNotebook

private addSortButton = ({ detail }: any) => { const elements = detail.elements; if (elements.length === 1) { const element = elements[0]; const isParentDoc = element.getAttribute("data-count") > 0; const isNotebook = element.getAttribute("data-type") === "navigation-root"; const id = isNotebook ? element.parentNode.getAttribute("data-url") : element.getAttribute("data-id"); if (isParentDoc || isNotebook) { detail.menu.addItem({ icon: "iconSort", label: "Sort Documents", click: () => this.sortDocuments(id, isNotebook) }); } } }

自然排序实现

Intl.Collator

  • numeric​ 设置为 true​,Intl.Collator​ 会识别字符串中的数字,并按照数值大小进行排序。
  • sensitivity​ 设置为 "base"​ 时,比较过程只考虑基本字母的差异,而忽略重音符号和大小写。

举例;

var collator = new Intl.Collator(undefined, {numeric: true, sensitivity: 'base'}); var myArray = ['1_Document', '11_Document', '2_Document']; console.log(myArray.sort(collator.compare));

['1_Document', '2_Document', '11_Document']

本插件用的代码

const collator = new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' }); idNamePairs.sort((a, b) => ascending ? collator.compare(a.name, b.name) : collator.compare(b.name, a.name)); // 创建排序结果对象 const sortedResult = {}; idNamePairs.forEach((pair, index) => { sortedResult[pair.id] = index; });
  • 思源笔记

    思源笔记是一款隐私优先的个人知识管理系统,支持完全离线使用,同时也支持端到端加密同步。

    融合块、大纲和双向链接,重构你的思维。

    25503 引用 • 105457 回帖
3 操作
Achuan-2 在 2024-11-28 23:17:47 更新了该帖
Achuan-2 在 2024-11-28 23:17:08 更新了该帖
Achuan-2 在 2024-11-28 17:35:57 更新了该帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...