+
appId:
+
+
${app?.appId}
+
+
API demo:
diff --git a/src/index.scss b/src/index.scss
index c8efd4f..e69de29 100644
--- a/src/index.scss
+++ b/src/index.scss
@@ -1,26 +0,0 @@
-#helloPanel {
- border: 1px rgb(189, 119, 119) dashed;
-}
-
-.plugin-sample {
- &__custom-tab {
- background-color: var(--b3-theme-background);
- height: 100%;
- width: 100%;
- display: flex;
- justify-content: center;
- align-items: center;
- }
-
- &__custom-dock {
- display: flex;
- justify-content: center;
- align-items: center;
- }
-
- &__time {
- background: var(--b3-card-info-background);
- border-radius: 4px;
- padding: 2px 8px;
- }
-}
\ No newline at end of file
diff --git a/src/index.ts b/src/index.ts
index aa7bd75..5b50c49 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -9,16 +9,22 @@ import {
getFrontend,
getBackend,
IModel,
- Setting,
- fetchPost,
- Protyle, openWindow, IOperation
+ Protyle,
+ openWindow,
+ IOperation,
+ Constants,
+ openMobileFileById,
+ lockScreen,
+ ICard,
+ ICardData
} from "siyuan";
import "@/index.scss";
import HelloExample from "@/hello.svelte";
-import SettingPannel from "@/libs/setting-panel.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";
@@ -26,7 +32,7 @@ const DOCK_TYPE = "dock_tab";
export default class PluginSample extends Plugin {
- private customTab: () => IModel;
+ customTab: () => IModel;
private isMobile: boolean;
private blockIconEventBindThis = this.blockIconEvent.bind(this);
private settingUtils: SettingUtils;
@@ -115,38 +121,66 @@ export default class PluginSample extends Plugin {
size: { width: 200, height: 0 },
icon: "iconSaving",
title: "Custom Dock",
+ hotkey: "⌥⌘W",
},
data: {
text: "This is my custom dock"
},
type: DOCK_TYPE,
- init() {
- this.element.innerHTML = `
-
-
-
- Custom Dock
-
-
-
-
-
- ${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.settingUtils = new SettingUtils(this, STORAGE_NAME);
+ 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",
@@ -154,6 +188,14 @@ export default class PluginSample extends Plugin {
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",
@@ -161,24 +203,31 @@ export default class PluginSample extends Plugin {
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: "Readonly text",
+ title: "Select",
description: "Select description",
- select: {
- options: [
- {
- val: 1,
- text: "Option 1"
- },
- {
- val: 2,
- text: "Option 2"
- }
- ]
+ 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({
@@ -187,10 +236,18 @@ export default class PluginSample extends Plugin {
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({
@@ -206,6 +263,42 @@ export default class PluginSample extends Plugin {
}
}
});
+ 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"],
@@ -216,6 +309,38 @@ export default class PluginSample extends Plugin {
}
}];
+ 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);
}
@@ -223,6 +348,14 @@ export default class PluginSample extends Plugin {
// 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,
@@ -247,26 +380,42 @@ export default class PluginSample extends Plugin {
async onunload() {
console.log(this.i18n.byePlugin);
- await this.settingUtils.save();
showMessage("Goodbye SiYuan Plugin");
console.log("onunload");
}
+ 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: "600px",
+ 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 SettingPannel({
+ let pannel = new SettingExample({
target: dialog.element.querySelector("#SettingPanel"),
});
}
@@ -307,18 +456,30 @@ export default class PluginSample extends Plugin {
}
private showDialog() {
- let dialog = new Dialog({
- title: "Hello World",
- content: `
`,
+ // 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",
- destroyCallback(options) {
- // hello.$destroy();
- },
- });
- new HelloExample({
- target: dialog.element.querySelector("#helloPanel"),
- props: {
- app: this.app,
+ constructor: (container: HTMLElement) => {
+ return new HelloExample({
+ target: container,
+ props: {
+ app: this.app,
+ }
+ });
}
});
}
@@ -427,7 +588,22 @@ export default class PluginSample extends Plugin {
});
}
});
+ } else {
+ menu.addItem({
+ icon: "iconFile",
+ label: "Open Doc(open help first)",
+ click: () => {
+ openMobileFileById(this.app, "20200812220555-lj3enxa");
+ }
+ });
}
+ menu.addItem({
+ icon: "iconLock",
+ label: "Lockscreen",
+ click: () => {
+ lockScreen(this.app);
+ }
+ });
menu.addItem({
icon: "iconScrollHoriz",
label: "Event Bus",
@@ -492,6 +668,18 @@ export default class PluginSample extends Plugin {
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",
@@ -528,6 +716,18 @@ export default class PluginSample extends Plugin {
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",
@@ -648,6 +848,18 @@ export default class PluginSample extends Plugin {
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",
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
index b46f655..27a27ed 100644
--- a/src/libs/index.d.ts
+++ b/src/libs/index.d.ts
@@ -1,20 +1,43 @@
-type TSettingItemType = "checkbox" | "select" | "textinput" | "textarea" | "slider" | "button";
-interface ISettingItem {
+/*
+ * 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;
- type: TSettingItemType;
- title: string;
- description?: string;
+ placeholder?: string;
slider?: {
min: number;
max: number;
step: number;
};
- select?: {
- options: {val: any; text: string}[];
- };
+ 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 b728cf4..0000000
--- a/src/libs/setting-item.svelte
+++ /dev/null
@@ -1,92 +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
index ff78af8..ae316e2 100644
--- a/src/libs/setting-utils.ts
+++ b/src/libs/setting-utils.ts
@@ -1,60 +1,142 @@
/*
* Copyright (c) 2023 by frostime. All Rights Reserved.
* @Author : frostime
- * @Date : 2023-09-16 18:05:00
+ * @Date : 2023-12-17 18:28:19
* @FilePath : /src/libs/setting-utils.ts
- * @LastEditTime : 2023-09-16 18:17:03
- * @Description : A utility for siyuan plugin settings
+ * @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();
+ settings: Map = new Map();
elements: Map = new Map();
- constructor(plugin: Plugin, name?: string, width?: string, height?: string) {
- this.name = name ?? 'settings';
- this.plugin = plugin;
+ 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: width,
- height: height,
+ width: args.width,
+ height: args.height,
confirmCallback: () => {
for (let key of this.settings.keys()) {
- this.updateValue(key);
+ this.updateValueFromElement(key);
}
let data = this.dump();
+ if (args.callback !== undefined) {
+ args.callback(data);
+ }
this.plugin.data[this.name] = data;
- this.save();
+ 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();
- console.log(data);
return data;
}
- async save() {
- let data = this.dump();
+ async save(data?: any) {
+ data = data ?? this.dump();
await this.plugin.saveData(this.file, this.dump());
+ console.debug('Save config:', data);
return data;
}
/**
- * Get setting item value
+ * read the data after saving
* @param key key name
* @returns setting item value
*/
@@ -62,6 +144,88 @@ export class SettingUtils {
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
@@ -75,9 +239,59 @@ export class SettingUtils {
return data;
}
- addItem(item: ISettingItem) {
+ 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');
@@ -85,17 +299,21 @@ export class SettingUtils {
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";
- for (let option of item.select?.options ?? []) {
+ let options = item?.options ?? {};
+ for (let val in options) {
let optionElement = document.createElement('option');
- optionElement.value = option.val;
- optionElement.text = option.text;
+ 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':
@@ -109,6 +327,7 @@ export class SettingUtils {
sliderElement.value = item.value;
sliderElement.onchange = () => {
sliderElement.ariaLabel = sliderElement.value;
+ item.action?.callback();
}
itemElement = sliderElement;
break;
@@ -116,77 +335,63 @@ export class SettingUtils {
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 ?? (() => {});
+ 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;
}
- this.elements.set(item.key, itemElement);
- this.plugin.setting.addItem({
- title: item.title,
- description: item?.description,
- createActionElement: () => {
- let element = this.getElement(item.key);
- return element;
- }
- })
+ return itemElement;
}
- private getElement(key: string) {
- let item = this.settings.get(key);
+ /**
+ * 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;
- switch (item.type) {
- case 'checkbox':
- element.checked = item.value;
- break;
- case 'select':
- element.value = item.value;
- break;
- case 'slider':
- element.value = item.value;
- break;
- case 'textinput':
- element.value = item.value;
- break;
- case 'textarea':
- element.value = item.value;
- break;
- }
return element;
}
- private updateValue(key: string) {
+ private updateValueFromElement(key: string) {
let item = this.settings.get(key);
+ if (item.type === 'button') return;
let element = this.elements.get(key) as any;
- console.log(element, element?.value);
- switch (item.type) {
- case 'checkbox':
- item.value = element.checked;
- break;
- case 'select':
- item.value = element.value;
- break;
- case 'slider':
- item.value = parseInt(element.value);
- break;
- case 'textinput':
- item.value = element.value;
- break;
- case 'textarea':
- item.value = element.value;
- break;
- }
+ 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/types/index.d.ts b/src/types/index.d.ts
index 1891c1a..f224b3e 100644
--- a/src/types/index.d.ts
+++ b/src/types/index.d.ts
@@ -1,10 +1,13 @@
-/**
- * Copyright (c) 2023 frostime. All rights reserved.
+/*
+ * 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
*/
-/**
- * Frequently used data structures in SiYuan
- */
+
type DocumentId = string;
type BlockId = string;
type NotebookId = string;
@@ -28,7 +31,25 @@ type NotebookConf = {
dailyNoteTemplatePath: string;
}
-type BlockType = "d" | "s" | "h" | "t" | "i" | "p" | "f" | "audio" | "video" | "other";
+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";
@@ -70,6 +91,7 @@ type doOperation = {
interface Window {
siyuan: {
+ config: any;
notebooks: any;
menus: any;
dialogs: any;
@@ -78,5 +100,7 @@ interface Window {
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 e196929..0fcc1ad 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -47,7 +47,8 @@
"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 2af9f2f..cb7511b 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -1,19 +1,22 @@
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: {
@@ -25,92 +28,69 @@ export default defineConfig({
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: {
@@ -124,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}`);
+ }
+ }
+ }
+ },
+ };
+}