`,
26 | width:"300px",
27 | });
28 | let div: HTMLDivElement = dialog.element.querySelector(`#${settingsDialog}`);
29 |
30 | new TemplateDialog({
31 | target: div,
32 | props:{
33 | onSave: ()=>{onSave(dialog)}
34 | }
35 | });
36 | }
37 |
38 | /**
39 | * 保存模板
40 | * @param dialog 弹窗
41 | * @returns void
42 | */
43 | async function onSave(dialog:Dialog){
44 |
45 | let el: HTMLInputElement = dialog.element.querySelector("#template-name");
46 | if(el.value == ""){
47 | showMessage(
48 | i18n.templateNameEepty,
49 | 3000,
50 | "error"
51 | );
52 | return;
53 | } else {
54 | let rs = await fetchSyncPost(
55 | "/api/file/readDir",
56 | {
57 | "path": "/data/storage/petal/siyuan-plugins-index"
58 | }
59 | );
60 | let data = rs.data;
61 |
62 | for (const iterator of data) {
63 | // console.log(iterator.name);
64 | if(iterator.name.indexOf("template-index-"+el.value) != -1){
65 | showMessage(
66 | i18n.templateAgain,
67 | 3000,
68 | "error"
69 | );
70 | return;
71 | }
72 | }
73 | await settings.saveCopy("template-index-"+el.value);
74 | eventBus.emit("addTemplateIndex","template-index-"+el.value);
75 |
76 | }
77 | // console.log("el:"+el.value);
78 | dialog.destroy();
79 | showMessage(
80 | i18n.templateCreated,
81 | 3000,
82 | "info"
83 | );
84 | }
85 |
86 | /**
87 | * 加载模板
88 | */
89 | export async function onGetTemplate(){
90 | let rs = await fetchSyncPost(
91 | "/api/file/readDir",
92 | {
93 | "path": "/data/storage/petal/siyuan-plugins-index"
94 | }
95 | );
96 |
97 | let data = rs.data;
98 |
99 | for (const iterator of data) {
100 | if(iterator.name.startsWith("template-index-")){
101 | await settings.load(iterator.name);
102 | }
103 | }
104 |
105 | // for (const key in plugin.data) {
106 | // console.log(key);
107 | // }
108 |
109 | }
--------------------------------------------------------------------------------
/src/creater/createnotebookindex.ts:
--------------------------------------------------------------------------------
1 | import { Dialog } from "siyuan";
2 | import { client, escapeHtml, i18n } from "../utils";
3 | import NotebookDialog from "../components/dialog/notebook-dialog.svelte"
4 | import { settings } from "../settings";
5 | import { getDocid, getProcessedDocIcon, insertDataSimple } from "./createIndex";
6 | // import { settings } from "./settings";
7 | // import { eventBus } from "./enventbus";
8 |
9 | /**
10 | * 创建配置模板
11 | * @returns void
12 | */
13 | export async function onCreatenbiButton() {
14 | getNotebookDialog();
15 | // console.log("create template");
16 |
17 | }
18 |
19 | /**
20 | * 接收模板名弹窗
21 | */
22 | function getNotebookDialog() {
23 | const settingsDialog = "index-get-notebook";
24 |
25 | const dialog = new Dialog({
26 | title: i18n.settingsTab.items.notebookDialog.dialogtitle,
27 | content: `
`,
28 | width: "70%",
29 | });
30 | let div: HTMLDivElement = dialog.element.querySelector(`#${settingsDialog}`);
31 |
32 | new NotebookDialog({
33 | target: div,
34 | props: {
35 | onSave: () => { onCreate(dialog) }
36 | }
37 | });
38 | }
39 |
40 | /**
41 | * 保存模板
42 | * @param dialog 弹窗
43 | * @returns void
44 | */
45 | async function onCreate(dialog: Dialog) {
46 |
47 | //载入配置
48 | await settings.load();
49 |
50 | // settings.set("autoUpdate", false);
51 |
52 | //寻找当前编辑的文档的id
53 | let parentId = getDocid();
54 | if (parentId == null) {
55 | client.pushErrMsg({
56 | msg: i18n.errorMsg_empty,
57 | timeout: 3000
58 | });
59 | return;
60 | }
61 |
62 | let el: HTMLInputElement = dialog.element.querySelector("#notebook-get");
63 | console.log(el.value);
64 | let docs = await client.listDocsByPath({
65 | notebook: el.value,
66 | path: "/"
67 | });
68 | console.log(docs);
69 |
70 | let data = "";
71 |
72 | //生成写入文本
73 | for (let doc of docs.data.files) {
74 |
75 | let id = doc.id;
76 | let name = doc.name.slice(0, -3);
77 | let icon = doc.icon;
78 | let subFileCount = doc.subFileCount;
79 |
80 | //转义
81 | name = escapeHtml(name);
82 |
83 | //应用设置
84 | let listType = settings.get("listTypeNotebook") == "unordered" ? true : false;
85 | if (listType) {
86 | data += "* ";
87 | } else {
88 | data += "1. ";
89 | }
90 |
91 | if (settings.get("iconNotebook")) {
92 | data += `${getSubdocIcon(icon, subFileCount != 0)} `;
93 | }
94 |
95 | //置入数据
96 | let linkType = settings.get("linkTypeNotebook") == "ref" ? true : false;
97 | if (linkType) {
98 | data += `[${name}](siyuan://blocks/${id})\n`;
99 | } else {
100 | data += `((${id} '${name}'))\n`;
101 | }
102 |
103 | }
104 |
105 | if (data != '') {
106 | await insertDataSimple(parentId, data);
107 | } else {
108 | client.pushErrMsg({
109 | msg: i18n.errorMsg_miss,
110 | timeout: 3000
111 | });
112 | return;
113 | }
114 |
115 | dialog.destroy();
116 | // showMessage(
117 | // i18n.msg_success,
118 | // 3000,
119 | // "info"
120 | // );
121 | }
--------------------------------------------------------------------------------
/scripts/make_dev_link.js:
--------------------------------------------------------------------------------
1 | import fs from 'fs';
2 | import readline from 'node:readline';
3 |
4 |
5 | //************************************ Write you dir here ************************************
6 |
7 | //Please write the "workspace/data/plugins" directory here
8 | //请在这里填写你的 "workspace/data/plugins" 目录
9 | let targetDir = 'D:\\思源dev\\data\\plugins';
10 | //Like this
11 | // const targetDir = `H:\\SiYuanDevSpace\\data\\plugins`;
12 | //********************************************************************************************
13 |
14 | const log = console.log;
15 |
16 | async function getSiYuanDir() {
17 | let url = 'http://127.0.0.1:6806/api/system/getWorkspaces';
18 | let header = {
19 | // "Authorization": `Token ${token}`,
20 | "Content-Type": "application/json",
21 | }
22 | let conf = {};
23 | try {
24 | let response = await fetch(url, {
25 | method: 'POST',
26 | headers: header
27 | });
28 | if (response.ok) {
29 | conf = await response.json();
30 | } else {
31 | log(`HTTP-Error: ${response.status}`);
32 | return null;
33 | }
34 | } catch (e) {
35 | log("Error:", e);
36 | log("Please make sure SiYuan is running!!!");
37 | return null;
38 | }
39 | return conf.data;
40 | }
41 |
42 | async function chooseTarget(workspaces) {
43 | let count = workspaces.length;
44 | log(`Got ${count} SiYuan ${count > 1 ? 'workspaces' : 'workspace'}`)
45 | for (let i = 0; i < workspaces.length; i++) {
46 | log(`[${i}] ${workspaces[i].path}`);
47 | }
48 |
49 | if (count == 1) {
50 | return `${workspaces[0].path}/data/plugins`;
51 | } else {
52 | const rl = readline.createInterface({
53 | input: process.stdin,
54 | output: process.stdout
55 | });
56 | let index = await new Promise((resolve, reject) => {
57 | rl.question(`Please select a workspace[0-${count-1}]: `, (answer) => {
58 | resolve(answer);
59 | });
60 | });
61 | rl.close();
62 | return `${workspaces[index].path}/data/plugins`;
63 | }
64 | }
65 |
66 | if (targetDir === '') {
67 | log('"targetDir" is empty, try to get SiYuan directory automatically....')
68 | let res = await getSiYuanDir();
69 |
70 | if (res === null) {
71 | log('Failed! You can set the plugin directory in scripts/make_dev_link.js and try again');
72 | process.exit(1);
73 | }
74 |
75 | targetDir = await chooseTarget(res);
76 | log(`Got target directory: ${targetDir}`);
77 | }
78 |
79 | //Check
80 | if (!fs.existsSync(targetDir)) {
81 | log(`Failed! plugin directory not exists: "${targetDir}"`);
82 | log(`Please set the plugin directory in scripts/make_dev_link.js`);
83 | process.exit(1);
84 | }
85 |
86 |
87 | //check if plugin.json exists
88 | if (!fs.existsSync('./plugin.json')) {
89 | console.error('Failed! plugin.json not found');
90 | process.exit(1);
91 | }
92 |
93 | //load plugin.json
94 | const plugin = JSON.parse(fs.readFileSync('./plugin.json', 'utf8'));
95 | const name = plugin?.name;
96 | if (!name || name === '') {
97 | log('Failed! Please set plugin name in plugin.json');
98 | process.exit(1);
99 | }
100 |
101 | //dev directory
102 | const devDir = `./dev`;
103 | //mkdir if not exists
104 | if (!fs.existsSync(devDir)) {
105 | fs.mkdirSync(devDir);
106 | }
107 |
108 | const targetPath = `${targetDir}/${name}`;
109 | //如果已经存在,就退出
110 | if (fs.existsSync(targetPath)) {
111 | log(`Failed! Target directory ${targetPath} already exists`);
112 | } else {
113 | //创建软链接
114 | fs.symlinkSync(`${process.cwd()}/dev`, targetPath, 'dir');
115 | log(`Done! Created symlink ${targetPath}`);
116 | }
117 |
118 |
--------------------------------------------------------------------------------
/src/components/dialog/notebook-dialog.svelte:
--------------------------------------------------------------------------------
1 |
51 |
52 |
95 |
96 |
97 |
100 |
101 |
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { resolve } from "path"
2 | import { defineConfig, loadEnv } from "vite"
3 | import minimist from "minimist"
4 | import { viteStaticCopy } from "vite-plugin-static-copy"
5 | import livereload from "rollup-plugin-livereload"
6 | import { svelte } from "@sveltejs/vite-plugin-svelte"
7 | import zipPack from "vite-plugin-zip-pack";
8 | import fg from 'fast-glob';
9 |
10 | const args = minimist(process.argv.slice(2))
11 | const isWatch = args.watch || args.w || false
12 | const devDistDir = "./dev"
13 | const distDir = isWatch ? devDistDir : "./dist"
14 |
15 | console.log("isWatch=>", isWatch)
16 | console.log("distDir=>", distDir)
17 |
18 | export default defineConfig({
19 | plugins: [
20 | svelte(),
21 |
22 | viteStaticCopy({
23 | targets: [
24 | {
25 | src: "./README*.md",
26 | dest: "./",
27 | },
28 | {
29 | src: "./icon.png",
30 | dest: "./",
31 | },
32 | {
33 | src: "./preview.png",
34 | dest: "./",
35 | },
36 | {
37 | src: "./plugin.json",
38 | dest: "./",
39 | },
40 | {
41 | src: "./src/i18n/**",
42 | dest: "./i18n/",
43 | },
44 | ],
45 | }),
46 | ],
47 |
48 | // https://github.com/vitejs/vite/issues/1930
49 | // https://vitejs.dev/guide/env-and-mode.html#env-files
50 | // https://github.com/vitejs/vite/discussions/3058#discussioncomment-2115319
51 | // 在这里自定义变量
52 | define: {
53 | "process.env.DEV_MODE": `"${isWatch}"`,
54 | },
55 |
56 | build: {
57 | // 输出路径
58 | outDir: distDir,
59 | emptyOutDir: false,
60 |
61 | // 构建后是否生成 source map 文件
62 | sourcemap: false,
63 |
64 | // 设置为 false 可以禁用最小化混淆
65 | // 或是用来指定是应用哪种混淆器
66 | // boolean | 'terser' | 'esbuild'
67 | // 不压缩,用于调试
68 | minify: !isWatch,
69 |
70 | lib: {
71 | // Could also be a dictionary or array of multiple entry points
72 | entry: resolve(__dirname, "src/index.ts"),
73 | // the proper extensions will be added
74 | fileName: "index",
75 | formats: ["cjs"],
76 | },
77 | rollupOptions: {
78 | plugins: [
79 | ...(
80 | isWatch ? [
81 | livereload(devDistDir),
82 | {
83 | //监听静态资源文件
84 | name: 'watch-external',
85 | async buildStart() {
86 | const files = await fg([
87 | 'src/i18n/*.json',
88 | './README*.md',
89 | './plugin.json'
90 | ]);
91 | for (let file of files) {
92 | this.addWatchFile(file);
93 | }
94 | }
95 | }
96 | ] : [
97 | zipPack({
98 | inDir: './dist',
99 | outDir: './',
100 | outFileName: 'package.zip'
101 | })
102 | ]
103 | )
104 | ],
105 |
106 | // make sure to externalize deps that shouldn't be bundled
107 | // into your library
108 | external: ["siyuan", "process"],
109 |
110 | output: {
111 | entryFileNames: "[name].js",
112 | assetFileNames: (assetInfo) => {
113 | if (assetInfo.name === "style.css") {
114 | return "index.css"
115 | }
116 | return assetInfo.name
117 | },
118 | },
119 | },
120 | }
121 | })
122 |
--------------------------------------------------------------------------------
/src/components/setting.svelte:
--------------------------------------------------------------------------------
1 |
42 |
43 |
44 |
45 |
46 |
47 | - {
53 | tabbarfocus = "normal";
54 | eventBus.emit("updateSettings");
55 | }}
56 | >
57 |
60 | {i18n.generalSettings}
61 |
62 |
63 |
64 | - {
70 | tabbarfocus = "template";
71 | onGetTemplate();
72 | }}
73 | >
74 |
77 | {i18n.templateSettings}
78 |
79 |
80 |
81 | - (tabbarfocus = "extra")}
87 | >
88 |
91 | {i18n.extraSettings}
92 |
93 |
94 |
95 |
100 |
101 |
104 |
105 |
109 |
110 |
111 |
--------------------------------------------------------------------------------
/src/components/setting copy.svelte:
--------------------------------------------------------------------------------
1 |
48 |
49 |
50 |
51 |
57 |
63 |
69 |
75 |
81 |
87 |
93 |
99 |
100 |
101 | {@html outlineLable}
102 |
103 |
104 |
112 |
120 |
121 |
122 |
--------------------------------------------------------------------------------
/src/components/template-index-tab.svelte:
--------------------------------------------------------------------------------
1 |
27 |
28 |
29 |
30 |
31 |
38 |
{realName}
39 |
40 |
41 |
42 |
48 |
49 |
50 |
51 |
52 |
53 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
71 |
72 |
79 |
80 |
87 |
88 |
95 |
96 |
103 |
104 |
111 |
112 |
119 |
120 |
--------------------------------------------------------------------------------
/src/components/template-outline-tab.svelte:
--------------------------------------------------------------------------------
1 |
27 |
28 |
29 |
30 |
31 |
38 |
{realName}
39 |
40 |
41 |
42 |
48 |
49 |
50 |
51 |
52 |
53 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
71 |
72 |
79 |
80 |
87 |
88 |
95 |
96 |
103 |
104 |
111 |
112 |
119 |
120 |
--------------------------------------------------------------------------------
/src/i18n/zh_CN.json:
--------------------------------------------------------------------------------
1 | {
2 | "addTopBarIcon": "插入目录",
3 | "insertIndex": "插入目录",
4 | "insertNotebookIndex": "插入笔记本目录",
5 | "insertSubIndexoutline": "插入子文档目录及大纲",
6 | "insertoutline": "插入当前文档大纲",
7 | "settings": "设置",
8 | "cancel": "取消",
9 | "save": "保存",
10 | "byeMenu": "再见,菜单!",
11 | "helloPlugin": "你好,插件!",
12 | "byePlugin": "再见,插件!",
13 | "errorMsg_empty": "你需要先打开一个文档,或者是在需要插入的文档中点击一下",
14 | "errorMsg_miss": "当前文档下无子文档",
15 | "msg_success": "插入成功",
16 | "update_success": "更新成功",
17 | "icon_enable": "启用图标",
18 | "icon_disable": "禁用图标",
19 | "dclike": "删除后请勿过快点击",
20 | "noneId": "
未获取到文档id,按钮被禁用,你需要先打开一个文档,或者是在需要插入的文档中点击一下",
21 | "hadId": "
已获取到文档id,可以点击按钮",
22 | "errorMsg_miss_outline": "当前文档下无大纲",
23 | "templateUsed": "模板套用成功",
24 | "templateNameEepty": "模板名不能为空",
25 | "templateCreated": "模板创建成功",
26 | "templateAgain": "请勿重复添加",
27 | "useTemplate": "套用模板",
28 | "deleteTemplate": "删除模板",
29 | "generalSettings": "常规设置",
30 | "indexSettings": "目录设置",
31 | "outlineSettings": "大纲设置",
32 | "templateSettings": "模板设置",
33 | "extraSettings": "拓展功能",
34 | "independentInsert": "独立插入",
35 | "nothasTemplate": "当前无模版",
36 | "cliketoAdd": "点击前往添加模板",
37 | "loading": "加载中……",
38 | "settingsTab": {
39 | "name": "设置",
40 | "items": {
41 | "depth": {
42 | "title": "目录索引深度",
43 | "content": "设置目录索引深度,为
0 时无限制",
44 | "max": 7,
45 | "min": 0,
46 | "step": 1
47 | },
48 | "fold": {
49 | "title": "目录折叠层级",
50 | "content": "设置目录折叠层级,为
0 时无限制,
如要保存手动修改的折叠状态,需要禁用自动更新",
51 | "max": 7,
52 | "min": 0,
53 | "step": 1
54 | },
55 | "col": {
56 | "title": "目录分列",
57 | "content": "设置分列数,默认不分列",
58 | "max": 5,
59 | "min": 1,
60 | "step": 1
61 | },
62 | "icon": {
63 | "title": "图标",
64 | "content": "设置插入目录时是否显示图标"
65 | },
66 | "autoUpdate": {
67 | "title": "自动更新",
68 | "content": "开启后,被插入的目录会在文档打开时自动更新
注:自动更新规则为最近一次点击插入的设置项,再次点击插入才能改变自动插入的规则"
69 | },
70 | "listType": {
71 | "title": "目录列表类型",
72 | "content": "设置目录列表类型",
73 | "options": {
74 | "unordered": "无序列表",
75 | "ordered": "有序列表"
76 | }
77 | },
78 | "linkType": {
79 | "title": "链接类型",
80 | "content": "设置目录链接类型",
81 | "options": {
82 | "ref": "链接",
83 | "embed": "引用"
84 | }
85 | },
86 | "docBuilder": {
87 | "title": "文档构建器
[测试功能]",
88 | "content": "启用后,当
只选中一个块,且该块为列表块 时,将出现该按钮
点击该按钮,能够根据无序列表层级来构建文档层级请保证该块中
只有列表块 ,以确保文档层次结构能够按预期的效果被构建
注:同级目录下,同名文件不会被重复创建,不要对已经成型的目录块进行操作,以免生成大量无用文档
此功能在绝大多数情况下能够正常工作,但仍可能在极少数情况下产生无法预知的错误,因此使用前请务必先创建快照"
89 | },
90 | "subOutlineButton": {
91 | "title": "插入子文档目录及大纲",
92 | "content": "插入子文档目录的同时插入文档大纲,
并为该文档禁用自动更新,因为大纲检索缓慢,所以有大纲时不支持自动更新,如果长时间没有显示插入成功,请耐心等待",
93 | "text": "插入",
94 | "icon": "#iconAlignCenter"
95 | },
96 | "docOutlineButton": {
97 | "title": "插入当前文档大纲",
98 | "content": "插入当前文档大纲,通常在导出文档时可能用到它",
99 | "text": "插入",
100 | "icon": "#iconAlignCenter"
101 | },
102 | "createTemplate": "创建模板",
103 | "templateDialog": {
104 | "title": "模板名称",
105 | "save": "保存"
106 | },
107 | "notebookDialog": {
108 | "dialogtitle": "插入笔记本目录",
109 | "insert": "确定",
110 | "title": "
目标笔记本",
111 | "content": "选择要生成目录的笔记本"
112 | },
113 | "at": {
114 | "title": "大纲前缀",
115 | "content": "启用后将会在大纲前加入
@符号"
116 | },
117 | "outlineType": {
118 | "title": "链接类型",
119 | "content": "设置大纲链接类型",
120 | "options": {
121 | "ref": "链接",
122 | "embed": "引用",
123 | "copy": "文本*"
124 | }
125 | },
126 | "outlineAutoUpdate": {
127 | "title": "当前文档大纲自动更新",
128 | "content": "开启后,被插入的当前文档大纲自动会在文档打开时自动更新
注:自动更新规则为最近一次点击插入的设置项,再次点击插入才能改变自动插入的规则"
129 | },
130 | "listTypeOutline": {
131 | "title": "大纲列表类型",
132 | "content": "设置大纲列表类型",
133 | "options": {
134 | "unordered": "无序列表",
135 | "ordered": "有序列表"
136 | }
137 | }
138 | }
139 | }
140 | }
--------------------------------------------------------------------------------
/src/settings.ts:
--------------------------------------------------------------------------------
1 | import { fetchSyncPost } from "siyuan";
2 | import { plugin } from "./utils";
3 |
4 | //配置文件名称
5 | export const CONFIG = "config";
6 |
7 | //配置文件内容
8 | // export const DEFAULT_CONFIG = {
9 | // icon: true,
10 | // depth: 0,
11 | // listType:"unordered",
12 | // linkType:"ref",
13 | // docBuilder: false,
14 | // autoUpdate: true,
15 | // col:1,
16 | // fold:0,
17 | // at:true,
18 | // outlineAutoUpdate: false,
19 | // outlineType:"ref",
20 | // listTypeOutline:"unordered",
21 | // };
22 |
23 | /**
24 | * 配置类
25 | */
26 | class Settings{
27 |
28 | //初始化配置文件
29 | // async initData() {
30 | // //载入配置
31 | // await this.load();
32 |
33 | // //配置不存在则按照默认值建立配置文件
34 | // if (plugin.data[CONFIG] === "" || plugin.data[CONFIG] === undefined || plugin.data[CONFIG] === null) {
35 | // await plugin.saveData(CONFIG, JSON.stringify(DEFAULT_CONFIG));
36 | // }
37 | // await this.load();
38 | // }
39 |
40 | async initData() {
41 | //载入配置
42 | await this.load();
43 |
44 | //配置不存在则按照默认值建立配置文件
45 | if (plugin.data[CONFIG] === "" || plugin.data[CONFIG] === undefined || plugin.data[CONFIG] === null) {
46 | await plugin.saveData(CONFIG, JSON.stringify(new SettingsProperty()));
47 | }
48 | await this.load();
49 | }
50 |
51 | set(key:any, value:any,config = CONFIG){
52 | plugin.data[config][key] = value;
53 | }
54 |
55 | get(key:any,config = CONFIG){
56 | return plugin.data[config]?.[key];
57 | }
58 |
59 | async load(config = CONFIG){
60 | await plugin.loadData(config);
61 | }
62 |
63 | async save(config = CONFIG){
64 | await plugin.saveData(config, plugin.data[config]);
65 | }
66 |
67 | async saveCopy(config = CONFIG){
68 | await plugin.saveData(config, plugin.data[CONFIG]);
69 | }
70 |
71 | async saveTo(config:string){
72 | plugin.data[config]["docBuilder"] = plugin.data[CONFIG]["docBuilder"];
73 | await plugin.saveData(CONFIG, plugin.data[config]);
74 | }
75 |
76 | async remove(config = CONFIG){
77 | await plugin.removeData(config);
78 | }
79 |
80 | async rename(config:string, newname:string){
81 | await fetchSyncPost(
82 | "/api/file/renameFile",
83 | {
84 | "path": `/data/storage/petal/siyuan-plugins-index/${config}`,
85 | "newPath": `/data/storage/petal/siyuan-plugins-index/${newname}`
86 | }
87 | );
88 | }
89 |
90 | loadSettings(data: any){
91 | this.set("icon",data.icon);
92 | this.set("depth",data.depth);
93 | this.set("listType",data.listType);
94 | this.set("linkType",data.linkType);
95 | this.set("fold",data.fold);
96 | this.set("col",data.col);
97 | this.set("autoUpdate",data.autoUpdate);
98 | }
99 |
100 | loadSettingsforOutline(data: any){
101 | this.set("at",data.at);
102 | this.set("outlineType",data.outlineType);
103 | this.set("outlineAutoUpdate",data.outlineAutoUpdate);
104 | this.set("listTypeOutline",data.listTypeOutline);
105 | }
106 |
107 | }
108 |
109 | export const settings: Settings = new Settings();
110 |
111 | export class SettingsProperty {
112 | icon: boolean;
113 | depth: number;
114 | listType: string;
115 | linkType: string;
116 | docBuilder: boolean;
117 | autoUpdate: boolean;
118 | col: number;
119 | fold: number;
120 | at: boolean;
121 | outlineAutoUpdate: boolean;
122 | outlineType: string;
123 | listTypeOutline: string;
124 |
125 | constructor(){
126 | this.icon = true;
127 | this.depth = 0;
128 | this.listType = "unordered";
129 | this.linkType = "ref";
130 | this.docBuilder = false;
131 | this.autoUpdate = true;
132 | this.col = 1;
133 | this.fold = 0;
134 | this.at = true;
135 | this.outlineAutoUpdate = false;
136 | this.outlineType = "ref";
137 | this.listTypeOutline = "unordered";
138 | }
139 |
140 | getAll(){
141 | this.icon = settings.get("icon");
142 | this.depth = settings.get("depth");
143 | this.listType = settings.get("listType");
144 | this.linkType = settings.get("linkType");
145 | this.docBuilder = settings.get("docBuilder");
146 | this.autoUpdate = settings.get("autoUpdate");
147 | this.col = settings.get("col");
148 | this.fold = settings.get("fold");
149 | this.at = settings.get("at");
150 | this.outlineAutoUpdate = settings.get("outlineAutoUpdate");
151 | this.outlineType = settings.get("outlineType");
152 | this.listTypeOutline = settings.get("listTypeOutline");
153 | }
154 |
155 | }
--------------------------------------------------------------------------------
/src/components/tab/template-tab.svelte:
--------------------------------------------------------------------------------
1 |
69 |
70 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
{
88 | templatefocus = "indexTemplate";
89 | // onGetIndexTemplate();
90 | }}
91 | >
92 |
93 | 目录模板
94 |
95 |
96 |
97 |
98 |
{
104 | templatefocus = "outlineTemplate";
105 | // onGetTemplate();
106 | }}
107 | >
108 |
109 | 大纲模板
110 |
111 |
112 |
113 |
114 |
121 | {#each Object.entries(plugin.data) as [key]}
122 | {#if key != "config" && key.indexOf("index") != -1}
123 |
124 |
125 |
126 | {/if}
127 | {/each}
128 |
129 |
136 | {#each Object.entries(plugin.data) as [key]}
137 | {#if key != "config" && key.indexOf("outline") != -1}
138 |
139 |
140 |
141 | {/if}
142 | {/each}
143 |
144 |
145 |
146 |
153 |
--------------------------------------------------------------------------------
/src/i18n/en_US.json:
--------------------------------------------------------------------------------
1 | {
2 | "addTopBarIcon": "Insert Index",
3 | "insertIndex": "Insert Index",
4 | "insertNotebookIndex": "Insert Notebook Index",
5 | "insertSubIndexoutline": "Insert Subdocument Index and Outline",
6 | "insertoutline": "Insert Outline",
7 | "settings": "Settings",
8 | "cancel": "Cancel",
9 | "save": "Save",
10 | "byeMenu": "Bye, Menu!",
11 | "helloPlugin": "Hello, Plugin!",
12 | "byePlugin": "Bye, Plugin!",
13 | "errorMsg_empty": "You need to first open a document or click within the document where you want to insert",
14 | "errorMsg_miss": "There are no child documents under the current document",
15 | "msg_success": "Insert successful",
16 | "update_success":"update successful",
17 | "icon_enable": "icon enable",
18 | "icon_disable": "icon disable",
19 | "dclike": "After deleting, please do not click too quickly",
20 | "noneId":"
The document ID was not obtained and the button is disabled. You need to first open a document or click on the document that needs to be inserted",
21 | "hadId":"
Document ID has been obtained, you can click the button",
22 | "errorMsg_miss_outline":"There is no outline under the current document",
23 | "templateUsed":"Template successfully applied",
24 | "templateNameEepty":"Template name cannot be empty",
25 | "templateCreated":"Template created successfully",
26 | "templateAgain":"Do not add again",
27 | "useTemplate":"Apply Template",
28 | "deleteTemplate":"Delete Template",
29 | "generalSettings":"General Settings",
30 | "indexSettings": "Index Settings",
31 | "outlineSettings": "Outline Settings",
32 | "templateSettings":"Template Settings",
33 | "extraSettings":"Extra Settings",
34 | "independentInsert":"Independent Insert",
35 | "nothasTemplate":"There is currently no template available",
36 | "cliketoAdd":"Click to add a template",
37 | "loading": "Loading……",
38 | "settingsTab": {
39 | "name":"Settings",
40 | "items": {
41 | "depth": {
42 | "title": "Index depth",
43 | "content":"Set the Index depth. If set to
0 , there will be no limitation",
44 | "max":7,
45 | "min":0,
46 | "step":1
47 | },
48 | "fold": {
49 | "title": "Index fold",
50 | "content":"Set the Index fold. If set to
0 , there will be no limitation.To save the manually modified folding state, automatic updates need to be disabled.",
51 | "max":7,
52 | "min":0,
53 | "step":1
54 | },
55 | "col": {
56 | "title": "Index Column",
57 | "content":"Set the index column",
58 | "max":5,
59 | "min":1,
60 | "step":1
61 | },
62 | "icon": {
63 | "title": "Icon",
64 | "content":"Set whether to display icons when inserting Index"
65 | },
66 | "autoUpdate": {
67 | "title": "Auto update",
68 | "content":"After opening, the inserted directory will be automatically updated when the document is opened
Note: The automatic update rule is the setting for this insertion, and manual insertion can only change the automatic insertion rule"
69 | },
70 | "listType": {
71 | "title": "Index List Type",
72 | "content": "Set the Index List Type",
73 | "options": {
74 | "unordered": "Unordered List",
75 | "ordered": "Ordered List"
76 | }
77 | },
78 | "linkType": {
79 | "title": "Link Type",
80 | "content": "Set the Block Link Type",
81 | "options": {
82 | "ref": "Link",
83 | "embed": "Embed"
84 | }
85 | },
86 | "docBuilder": {
87 | "title": "Document Builder
[Beta]",
88 | "content":"When enabled, when only one block is selected and it is a list block, this button will appear. Clicking this button can construct a document hierarchy based on an unordered list hierarchy
Please ensure that there is only a list block in this block to ensure that the document hierarchy is constructed as expected
Note: In the same level directory, files with the same name will not be created repeatedly. Do not operate on formed directory blocks to avoid generating a large number of useless documents.
This feature works normally in the vast majority of cases, but it may still cause unpredictable errors in rare cases. Therefore, it is important to create a snapshot before using it."
89 | },
90 | "subOutlineButton": {
91 | "title": "insert sub doc and outline",
92 | "content":"Insert the document outline while inserting the sub document directory,
And disable automatic updates for this document. Due to slow outline retrieval, automatic updates are not supported when there is an outline. If successful insertion is not displayed for a long time, please be patient and wait",
93 | "text":"insert",
94 | "icon":"#iconAlignCenter"
95 | },
96 | "docOutlineButton": {
97 | "title": "insert doc outline",
98 | "content":"Only the current document outline will be inserted. It may be used when exporting documents",
99 | "text":"insert",
100 | "icon":"#iconAlignCenter"
101 | },
102 | "createTemplate":"Create Template",
103 | "templateDialog": {
104 | "title": "Template Name",
105 | "save": "Save"
106 | },
107 | "notebookDialog": {
108 | "dialogtitle": "Insert Notebook Index",
109 | "insert": "OK",
110 | "title": "
Destination notebook",
111 | "content": "Select the notebook to generate a directory"
112 | },
113 | "at": {
114 | "title": "Outline Prefix",
115 | "content":"After activation, an
@ symbol will be added before the outline."
116 | },
117 | "outlineType": {
118 | "title": "Outline type",
119 | "content": "Set outline type",
120 | "options": {
121 | "ref": "link",
122 | "embed": "embed",
123 | "copy": "text*"
124 | }
125 | },
126 | "outlineAutoUpdate": {
127 | "title": "The current document outline is automatically updated",
128 | "content": "After opening, the outline of the inserted current document will be automatically updated when the document is opened
PS: The automatic update rule is based on the settings item that was last clicked for insertion. Clicking Insert again will change the automatic insertion rule"
129 | },
130 | "listTypeOutline": {
131 | "title": "Outline List type",
132 | "content": "Set outline List type",
133 | "options": {
134 | "unordered": "Unordered List",
135 | "ordered": "Ordered List"
136 | }
137 | }
138 | }
139 | }
140 | }
--------------------------------------------------------------------------------
/src/topbar.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Dialog, Menu,
3 | // fetchSyncPost,
4 | // openTab
5 | } from "siyuan";
6 | import { insert, insertButton, insertDocButton, insertNotebookButton } from "./creater/createIndex";
7 | import { i18n, isMobile, plugin } from "./utils";
8 | import SettingsTab from "./components/setting.svelte"
9 | import { settings } from "./settings";
10 | import { onGetTemplate } from "./creater/createtemplate";
11 |
12 | // //tab类型
13 | // const TAB_TYPE = "custom_tab";
14 |
15 | export async function initTopbar() {
16 |
17 | //添加顶栏按钮
18 | const topBarElement = plugin.addTopBar({
19 | icon: "iconList",
20 | title: i18n.addTopBarIcon,
21 | position: "right",
22 | callback: async () => {
23 | insert();
24 | }
25 | });
26 |
27 | //添加快捷键
28 | plugin.addCommand({
29 | langKey: "addTopBarIcon",
30 | hotkey: "⌥⌘I",
31 | callback: async () => {
32 | insert();
33 | }
34 | });
35 |
36 | plugin.addCommand({
37 | langKey: "insertSubIndexoutline",
38 | hotkey: "⌥⌘O",
39 | callback: async () => {
40 | insertButton();
41 | }
42 | });
43 |
44 | plugin.addCommand({
45 | langKey: "insertoutline",
46 | hotkey: "⌥⌘P",
47 | callback: async () => {
48 | insertDocButton();
49 | }
50 | });
51 |
52 | plugin.addCommand({
53 | langKey: "insertNotebookIndex",
54 | hotkey: "⌥⌘N",
55 | callback: async () => {
56 | insertNotebookButton();
57 | }
58 | });
59 |
60 | // //设置右键监听
61 | // topBarElement.addEventListener("contextmenu", async () => {
62 | // await createDialog();
63 | // });
64 | //设置右键监听
65 | topBarElement.addEventListener("contextmenu", async () => {
66 | if (isMobile) {
67 | addMenu();
68 | } else {
69 | let rect = topBarElement.getBoundingClientRect();
70 | // 如果被隐藏,则使用更多按钮
71 | if (rect.width === 0) {
72 | rect = document.querySelector("#barMore").getBoundingClientRect();
73 | }
74 | if (rect.width === 0) {
75 | rect = document.querySelector("#barPlugins").getBoundingClientRect();
76 | }
77 | addMenu(rect);
78 | }
79 | });
80 |
81 | // //载入配置
82 | // await settings.load();
83 |
84 | // //创建一个div节点,将设置界面的svelte导入其中
85 | // let settingsTab: SettingsTab;
86 | // let div: HTMLDivElement = document.createElement('div');
87 | // settingsTab = new SettingsTab({
88 | // target: div,
89 | // });
90 |
91 | // // openTab方法的fn参数
92 | // let customTab = plugin.addTab({
93 | // type: TAB_TYPE,
94 | // async init() {
95 | // this.element.appendChild(div);
96 | // },
97 | // destroy() {
98 | // }
99 | // });
100 |
101 | // topBarElement.addEventListener("contextmenu", () => {
102 | // addMenu(topBarElement.getBoundingClientRect());
103 | // });
104 |
105 | // //设置右键监听
106 | // topBarElement.addEventListener("contextmenu", async () => {
107 | // openTab({
108 | // app:plugin.app,
109 | // custom: {
110 | // icon: "iconSettings",
111 | // title: i18n.settingsTab.name,
112 | // // data: {
113 | // // text: "This is my custom tab",
114 | // // },
115 | // fn: customTab
116 | // },
117 | // })
118 | // });
119 |
120 | }
121 |
122 | export async function createDialog() {
123 | //载入配置
124 | await settings.load();
125 | await onGetTemplate();
126 |
127 | const settingsDialog = "index-settings"
128 |
129 | const dialog = new Dialog({
130 | title: i18n.settingsTab.name,
131 | content: `
`,
132 | width: "70%",
133 | height: "70%",
134 | });
135 |
136 | let div: HTMLDivElement = dialog.element.querySelector(`#${settingsDialog}`);
137 |
138 | new SettingsTab({
139 | target: div,
140 | });
141 | }
142 |
143 | function addMenu(rect?: DOMRect) {
144 | const menu = new Menu();
145 | menu.addItem({
146 | icon: "iconList",
147 | label: i18n.insertIndex,
148 | accelerator: plugin.commands[0].customHotkey,
149 | click: () => {
150 | insert();
151 | }
152 | });
153 | menu.addItem({
154 | icon: "iconAlignLeft",
155 | label: i18n.insertSubIndexoutline,
156 | accelerator: plugin.commands[1].customHotkey,
157 | click: () => {
158 | insertButton();
159 | }
160 | });
161 | menu.addItem({
162 | icon: "iconAlignCenter",
163 | label: i18n.insertoutline,
164 | accelerator: plugin.commands[2].customHotkey,
165 | click: () => {
166 | insertDocButton();
167 | }
168 | });
169 | menu.addItem({
170 | icon: "iconFilesRoot",
171 | label: i18n.insertNotebookIndex,
172 | accelerator: plugin.commands[3].customHotkey,
173 | click: () => {
174 | insertNotebookButton();
175 | }
176 | });
177 | menu.addSeparator();
178 | menu.addItem({
179 | icon: "iconSettings",
180 | label: i18n.settings,
181 | // accelerator: this.commands[0].customHotkey,
182 | click: async () => {
183 | await createDialog();
184 | }
185 | });
186 | if (isMobile) {
187 | menu.fullscreen();
188 | } else {
189 | menu.open({
190 | x: rect.right,
191 | y: rect.bottom,
192 | isLeft: true,
193 | });
194 | }
195 | }
196 |
197 | // export function initObserver() {
198 | // let config = {
199 | // attributes: true,
200 | // childList: true,
201 | // subtree: true
202 | // }
203 |
204 | // let callback = function (mutationRecords: MutationRecord[]) {
205 | // mutationRecords.forEach(function (value, index, array) {
206 | // // console.log(value);
207 | // if (value.type == "attributes") {
208 | // // console.log("yes");
209 | // if(value.attributeName == "data-node-id") {
210 | // // console.log(value.attributeName);
211 | // console.log(value);
212 | // }
213 | // }
214 | // });
215 | // }
216 |
217 | // let observer = new MutationObserver(callback);
218 |
219 | // let target: any;
220 |
221 | // if (isMobile)
222 | // target = document.querySelector('#editor .protyle-content .protyle-background');
223 | // else
224 | // target = document.querySelector('.layout__wnd--active .protyle.fn__flex-1:not(.fn__none) .protyle-background');
225 |
226 | // console.log(target);
227 | // observer.observe(document.querySelector('.layout__center.fn__flex.fn__flex-1'), config);
228 | // }
--------------------------------------------------------------------------------
/src/components/tab/normal-tab.svelte:
--------------------------------------------------------------------------------
1 |
11 |
12 |
18 |
19 |
20 |
21 |
22 |
23 |
{
29 | normalfocus = "indexSettings";
30 | // onGetIndexTemplate();
31 | }}
32 | >
33 |
34 | {i18n.indexSettings}
35 |
36 |
37 |
38 |
39 |
{
45 | normalfocus = "outlineSettings";
46 | // onGetTemplate();
47 | }}
48 | >
49 |
50 | {i18n.outlineSettings}
51 |
52 |
53 |
54 |
55 |
61 |
62 |
68 |
74 |
80 |
86 |
92 |
98 |
104 |
108 |
109 |
110 |
118 |
119 |
120 |
121 |
127 |
133 |
139 |
145 |
151 |
167 |
168 |
169 |
170 |
--------------------------------------------------------------------------------
/src/event/blockiconevent.ts:
--------------------------------------------------------------------------------
1 | import { insertDataSimple } from "../creater/createIndex";
2 | import { IndexStackNode, IndexStack } from "../indexnode";
3 | import { settings } from "../settings";
4 | import { client, i18n } from "../utils";
5 | import { getProcessedDocIcon } from "../creater/createIndex";
6 |
7 | // Helper to strip icon prefixes from text
8 | const stripIconPrefix = (text: string) => {
9 | // Matches leading emojis (like 📑, 📄) or :word: patterns
10 | // Also matches `[
](siyuan://blocks/) ` pattern to catch previously generated links with icons
11 | const iconOrLinkRegex = /^(?:[\uD800-\uDBFF][\uDC00-\uDFFF]|:\w+:|\[.*?\]\(siyuan:\/\/blocks\/.*?\))\s*/;
12 | return text.replace(iconOrLinkRegex, '').trim();
13 | };
14 |
15 | //目录栈
16 | let indexStack : IndexStack;
17 |
18 | /**
19 | * 块标菜单回调
20 | * @param detail 事件细节
21 | * @returns void
22 | */
23 | export function buildDoc({ detail }: any) {
24 | //如果选中块大于1或不是列表块或未开启按钮,则直接结束
25 | if (detail.blockElements.length > 1 ||
26 | detail.blockElements[0].getAttribute('data-type') != "NodeList" ||
27 | !settings.get("docBuilder")) {
28 | return;
29 | }
30 | //插入按钮到块菜单
31 | detail.menu.addItem({
32 | icon: "iconList",
33 | label: i18n.settingsTab.items.docBuilder.title,
34 | click: async () => {
35 | await parseBlockDOM(detail);
36 | }
37 | });
38 | }
39 |
40 | /**
41 | * 解析detail中块的DOM
42 | * @param detail
43 | */
44 | async function parseBlockDOM(detail: any) {
45 | indexStack = new IndexStack();
46 | indexStack.notebookId = detail.protyle.notebookId;
47 | let docId = detail.blockElements[0].getAttribute("data-node-id");
48 | let block = detail.blockElements[0].childNodes;
49 | let blockElement = detail.blockElements[0];
50 |
51 | let initialListType = "unordered"; // Default
52 | const subType = blockElement.getAttribute('data-subtype');
53 | if (subType === 'o') {
54 | initialListType = "ordered";
55 | } else if (subType === 't') {
56 | initialListType = "task";
57 | }
58 | indexStack.basePath = await getRootDoc(docId);
59 | // We still need docData for pPath, so let's get it separately
60 | let docDataForPath = await client.getBlockInfo({
61 | id: detail.protyle.block.rootID
62 | });
63 | indexStack.pPath = docDataForPath.data.path.slice(0, -3);
64 | await parseChildNodes(block,indexStack,0,initialListType);
65 | await stackPopAll(indexStack);
66 |
67 | // Call the new function to reconstruct the markdown for the list
68 | let reconstructedMarkdown = await reconstructListMarkdownWithLinks(detail.blockElements[0], indexStack);
69 |
70 | // Update the original list block with the reconstructed markdown
71 | if (reconstructedMarkdown !== '') {
72 | await client.updateBlock({
73 | id: docId, // Update the root NodeList block
74 | data: reconstructedMarkdown,
75 | dataType: 'markdown',
76 | });
77 | } else {
78 | client.pushErrMsg({
79 | msg: i18n.errorMsg_miss,
80 | timeout: 3000
81 | });
82 | }
83 | }
84 |
85 | async function parseChildNodes(childNodes: any, currentStack: IndexStack, tab = 0, parentListType: string) {
86 | tab++;
87 | for (const childNode of childNodes) { // childNode is a NodeListItem
88 | if (childNode.getAttribute('data-type') == "NodeListItem") {
89 | let sChildNodes = childNode.childNodes;
90 | let itemText = "";
91 | let existingBlockId = ""; // This is for the generated page ID.
92 | let subListNodes = [];
93 | let cleanMarkdown = "";
94 |
95 | for (const sChildNode of sChildNodes) {
96 | if (sChildNode.getAttribute('data-type') == "NodeParagraph") {
97 | const paragraphId = sChildNode.getAttribute('data-node-id');
98 | const paragraphContent = sChildNode.innerHTML;
99 |
100 | try {
101 | const kramdownResponse = await client.getBlockKramdown({ id: paragraphId });
102 | if (kramdownResponse?.data?.kramdown) {
103 | let kramdown = kramdownResponse.data.kramdown.split('\n')[0];
104 |
105 | const finalizedMatch = kramdown.match(/^\[(.*?)\]\(siyuan:\/\/blocks\/([a-zA-Z0-9-]+)\)\s*--\s*(.*)$/s);
106 |
107 | if (finalizedMatch) { // Run 2+ with the "ICON -- CONTENT" format
108 | existingBlockId = finalizedMatch[2]; // The generated page ID
109 | cleanMarkdown = finalizedMatch[3].trim(); // The original content part
110 | itemText = window.Lute.BlockDOM2Content(paragraphContent).replace(/^.*?--\s*/, "").trim();
111 | } else { // First run or other format
112 | cleanMarkdown = kramdown.replace(/\s*{:.*?}\s*/g, '').trim();
113 | itemText = stripIconPrefix(window.Lute.BlockDOM2Content(paragraphContent)).trim();
114 | }
115 | }
116 | } catch (e) {
117 | console.error(`[Parse][Error] Failed to get kramdown for ${paragraphId}`, e);
118 | }
119 |
120 | if (!cleanMarkdown) {
121 | cleanMarkdown = itemText; // Fallback
122 | }
123 |
124 | } else if (sChildNode.getAttribute('data-type') == "NodeList") {
125 | subListNodes.push(sChildNode);
126 | }
127 | }
128 |
129 | let currentItemType = parentListType;
130 | let taskStatus = "";
131 | if (currentItemType === "task") {
132 | const taskMarkerElement = childNode.querySelector('[data-type="NodeTaskListItemMarker"]');
133 | taskStatus = (taskMarkerElement && taskMarkerElement.getAttribute('data-task') === 'true') ? "[x]" : "[ ]";
134 | }
135 | let existingSubFileCount = 0;
136 |
137 | let contentBlockId;
138 | const refMatch = cleanMarkdown.match(/\(\((.*?)\s/);
139 | if (refMatch) {
140 | contentBlockId = refMatch[1];
141 | } else {
142 | const linkMatch = cleanMarkdown.match(/siyuan:\/\/blocks\/(.*?)\)/);
143 | if (linkMatch) {
144 | contentBlockId = linkMatch[1];
145 | }
146 | }
147 |
148 | if (contentBlockId) {
149 | try {
150 | let blockInfo = await client.getBlockInfo({ id: contentBlockId });
151 | if (blockInfo && blockInfo.data) {
152 | existingSubFileCount = blockInfo.data.subFileCount || 0;
153 | }
154 | } catch(e) { /* ignore if block not found */ }
155 | }
156 | let existingIcon = existingSubFileCount > 0 ? "📑" : "📄";
157 |
158 | let item = new IndexStackNode(tab, itemText, currentItemType, taskStatus, existingIcon, existingSubFileCount, existingBlockId, cleanMarkdown);
159 | currentStack.push(item);
160 |
161 | for (const subListNode of subListNodes) {
162 | let subListType = "unordered";
163 | const subType = subListNode.getAttribute('data-subtype');
164 | if (subType === 'o') subListType = "ordered";
165 | else if (subType === 't') subListType = "task";
166 | await parseChildNodes(subListNode.childNodes, item.children, tab, subListType);
167 | }
168 | }
169 | }
170 | }
171 |
172 | async function getRootDoc(id:string){
173 | let response = await client.sql({
174 | stmt: `SELECT hpath FROM blocks WHERE id = '${id}'`
175 | });
176 | let result = response.data[0];
177 | return result?.hpath;
178 | }
179 |
180 | async function createDoc(notebookId:string,hpath:string){
181 | const escapedHpath = hpath.replace(/'/g, "''");
182 | let existingDocResponse = await client.sql({
183 | stmt: `SELECT id FROM blocks WHERE hpath = '${escapedHpath}' AND type = 'd' AND box = '${notebookId}'`
184 | });
185 |
186 | if (existingDocResponse.data && existingDocResponse.data.length > 0) {
187 | return existingDocResponse.data[0].id;
188 | } else {
189 | await new Promise(resolve => setTimeout(resolve, 50));
190 | let response = await client.createDocWithMd({
191 | markdown: "",
192 | notebook: notebookId,
193 | path: hpath
194 | });
195 | return response.data;
196 | }
197 | }
198 |
199 | async function stackPopAll(stack:IndexStack){
200 | for (let i = stack.stack.length - 1; i >= 0; i--) {
201 | const item = stack.stack[i];
202 | const text = item.text;
203 | const subPath = stack.basePath+"/"+text;
204 |
205 | if (!item.blockId) {
206 | item.blockId = await createDoc(indexStack.notebookId, subPath);
207 | }
208 | let currentBlockId = item.blockId;
209 |
210 | item.documentPath = stack.pPath + "/" + currentBlockId;
211 |
212 | try {
213 | let blockInfo = await client.getBlockInfo({ id: currentBlockId });
214 | let docsInParent = await client.listDocsByPath({
215 | notebook: indexStack.notebookId,
216 | path: stack.pPath
217 | });
218 |
219 | let foundDocIcon = null;
220 | if (docsInParent?.data?.files) {
221 | const matchingDoc = docsInParent.data.files.find(doc => doc.id === currentBlockId);
222 | if (matchingDoc) foundDocIcon = matchingDoc.icon;
223 | }
224 |
225 | if (blockInfo?.data) {
226 | item.subFileCount = blockInfo.data.subFileCount || 0;
227 | item.icon = foundDocIcon || (item.subFileCount > 0 ? "📑" : "📄");
228 | }
229 | } catch (e) {
230 | console.error(`[StackPop] Error processing block info for ${currentBlockId}:`, e);
231 | item.icon = item.subFileCount > 0 ? "📑" : "📄"; // Fallback icon
232 | }
233 |
234 | if(!item.children.isEmpty()){
235 | item.children.basePath = subPath;
236 | item.children.pPath = item.documentPath;
237 | await stackPopAll(item.children);
238 | }
239 | }
240 | }
241 |
242 | async function reconstructListMarkdownWithLinks(originalListElement: HTMLElement, currentStack: IndexStack, indentLevel: number = 0, orderedListCounters: { [key: number]: number } = {}): Promise {
243 | let markdown = "";
244 | const originalListItems = originalListElement.children;
245 | let stackIndex = 0;
246 |
247 | if (currentStack.stack.length > 0 && currentStack.stack[0].listType === "ordered" && !orderedListCounters[indentLevel]) {
248 | orderedListCounters[indentLevel] = 1;
249 | }
250 |
251 | for (const originalListItem of Array.from(originalListItems)) {
252 | if (originalListItem instanceof HTMLElement && originalListItem.getAttribute('data-type') === "NodeListItem") {
253 | const paragraphElement = originalListItem.querySelector('[data-type="NodeParagraph"]');
254 | if (paragraphElement) {
255 | let itemTextFromDOM = window.Lute.BlockDOM2Content(paragraphElement.innerHTML);
256 |
257 | let comparableItemText = itemTextFromDOM.includes(' -- ')
258 | ? itemTextFromDOM.replace(/^.*?--\s*/, "").trim()
259 | : stripIconPrefix(itemTextFromDOM);
260 |
261 | const correspondingIndexNode = currentStack.stack[stackIndex];
262 |
263 | if (correspondingIndexNode && correspondingIndexNode.text === comparableItemText.replace(/!\[\]\([^)]*\)/g, '').trim() && correspondingIndexNode.blockId) {
264 | let prefix = " ".repeat(indentLevel);
265 | if (correspondingIndexNode.listType === "ordered") {
266 | prefix += `${orderedListCounters[indentLevel]++}. `;
267 | } else if (correspondingIndexNode.listType === "task") {
268 | prefix += `- ${correspondingIndexNode.taskStatus} `;
269 | } else { // unordered
270 | prefix += "- ";
271 | }
272 |
273 | const gdcIconInput = correspondingIndexNode.icon;
274 | const gdcHasChildInput = correspondingIndexNode.subFileCount != undefined && correspondingIndexNode.subFileCount != 0;
275 | let iconPrefix = `${getProcessedDocIcon(gdcIconInput, gdcHasChildInput)} `;
276 |
277 | const node = correspondingIndexNode;
278 |
279 | markdown += `${prefix}[${iconPrefix.trim()}](siyuan://blocks/${node.blockId}) -- ${node.originalMarkdown}\n`;
280 |
281 | const nestedListElement = originalListItem.querySelector('[data-type="NodeList"]');
282 | if (nestedListElement instanceof HTMLElement && !correspondingIndexNode.children.isEmpty()) {
283 | markdown += await reconstructListMarkdownWithLinks(nestedListElement, correspondingIndexNode.children, indentLevel + 1, { ...orderedListCounters });
284 | }
285 | }
286 | }
287 | stackIndex++;
288 | }
289 | }
290 | return markdown;
291 | }
--------------------------------------------------------------------------------
/src/creater/createIndex.ts:
--------------------------------------------------------------------------------
1 | // import { Dialog } from 'siyuan';
2 | import { client, escapeHtml, i18n, isMobile, plugin } from '../utils';
3 | import { CONFIG, settings } from '../settings';
4 | import { IndexQueue, IndexQueueNode } from '../indexnode';
5 | import { onCreatenbiButton } from './createnotebookindex';
6 |
7 | let indexQueue: IndexQueue;
8 |
9 | /**
10 | * 左键点击topbar按钮插入目录
11 | * @returns void
12 | */
13 | export async function insert() {
14 | //载入配置
15 | await settings.load();
16 |
17 | //寻找当前编辑的文档的id
18 | let parentId = getDocid();
19 | if (parentId == null) {
20 | client.pushErrMsg({
21 | msg: i18n.errorMsg_empty,
22 | timeout: 3000
23 | });
24 | return;
25 | }
26 |
27 | //获取文档数据
28 | let block = await client.getBlockInfo({
29 | id: parentId
30 | });
31 |
32 | //插入目录
33 | let data = '';
34 | indexQueue = new IndexQueue();
35 | await createIndex(block.data.box, block.data.path, indexQueue);
36 | data = queuePopAll(indexQueue, data);
37 | console.log(data);
38 | if (data != '') {
39 | await insertData(parentId, data, "index");
40 | } else {
41 | client.pushErrMsg({
42 | msg: i18n.errorMsg_miss,
43 | timeout: 3000
44 | });
45 | }
46 | }
47 |
48 | /**
49 | * 点击插入带大纲的目录
50 | * @returns void
51 | */
52 | // export async function insertButton(dialog?: Dialog) {
53 | export async function insertButton() {
54 | //载入配置
55 | await settings.load();
56 |
57 | // settings.set("autoUpdate", false);
58 |
59 | //寻找当前编辑的文档的id
60 | let parentId = getDocid();
61 | if (parentId == null) {
62 | client.pushErrMsg({
63 | msg: i18n.errorMsg_empty,
64 | timeout: 3000
65 | });
66 | return;
67 | }
68 |
69 | //获取文档数据
70 | let block = await client.getBlockInfo({
71 | id: parentId
72 | });
73 |
74 | //插入目录
75 | let data = '';
76 | indexQueue = new IndexQueue();
77 | await createIndexandOutline(block.data.box, block.data.path, indexQueue);
78 | data = queuePopAll(indexQueue, data);
79 | console.log(data);
80 | if (data != '') {
81 | await insertDataSimple(parentId, data);
82 | } else {
83 | client.pushErrMsg({
84 | msg: i18n.errorMsg_miss,
85 | timeout: 3000
86 | });
87 | return;
88 | }
89 | // dialog.destroy();
90 | }
91 |
92 | /**
93 | * 点击插入大纲
94 | * @returns void
95 | */
96 | export async function insertDocButton() {
97 | //载入配置
98 | await settings.load();
99 |
100 | //寻找当前编辑的文档的id
101 | let parentId = getDocid();
102 | if (parentId == null) {
103 | client.pushErrMsg({
104 | msg: i18n.errorMsg_empty,
105 | timeout: 3000
106 | });
107 | return;
108 | }
109 |
110 | //插入目录
111 | let data = '';
112 |
113 | let outlineData = await requestGetDocOutline(parentId);
114 | // console.log(outlineData);
115 | data = insertOutline(data, outlineData, 0, 0);
116 | if (data != '') {
117 | await insertData(parentId, data, "outline");
118 | } else {
119 | client.pushErrMsg({
120 | msg: i18n.errorMsg_miss_outline,
121 | timeout: 3000
122 | });
123 | return;
124 | }
125 | }
126 |
127 | //todo
128 | /**
129 | * 点击插入笔记本目录
130 | * @returns void
131 | */
132 | export async function insertNotebookButton() {
133 | //载入配置
134 | await settings.load();
135 |
136 | onCreatenbiButton();
137 |
138 | }
139 |
140 | /**
141 | * 文档构建器构建后插入目录
142 | * @param notebookId 笔记本id
143 | * @param parentId 目录块id
144 | * @param path 目录块path
145 | */
146 | export async function insertAfter(notebookId: string, parentId: string, path: string) {
147 | //载入配置
148 | await settings.load();
149 |
150 | //插入目录
151 | let data = '';
152 | indexQueue = new IndexQueue();
153 | await createIndex(notebookId, path, indexQueue);
154 | data = queuePopAll(indexQueue, data);
155 | if (data != '') {
156 | await insertDataAfter(parentId, data, "index");
157 | } else{
158 | client.pushErrMsg({
159 | msg: i18n.errorMsg_miss,
160 | timeout: 3000
161 | });
162 | }
163 | }
164 |
165 | /**
166 | * 自动更新目录
167 | * @param notebookId 笔记本id
168 | * @param path 目标文档路径
169 | * @param parentId 目标文档id
170 | */
171 | export async function insertAuto(notebookId: string, path: string, parentId: string) {
172 |
173 | //载入配置
174 | await settings.load();
175 |
176 | let rs = await client.sql({
177 | stmt: `SELECT * FROM blocks WHERE root_id = '${parentId}' AND ial like '%custom-index-create%' order by updated desc limit 1`
178 | })
179 |
180 | // console.log(path);
181 | if (rs.data[0]?.id != undefined) {
182 | let ial = await client.getBlockAttrs({
183 | id: rs.data[0].id
184 | });
185 |
186 | //载入配置
187 | let str = ial.data["custom-index-create"];
188 | if (str) { // Only parse if str is not undefined or null
189 | try {
190 | settings.loadSettings(JSON.parse(str));
191 | } catch (e) {
192 | console.error("Error parsing custom-index-create settings:", e);
193 | // Optionally, push an error message to the user
194 | client.pushErrMsg({
195 | msg: i18n.errorMsg_settingsParseError,
196 | timeout: 3000
197 | });
198 | return; // Stop execution if settings are invalid
199 | }
200 | } else {
201 | // If no custom settings, use defaults or handle as appropriate
202 | console.log("No custom-index-create settings found, using defaults.");
203 | }
204 | if (!settings.get("autoUpdate")) {
205 | return;
206 | }
207 | //插入目录
208 | let data = '';
209 | indexQueue = new IndexQueue();
210 | await createIndex(notebookId, path, indexQueue);
211 | data = queuePopAll(indexQueue, data);
212 | // console.log(plugin.data);
213 | // console.log("data=" + data);
214 | if (data != '') {
215 | await insertDataAfter(rs.data[0].id, data, "index");
216 | } else {
217 | client.pushErrMsg({
218 | msg: i18n.errorMsg_miss,
219 | timeout: 3000
220 | });
221 | }
222 | }
223 |
224 | }
225 |
226 | /**
227 | * 自动更新大纲
228 | * @param notebookId 笔记本id
229 | * @param path 目标文档路径
230 | * @param parentId 目标文档id
231 | */
232 | export async function insertOutlineAuto(parentId: string) {
233 |
234 | //载入配置
235 | await settings.load();
236 |
237 | let rs = await client.sql({
238 | stmt: `SELECT * FROM blocks WHERE root_id = '${parentId}' AND ial like '%custom-outline-create%' order by updated desc limit 1`
239 | })
240 |
241 |
242 | // console.log(path);
243 |
244 | if (rs.data[0]?.id != undefined) {
245 | let ial = await client.getBlockAttrs({
246 | id: rs.data[0].id
247 | });
248 | //载入配置
249 | let str = ial.data["custom-outline-create"];
250 | // console.log(str);
251 | settings.loadSettings(JSON.parse(str));
252 | if (!settings.get("outlineAutoUpdate")) {
253 | return;
254 | }
255 | //插入目录
256 | let data = '';
257 | let outlineData = await requestGetDocOutline(parentId);
258 | data = insertOutline(data, outlineData, 0, 0);
259 | // console.log(plugin.data);
260 | // console.log("data=" + data);
261 | if (data != '') {
262 | await insertDataAfter(rs.data[0].id, data, "outline");
263 | } else {
264 | client.pushErrMsg({
265 | msg: i18n.errorMsg_miss,
266 | timeout: 3000
267 | });
268 | }
269 |
270 | }
271 |
272 | }
273 |
274 | //获取当前文档id
275 | export function getDocid() {
276 | if (isMobile)
277 | return document.querySelector('#editor .protyle-content .protyle-background')?.getAttribute("data-node-id");
278 | else
279 | return document.querySelector('.layout__wnd--active .protyle.fn__flex-1:not(.fn__none) .protyle-background')?.getAttribute("data-node-id");
280 | }
281 |
282 | async function requestGetDocOutline(blockId: string) {
283 | let response = await client.getDocOutline({
284 | id: blockId
285 | });
286 | let result = response.data;
287 | if (result == null) return [];
288 | return result;
289 | }
290 |
291 | function insertOutline(data: string, outlineData: any[], tab: number, stab: number) {
292 |
293 | tab++;
294 |
295 | //生成写入文本
296 | // console.log("outlineData.length:" + outlineData.length)
297 | for (let outline of outlineData) {
298 | let id = outline.id;
299 | let name = "";
300 | if (outline.depth == 0) {
301 | name = outline.name;
302 | } else {
303 | name = outline.content;
304 | }
305 |
306 | // let icon = doc.icon;
307 | let subOutlineCount = outline.count;
308 | for (let n = 1; n <= stab; n++) {
309 | data += ' ';
310 | }
311 |
312 | data += "> ";
313 |
314 | for (let n = 1; n < tab - stab; n++) {
315 | data += ' ';
316 | }
317 |
318 | //转义
319 | name = escapeHtml(name);
320 |
321 | //应用设置
322 | let listType = settings.get("listTypeOutline") == "unordered" ? true : false;
323 | if (listType) {
324 | data += "* ";
325 | } else {
326 | data += "1. ";
327 | }
328 |
329 | //置入数据
330 | let outlineType = settings.get("outlineType") == "copy" ? true : false;
331 | let at = settings.get("at") ? "@" : "";
332 |
333 | if(outlineType){
334 | data += `${at}${name}((${id} '*'))\n`;
335 | } else {
336 | outlineType = settings.get("outlineType") == "ref" ? true : false;
337 | if (outlineType) {
338 | data += `[${at}${name}](siyuan://blocks/${id})\n`;
339 | } else {
340 | data += `((${id} '${at}${name}'))\n`;
341 | }
342 | }
343 |
344 | //`((id "锚文本"))`
345 | if (subOutlineCount > 0) {//获取下一层级子文档
346 | if (outline.depth == 0) {
347 | data = insertOutline(data, outline.blocks, tab, stab);
348 | } else {
349 | data = insertOutline(data, outline.children, tab, stab);
350 | }
351 | }
352 |
353 | }
354 | return data;
355 | }
356 |
357 |
358 |
359 | //获取图标
360 | export function getProcessedDocIcon(icon: string, hasChild: boolean) {
361 | if (icon == '' || icon == undefined) {
362 | return hasChild ? "📑" : "📄";
363 | } else if (icon.indexOf(".") != -1) {
364 | if (icon.indexOf("http://") != -1 || icon.indexOf("https://") != -1) {
365 | return hasChild ? "📑" : "📄";
366 | } else {
367 | // 移除扩展名
368 | let removeFileFormat = icon.substring(0, icon.lastIndexOf("."));
369 | return `:${removeFileFormat}:`;
370 | }
371 | } else if (icon.includes("api/icon/getDynamicIcon")) {
372 | return ``;
373 | } else {
374 | // If it's not a hex string, and not a URL, and not a file extension, assume it's a direct emoji.
375 | // This is a heuristic, as a robust emoji check is complex.
376 | if (!/^[0-9a-fA-F-]+$/.test(icon) && !icon.includes("http://") && !icon.includes("https://") && icon.length <= 4) {
377 | console.log(`[Gemini-20251024-1] getProcessedDocIcon: Assuming direct emoji: '${icon}'`);
378 | return icon;
379 | }
380 |
381 | let result = "";
382 | for (const element of icon.split("-")) {
383 | const codePoint = parseInt(element, 16);
384 | if (isNaN(codePoint)) {
385 | // If any part is not a valid hex, return default icons
386 | console.log(`[Gemini-20251024-1] getProcessedDocIcon: parseInt failed for element '${element}', returning default.`);
387 | return hasChild ? "📑" : "📄";
388 | }
389 | result += String.fromCodePoint(codePoint);
390 | }
391 | console.log(`[Gemini-20251024-1] getProcessedDocIcon: For icon '${icon}', final result: '${result}'`);
392 | return result;
393 | }
394 | }
395 |
396 | //创建目录
397 | async function createIndexandOutline(notebook: any, ppath: any, pitem: IndexQueue, tab = 0) {
398 |
399 | if (settings.get("depth") == 0 || settings.get("depth") > tab) {
400 |
401 | let docs;
402 | try {
403 | docs = await client.listDocsByPath({
404 | notebook: notebook,
405 | path: ppath
406 | });
407 | } catch (err) {
408 | console.error(`Failed to list docs for path "${ppath}":`, err);
409 | return; // Stop processing this branch if listing docs fails
410 | }
411 |
412 | if (!docs?.data?.files?.length) {
413 | return; // No sub-documents, which is valid, so just return.
414 | }
415 |
416 | tab++;
417 |
418 | //生成写入文本
419 | for (let doc of docs.data.files) {
420 | try {
421 | let data = "";
422 | let id = doc.id;
423 | let name = doc.name.slice(0, -3);
424 | let icon = doc.icon;
425 | let subFileCount = doc.subFileCount;
426 | let path = doc.path;
427 | for (let n = 1; n < tab; n++) {
428 | data += ' ';
429 | }
430 |
431 | //转义
432 | name = escapeHtml(name);
433 |
434 | //应用设置
435 | let listType = settings.get("listType") == "unordered" ? true : false;
436 | if (listType) {
437 | data += "* ";
438 | } else {
439 | data += "1. ";
440 | }
441 |
442 | if (settings.get("icon")) {
443 | data += `${getProcessedDocIcon(icon, subFileCount != 0)} `;
444 | }
445 |
446 | //置入数据
447 | let linkType = settings.get("linkType") == "ref" ? true : false;
448 | if (linkType) {
449 | data += `[${name}](siyuan://blocks/${id})\n`;
450 | } else {
451 | data += `((${id} '${name}'))\n`;
452 | }
453 |
454 | let outlineData = await requestGetDocOutline(id);
455 | data = insertOutline(data, outlineData, tab, tab);
456 |
457 | let item = new IndexQueueNode(tab, data);
458 | pitem.push(item);
459 | //`((id "锚文本"))`
460 | if (subFileCount > 0) {//获取下一层级子文档
461 | await createIndexandOutline(notebook, path, item.children, tab);
462 | }
463 | } catch (err) {
464 | console.error(`Failed to process document "${doc.id}" in createIndexandOutline:`, err);
465 | // Continue to the next document
466 | }
467 | }
468 | }
469 | }
470 |
471 | /**
472 | * 创建目录
473 | * @param notebook 笔记本id
474 | * @param ppath 父文档路径
475 | * @param data 数据
476 | * @param tab 深度
477 | * @returns 待插入数据
478 | */
479 | async function createIndex(notebook: any, ppath: any, pitem: IndexQueue, tab = 0) {
480 |
481 | if (settings.get("depth") == 0 || settings.get("depth") > tab) {
482 |
483 | let docs = await client.listDocsByPath({
484 | notebook: notebook,
485 | path: ppath
486 | });
487 | tab++;
488 |
489 | //生成写入文本
490 | for (let doc of docs.data.files) {
491 |
492 | let data = "";
493 | let id = doc.id;
494 | let name = doc.name.slice(0, -3);
495 | let icon = doc.icon;
496 | let subFileCount = doc.subFileCount;
497 | let path = doc.path;
498 | for (let n = 1; n < tab; n++) {
499 | data += ' ';
500 | }
501 |
502 | //转义
503 | name = escapeHtml(name);
504 |
505 | //应用设置
506 | let listType = settings.get("listType") == "unordered" ? true : false;
507 | if (listType) {
508 | data += "* ";
509 | } else {
510 | data += "1. ";
511 | }
512 |
513 | // if(settings.get("fold") == tab){
514 | // data += '{: fold="1"}';
515 | // }
516 |
517 | if (settings.get("icon")) {
518 | data += `${getProcessedDocIcon(icon, subFileCount != 0)} `;
519 | }
520 |
521 | //置入数据
522 | let linkType = settings.get("linkType") == "ref" ? true : false;
523 | if (linkType) {
524 | data += `[${name}](siyuan://blocks/${id})\n`;
525 | } else {
526 | data += `((${id} '${name}'))\n`;
527 | }
528 | // console.log(data);
529 | let item = new IndexQueueNode(tab, data);
530 | pitem.push(item);
531 | if (subFileCount > 0) {//获取下一层级子文档
532 | await createIndex(notebook, path, item.children, tab);
533 | }
534 |
535 | }
536 | }
537 | }
538 |
539 |
540 | //插入数据
541 | export async function insertDataSimple(id: string, data: string) {
542 |
543 | await client.insertBlock({
544 | data: data,
545 | dataType: 'markdown',
546 | parentID: id
547 | });
548 |
549 | client.pushMsg({
550 | msg: i18n.msg_success,
551 | timeout: 3000
552 | });
553 |
554 | }
555 |
556 | //插入数据
557 | async function insertData(id: string, data: string, type: string) {
558 |
559 | let attrs : any;
560 |
561 | if(type == "index"){
562 | attrs = {
563 | "custom-index-create": JSON.stringify(plugin.data[CONFIG])
564 | };
565 | } else if(type == "outline"){
566 | attrs = {
567 | "custom-outline-create": JSON.stringify(plugin.data[CONFIG])
568 | };
569 | }
570 |
571 | try {
572 | let rs = await client.sql({
573 | stmt: `SELECT * FROM blocks WHERE root_id = '${id}' AND ial like '%custom-${type}-create%' order by updated desc limit 1`
574 | });
575 | if (rs.data[0]?.id == undefined) {
576 | let result = await client.insertBlock({
577 | data: data,
578 | dataType: 'markdown',
579 | parentID: id
580 | });
581 | await client.setBlockAttrs({
582 | attrs: attrs,
583 | id: result.data[0].doOperations[0].id
584 | });
585 | client.pushMsg({
586 | msg: i18n.msg_success,
587 | timeout: 3000
588 | });
589 | } else {
590 | let result = await client.updateBlock({
591 | data: data,
592 | dataType: 'markdown',
593 | id: rs.data[0].id
594 | });
595 | await client.setBlockAttrs({
596 | attrs: attrs,
597 | id: result.data[0].doOperations[0].id
598 | });
599 | client.pushMsg({
600 | msg: i18n.update_success,
601 | timeout: 3000
602 | });
603 | }
604 | } catch (error) {
605 | client.pushErrMsg({
606 | msg: i18n.dclike,
607 | timeout: 3000
608 | });
609 | }
610 |
611 |
612 | }
613 |
614 | //插入数据
615 | async function insertDataAfter(id: string, data: string, type: string) {
616 |
617 | let attrs : any;
618 |
619 | if(type == "index"){
620 | attrs = {
621 | "custom-index-create": JSON.stringify(plugin.data[CONFIG])
622 | };
623 | } else if(type == "outline"){
624 | attrs = {
625 | "custom-outline-create": JSON.stringify(plugin.data[CONFIG])
626 | };
627 | }
628 |
629 | let result = await client.updateBlock({
630 | data: data,
631 | dataType: "markdown",
632 | id: id
633 | });
634 |
635 | await client.setBlockAttrs({
636 | id: result.data[0].doOperations[0].id,
637 | attrs: attrs
638 | });
639 |
640 | }
641 |
642 | function queuePopAll(queue: IndexQueue, data: string) {
643 |
644 | if (queue.getFront()?.depth == undefined) {
645 | return "";
646 | }
647 |
648 | let item: IndexQueueNode;
649 |
650 | let num = 0;
651 | let temp = 0;
652 | let times = 0;
653 | let depth = queue.getFront().depth;
654 | if (depth == 1 && settings.get("col") != 1) {
655 | data += "{{{col\n";
656 | temp = Math.trunc(queue.getSize() / settings.get("col"));
657 | times = settings.get("col") - 1;
658 | }
659 |
660 | while (!queue.isEmpty()) {
661 | num++;
662 | item = queue.pop();
663 |
664 | //有子层级时才折叠
665 | if (!item.children.isEmpty() && settings.get("fold")!=0 &&settings.get("fold") <= item.depth ) {
666 | let n = 0;
667 | let listType = settings.get("listType") == "unordered" ? true : false;
668 | if (listType) {
669 | n = item.text.indexOf("*");
670 | item.text = item.text.substring(0, n + 2) + '{: fold="1"}' + item.text.substring(n + 2);
671 | } else {
672 | n = item.text.indexOf("1");
673 | item.text = item.text.substring(0, n + 3) + '{: fold="1"}' + item.text.substring(n + 3);
674 | }
675 | }
676 | data += item.text;
677 | // console.log("queuePopAll", item.text);
678 |
679 | if (!item.children.isEmpty()) {
680 | data = queuePopAll(item.children, data);
681 | }
682 | if (item.depth == 1 && num == temp && times > 0) {
683 | data += `\n{: id}\n`;
684 | num = 0;
685 | times--;
686 | }
687 | }
688 | if (depth == 1 && settings.get("col") != 1) {
689 | data += "}}}";
690 | }
691 | return data;
692 | }
--------------------------------------------------------------------------------
/src/components/setting copy 2.svelte:
--------------------------------------------------------------------------------
1 |
140 |
141 |
142 |
143 |
144 |
145 | - {
151 | tabbarfocus = "normal";
152 | eventBus.emit("updateSettings");
153 | }}
154 | >
155 |
158 | {i18n.generalSettings}
159 |
160 |
161 |
162 |
177 |
178 |
179 | - {
185 | tabbarfocus = "template";
186 | onGetIndexTemplate();
187 | }}
188 | >
189 |
192 | {i18n.templateSettings}
193 |
194 |
195 |
196 | - (tabbarfocus = "extra")}
202 | >
203 |
206 | {i18n.extraSettings}
207 |
208 |
209 |
210 |
222 |
223 |
224 |
230 |
231 |
232 |
233 |
234 |
235 |
{
241 | normalfocus = "indexSettings";
242 | // onGetIndexTemplate();
243 | }}
244 | >
245 |
246 | {i18n.indexSettings}
247 |
248 |
249 |
250 |
251 |
{
257 | normalfocus = "outlineSettings";
258 | // onGetTemplate();
259 | }}
260 | >
261 |
262 | {i18n.outlineSettings}
263 |
264 |
265 |
266 |
267 |
273 |
274 |
280 |
286 |
292 |
298 |
304 |
310 |
316 |
320 |
321 |
322 |
330 |
331 |
332 |
333 |
339 |
345 |
351 |
357 |
363 |
379 |
380 |
381 |
382 |
383 |
424 |
430 |
431 |
432 |
433 |
434 |
435 |
436 |
{
442 | templatefocus = "indexTemplate";
443 | // onGetIndexTemplate();
444 | }}
445 | >
446 |
447 | 目录模板
448 |
449 |
450 |
451 |
452 |
{
458 | templatefocus = "outlineTemplate";
459 | // onGetTemplate();
460 | }}
461 | >
462 |
463 | 大纲模板
464 |
465 |
466 |
467 |
468 |
475 | {#each Object.entries(plugin.data) as [key]}
476 | {#if key != "config" && key.indexOf("index") != -1}
477 |
478 |
479 |
480 | {/if}
481 | {/each}
482 |
483 |
490 | {#each Object.entries(plugin.data) as [key]}
491 | {#if key != "config" && key.indexOf("outline") != -1}
492 |
493 |
494 |
495 | {/if}
496 | {/each}
497 |
498 |
499 |
500 |
507 |
508 |
514 |
520 |
521 |
549 |
550 |
551 |
--------------------------------------------------------------------------------