-
-
Hello {name} v{ver}
-
-
- {time_str}
-
+
+
appId:
+
+
${app?.appId}
+
+
+
API demo:
+
+
+ System current time: {time}
-
-
- Wellcome to plugin sample with vite & svelte
- {@html i18n.makesure}
-
-
+
+
+
Protyle demo: id = {blockID}
+
+
-
diff --git a/src/i18n/zh_CN.json b/src/i18n/zh_CN.json
deleted file mode 100644
index 7eaa3eb..0000000
--- a/src/i18n/zh_CN.json
+++ /dev/null
@@ -1,12 +0,0 @@
-{
- "addTopBarIcon": "使用插件添加一个顶栏按钮",
- "cancel": "取消",
- "save": "保存",
- "byeMenu": "再见,菜单!",
- "helloPlugin": "你好,插件!",
- "byePlugin": "再见,插件!",
- "name": "思源",
- "hello": {
- "makesure": "使用这个模板之前,请阅读
官方教程, 确保自己已经理解了插件的基本开发流程。"
- }
-}
\ No newline at end of file
diff --git a/src/index.scss b/src/index.scss
index cb1be95..e69de29 100644
--- a/src/index.scss
+++ b/src/index.scss
@@ -1,3 +0,0 @@
-#helloPanel {
- border: 1px rgb(189, 119, 119) dashed;
-}
\ No newline at end of file
diff --git a/src/index.ts b/src/index.ts
index 5e566c4..5b50c49 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,168 +1,607 @@
-import { Plugin, showMessage, confirm, Dialog, Menu, isMobile, openTab } from "siyuan";
-import "./index.scss";
+import {
+ Plugin,
+ showMessage,
+ confirm,
+ Dialog,
+ Menu,
+ openTab,
+ adaptHotkey,
+ getFrontend,
+ getBackend,
+ IModel,
+ Protyle,
+ openWindow,
+ IOperation,
+ Constants,
+ openMobileFileById,
+ lockScreen,
+ ICard,
+ ICardData
+} from "siyuan";
+import "@/index.scss";
-import HelloExample from "./hello.svelte";
-import DockExample from "./dock.svelte";
-import SettingPannel from "./libs/setting-panel.svelte";
+import HelloExample from "@/hello.svelte";
+import SettingExample from "@/setting-example.svelte";
+
+import { SettingUtils } from "./libs/setting-utils";
+import { svelteDialog } from "./libs/dialog";
const STORAGE_NAME = "menu-config";
const TAB_TYPE = "custom_tab";
const DOCK_TYPE = "dock_tab";
-export default class SamplePlugin extends Plugin {
+export default class PluginSample extends Plugin {
- private customTab: () => any;
+ customTab: () => IModel;
+ private isMobile: boolean;
+ private blockIconEventBindThis = this.blockIconEvent.bind(this);
+ private settingUtils: SettingUtils;
async onload() {
- showMessage("Hello SiYuan Plugin");
- this.data[STORAGE_NAME] = {readonlyText: "Readonly"};
+ this.data[STORAGE_NAME] = { readonlyText: "Readonly" };
+
+ console.log("loading plugin-sample", this.i18n);
+
+ const frontEnd = getFrontend();
+ this.isMobile = frontEnd === "mobile" || frontEnd === "browser-mobile";
+ // 图标的制作参见帮助文档
+ this.addIcons(`
+
+
+
+
+`);
const topBarElement = this.addTopBar({
- icon: "iconEmoji",
+ icon: "iconFace",
title: this.i18n.addTopBarIcon,
- position: "left",
+ position: "right",
callback: () => {
- this.addMenu(topBarElement.getBoundingClientRect());
+ if (this.isMobile) {
+ this.addMenu();
+ } else {
+ let rect = topBarElement.getBoundingClientRect();
+ // 如果被隐藏,则使用更多按钮
+ if (rect.width === 0) {
+ rect = document.querySelector("#barMore").getBoundingClientRect();
+ }
+ if (rect.width === 0) {
+ rect = document.querySelector("#barPlugins").getBoundingClientRect();
+ }
+ this.addMenu(rect);
+ }
}
});
- let div = document.createElement("div");
- new HelloExample({
- target: div,
- props: {
- name: this.i18n.name,
- i18n: this.i18n.hello
- }
+ const statusIconTemp = document.createElement("template");
+ statusIconTemp.innerHTML = `
+
+
`;
+ statusIconTemp.content.firstElementChild.addEventListener("click", () => {
+ confirm("⚠️", this.i18n.confirmRemove.replace("${name}", this.name), () => {
+ this.removeData(STORAGE_NAME).then(() => {
+ this.data[STORAGE_NAME] = { readonlyText: "Readonly" };
+ showMessage(`[${this.name}]: ${this.i18n.removedData}`);
+ });
+ });
});
- this.customTab = this.addTab({
- type: TAB_TYPE,
- init() {
- this.element.appendChild(div);
- console.log(this.element);
+ this.addStatusBar({
+ element: statusIconTemp.content.firstElementChild as HTMLElement,
+ });
+
+ this.addCommand({
+ langKey: "showDialog",
+ hotkey: "⇧⌘O",
+ callback: () => {
+ this.showDialog();
+ },
+ fileTreeCallback: (file: any) => {
+ console.log(file, "fileTreeCallback");
+ },
+ editorCallback: (protyle: any) => {
+ console.log(protyle, "editorCallback");
+ },
+ dockCallback: (element: HTMLElement) => {
+ console.log(element, "dockCallback");
+ },
+ });
+ this.addCommand({
+ langKey: "getTab",
+ hotkey: "⇧⌘M",
+ globalCallback: () => {
+ console.log(this.getOpenedTab());
},
- destroy() {
- console.log("destroy tab:", TAB_TYPE);
- }
});
this.addDock({
config: {
position: "LeftBottom",
size: { width: 200, height: 0 },
- icon: "iconEmoji",
+ icon: "iconSaving",
title: "Custom Dock",
+ hotkey: "⌥⌘W",
},
data: {
text: "This is my custom dock"
},
type: DOCK_TYPE,
- init() {
- this.component = new DockExample({
- target: this.element,
- props: {
- text: this.data.text,
- }
- });
+ resize() {
+ console.log(DOCK_TYPE + " resize");
+ },
+ update() {
+ console.log(DOCK_TYPE + " update");
+ },
+ init: (dock) => {
+ if (this.isMobile) {
+ dock.element.innerHTML = `
+
+ ${dock.data.text}
+
+
`;
+ } else {
+ dock.element.innerHTML = `
+
+
+
+ Custom Dock
+
+
+
+
+
+ ${dock.data.text}
+
+
`;
+ }
},
destroy() {
console.log("destroy dock:", DOCK_TYPE);
- this.component.$destroy();
}
});
+ this.settingUtils = new SettingUtils({
+ plugin: this, name: STORAGE_NAME
+ });
+ this.settingUtils.addItem({
+ key: "Input",
+ value: "",
+ type: "textinput",
+ title: "Readonly text",
+ description: "Input description",
+ action: {
+ // Called when focus is lost and content changes
+ callback: () => {
+ // Return data and save it in real time
+ let value = this.settingUtils.takeAndSave("Input");
+ console.log(value);
+ }
+ }
+ });
+ this.settingUtils.addItem({
+ key: "InputArea",
+ value: "",
+ type: "textarea",
+ title: "Readonly text",
+ description: "Input description",
+ // Called when focus is lost and content changes
+ action: {
+ callback: () => {
+ // Read data in real time
+ let value = this.settingUtils.take("InputArea");
+ console.log(value);
+ }
+ }
+ });
+ this.settingUtils.addItem({
+ key: "Check",
+ value: true,
+ type: "checkbox",
+ title: "Checkbox text",
+ description: "Check description",
+ action: {
+ callback: () => {
+ // Return data and save it in real time
+ let value = !this.settingUtils.get("Check");
+ this.settingUtils.set("Check", value);
+ console.log(value);
+ }
+ }
+ });
+ this.settingUtils.addItem({
+ key: "Select",
+ value: 1,
+ type: "select",
+ title: "Select",
+ description: "Select description",
+ options: {
+ 1: "Option 1",
+ 2: "Option 2"
+ },
+ action: {
+ callback: () => {
+ // Read data in real time
+ let value = this.settingUtils.take("Select");
+ console.log(value);
+ }
+ }
+ });
+ this.settingUtils.addItem({
+ key: "Slider",
+ value: 50,
+ type: "slider",
+ title: "Slider text",
+ description: "Slider description",
+ direction: "column",
+ slider: {
+ min: 0,
+ max: 100,
+ step: 1,
+ },
+ action:{
+ callback: () => {
+ // Read data in real time
+ let value = this.settingUtils.take("Slider");
+ console.log(value);
+ }
+ }
+ });
+ this.settingUtils.addItem({
+ key: "Btn",
+ value: "",
+ type: "button",
+ title: "Button",
+ description: "Button description",
+ button: {
+ label: "Button",
+ callback: () => {
+ showMessage("Button clicked");
+ }
+ }
+ });
+ this.settingUtils.addItem({
+ key: "Custom Element",
+ value: "",
+ type: "custom",
+ direction: "row",
+ title: "Custom Element",
+ description: "Custom Element description",
+ //Any custom element must offer the following methods
+ createElement: (currentVal: any) => {
+ let div = document.createElement('div');
+ div.style.border = "1px solid var(--b3-theme-primary)";
+ div.contentEditable = "true";
+ div.textContent = currentVal;
+ return div;
+ },
+ getEleVal: (ele: HTMLElement) => {
+ return ele.textContent;
+ },
+ setEleVal: (ele: HTMLElement, val: any) => {
+ ele.textContent = val;
+ }
+ });
+ this.settingUtils.addItem({
+ key: "Hint",
+ value: "",
+ type: "hint",
+ title: this.i18n.hintTitle,
+ description: this.i18n.hintDesc,
+ });
+
+ try {
+ this.settingUtils.load();
+ } catch (error) {
+ console.error("Error loading settings storage, probably empty config json:", error);
+ }
+
+
+ this.protyleSlash = [{
+ filter: ["insert emoji 😊", "插入表情 😊", "crbqwx"],
+ html: `
${this.i18n.insertEmoji}😊
`,
+ id: "insertEmoji",
+ callback(protyle: Protyle) {
+ protyle.insert("😊");
+ }
+ }];
+
+ this.protyleOptions = {
+ toolbar: ["block-ref",
+ "a",
+ "|",
+ "text",
+ "strong",
+ "em",
+ "u",
+ "s",
+ "mark",
+ "sup",
+ "sub",
+ "clear",
+ "|",
+ "code",
+ "kbd",
+ "tag",
+ "inline-math",
+ "inline-memo",
+ "|",
+ {
+ name: "insert-smail-emoji",
+ icon: "iconEmoji",
+ hotkey: "⇧⌘I",
+ tipPosition: "n",
+ tip: this.i18n.insertEmoji,
+ click(protyle: Protyle) {
+ protyle.insert("😊");
+ }
+ }],
+ };
+
+ console.log(this.i18n.helloPlugin);
}
onLayoutReady() {
- this.loadData(STORAGE_NAME);
+ // this.loadData(STORAGE_NAME);
+ this.settingUtils.load();
+ console.log(`frontend: ${getFrontend()}; backend: ${getBackend()}`);
+
+ console.log(
+ "Official settings value calling example:\n" +
+ this.settingUtils.get("InputArea") + "\n" +
+ this.settingUtils.get("Slider") + "\n" +
+ this.settingUtils.get("Select") + "\n"
+ );
+
+ let tabDiv = document.createElement("div");
+ new HelloExample({
+ target: tabDiv,
+ props: {
+ app: this.app,
+ }
+ });
+ this.customTab = this.addTab({
+ type: TAB_TYPE,
+ init() {
+ this.element.appendChild(tabDiv);
+ console.log(this.element);
+ },
+ beforeDestroy() {
+ console.log("before destroy tab:", TAB_TYPE);
+ },
+ destroy() {
+ console.log("destroy tab:", TAB_TYPE);
+ }
+ });
}
- onunload() {
+ async onunload() {
console.log(this.i18n.byePlugin);
showMessage("Goodbye SiYuan Plugin");
console.log("onunload");
}
- private wsEvent({ detail }: any) {
+ uninstall() {
+ console.log("uninstall");
+ }
+
+ async updateCards(options: ICardData) {
+ options.cards.sort((a: ICard, b: ICard) => {
+ if (a.blockID < b.blockID) {
+ return -1;
+ }
+ if (a.blockID > b.blockID) {
+ return 1;
+ }
+ return 0;
+ });
+ return options;
+ }
+
+ /**
+ * A custom setting pannel provided by svelte
+ */
+ openDIYSetting(): void {
+ let dialog = new Dialog({
+ title: "SettingPannel",
+ content: `
`,
+ width: "800px",
+ destroyCallback: (options) => {
+ console.log("destroyCallback", options);
+ //You'd better destroy the component when the dialog is closed
+ pannel.$destroy();
+ }
+ });
+ let pannel = new SettingExample({
+ target: dialog.element.querySelector("#SettingPanel"),
+ });
+ }
+
+ private eventBusPaste(event: any) {
+ // 如果需异步处理请调用 preventDefault, 否则会进行默认处理
+ event.preventDefault();
+ // 如果使用了 preventDefault,必须调用 resolve,否则程序会卡死
+ event.detail.resolve({
+ textPlain: event.detail.textPlain.trim(),
+ });
+ }
+
+ private eventBusLog({ detail }: any) {
console.log(detail);
}
- private blockIconEvent({detail}: any) {
- console.log(detail);
- detail.menu.addSeparator(0);
- const ids: string[] = [];
- detail.blockElements.forEach((item: HTMLElement) => {
- ids.push(item.getAttribute("data-node-id"));
- });
+ private blockIconEvent({ detail }: any) {
detail.menu.addItem({
- index: 1,
iconHTML: "",
- type: "readonly",
- label: "IDs
" + ids.join("
"),
+ label: this.i18n.removeSpace,
+ click: () => {
+ const doOperations: IOperation[] = [];
+ detail.blockElements.forEach((item: HTMLElement) => {
+ const editElement = item.querySelector('[contenteditable="true"]');
+ if (editElement) {
+ editElement.textContent = editElement.textContent.replace(/ /g, "");
+ doOperations.push({
+ id: item.dataset.nodeId,
+ data: item.outerHTML,
+ action: "update"
+ });
+ }
+ });
+ detail.protyle.getInstance().transaction(doOperations);
+ }
});
}
- private async addMenu(rect: DOMRect) {
+ private showDialog() {
+ // let dialog = new Dialog({
+ // title: `SiYuan ${Constants.SIYUAN_VERSION}`,
+ // content: `
`,
+ // width: this.isMobile ? "92vw" : "720px",
+ // destroyCallback() {
+ // // hello.$destroy();
+ // },
+ // });
+ // new HelloExample({
+ // target: dialog.element.querySelector("#helloPanel"),
+ // props: {
+ // app: this.app,
+ // }
+ // });
+ svelteDialog({
+ title: `SiYuan ${Constants.SIYUAN_VERSION}`,
+ width: this.isMobile ? "92vw" : "720px",
+ constructor: (container: HTMLElement) => {
+ return new HelloExample({
+ target: container,
+ props: {
+ app: this.app,
+ }
+ });
+ }
+ });
+ }
+
+ private addMenu(rect?: DOMRect) {
const menu = new Menu("topBarSample", () => {
console.log(this.i18n.byeMenu);
});
- menu.addItem({
- icon: "iconHelp",
- label: "Confirm",
- click() {
- confirm("Confirm", "Is this a confirm?", () => {
- showMessage("confirm");
- }, () => {
- showMessage("cancel");
- });
- }
- });
- menu.addItem({
- icon: "iconFeedback",
- label: "Message",
- click: () => {
- showMessage(this.i18n.helloPlugin);
- }
- });
menu.addItem({
icon: "iconInfo",
- label: "Dialog",
- click: () => this.openHelloInDialog()
- });
- menu.addItem({
- icon: "iconLayoutBottom",
- label: "Open Tab",
+ label: "Dialog(open help first)",
+ accelerator: this.commands[0].customHotkey,
click: () => {
- openTab({
- custom: {
- icon: "iconEmoji",
- title: "Custom Tab",
- data: {
- text: "This is my custom tab",
+ this.showDialog();
+ }
+ });
+ if (!this.isMobile) {
+ menu.addItem({
+ icon: "iconFace",
+ label: "Open Custom Tab",
+ click: () => {
+ const tab = openTab({
+ app: this.app,
+ custom: {
+ icon: "iconFace",
+ title: "Custom Tab",
+ data: {
+ text: "This is my custom tab",
+ },
+ id: this.name + TAB_TYPE
},
- fn: this.customTab
- },
- });
- }
- });
+ });
+ console.log(tab);
+ }
+ });
+ menu.addItem({
+ icon: "iconImage",
+ label: "Open Asset Tab(open help first)",
+ click: () => {
+ const tab = openTab({
+ app: this.app,
+ asset: {
+ path: "assets/paragraph-20210512165953-ag1nib4.svg"
+ }
+ });
+ console.log(tab);
+ }
+ });
+ menu.addItem({
+ icon: "iconFile",
+ label: "Open Doc Tab(open help first)",
+ click: async () => {
+ const tab = await openTab({
+ app: this.app,
+ doc: {
+ id: "20200812220555-lj3enxa",
+ }
+ });
+ console.log(tab);
+ }
+ });
+ menu.addItem({
+ icon: "iconSearch",
+ label: "Open Search Tab",
+ click: () => {
+ const tab = openTab({
+ app: this.app,
+ search: {
+ k: "SiYuan"
+ }
+ });
+ console.log(tab);
+ }
+ });
+ menu.addItem({
+ icon: "iconRiffCard",
+ label: "Open Card Tab",
+ click: () => {
+ const tab = openTab({
+ app: this.app,
+ card: {
+ type: "all"
+ }
+ });
+ console.log(tab);
+ }
+ });
+ menu.addItem({
+ icon: "iconLayout",
+ label: "Open Float Layer(open help first)",
+ click: () => {
+ this.addFloatLayer({
+ ids: ["20210428212840-8rqwn5o", "20201225220955-l154bn4"],
+ defIds: ["20230415111858-vgohvf3", "20200813131152-0wk5akh"],
+ x: window.innerWidth - 768 - 120,
+ y: 32
+ });
+ }
+ });
+ menu.addItem({
+ icon: "iconOpenWindow",
+ label: "Open Doc Window(open help first)",
+ click: () => {
+ openWindow({
+ doc: {id: "20200812220555-lj3enxa"}
+ });
+ }
+ });
+ } else {
+ menu.addItem({
+ icon: "iconFile",
+ label: "Open Doc(open help first)",
+ click: () => {
+ openMobileFileById(this.app, "20200812220555-lj3enxa");
+ }
+ });
+ }
menu.addItem({
- icon: "iconLayout",
- label: "Open Float Layer(open help)",
+ icon: "iconLock",
+ label: "Lockscreen",
click: () => {
- this.addFloatLayer({
- ids: ["20230523173319-xj1l3qu", "20230523173321-55o0w2n"],
- defIds: ["20230523173323-imgm9tp", "20230523173324-cxu98t3"],
- x: window.innerWidth - 768 - 120,
- y: 32
- });
- }
- });
- menu.addItem({
- icon: "iconTrashcan",
- label: "Remove Data",
- click: () => {
- this.removeData(STORAGE_NAME).then(() => {
- this.data[STORAGE_NAME] = {readonlyText: "Readonly"};
- });
+ lockScreen(this.app);
}
});
menu.addItem({
@@ -173,59 +612,325 @@ export default class SamplePlugin extends Plugin {
icon: "iconSelect",
label: "On ws-main",
click: () => {
- this.eventBus.on("ws-main", this.wsEvent);
+ this.eventBus.on("ws-main", this.eventBusLog);
}
}, {
icon: "iconClose",
label: "Off ws-main",
click: () => {
- this.eventBus.off("ws-main", this.wsEvent);
+ this.eventBus.off("ws-main", this.eventBusLog);
}
}, {
icon: "iconSelect",
label: "On click-blockicon",
click: () => {
- this.eventBus.on("click-blockicon", this.blockIconEvent);
+ this.eventBus.on("click-blockicon", this.blockIconEventBindThis);
}
}, {
icon: "iconClose",
label: "Off click-blockicon",
click: () => {
- this.eventBus.off("click-blockicon", this.blockIconEvent);
+ this.eventBus.off("click-blockicon", this.blockIconEventBindThis);
}
}, {
icon: "iconSelect",
label: "On click-pdf",
click: () => {
- this.eventBus.on("click-pdf", this.wsEvent);
+ this.eventBus.on("click-pdf", this.eventBusLog);
}
}, {
icon: "iconClose",
label: "Off click-pdf",
click: () => {
- this.eventBus.off("click-pdf", this.wsEvent);
+ this.eventBus.off("click-pdf", this.eventBusLog);
}
}, {
icon: "iconSelect",
label: "On click-editorcontent",
click: () => {
- this.eventBus.on("click-editorcontent", this.wsEvent);
+ this.eventBus.on("click-editorcontent", this.eventBusLog);
}
}, {
icon: "iconClose",
label: "Off click-editorcontent",
click: () => {
- this.eventBus.off("click-editorcontent", this.wsEvent);
+ this.eventBus.off("click-editorcontent", this.eventBusLog);
+ }
+ }, {
+ icon: "iconSelect",
+ label: "On click-editortitleicon",
+ click: () => {
+ this.eventBus.on("click-editortitleicon", this.eventBusLog);
+ }
+ }, {
+ icon: "iconClose",
+ label: "Off click-editortitleicon",
+ click: () => {
+ this.eventBus.off("click-editortitleicon", this.eventBusLog);
+ }
+ }, {
+ icon: "iconSelect",
+ label: "On click-flashcard-action",
+ click: () => {
+ this.eventBus.on("click-flashcard-action", this.eventBusLog);
+ }
+ }, {
+ icon: "iconClose",
+ label: "Off click-flashcard-action",
+ click: () => {
+ this.eventBus.off("click-flashcard-action", this.eventBusLog);
+ }
+ }, {
+ icon: "iconSelect",
+ label: "On open-noneditableblock",
+ click: () => {
+ this.eventBus.on("open-noneditableblock", this.eventBusLog);
+ }
+ }, {
+ icon: "iconClose",
+ label: "Off open-noneditableblock",
+ click: () => {
+ this.eventBus.off("open-noneditableblock", this.eventBusLog);
+ }
+ }, {
+ icon: "iconSelect",
+ label: "On loaded-protyle-static",
+ click: () => {
+ this.eventBus.on("loaded-protyle-static", this.eventBusLog);
+ }
+ }, {
+ icon: "iconClose",
+ label: "Off loaded-protyle-static",
+ click: () => {
+ this.eventBus.off("loaded-protyle-static", this.eventBusLog);
+ }
+ }, {
+ icon: "iconSelect",
+ label: "On loaded-protyle-dynamic",
+ click: () => {
+ this.eventBus.on("loaded-protyle-dynamic", this.eventBusLog);
+ }
+ }, {
+ icon: "iconClose",
+ label: "Off loaded-protyle-dynamic",
+ click: () => {
+ this.eventBus.off("loaded-protyle-dynamic", this.eventBusLog);
+ }
+ }, {
+ icon: "iconSelect",
+ label: "On switch-protyle",
+ click: () => {
+ this.eventBus.on("switch-protyle", this.eventBusLog);
+ }
+ }, {
+ icon: "iconClose",
+ label: "Off switch-protyle",
+ click: () => {
+ this.eventBus.off("switch-protyle", this.eventBusLog);
+ }
+ }, {
+ icon: "iconSelect",
+ label: "On destroy-protyle",
+ click: () => {
+ this.eventBus.on("destroy-protyle", this.eventBusLog);
+ }
+ }, {
+ icon: "iconClose",
+ label: "Off destroy-protyle",
+ click: () => {
+ this.eventBus.off("destroy-protyle", this.eventBusLog);
+ }
+ }, {
+ icon: "iconSelect",
+ label: "On open-menu-doctree",
+ click: () => {
+ this.eventBus.on("open-menu-doctree", this.eventBusLog);
+ }
+ }, {
+ icon: "iconClose",
+ label: "Off open-menu-doctree",
+ click: () => {
+ this.eventBus.off("open-menu-doctree", this.eventBusLog);
+ }
+ }, {
+ icon: "iconSelect",
+ label: "On open-menu-blockref",
+ click: () => {
+ this.eventBus.on("open-menu-blockref", this.eventBusLog);
+ }
+ }, {
+ icon: "iconClose",
+ label: "Off open-menu-blockref",
+ click: () => {
+ this.eventBus.off("open-menu-blockref", this.eventBusLog);
+ }
+ }, {
+ icon: "iconSelect",
+ label: "On open-menu-fileannotationref",
+ click: () => {
+ this.eventBus.on("open-menu-fileannotationref", this.eventBusLog);
+ }
+ }, {
+ icon: "iconClose",
+ label: "Off open-menu-fileannotationref",
+ click: () => {
+ this.eventBus.off("open-menu-fileannotationref", this.eventBusLog);
+ }
+ }, {
+ icon: "iconSelect",
+ label: "On open-menu-tag",
+ click: () => {
+ this.eventBus.on("open-menu-tag", this.eventBusLog);
+ }
+ }, {
+ icon: "iconClose",
+ label: "Off open-menu-tag",
+ click: () => {
+ this.eventBus.off("open-menu-tag", this.eventBusLog);
+ }
+ }, {
+ icon: "iconSelect",
+ label: "On open-menu-link",
+ click: () => {
+ this.eventBus.on("open-menu-link", this.eventBusLog);
+ }
+ }, {
+ icon: "iconClose",
+ label: "Off open-menu-link",
+ click: () => {
+ this.eventBus.off("open-menu-link", this.eventBusLog);
+ }
+ }, {
+ icon: "iconSelect",
+ label: "On open-menu-image",
+ click: () => {
+ this.eventBus.on("open-menu-image", this.eventBusLog);
+ }
+ }, {
+ icon: "iconClose",
+ label: "Off open-menu-image",
+ click: () => {
+ this.eventBus.off("open-menu-image", this.eventBusLog);
+ }
+ }, {
+ icon: "iconSelect",
+ label: "On open-menu-av",
+ click: () => {
+ this.eventBus.on("open-menu-av", this.eventBusLog);
+ }
+ }, {
+ icon: "iconClose",
+ label: "Off open-menu-av",
+ click: () => {
+ this.eventBus.off("open-menu-av", this.eventBusLog);
+ }
+ }, {
+ icon: "iconSelect",
+ label: "On open-menu-content",
+ click: () => {
+ this.eventBus.on("open-menu-content", this.eventBusLog);
+ }
+ }, {
+ icon: "iconClose",
+ label: "Off open-menu-content",
+ click: () => {
+ this.eventBus.off("open-menu-content", this.eventBusLog);
+ }
+ }, {
+ icon: "iconSelect",
+ label: "On open-menu-breadcrumbmore",
+ click: () => {
+ this.eventBus.on("open-menu-breadcrumbmore", this.eventBusLog);
+ }
+ }, {
+ icon: "iconClose",
+ label: "Off open-menu-breadcrumbmore",
+ click: () => {
+ this.eventBus.off("open-menu-breadcrumbmore", this.eventBusLog);
+ }
+ }, {
+ icon: "iconSelect",
+ label: "On open-menu-inbox",
+ click: () => {
+ this.eventBus.on("open-menu-inbox", this.eventBusLog);
+ }
+ }, {
+ icon: "iconClose",
+ label: "Off open-menu-inbox",
+ click: () => {
+ this.eventBus.off("open-menu-inbox", this.eventBusLog);
+ }
+ }, {
+ icon: "iconSelect",
+ label: "On input-search",
+ click: () => {
+ this.eventBus.on("input-search", this.eventBusLog);
+ }
+ }, {
+ icon: "iconClose",
+ label: "Off input-search",
+ click: () => {
+ this.eventBus.off("input-search", this.eventBusLog);
+ }
+ }, {
+ icon: "iconSelect",
+ label: "On paste",
+ click: () => {
+ this.eventBus.on("paste", this.eventBusPaste);
+ }
+ }, {
+ icon: "iconClose",
+ label: "Off paste",
+ click: () => {
+ this.eventBus.off("paste", this.eventBusPaste);
+ }
+ }, {
+ icon: "iconSelect",
+ label: "On open-siyuan-url-plugin",
+ click: () => {
+ this.eventBus.on("open-siyuan-url-plugin", this.eventBusLog);
+ }
+ }, {
+ icon: "iconClose",
+ label: "Off open-siyuan-url-plugin",
+ click: () => {
+ this.eventBus.off("open-siyuan-url-plugin", this.eventBusLog);
+ }
+ }, {
+ icon: "iconSelect",
+ label: "On open-siyuan-url-block",
+ click: () => {
+ this.eventBus.on("open-siyuan-url-block", this.eventBusLog);
+ }
+ }, {
+ icon: "iconClose",
+ label: "Off open-siyuan-url-block",
+ click: () => {
+ this.eventBus.off("open-siyuan-url-block", this.eventBusLog);
}
}]
});
menu.addSeparator();
+ menu.addItem({
+ icon: "iconSettings",
+ label: "Official Setting Dialog",
+ click: () => {
+ this.openSetting();
+ }
+ });
+ menu.addItem({
+ icon: "iconSettings",
+ label: "A custom setting dialog (by svelte)",
+ click: () => {
+ this.openDIYSetting();
+ }
+ });
menu.addItem({
icon: "iconSparkles",
- label: this.data[STORAGE_NAME] || "Readonly",
+ label: this.data[STORAGE_NAME].readonlyText || "Readonly",
type: "readonly",
});
- if (isMobile()) {
+ if (this.isMobile) {
menu.fullscreen();
} else {
menu.open({
@@ -235,38 +940,4 @@ export default class SamplePlugin extends Plugin {
});
}
}
-
- openSetting(): void {
- let dialog = new Dialog({
- title: "SettingPannel",
- content: `
`,
- width: "600px",
- destroyCallback: (options) => {
- console.log("destroyCallback", options);
- //You must destroy the component when the dialog is closed
- pannel.$destroy();
- }
- });
- let pannel = new SettingPannel({
- target: dialog.element.querySelector("#SettingPanel"),
- });
- }
-
- private openHelloInDialog() {
- let dialog = new Dialog({
- title: "Hello World",
- content: `
`,
- destroyCallback(options) {
- //You must destroy the component when the dialog is closed
- hello.$destroy();
- },
- });
- let hello = new HelloExample({
- target: dialog.element.querySelector("#helloPanel"),
- props: {
- name: this.i18n.name,
- i18n: this.i18n.hello
- }
- });
- }
}
diff --git a/src/libs/components/Form/form-input.svelte b/src/libs/components/Form/form-input.svelte
new file mode 100644
index 0000000..cbf5a7e
--- /dev/null
+++ b/src/libs/components/Form/form-input.svelte
@@ -0,0 +1,118 @@
+
+
+{#if type === "checkbox"}
+
+
+{:else if type === "textinput"}
+
+
+{:else if type === "textarea"}
+
+{:else if type === "number"}
+
+{:else if type === "button"}
+
+
+{:else if type === "select"}
+
+
+{:else if type == "slider"}
+
+
+
+
+{/if}
diff --git a/src/libs/components/Form/form-wrap.svelte b/src/libs/components/Form/form-wrap.svelte
new file mode 100644
index 0000000..4d8092e
--- /dev/null
+++ b/src/libs/components/Form/form-wrap.svelte
@@ -0,0 +1,53 @@
+
+
+
+{#if direction === "row"}
+
+
+
{title}
+
{@html description}
+
+
+
+
+
+
+{:else}
+
+
+
{title}
+
+ {@html description}
+
+
+
+
+
+{/if}
+
+
diff --git a/src/libs/components/Form/index.ts b/src/libs/components/Form/index.ts
new file mode 100644
index 0000000..a5c81c0
--- /dev/null
+++ b/src/libs/components/Form/index.ts
@@ -0,0 +1,6 @@
+import FormInput from './form-input.svelte';
+import FormWrap from './form-wrap.svelte';
+
+const Form = { Wrap: FormWrap, Input: FormInput };
+export default Form;
+export { FormInput, FormWrap };
diff --git a/src/libs/b3-typography.svelte b/src/libs/components/b3-typography.svelte
similarity index 100%
rename from src/libs/b3-typography.svelte
rename to src/libs/components/b3-typography.svelte
diff --git a/src/libs/components/setting-panel.svelte b/src/libs/components/setting-panel.svelte
new file mode 100644
index 0000000..783e2b6
--- /dev/null
+++ b/src/libs/components/setting-panel.svelte
@@ -0,0 +1,51 @@
+
+
+
+
+
+ {#each settingItems as item (item.key)}
+
+
+
+ {/each}
+
\ No newline at end of file
diff --git a/src/libs/const.ts b/src/libs/const.ts
new file mode 100644
index 0000000..2dd3e06
--- /dev/null
+++ b/src/libs/const.ts
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2024 by frostime. All Rights Reserved.
+ * @Author : frostime
+ * @Date : 2024-06-08 20:36:30
+ * @FilePath : /src/libs/const.ts
+ * @LastEditTime : 2024-06-08 20:48:06
+ * @Description :
+ */
+
+
+export const BlockType2NodeType: {[key in BlockType]: string} = {
+ d: 'NodeDocument',
+ p: 'NodeParagraph',
+ query_embed: 'NodeBlockQueryEmbed',
+ l: 'NodeList',
+ i: 'NodeListItem',
+ h: 'NodeHeading',
+ iframe: 'NodeIFrame',
+ tb: 'NodeThematicBreak',
+ b: 'NodeBlockquote',
+ s: 'NodeSuperBlock',
+ c: 'NodeCodeBlock',
+ widget: 'NodeWidget',
+ t: 'NodeTable',
+ html: 'NodeHTMLBlock',
+ m: 'NodeMathBlock',
+ av: 'NodeAttributeView',
+ audio: 'NodeAudio'
+}
+
+
+export const NodeIcons = {
+ NodeAttributeView: {
+ icon: "iconDatabase"
+ },
+ NodeAudio: {
+ icon: "iconRecord"
+ },
+ NodeBlockQueryEmbed: {
+ icon: "iconSQL"
+ },
+ NodeBlockquote: {
+ icon: "iconQuote"
+ },
+ NodeCodeBlock: {
+ icon: "iconCode"
+ },
+ NodeDocument: {
+ icon: "iconFile"
+ },
+ NodeHTMLBlock: {
+ icon: "iconHTML5"
+ },
+ NodeHeading: {
+ icon: "iconHeadings",
+ subtypes: {
+ h1: { icon: "iconH1" },
+ h2: { icon: "iconH2" },
+ h3: { icon: "iconH3" },
+ h4: { icon: "iconH4" },
+ h5: { icon: "iconH5" },
+ h6: { icon: "iconH6" }
+ }
+ },
+ NodeIFrame: {
+ icon: "iconLanguage"
+ },
+ NodeList: {
+ subtypes: {
+ o: { icon: "iconOrderedList" },
+ t: { icon: "iconCheck" },
+ u: { icon: "iconList" }
+ }
+ },
+ NodeListItem: {
+ icon: "iconListItem"
+ },
+ NodeMathBlock: {
+ icon: "iconMath"
+ },
+ NodeParagraph: {
+ icon: "iconParagraph"
+ },
+ NodeSuperBlock: {
+ icon: "iconSuper"
+ },
+ NodeTable: {
+ icon: "iconTable"
+ },
+ NodeThematicBreak: {
+ icon: "iconLine"
+ },
+ NodeVideo: {
+ icon: "iconVideo"
+ },
+ NodeWidget: {
+ icon: "iconBoth"
+ }
+};
diff --git a/src/libs/dialog.ts b/src/libs/dialog.ts
new file mode 100644
index 0000000..d0fe582
--- /dev/null
+++ b/src/libs/dialog.ts
@@ -0,0 +1,164 @@
+/*
+ * Copyright (c) 2024 by frostime. All Rights Reserved.
+ * @Author : frostime
+ * @Date : 2024-03-23 21:37:33
+ * @FilePath : /src/libs/dialog.ts
+ * @LastEditTime : 2024-10-16 14:31:04
+ * @Description : Kits about dialogs
+ */
+import { Dialog } from "siyuan";
+import { type SvelteComponent } from "svelte";
+
+export const inputDialog = (args: {
+ title: string, placeholder?: string, defaultText?: string,
+ confirm?: (text: string) => void, cancel?: () => void,
+ width?: string, height?: string
+}) => {
+ const dialog = new Dialog({
+ title: args.title,
+ content: `
+
+
+
+
`,
+ width: args.width ?? "520px",
+ height: args.height
+ });
+ const target: HTMLTextAreaElement = dialog.element.querySelector(".b3-dialog__content>div.ft__breakword>textarea");
+ const btnsElement = dialog.element.querySelectorAll(".b3-button");
+ btnsElement[0].addEventListener("click", () => {
+ if (args?.cancel) {
+ args.cancel();
+ }
+ dialog.destroy();
+ });
+ btnsElement[1].addEventListener("click", () => {
+ if (args?.confirm) {
+ args.confirm(target.value);
+ }
+ dialog.destroy();
+ });
+};
+
+export const inputDialogSync = async (args: {
+ title: string, placeholder?: string, defaultText?: string,
+ width?: string, height?: string
+}) => {
+ return new Promise
((resolve) => {
+ let newargs = {
+ ...args, confirm: (text) => {
+ resolve(text);
+ }, cancel: () => {
+ resolve(null);
+ }
+ };
+ inputDialog(newargs);
+ });
+}
+
+
+interface IConfirmDialogArgs {
+ title: string;
+ content: string | HTMLElement;
+ confirm?: (ele?: HTMLElement) => void;
+ cancel?: (ele?: HTMLElement) => void;
+ width?: string;
+ height?: string;
+}
+
+export const confirmDialog = (args: IConfirmDialogArgs) => {
+ const { title, content, confirm, cancel, width, height } = args;
+
+ const dialog = new Dialog({
+ title,
+ content: `
+
+
+
+
`,
+ width: width,
+ height: height
+ });
+
+ const target: HTMLElement = dialog.element.querySelector(".b3-dialog__content>div.ft__breakword");
+ if (typeof content === "string") {
+ target.innerHTML = content;
+ } else {
+ target.appendChild(content);
+ }
+
+ const btnsElement = dialog.element.querySelectorAll(".b3-button");
+ btnsElement[0].addEventListener("click", () => {
+ if (cancel) {
+ cancel(target);
+ }
+ dialog.destroy();
+ });
+ btnsElement[1].addEventListener("click", () => {
+ if (confirm) {
+ confirm(target);
+ }
+ dialog.destroy();
+ });
+};
+
+
+export const confirmDialogSync = async (args: IConfirmDialogArgs) => {
+ return new Promise((resolve) => {
+ let newargs = {
+ ...args, confirm: (ele: HTMLElement) => {
+ resolve(ele);
+ }, cancel: (ele: HTMLElement) => {
+ resolve(ele);
+ }
+ };
+ confirmDialog(newargs);
+ });
+};
+
+
+export const simpleDialog = (args: {
+ title: string, ele: HTMLElement | DocumentFragment,
+ width?: string, height?: string,
+ callback?: () => void;
+}) => {
+ const dialog = new Dialog({
+ title: args.title,
+ content: ``,
+ width: args.width,
+ height: args.height,
+ destroyCallback: args.callback
+ });
+ dialog.element.querySelector(".dialog-content").appendChild(args.ele);
+ return {
+ dialog,
+ close: dialog.destroy.bind(dialog)
+ };
+}
+
+
+export const svelteDialog = (args: {
+ title: string, constructor: (container: HTMLElement) => SvelteComponent,
+ width?: string, height?: string,
+ callback?: () => void;
+}) => {
+ let container = document.createElement('div')
+ container.style.display = 'contents';
+ let component = args.constructor(container);
+ const { dialog, close } = simpleDialog({
+ ...args, ele: container, callback: () => {
+ component.$destroy();
+ if (args.callback) args.callback();
+ }
+ });
+ return {
+ component,
+ dialog,
+ close
+ }
+}
diff --git a/src/libs/index.d.ts b/src/libs/index.d.ts
new file mode 100644
index 0000000..27a27ed
--- /dev/null
+++ b/src/libs/index.d.ts
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2024 by frostime. All Rights Reserved.
+ * @Author : frostime
+ * @Date : 2024-04-19 18:30:12
+ * @FilePath : /src/libs/index.d.ts
+ * @LastEditTime : 2024-04-30 16:39:54
+ * @Description :
+ */
+type TSettingItemType = "checkbox" | "select" | "textinput" | "textarea" | "number" | "slider" | "button" | "hint" | "custom";
+
+interface ISettingItemCore {
+ type: TSettingItemType;
+ key: string;
+ value: any;
+ placeholder?: string;
+ slider?: {
+ min: number;
+ max: number;
+ step: number;
+ };
+ options?: { [key: string | number]: string };
+ button?: {
+ label: string;
+ callback: () => void;
+ }
+}
+
+interface ISettingItem extends ISettingItemCore {
+ title: string;
+ description: string;
+ direction?: "row" | "column";
+}
+
+
+//Interface for setting-utils
+interface ISettingUtilsItem extends ISettingItem {
+ action?: {
+ callback: () => void;
+ }
+ createElement?: (currentVal: any) => HTMLElement;
+ getEleVal?: (ele: HTMLElement) => any;
+ setEleVal?: (ele: HTMLElement, val: any) => void;
+}
diff --git a/src/libs/promise-pool.ts b/src/libs/promise-pool.ts
new file mode 100644
index 0000000..e20b0b8
--- /dev/null
+++ b/src/libs/promise-pool.ts
@@ -0,0 +1,48 @@
+export default class PromiseLimitPool {
+ private maxConcurrent: number;
+ private currentRunning = 0;
+ private queue: (() => void)[] = [];
+ private promises: Promise[] = [];
+
+ constructor(maxConcurrent: number) {
+ this.maxConcurrent = maxConcurrent;
+ }
+
+ add(fn: () => Promise): void {
+ const promise = new Promise((resolve, reject) => {
+ const run = async () => {
+ try {
+ this.currentRunning++;
+ const result = await fn();
+ resolve(result);
+ } catch (error) {
+ reject(error);
+ } finally {
+ this.currentRunning--;
+ this.next();
+ }
+ };
+
+ if (this.currentRunning < this.maxConcurrent) {
+ run();
+ } else {
+ this.queue.push(run);
+ }
+ });
+ this.promises.push(promise);
+ }
+
+ async awaitAll(): Promise {
+ return Promise.all(this.promises);
+ }
+
+ /**
+ * Handles the next task in the queue.
+ */
+ private next(): void {
+ if (this.queue.length > 0 && this.currentRunning < this.maxConcurrent) {
+ const nextRun = this.queue.shift()!;
+ nextRun();
+ }
+ }
+}
diff --git a/src/libs/setting-item.svelte b/src/libs/setting-item.svelte
deleted file mode 100644
index 74e08b5..0000000
--- a/src/libs/setting-item.svelte
+++ /dev/null
@@ -1,90 +0,0 @@
-
-
-
diff --git a/src/libs/setting-panel.svelte b/src/libs/setting-panel.svelte
deleted file mode 100644
index ccf424e..0000000
--- a/src/libs/setting-panel.svelte
+++ /dev/null
@@ -1,99 +0,0 @@
-
-
-
-
-
-
-
-
This setting panel is provided by a svelte component
-
-
- See:
- /lib/setting-pannel.svelte
-
-
-
-
-
{
- showMessage(
- `Checkbox changed: ${event.detail.key} = ${event.detail.value}`
- );
- }}
- />
- {
- showMessage(
- `Input changed: ${event.detail.key} = ${event.detail.value}`
- );
- }}
- />
- {
- showMessage("Button clicked");
- }}
- />
- {
- showMessage(
- `Select changed: ${event.detail.key} = ${event.detail.value}`
- );
- }}
- />
- {
- showMessage(
- `Slide changed: ${event.detail.key} = ${event.detail.value}`
- );
- }}
- />
-
diff --git a/src/libs/setting-utils.ts b/src/libs/setting-utils.ts
new file mode 100644
index 0000000..ae316e2
--- /dev/null
+++ b/src/libs/setting-utils.ts
@@ -0,0 +1,397 @@
+/*
+ * Copyright (c) 2023 by frostime. All Rights Reserved.
+ * @Author : frostime
+ * @Date : 2023-12-17 18:28:19
+ * @FilePath : /src/libs/setting-utils.ts
+ * @LastEditTime : 2024-05-01 17:44:16
+ * @Description :
+ */
+
+import { Plugin, Setting } from 'siyuan';
+
+
+/**
+ * The default function to get the value of the element
+ * @param type
+ * @returns
+ */
+const createDefaultGetter = (type: TSettingItemType) => {
+ let getter: (ele: HTMLElement) => any;
+ switch (type) {
+ case 'checkbox':
+ getter = (ele: HTMLInputElement) => {
+ return ele.checked;
+ };
+ break;
+ case 'select':
+ case 'slider':
+ case 'textinput':
+ case 'textarea':
+ getter = (ele: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement) => {
+ return ele.value;
+ };
+ break;
+ case 'number':
+ getter = (ele: HTMLInputElement) => {
+ return parseInt(ele.value);
+ }
+ break;
+ default:
+ getter = () => null;
+ break;
+ }
+ return getter;
+}
+
+
+/**
+ * The default function to set the value of the element
+ * @param type
+ * @returns
+ */
+const createDefaultSetter = (type: TSettingItemType) => {
+ let setter: (ele: HTMLElement, value: any) => void;
+ switch (type) {
+ case 'checkbox':
+ setter = (ele: HTMLInputElement, value: any) => {
+ ele.checked = value;
+ };
+ break;
+ case 'select':
+ case 'slider':
+ case 'textinput':
+ case 'textarea':
+ case 'number':
+ setter = (ele: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement, value: any) => {
+ ele.value = value;
+ };
+ break;
+ default:
+ setter = () => {};
+ break;
+ }
+ return setter;
+
+}
+
+
+export class SettingUtils {
+ plugin: Plugin;
+ name: string;
+ file: string;
+
+ settings: Map = new Map();
+ elements: Map = new Map();
+
+ constructor(args: {
+ plugin: Plugin,
+ name?: string,
+ callback?: (data: any) => void,
+ width?: string,
+ height?: string
+ }) {
+ this.name = args.name ?? 'settings';
+ this.plugin = args.plugin;
+ this.file = this.name.endsWith('.json') ? this.name : `${this.name}.json`;
+ this.plugin.setting = new Setting({
+ width: args.width,
+ height: args.height,
+ confirmCallback: () => {
+ for (let key of this.settings.keys()) {
+ this.updateValueFromElement(key);
+ }
+ let data = this.dump();
+ if (args.callback !== undefined) {
+ args.callback(data);
+ }
+ this.plugin.data[this.name] = data;
+ this.save(data);
+ },
+ destroyCallback: () => {
+ //Restore the original value
+ for (let key of this.settings.keys()) {
+ this.updateElementFromValue(key);
+ }
+ }
+ });
+ }
+
+ async load() {
+ let data = await this.plugin.loadData(this.file);
+ console.debug('Load config:', data);
+ if (data) {
+ for (let [key, item] of this.settings) {
+ item.value = data?.[key] ?? item.value;
+ }
+ }
+ this.plugin.data[this.name] = this.dump();
+ return data;
+ }
+
+ async save(data?: any) {
+ data = data ?? this.dump();
+ await this.plugin.saveData(this.file, this.dump());
+ console.debug('Save config:', data);
+ return data;
+ }
+
+ /**
+ * read the data after saving
+ * @param key key name
+ * @returns setting item value
+ */
+ get(key: string) {
+ return this.settings.get(key)?.value;
+ }
+
+ /**
+ * Set data to this.settings,
+ * but do not save it to the configuration file
+ * @param key key name
+ * @param value value
+ */
+ set(key: string, value: any) {
+ let item = this.settings.get(key);
+ if (item) {
+ item.value = value;
+ this.updateElementFromValue(key);
+ }
+ }
+
+ /**
+ * Set and save setting item value
+ * If you want to set and save immediately you can use this method
+ * @param key key name
+ * @param value value
+ */
+ async setAndSave(key: string, value: any) {
+ let item = this.settings.get(key);
+ if (item) {
+ item.value = value;
+ this.updateElementFromValue(key);
+ await this.save();
+ }
+ }
+
+ /**
+ * Read in the value of element instead of setting obj in real time
+ * @param key key name
+ * @param apply whether to apply the value to the setting object
+ * if true, the value will be applied to the setting object
+ * @returns value in html
+ */
+ take(key: string, apply: boolean = false) {
+ let item = this.settings.get(key);
+ let element = this.elements.get(key) as any;
+ if (!element) {
+ return
+ }
+ if (apply) {
+ this.updateValueFromElement(key);
+ }
+ return item.getEleVal(element);
+ }
+
+ /**
+ * Read data from html and save it
+ * @param key key name
+ * @param value value
+ * @return value in html
+ */
+ async takeAndSave(key: string) {
+ let value = this.take(key, true);
+ await this.save();
+ return value;
+ }
+
+ /**
+ * Disable setting item
+ * @param key key name
+ */
+ disable(key: string) {
+ let element = this.elements.get(key) as any;
+ if (element) {
+ element.disabled = true;
+ }
+ }
+
+ /**
+ * Enable setting item
+ * @param key key name
+ */
+ enable(key: string) {
+ let element = this.elements.get(key) as any;
+ if (element) {
+ element.disabled = false;
+ }
+ }
+
+ /**
+ * 将设置项目导出为 JSON 对象
+ * @returns object
+ */
+ dump(): Object {
+ let data: any = {};
+ for (let [key, item] of this.settings) {
+ if (item.type === 'button') continue;
+ data[key] = item.value;
+ }
+ return data;
+ }
+
+ addItem(item: ISettingUtilsItem) {
+ this.settings.set(item.key, item);
+ const IsCustom = item.type === 'custom';
+ let error = IsCustom && (item.createElement === undefined || item.getEleVal === undefined || item.setEleVal === undefined);
+ if (error) {
+ console.error('The custom setting item must have createElement, getEleVal and setEleVal methods');
+ return;
+ }
+
+ if (item.getEleVal === undefined) {
+ item.getEleVal = createDefaultGetter(item.type);
+ }
+ if (item.setEleVal === undefined) {
+ item.setEleVal = createDefaultSetter(item.type);
+ }
+
+ if (item.createElement === undefined) {
+ let itemElement = this.createDefaultElement(item);
+ this.elements.set(item.key, itemElement);
+ this.plugin.setting.addItem({
+ title: item.title,
+ description: item?.description,
+ direction: item?.direction,
+ createActionElement: () => {
+ this.updateElementFromValue(item.key);
+ let element = this.getElement(item.key);
+ return element;
+ }
+ });
+ } else {
+ this.plugin.setting.addItem({
+ title: item.title,
+ description: item?.description,
+ direction: item?.direction,
+ createActionElement: () => {
+ let val = this.get(item.key);
+ let element = item.createElement(val);
+ this.elements.set(item.key, element);
+ return element;
+ }
+ });
+ }
+ }
+
+ createDefaultElement(item: ISettingUtilsItem) {
+ let itemElement: HTMLElement;
+ //阻止思源内置的回车键确认
+ const preventEnterConfirm = (e) => {
+ if (e.key === 'Enter') {
+ e.preventDefault();
+ e.stopImmediatePropagation();
+ }
+ }
+ switch (item.type) {
+ case 'checkbox':
+ let element: HTMLInputElement = document.createElement('input');
+ element.type = 'checkbox';
+ element.checked = item.value;
+ element.className = "b3-switch fn__flex-center";
+ itemElement = element;
+ element.onchange = item.action?.callback ?? (() => { });
+ break;
+ case 'select':
+ let selectElement: HTMLSelectElement = document.createElement('select');
+ selectElement.className = "b3-select fn__flex-center fn__size200";
+ let options = item?.options ?? {};
+ for (let val in options) {
+ let optionElement = document.createElement('option');
+ let text = options[val];
+ optionElement.value = val;
+ optionElement.text = text;
+ selectElement.appendChild(optionElement);
+ }
+ selectElement.value = item.value;
+ selectElement.onchange = item.action?.callback ?? (() => { });
+ itemElement = selectElement;
+ break;
+ case 'slider':
+ let sliderElement: HTMLInputElement = document.createElement('input');
+ sliderElement.type = 'range';
+ sliderElement.className = 'b3-slider fn__size200 b3-tooltips b3-tooltips__n';
+ sliderElement.ariaLabel = item.value;
+ sliderElement.min = item.slider?.min.toString() ?? '0';
+ sliderElement.max = item.slider?.max.toString() ?? '100';
+ sliderElement.step = item.slider?.step.toString() ?? '1';
+ sliderElement.value = item.value;
+ sliderElement.onchange = () => {
+ sliderElement.ariaLabel = sliderElement.value;
+ item.action?.callback();
+ }
+ itemElement = sliderElement;
+ break;
+ case 'textinput':
+ let textInputElement: HTMLInputElement = document.createElement('input');
+ textInputElement.className = 'b3-text-field fn__flex-center fn__size200';
+ textInputElement.value = item.value;
+ textInputElement.onchange = item.action?.callback ?? (() => { });
+ itemElement = textInputElement;
+ textInputElement.addEventListener('keydown', preventEnterConfirm);
+ break;
+ case 'textarea':
+ let textareaElement: HTMLTextAreaElement = document.createElement('textarea');
+ textareaElement.className = "b3-text-field fn__block";
+ textareaElement.value = item.value;
+ textareaElement.onchange = item.action?.callback ?? (() => { });
+ itemElement = textareaElement;
+ break;
+ case 'number':
+ let numberElement: HTMLInputElement = document.createElement('input');
+ numberElement.type = 'number';
+ numberElement.className = 'b3-text-field fn__flex-center fn__size200';
+ numberElement.value = item.value;
+ itemElement = numberElement;
+ numberElement.addEventListener('keydown', preventEnterConfirm);
+ break;
+ case 'button':
+ let buttonElement: HTMLButtonElement = document.createElement('button');
+ buttonElement.className = "b3-button b3-button--outline fn__flex-center fn__size200";
+ buttonElement.innerText = item.button?.label ?? 'Button';
+ buttonElement.onclick = item.button?.callback ?? (() => { });
+ itemElement = buttonElement;
+ break;
+ case 'hint':
+ let hintElement: HTMLElement = document.createElement('div');
+ hintElement.className = 'b3-label fn__flex-center';
+ itemElement = hintElement;
+ break;
+ }
+ return itemElement;
+ }
+
+ /**
+ * return the setting element
+ * @param key key name
+ * @returns element
+ */
+ getElement(key: string) {
+ // let item = this.settings.get(key);
+ let element = this.elements.get(key) as any;
+ return element;
+ }
+
+ private updateValueFromElement(key: string) {
+ let item = this.settings.get(key);
+ if (item.type === 'button') return;
+ let element = this.elements.get(key) as any;
+ item.value = item.getEleVal(element);
+ }
+
+ private updateElementFromValue(key: string) {
+ let item = this.settings.get(key);
+ if (item.type === 'button') return;
+ let element = this.elements.get(key) as any;
+ item.setEleVal(element, item.value);
+ }
+}
\ No newline at end of file
diff --git a/src/setting-example.svelte b/src/setting-example.svelte
new file mode 100644
index 0000000..2a2c809
--- /dev/null
+++ b/src/setting-example.svelte
@@ -0,0 +1,139 @@
+
+
+
+
+ {#each groups as group}
+
+ - {
+ focusGroup = group;
+ }}
+ on:keydown={() => {}}
+ >
+ {group}
+
+ {/each}
+
+
+
{ console.debug("Click:", detail.key); }}
+ >
+
+ 💡 This is our default settings.
+
+
+
{ console.debug("Click:", detail.key); }}
+ >
+
+
+
+
+
+
diff --git a/src/siyuan.d.ts b/src/siyuan.d.ts
deleted file mode 100644
index f4bb1c0..0000000
--- a/src/siyuan.d.ts
+++ /dev/null
@@ -1,314 +0,0 @@
-declare module "siyuan" {
- type TEventBus = "ws-main" | "click-blockicon" | "click-editorcontent" | "click-pdf"
-
- declare global {
- interface Window {
- Lute: Lute
- }
- }
-
-
- interface IObject {
- [key: string]: string;
- }
-
- interface ILuteNode {
- TokensStr: () => string;
- __internal_object__: {
- Parent: {
- Type: number,
- },
- HeadingLevel: string,
- };
- }
-
- interface IWebSocketData {
- cmd: string
- callback?: string
- data: any
- msg: string
- code: number
- sid: string
- }
-
- interface IPluginDockTab {
- position: "LeftTop" | "LeftBottom" | "RightTop" | "RightBottom" | "BottomLeft" | "BottomRight",
- size: { width: number, height: number },
- icon: string,
- hotkey?: string,
- title: string,
- }
-
- interface IMenuItemOption {
- label?: string,
- click?: (element: HTMLElement) => void,
- type?: "separator" | "submenu" | "readonly",
- accelerator?: string,
- action?: string,
- id?: string,
- submenu?: IMenuItemOption[]
- disabled?: boolean
- icon?: string
- iconHTML?: string
- current?: boolean
- bind?: (element: HTMLElement) => void
- }
-
- export function fetchPost(url: string, data?: any, cb?: (response: IWebSocketData) => void, headers?: IObject): void;
-
- export function fetchSyncPost(url: string, data?: any): Promise;
-
- export function fetchGet(url: string, cb: (response: IWebSocketData) => void): void;
-
- export function openTab(options: {
- custom?: {
- title: string,
- icon: string,
- data?: any
- fn?: () => any,
- } // card 和自定义页签 必填
- position?: "right" | "bottom",
- keepCursor?: boolean // 是否跳转到新 tab 上
- removeCurrentTab?: boolean // 在当前页签打开时需移除原有页签
- afterOpen?: () => void // 打开后回调
- }): void
-
- export function isMobile(): boolean;
-
- export function adaptHotkey(hotkey: string): string;
-
- export function confirm(title: string, text: string, confirmCB?: () => void, cancelCB?: () => void): void;
-
- /**
- * @param timeout - ms. 0: manual close;-1: always show; 6000: default
- * @param {string} [type=info]
- */
- export function showMessage(text: string, timeout?: number, type?: "info" | "error", id?: string): void;
-
- export class App {
- plugins: Plugin[];
- }
-
- export abstract class Plugin {
- eventBus: EventBus;
- i18n: IObject;
- data: any;
- name: string;
-
- constructor(options: {
- app: App,
- id: string,
- name: string,
- i18n: IObject
- })
-
- onload(): void;
-
- onunload(): void;
-
- onLayoutReady(): void;
-
- /*
- * @param {string} [options.position=right]
- */
- addTopBar(options: {
- icon: string,
- title: string,
- callback: (evt: MouseEvent) => void
- position?: "right" | "left"
- }): HTMLDivElement;
-
- openSetting(): void
-
- // registerCommand(command: IPluginCommand): void;
-
- // registerSettingRender(settingRender: SettingRender): void;
-
- loadData(storageName: string): Promise;
-
- saveData(storageName: string, content: any): Promise;
-
- removeData(storageName: string): Promise;
-
- addTab(options: {
- type: string,
- destroy?: () => void,
- resize?: () => void,
- update?: () => void,
- init: () => void
- }): () => any
-
- addDock(options: {
- config: IPluginDockTab,
- data: any,
- type: string,
- destroy?: () => void,
- resize?: () => void,
- update?: () => void,
- init: () => void
- }): any
-
- addFloatLayer(options: {
- ids: string[],
- defIds?: string[],
- x?: number,
- y?: number,
- targetElement?: HTMLElement
- }): void
- }
-
- export class EventBus {
- on(type: TEventBus, listener: (event: CustomEvent) => void): void;
-
- once(type: TEventBus, listener: (event: CustomEvent) => void): void;
-
- off(type: TEventBus, listener: (event: CustomEvent) => void): void;
-
- emit(type: TEventBus, detail?: any): boolean;
- }
-
- export class Dialog {
-
- element: HTMLElement;
-
- constructor(options: {
- title?: string,
- transparent?: boolean,
- content: string,
- width?: string
- height?: string,
- destroyCallback?: (options?: IObject) => void
- disableClose?: boolean
- disableAnimation?: boolean
- });
-
- destroy(options?: IObject): void;
-
- bindInput(inputElement: HTMLInputElement | HTMLTextAreaElement, enterEvent?: () => void): void;
- }
-
- export class Menu {
- constructor(id?: string, closeCB?: () => void);
-
- showSubMenu(subMenuElement: HTMLElement): void;
-
- addItem(options: IMenuItemOption): HTMLElement;
-
- addSeparator(): void;
-
- open(options: { x: number, y: number, h?: number, w?: number, isLeft?: boolean }): void;
-
- /*
- * @param {string} [position=all]
- */
- fullscreen(position?: "bottom" | "all"): void;
-
- close(): void;
- }
-
- declare class Lute {
- public static WalkStop: number;
- public static WalkSkipChildren: number;
- public static WalkContinue: number;
- public static Version: string;
- public static Caret: string;
-
- public static New(): Lute;
-
- public static EChartsMindmapStr(text: string): string;
-
- public static NewNodeID(): string;
-
- public static Sanitize(html: string): string;
-
- public static EscapeHTMLStr(str: string): string;
-
- public static UnEscapeHTMLStr(str: string): string;
-
- public static GetHeadingID(node: ILuteNode): string;
-
- public static BlockDOM2Content(html: string): string;
-
- private constructor();
-
- public BlockDOM2Content(text: string): string;
-
- public BlockDOM2EscapeMarkerContent(text: string): string;
-
- public SetTextMark(enable: boolean): void;
-
- public SetHeadingID(enable: boolean): void;
-
- public SetProtyleMarkNetImg(enable: boolean): void;
-
- public SetSpellcheck(enable: boolean): void;
-
- public SetFileAnnotationRef(enable: boolean): void;
-
- public SetSetext(enable: boolean): void;
-
- public SetYamlFrontMatter(enable: boolean): void;
-
- public SetChineseParagraphBeginningSpace(enable: boolean): void;
-
- public SetRenderListStyle(enable: boolean): void;
-
- public SetImgPathAllowSpace(enable: boolean): void;
-
- public SetKramdownIAL(enable: boolean): void;
-
- public BlockDOM2Md(html: string): string;
-
- public BlockDOM2StdMd(html: string): string;
-
- public SetGitConflict(enable: boolean): void;
-
- public SetSuperBlock(enable: boolean): void;
-
- public SetTag(enable: boolean): void;
-
- public SetMark(enable: boolean): void;
-
- public SetSub(enable: boolean): void;
-
- public SetSup(enable: boolean): void;
-
- public SetBlockRef(enable: boolean): void;
-
- public SetSanitize(enable: boolean): void;
-
- public SetHeadingAnchor(enable: boolean): void;
-
- public SetImageLazyLoading(imagePath: string): void;
-
- public SetInlineMathAllowDigitAfterOpenMarker(enable: boolean): void;
-
- public SetToC(enable: boolean): void;
-
- public SetIndentCodeBlock(enable: boolean): void;
-
- public SetParagraphBeginningSpace(enable: boolean): void;
-
- public SetFootnotes(enable: boolean): void;
-
- public SetLinkRef(enalbe: boolean): void;
-
- public SetEmojiSite(emojiSite: string): void;
-
- public PutEmojis(emojis: IObject): void;
-
- public SpinBlockDOM(html: string): string;
-
- public Md2BlockDOM(html: string): string;
-
- public SetProtyleWYSIWYG(wysiwyg: boolean): void;
-
- public MarkdownStr(name: string, md: string): string;
-
- public IsValidLinkDest(text: string): boolean;
-
- public BlockDOM2InlineBlockDOM(html: string): string;
-
- public BlockDOM2HTML(html: string): string;
- }
-}
diff --git a/src/sy-dtype.d.ts b/src/sy-dtype.d.ts
deleted file mode 100644
index 0c3b465..0000000
--- a/src/sy-dtype.d.ts
+++ /dev/null
@@ -1,70 +0,0 @@
-/**
- * Copyright (c) 2023 frostime. All rights reserved.
- * https://github.com/frostime/sy-plugin-template-vite
- */
-
-/**
- * Frequently used data structures in SiYuan
- */
-declare module "sy-dtype" {
-
- export type DocumentId = string;
- export type BlockId = string;
- export type NotebookId = string;
- export type PreviousID = BlockId;
- export type ParentID = BlockId | DocumentId;
-
- export type Notebook = {
- id: NotebookId;
- name: string;
- icon: string;
- sort: number;
- closed: boolean;
- }
-
- export type NotebookConf = {
- name: string;
- closed: boolean;
- refCreateSavePath: string;
- createDocNameTemplate: string;
- dailyNoteSavePath: string;
- dailyNoteTemplatePath: string;
- }
-
- export type BlockType = "d" | "s" | "h" | "t" | "i" | "p" | "f" | "audio" | "video" | "other";
-
- export type BlockSubType = "d1" | "d2" | "s1" | "s2" | "s3" | "t1" | "t2" | "h1" | "h2" | "h3" | "h4" | "h5" | "h6" | "table" | "task" | "toggle" | "latex" | "quote" | "html" | "code" | "footnote" | "cite" | "collection" | "bookmark" | "attachment" | "comment" | "mindmap" | "spreadsheet" | "calendar" | "image" | "audio" | "video" | "other";
-
- export type Block = {
- id: BlockId;
- parent_id?: BlockId;
- root_id: DocumentId;
- hash: string;
- box: string;
- path: string;
- hpath: string;
- name: string;
- alias: string;
- memo: string;
- tag: string;
- content: string;
- fcontent?: string;
- markdown: string;
- length: number;
- type: BlockType;
- subtype: BlockSubType;
- ial?: { [key: string]: string };
- sort: number;
- created: string;
- updated: string;
- }
-
- export type doOperation = {
- action: string;
- data: string;
- id: BlockId;
- parentID: BlockId | DocumentId;
- previousID: BlockId;
- retData: null;
- }
-}
\ No newline at end of file
diff --git a/src/types/api.d.ts b/src/types/api.d.ts
new file mode 100644
index 0000000..3c08859
--- /dev/null
+++ b/src/types/api.d.ts
@@ -0,0 +1,65 @@
+interface IResGetNotebookConf {
+ box: string;
+ conf: NotebookConf;
+ name: string;
+}
+
+interface IReslsNotebooks {
+ notebooks: Notebook[];
+}
+
+interface IResUpload {
+ errFiles: string[];
+ succMap: { [key: string]: string };
+}
+
+interface IResdoOperations {
+ doOperations: doOperation[];
+ undoOperations: doOperation[] | null;
+}
+
+interface IResGetBlockKramdown {
+ id: BlockId;
+ kramdown: string;
+}
+
+interface IResGetChildBlock {
+ id: BlockId;
+ type: BlockType;
+ subtype?: BlockSubType;
+}
+
+interface IResGetTemplates {
+ content: string;
+ path: string;
+}
+
+interface IResReadDir {
+ isDir: boolean;
+ isSymlink: boolean;
+ name: string;
+}
+
+interface IResExportMdContent {
+ hPath: string;
+ content: string;
+}
+
+interface IResBootProgress {
+ progress: number;
+ details: string;
+}
+
+interface IResForwardProxy {
+ body: string;
+ contentType: string;
+ elapsed: number;
+ headers: { [key: string]: string };
+ status: number;
+ url: string;
+}
+
+interface IResExportResources {
+ path: string;
+}
+
diff --git a/src/types/index.d.ts b/src/types/index.d.ts
new file mode 100644
index 0000000..f224b3e
--- /dev/null
+++ b/src/types/index.d.ts
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2024 by frostime. All Rights Reserved.
+ * @Author : frostime
+ * @Date : 2023-08-15 10:28:10
+ * @FilePath : /src/types/index.d.ts
+ * @LastEditTime : 2024-06-08 20:50:53
+ * @Description : Frequently used data structures in SiYuan
+ */
+
+
+type DocumentId = string;
+type BlockId = string;
+type NotebookId = string;
+type PreviousID = BlockId;
+type ParentID = BlockId | DocumentId;
+
+type Notebook = {
+ id: NotebookId;
+ name: string;
+ icon: string;
+ sort: number;
+ closed: boolean;
+}
+
+type NotebookConf = {
+ name: string;
+ closed: boolean;
+ refCreateSavePath: string;
+ createDocNameTemplate: string;
+ dailyNoteSavePath: string;
+ dailyNoteTemplatePath: string;
+}
+
+type BlockType =
+ | 'd'
+ | 'p'
+ | 'query_embed'
+ | 'l'
+ | 'i'
+ | 'h'
+ | 'iframe'
+ | 'tb'
+ | 'b'
+ | 's'
+ | 'c'
+ | 'widget'
+ | 't'
+ | 'html'
+ | 'm'
+ | 'av'
+ | 'audio';
+
+
+type BlockSubType = "d1" | "d2" | "s1" | "s2" | "s3" | "t1" | "t2" | "h1" | "h2" | "h3" | "h4" | "h5" | "h6" | "table" | "task" | "toggle" | "latex" | "quote" | "html" | "code" | "footnote" | "cite" | "collection" | "bookmark" | "attachment" | "comment" | "mindmap" | "spreadsheet" | "calendar" | "image" | "audio" | "video" | "other";
+
+type Block = {
+ id: BlockId;
+ parent_id?: BlockId;
+ root_id: DocumentId;
+ hash: string;
+ box: string;
+ path: string;
+ hpath: string;
+ name: string;
+ alias: string;
+ memo: string;
+ tag: string;
+ content: string;
+ fcontent?: string;
+ markdown: string;
+ length: number;
+ type: BlockType;
+ subtype: BlockSubType;
+ /** string of { [key: string]: string }
+ * For instance: "{: custom-type=\"query-code\" id=\"20230613234017-zkw3pr0\" updated=\"20230613234509\"}"
+ */
+ ial?: string;
+ sort: number;
+ created: string;
+ updated: string;
+}
+
+type doOperation = {
+ action: string;
+ data: string;
+ id: BlockId;
+ parentID: BlockId | DocumentId;
+ previousID: BlockId;
+ retData: null;
+}
+
+interface Window {
+ siyuan: {
+ config: any;
+ notebooks: any;
+ menus: any;
+ dialogs: any;
+ blockPanels: any;
+ storage: any;
+ user: any;
+ ws: any;
+ languages: any;
+ emojis: any;
+ };
+ Lute: any;
+}
diff --git a/svelte.config.js b/svelte.config.js
index 7c8df62..d62a343 100644
--- a/svelte.config.js
+++ b/svelte.config.js
@@ -1,7 +1,26 @@
+/*
+ * Copyright (c) 2024 by frostime. All Rights Reserved.
+ * @Author : frostime
+ * @Date : 2023-05-19 19:49:13
+ * @FilePath : /svelte.config.js
+ * @LastEditTime : 2024-04-19 19:01:55
+ * @Description :
+ */
import { vitePreprocess } from "@sveltejs/vite-plugin-svelte"
+const NoWarns = new Set([
+ "a11y-click-events-have-key-events",
+ "a11y-no-static-element-interactions",
+ "a11y-no-noninteractive-element-interactions"
+]);
+
export default {
// Consult https://svelte.dev/docs#compile-time-svelte-preprocess
// for more information about preprocessors
preprocess: vitePreprocess(),
+ onwarn: (warning, handler) => {
+ // suppress warnings on `vite dev` and `vite build`; but even without this, things still work
+ if (NoWarns.has(warning.code)) return;
+ handler(warning);
+ }
}
diff --git a/tsconfig.json b/tsconfig.json
index 21cb9cb..0fcc1ad 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -35,14 +35,20 @@
"node",
"vite/client",
"svelte"
- ]
+ ],
+ // "baseUrl": "./src",
+ "paths": {
+ "@/*": ["./src/*"],
+ "@/libs/*": ["./src/libs/*"],
+ }
},
"include": [
"tools/**/*.ts",
"src/**/*.ts",
"src/**/*.d.ts",
"src/**/*.tsx",
- "src/**/*.vue"
+ "src/**/*.vue",
+ "src/**/*.svelte"
],
"references": [
{
diff --git a/vite.config.ts b/vite.config.ts
index 9e9f127..cb7511b 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -1,110 +1,96 @@
import { resolve } from "path"
import { defineConfig, loadEnv } from "vite"
-import minimist from "minimist"
import { viteStaticCopy } from "vite-plugin-static-copy"
import livereload from "rollup-plugin-livereload"
import { svelte } from "@sveltejs/vite-plugin-svelte"
import zipPack from "vite-plugin-zip-pack";
import fg from 'fast-glob';
-const args = minimist(process.argv.slice(2))
-const isWatch = args.watch || args.w || false
-const devDistDir = "./dev"
-const distDir = isWatch ? devDistDir : "./dist"
+import vitePluginYamlI18n from './yaml-plugin';
-console.log("isWatch=>", isWatch)
-console.log("distDir=>", distDir)
+const env = process.env;
+const isSrcmap = env.VITE_SOURCEMAP === 'inline';
+const isDev = env.NODE_ENV === 'development';
+
+const outputDir = isDev ? "dev" : "dist";
+
+console.log("isDev=>", isDev);
+console.log("isSrcmap=>", isSrcmap);
+console.log("outputDir=>", outputDir);
export default defineConfig({
+ resolve: {
+ alias: {
+ "@": resolve(__dirname, "src"),
+ }
+ },
+
plugins: [
svelte(),
+ vitePluginYamlI18n({
+ inDir: 'public/i18n',
+ outDir: `${outputDir}/i18n`
+ }),
+
viteStaticCopy({
targets: [
- {
- src: "./README*.md",
- dest: "./",
- },
- {
- src: "./icon.png",
- dest: "./",
- },
- {
- src: "./preview.png",
- dest: "./",
- },
- {
- src: "./plugin.json",
- dest: "./",
- },
- {
- src: "./src/i18n/**",
- dest: "./i18n/",
- },
+ { src: "./README*.md", dest: "./" },
+ { src: "./plugin.json", dest: "./" },
+ { src: "./preview.png", dest: "./" },
+ { src: "./icon.png", dest: "./" }
],
}),
+
],
- // https://github.com/vitejs/vite/issues/1930
- // https://vitejs.dev/guide/env-and-mode.html#env-files
- // https://github.com/vitejs/vite/discussions/3058#discussioncomment-2115319
- // 在这里自定义变量
define: {
- "process.env.DEV_MODE": `"${isWatch}"`,
+ "process.env.DEV_MODE": JSON.stringify(isDev),
+ "process.env.NODE_ENV": JSON.stringify(env.NODE_ENV)
},
build: {
- // 输出路径
- outDir: distDir,
+ outDir: outputDir,
emptyOutDir: false,
-
- // 构建后是否生成 source map 文件
- sourcemap: false,
-
- // 设置为 false 可以禁用最小化混淆
- // 或是用来指定是应用哪种混淆器
- // boolean | 'terser' | 'esbuild'
- // 不压缩,用于调试
- minify: !isWatch,
+ minify: true,
+ sourcemap: isSrcmap ? 'inline' : false,
lib: {
- // Could also be a dictionary or array of multiple entry points
entry: resolve(__dirname, "src/index.ts"),
- // the proper extensions will be added
fileName: "index",
formats: ["cjs"],
},
rollupOptions: {
plugins: [
- ...(
- isWatch ? [
- livereload(devDistDir),
- {
- //监听静态资源文件
- name: 'watch-external',
- async buildStart() {
- const files = await fg([
- 'src/i18n/*.json',
- './README*.md',
- './plugin.json'
- ]);
- for (let file of files) {
- this.addWatchFile(file);
- }
+ ...(isDev ? [
+ livereload(outputDir),
+ {
+ name: 'watch-external',
+ async buildStart() {
+ const files = await fg([
+ 'public/i18n/**',
+ './README*.md',
+ './plugin.json'
+ ]);
+ for (let file of files) {
+ this.addWatchFile(file);
}
}
- ] : [
- zipPack({
- inDir: './dist',
- outDir: './',
- outFileName: 'package.zip'
- })
- ]
- )
+ }
+ ] : [
+ // Clean up unnecessary files under dist dir
+ cleanupDistFiles({
+ patterns: ['i18n/*.yaml', 'i18n/*.md'],
+ distDir: outputDir
+ }),
+ zipPack({
+ inDir: './dist',
+ outDir: './',
+ outFileName: 'package.zip'
+ })
+ ])
],
- // make sure to externalize deps that shouldn't be bundled
- // into your library
external: ["siyuan", "process"],
output: {
@@ -118,4 +104,60 @@ export default defineConfig({
},
},
}
-})
+});
+
+
+/**
+ * Clean up some dist files after compiled
+ * @author frostime
+ * @param options:
+ * @returns
+ */
+function cleanupDistFiles(options: { patterns: string[], distDir: string }) {
+ const {
+ patterns,
+ distDir
+ } = options;
+
+ return {
+ name: 'rollup-plugin-cleanup',
+ enforce: 'post',
+ writeBundle: {
+ sequential: true,
+ order: 'post' as 'post',
+ async handler() {
+ const fg = await import('fast-glob');
+ const fs = await import('fs');
+ // const path = await import('path');
+
+ // 使用 glob 语法,确保能匹配到文件
+ const distPatterns = patterns.map(pat => `${distDir}/${pat}`);
+ console.debug('Cleanup searching patterns:', distPatterns);
+
+ const files = await fg.default(distPatterns, {
+ dot: true,
+ absolute: true,
+ onlyFiles: false
+ });
+
+ // console.info('Files to be cleaned up:', files);
+
+ for (const file of files) {
+ try {
+ if (fs.default.existsSync(file)) {
+ const stat = fs.default.statSync(file);
+ if (stat.isDirectory()) {
+ fs.default.rmSync(file, { recursive: true });
+ } else {
+ fs.default.unlinkSync(file);
+ }
+ console.log(`Cleaned up: ${file}`);
+ }
+ } catch (error) {
+ console.error(`Failed to clean up ${file}:`, error);
+ }
+ }
+ }
+ }
+ };
+}
diff --git a/yaml-plugin.js b/yaml-plugin.js
new file mode 100644
index 0000000..01c85e2
--- /dev/null
+++ b/yaml-plugin.js
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2024 by frostime. All Rights Reserved.
+ * @Author : frostime
+ * @Date : 2024-04-05 21:27:55
+ * @FilePath : /yaml-plugin.js
+ * @LastEditTime : 2024-04-05 22:53:34
+ * @Description : 去妮玛的 json 格式,我就是要用 yaml 写 i18n
+ */
+// plugins/vite-plugin-parse-yaml.js
+import fs from 'fs';
+import yaml from 'js-yaml';
+import { resolve } from 'path';
+
+export default function vitePluginYamlI18n(options = {}) {
+ // Default options with a fallback
+ const DefaultOptions = {
+ inDir: 'src/i18n',
+ outDir: 'dist/i18n',
+ };
+
+ const finalOptions = { ...DefaultOptions, ...options };
+
+ return {
+ name: 'vite-plugin-yaml-i18n',
+ buildStart() {
+ console.log('🌈 Parse I18n: YAML to JSON..');
+ const inDir = finalOptions.inDir;
+ const outDir = finalOptions.outDir
+
+ if (!fs.existsSync(outDir)) {
+ fs.mkdirSync(outDir, { recursive: true });
+ }
+
+ //Parse yaml file, output to json
+ const files = fs.readdirSync(inDir);
+ for (const file of files) {
+ if (file.endsWith('.yaml') || file.endsWith('.yml')) {
+ console.log(`-- Parsing ${file}`)
+ //检查是否有同名的json文件
+ const jsonFile = file.replace(/\.(yaml|yml)$/, '.json');
+ if (files.includes(jsonFile)) {
+ console.log(`---- File ${jsonFile} already exists, skipping...`);
+ continue;
+ }
+ try {
+ const filePath = resolve(inDir, file);
+ const fileContents = fs.readFileSync(filePath, 'utf8');
+ const parsed = yaml.load(fileContents);
+ const jsonContent = JSON.stringify(parsed, null, 2);
+ const outputFilePath = resolve(outDir, file.replace(/\.(yaml|yml)$/, '.json'));
+ console.log(`---- Writing to ${outputFilePath}`);
+ fs.writeFileSync(outputFilePath, jsonContent);
+ } catch (error) {
+ this.error(`---- Error parsing YAML file ${file}: ${error.message}`);
+ }
+ }
+ }
+ },
+ };
+}