`,
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 | }
--------------------------------------------------------------------------------
/src/creater/createtemplate.ts:
--------------------------------------------------------------------------------
1 | import { Dialog, fetchSyncPost, showMessage } from "siyuan";
2 | import { i18n } from "../utils";
3 | import TemplateDialog from "../components/dialog/template-dialog.svelte"
4 | import { settings } from "../settings";
5 | import { eventBus } from "../event/eventbus";
6 |
7 | /**
8 | * 创建配置模板
9 | * @returns void
10 | */
11 | export async function onCreateTemplateButton() {
12 | getNameDialog();
13 | // console.log("create template");
14 |
15 | }
16 |
17 | /**
18 | * 接收模板名弹窗
19 | */
20 | function getNameDialog(){
21 | const settingsDialog = "index-get-template-name";
22 |
23 | const dialog = new Dialog({
24 | title: i18n.settingsTab.items.templateDialog.title,
25 | content: `
`,
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/event/blockiconevent.ts:
--------------------------------------------------------------------------------
1 | import { IndexStackNode, IndexStack } from "../indexnode";
2 | import { insertAfter } from "../creater/createIndex";
3 | import { settings } from "../settings";
4 | import { client, i18n } from "../utils";
5 |
6 | //目录栈
7 | let indexStack : IndexStack;
8 |
9 | /**
10 | * 块标菜单回调
11 | * @param detail 事件细节
12 | * @returns void
13 | */
14 | export function buildDoc({ detail }: any) {
15 | //如果选中块大于1或不是列表块或未开启按钮,则直接结束
16 | if (detail.blockElements.length > 1 ||
17 | detail.blockElements[0].getAttribute('data-type') != "NodeList" ||
18 | !settings.get("docBuilder")) {
19 | return;
20 | }
21 | //插入按钮到块菜单
22 | detail.menu.addItem({
23 | icon: "iconList",
24 | label: i18n.settingsTab.items.docBuilder.title,
25 | click: async () => {
26 | await parseBlockDOM(detail);
27 | }
28 | });
29 | }
30 |
31 | /**
32 | * 解析detail中块的DOM
33 | * @param detail
34 | */
35 | async function parseBlockDOM(detail: any) {
36 | // console.log(detail);
37 | indexStack = new IndexStack();
38 | indexStack.notebookId = detail.protyle.notebookId;
39 | let docId = detail.blockElements[0].getAttribute("data-node-id");
40 | let block = detail.blockElements[0].childNodes;
41 | indexStack.basePath = await getRootDoc(docId);
42 | let docData = await client.getBlockInfo({
43 | id: detail.protyle.block.rootID
44 | });
45 | // let docData = await getParentDoc(detail.protyle.block.rootID);
46 | indexStack.pPath = docData.data.path.slice(0, -3);
47 | await parseChildNodes(block,indexStack);
48 | await stackPopAll(indexStack);
49 | await insertAfter(indexStack.notebookId,docId,docData.data.path);
50 | // window.location.reload();
51 | }
52 |
53 | async function parseChildNodes(childNodes: any,pitem:IndexStack, tab = 0) {
54 | tab++;
55 | let newItem: IndexStack;
56 | for (const childNode of childNodes) {
57 | if (childNode.getAttribute('data-type') == "NodeListItem") {
58 | let sChildNodes = childNode.childNodes;
59 | for (const sChildNode of sChildNodes) {
60 | if (sChildNode.getAttribute('data-type') == "NodeParagraph") {
61 | //获取文档标题
62 | let text = window.Lute.BlockDOM2Content(sChildNode.innerHTML);
63 | // console.log(text);
64 | //创建文档
65 | let item = new IndexStackNode(tab,text);
66 | pitem.push(item);
67 | newItem = item.children;
68 | } else if (sChildNode.getAttribute('data-type') == "NodeList") {
69 | await parseChildNodes(sChildNode.childNodes,newItem,tab);
70 | }
71 | }
72 | }
73 | }
74 | }
75 |
76 | /**
77 | * 获取文档块路径
78 | * @param id 文档块id
79 | * @returns 文档块路径
80 | */
81 | async function getRootDoc(id:string){
82 |
83 | let response = await client.sql({
84 | stmt: `SELECT * FROM blocks WHERE id = '${id}'`
85 | });
86 |
87 | let result = response.data[0];
88 | return result?.hpath;
89 | }
90 |
91 | /**
92 | * 创建文档
93 | * @param notebookId 笔记本id
94 | * @param hpath 文档路径
95 | * @returns 响应内容
96 | */
97 | async function createDoc(notebookId:string,hpath:string){
98 |
99 | let response = await client.createDocWithMd({
100 | markdown: "",
101 | notebook: notebookId,
102 | path: hpath
103 | });
104 | return response.data;
105 |
106 | }
107 |
108 | /**
109 | * 全部出栈
110 | * @param stack 目录栈
111 | */
112 | async function stackPopAll(stack:IndexStack){
113 | let item : IndexStackNode;
114 | let temp = new IndexStack();
115 | while(!stack.isEmpty()){
116 | item = stack.pop();
117 |
118 | let text = item.text;
119 |
120 | // if(hasEmoji(text.slice(0,2))){
121 | // text = text.slice(3);
122 | // }
123 |
124 | let subPath = stack.basePath+"/"+text;
125 |
126 | item.path = await createDoc(indexStack.notebookId, subPath);
127 | item.path = stack.pPath + "/" + item.path
128 | temp.push(item);
129 | if(!item.children.isEmpty()){
130 | item.children.basePath = subPath;
131 | item.children.pPath = item.path;
132 | // await stackPopAll(item.children);
133 | stackPopAll(item.children); //可能更快
134 | }
135 | }
136 | temp.pPath = stack.pPath;
137 | // await sortDoc(temp);
138 | }
139 |
140 | // /**
141 | // * 文档排序
142 | // * @param item 文档id栈
143 | // */
144 | // async function sortDoc(item : IndexStack){
145 | // //构建真实顺序
146 | // let paths = [];
147 | // while(!item.isEmpty()){
148 | // paths.push(item.pop().path+".sy");
149 | // }
150 | // await requestChangeSort(paths,indexStack.notebookId);
151 | // }
152 |
153 | // /**
154 | // * 排序请求
155 | // * @param paths 路径顺序
156 | // * @param notebook 笔记本id
157 | // */
158 | // async function requestChangeSort(paths:any[],notebook:string){
159 | // await fetchSyncPost(
160 | // "/api/filetree/changeSort",
161 | // {
162 | // paths: paths,
163 | // notebook: notebook
164 | // }
165 | // );
166 | // }
--------------------------------------------------------------------------------
/src/event/eventbus.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * 事件类——发布者、订阅者模式
3 | */
4 | class EventBus {
5 | private events: {};
6 | constructor() {
7 | this.events = {};
8 | }
9 |
10 | //发布
11 | on(event: any, listener: any) {
12 | if (!this.events[event]) {
13 | this.events[event] = [];
14 | }
15 | this.events[event].push(listener);
16 | }
17 |
18 | //关闭
19 | off(event: any, listener: any) {
20 | if (!this.events[event]) {
21 | return;
22 | }
23 | const index = this.events[event].indexOf(listener);
24 | if (index >= 0) {
25 | this.events[event].splice(index, 1);
26 | }
27 | }
28 |
29 | //订阅
30 | emit(event: any, ...args: any[]) {
31 | if (!this.events[event]) {
32 | return;
33 | }
34 | this.events[event].forEach((listener:any) => {
35 | listener.apply(this, args);
36 | });
37 | }
38 |
39 | //订阅一次
40 | once(event: any, listener: any){
41 | function callback(...args: any[]){
42 | listener.apply(this, args);
43 | this.off(event,callback);
44 | }
45 | this.on(event,callback);
46 | }
47 | }
48 |
49 | export let eventBus = new EventBus();
--------------------------------------------------------------------------------
/src/event/protyleevent.ts:
--------------------------------------------------------------------------------
1 | import { insertAuto, insertOutlineAuto } from "../creater/createIndex";
2 | import { isMobile } from "../utils";
3 | // import { settings } from "./settings";
4 |
5 | /**
6 | * 文档加载完成事件回调
7 | * @param param0 事件细节
8 | * @returns void
9 | */
10 | export function updateIndex({ detail }: any) {
11 | // console.log(detail);
12 | // console.log(detail.protyle.element.className);
13 | //如果不为手机端且为聚焦状态,就直接返回,否则查询更新
14 | if (!isMobile) {
15 | if(
16 | //为搜索界面
17 | detail.protyle.element.className.indexOf("search") != -1 ||
18 | // 为浮窗
19 | // detail.model == undefined ||
20 | detail.protyle.block.showAll){
21 | // || !settings.get("autoUpdate")
22 | return;
23 | }
24 | }
25 | // console.log(detail);
26 | // 获取笔记本id
27 | let notebookId = detail.protyle.notebookId;
28 | // 获取文档块路径
29 | let path = detail.protyle.path;
30 | // 获取文档块id
31 | let parentId = detail.protyle.block.rootID;
32 | // 自动插入
33 | insertAuto(notebookId,path,parentId);
34 | insertOutlineAuto(parentId);
35 | }
--------------------------------------------------------------------------------
/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/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/index.ts:
--------------------------------------------------------------------------------
1 | import { Plugin } from "siyuan";
2 | import { setI18n, setPlugin } from "./utils";
3 | import { createDialog, initTopbar } from "./topbar";
4 | import { settings } from "./settings";
5 | import { buildDoc } from "./event/blockiconevent";
6 | import { updateIndex } from "./event/protyleevent";
7 |
8 | export default class IndexPlugin extends Plugin {
9 |
10 | //加载插件
11 | async onload() {
12 | console.log("IndexPlugin onload");
13 | this.init();
14 | await initTopbar();
15 | // await this.initSettings();
16 | await settings.initData();
17 | //监听块菜单事件
18 | this.eventBus.on("click-blockicon", buildDoc);
19 | //监听文档载入事件
20 | this.eventBus.on("loaded-protyle-static", updateIndex);
21 | // this.eventBus.on("ws-main",this.eventBusLog);
22 |
23 | }
24 | // onLayoutReady() {
25 | // initObserver();
26 | // }
27 |
28 | onunload() {
29 | this.eventBus.off("click-blockicon", buildDoc);
30 | this.eventBus.off("loaded-protyle-static", updateIndex);
31 | console.log("IndexPlugin onunload");
32 | }
33 |
34 | //获取i18n和插件类实例
35 | init(){
36 | setI18n(this.i18n);
37 | setPlugin(this);
38 | // console.log(this.getOpenedTab());
39 | }
40 |
41 | //输出事件detail
42 | // private eventBusLog({detail}: any) {
43 | // console.log(detail);
44 | // }
45 | async openSetting(){
46 | await createDialog();
47 | }
48 |
49 | }
--------------------------------------------------------------------------------
/src/indexnode.ts:
--------------------------------------------------------------------------------
1 | //目录栈类节点
2 | export class IndexStackNode {
3 | depth: number;
4 | text: string;
5 | path: string;
6 | children: IndexStack;
7 | constructor(depth: number, text: string) {
8 | this.depth = depth;
9 | this.text = text;
10 | this.children = new IndexStack();
11 | }
12 | }
13 |
14 | //目录栈类
15 | export class IndexStack {
16 | stack: IndexStackNode[];
17 | basePath: string;
18 | notebookId: string;
19 | pPath: string;
20 | constructor() {
21 | this.stack = [];
22 | }
23 |
24 | push(item: IndexStackNode) {
25 | return this.stack.push(item);
26 | }
27 |
28 | pop() {
29 | return this.stack.pop();
30 | }
31 |
32 | peek() {
33 | if (this.stack.length > 0) {
34 | return this.stack[this.stack.length - 1]
35 | }
36 | }
37 |
38 | clear() {
39 | this.stack = [];
40 | }
41 |
42 | isEmpty() {
43 | return this.stack.length === 0;
44 | }
45 | }
46 |
47 | //目录队列节点
48 | export class IndexQueueNode {
49 | depth: number;
50 | text: string;
51 | children: IndexQueue;
52 | constructor(depth: number, text: string) {
53 | this.depth = depth;
54 | this.text = text;
55 | this.children = new IndexQueue();
56 | }
57 | }
58 |
59 | //目录队列
60 | export class IndexQueue {
61 |
62 | queue: IndexQueueNode[];
63 |
64 | constructor() {
65 | this.queue = [];
66 | }
67 |
68 | push(item: IndexQueueNode) {
69 | return this.queue.push(item);
70 | }
71 |
72 | pop() {
73 | return this.queue.shift();
74 | }
75 |
76 | getFront() {
77 | return this.queue[0];
78 | }
79 | getRear() {
80 | return this.queue[this.queue.length - 1]
81 | }
82 |
83 | clear() {
84 | this.queue = [];
85 | }
86 |
87 | isEmpty() {
88 | return this.queue.length === 0;
89 | }
90 |
91 | getSize(){
92 | return this.queue.length;
93 | }
94 | }
--------------------------------------------------------------------------------
/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/slash.ts:
--------------------------------------------------------------------------------
1 | import { Protyle } from "siyuan";
2 | import { plugin } from "./utils";
3 |
4 | export function addSlash() {
5 | plugin.protyleSlash = [{
6 | filter: ["insert emoji 😊", "插入表情 😊", "crbqwx"],
7 | html: `
${this.i18n.insertEmoji}😊
`,
8 | id: "insertEmoji",
9 | callback(protyle: Protyle) {
10 | protyle.insert("😊");
11 | }
12 | }];
13 | }
--------------------------------------------------------------------------------
/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/utils.ts:
--------------------------------------------------------------------------------
1 | import { getFrontend } from "siyuan";
2 | import IndexPlugin from ".";
3 | import { Client } from "@siyuan-community/siyuan-sdk";
4 |
5 | /**
6 | * 延迟函数
7 | * @param time 时间
8 | * @returns 返回后需await
9 | */
10 | export function sleep (time:number) {
11 | return new Promise((resolve) => setTimeout(resolve, time));
12 | }
13 |
14 | //i18n全局实例
15 | export let i18n: any;
16 | export function setI18n(_i18n: any) {
17 | i18n = _i18n;
18 | }
19 |
20 | //插件全局对象
21 | export let plugin: IndexPlugin;
22 | export function setPlugin(_plugin: any) {
23 | plugin = _plugin;
24 | }
25 |
26 | /**
27 | * 替换字符串中的导致异常的字符字符
28 | * @param unsafe 待处理字符串
29 | * @returns 处理后的字符串
30 | */
31 | export function escapeHtml(unsafe:any){
32 | return unsafe.replaceAll('[', '\[')
33 | .replaceAll(']', '\]')
34 | .replaceAll('\'', ''')
35 | .replaceAll('\\', '\');
36 | }
37 |
38 | // /**
39 | // * 检测是否包含emoji
40 | // * @param text 待检测文本
41 | // * @returns 检测结果
42 | // */
43 | // export function hasEmoji(text:string){
44 | // const emojiRegex = /\p{Emoji}/u;
45 | // // const customemojiRegex = /:.*:/;
46 | // // return emojiRegex.test(text) && customemojiRegex.test(text);
47 | // return emojiRegex.test(text);
48 | // }
49 |
50 | //运行环境检测
51 | const frontEnd = getFrontend();
52 | export const isMobile = frontEnd === "mobile" || frontEnd === "browser-mobile";
53 |
54 | /* 初始化客户端 (默认使用 Axios 发起 XHR 请求) */
55 | export const client = new Client();
--------------------------------------------------------------------------------
/svelte.config.js:
--------------------------------------------------------------------------------
1 | import { vitePreprocess } from "@sveltejs/vite-plugin-svelte"
2 |
3 | export default {
4 | // Consult https://svelte.dev/docs#compile-time-svelte-preprocess
5 | // for more information about preprocessors
6 | preprocess: vitePreprocess(),
7 | }
8 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "useDefineForClassFields": true,
5 | "module": "ESNext",
6 | "lib": [
7 | "ES2020",
8 | "DOM",
9 | "DOM.Iterable"
10 | ],
11 | "skipLibCheck": true,
12 | /* Bundler mode */
13 | "moduleResolution": "Node",
14 | // "allowImportingTsExtensions": true,
15 | "allowSyntheticDefaultImports": true,
16 | "resolveJsonModule": true,
17 | "isolatedModules": true,
18 | "noEmit": true,
19 | "jsx": "preserve",
20 | /* Linting */
21 | "strict": false,
22 | "noUnusedLocals": true,
23 | "noUnusedParameters": true,
24 | "noFallthroughCasesInSwitch": true,
25 | /* Svelte */
26 | /**
27 | * Typecheck JS in `.svelte` and `.js` files by default.
28 | * Disable checkJs if you'd like to use dynamic types in JS.
29 | * Note that setting allowJs false does not prevent the use
30 | * of JS in `.svelte` files.
31 | */
32 | "allowJs": true,
33 | "checkJs": true,
34 | "types": [
35 | "node",
36 | "vite/client",
37 | "svelte"
38 | ]
39 | },
40 | "include": [
41 | "tools/**/*.ts",
42 | "src/**/*.ts",
43 | "src/**/*.d.ts",
44 | "src/**/*.tsx",
45 | "src/**/*.vue"
46 | ],
47 | "references": [
48 | {
49 | "path": "./tsconfig.node.json"
50 | }
51 | ],
52 | "root": "."
53 | }
--------------------------------------------------------------------------------
/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "skipLibCheck": true,
5 | "module": "ESNext",
6 | "moduleResolution": "Node",
7 | "allowSyntheticDefaultImports": true
8 | },
9 | "include": [
10 | "vite.config.ts"
11 | ]
12 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------