47 |
appId:
48 |
49 |
${app?.appId}
50 |
51 |
52 |
API demo:
53 |
54 |
55 | System current time: {time}
56 |
57 |
58 |
59 |
Protyle demo: id = {blockID}
60 |
61 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/scripts/make_dev_link.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2024 by frostime. All Rights Reserved.
3 | * @Author : frostime
4 | * @Date : 2023-07-15 15:31:31
5 | * @FilePath : /scripts/make_dev_link.js
6 | * @LastEditTime : 2024-09-06 18:13:53
7 | * @Description :
8 | */
9 | // make_dev_link.js
10 | import fs from 'fs';
11 | import { log, error, getSiYuanDir, chooseTarget, getThisPluginName, makeSymbolicLink } from './utils.js';
12 |
13 | let targetDir = '';
14 |
15 | /**
16 | * 1. Get the parent directory to install the plugin
17 | */
18 | log('>>> Try to visit constant "targetDir" in make_dev_link.js...');
19 | if (targetDir === '') {
20 | log('>>> Constant "targetDir" is empty, try to get SiYuan directory automatically....');
21 | let res = await getSiYuanDir();
22 |
23 | if (!res || res.length === 0) {
24 | log('>>> Can not get SiYuan directory automatically, try to visit environment variable "SIYUAN_PLUGIN_DIR"....');
25 | let env = process.env?.SIYUAN_PLUGIN_DIR;
26 | if (env) {
27 | targetDir = env;
28 | log(`\tGot target directory from environment variable "SIYUAN_PLUGIN_DIR": ${targetDir}`);
29 | } else {
30 | error('\tCan not get SiYuan directory from environment variable "SIYUAN_PLUGIN_DIR", failed!');
31 | process.exit(1);
32 | }
33 | } else {
34 | targetDir = await chooseTarget(res);
35 | }
36 |
37 | log(`>>> Successfully got target directory: ${targetDir}`);
38 | }
39 | if (!fs.existsSync(targetDir)) {
40 | error(`Failed! Plugin directory not exists: "${targetDir}"`);
41 | error('Please set the plugin directory in scripts/make_dev_link.js');
42 | process.exit(1);
43 | }
44 |
45 | /**
46 | * 2. The dev directory, which contains the compiled plugin code
47 | */
48 | const devDir = `${process.cwd()}/dev`;
49 | if (!fs.existsSync(devDir)) {
50 | fs.mkdirSync(devDir);
51 | }
52 |
53 |
54 | /**
55 | * 3. The target directory to make symbolic link to dev directory
56 | */
57 | const name = getThisPluginName();
58 | if (name === null) {
59 | process.exit(1);
60 | }
61 | const targetPath = `${targetDir}/${name}`;
62 |
63 | /**
64 | * 4. Make symbolic link
65 | */
66 | makeSymbolicLink(devDir, targetPath);
67 |
--------------------------------------------------------------------------------
/yaml-plugin.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2024 by frostime. All Rights Reserved.
3 | * @Author : frostime
4 | * @Date : 2024-04-05 21:27:55
5 | * @FilePath : /yaml-plugin.js
6 | * @LastEditTime : 2024-04-05 22:53:34
7 | * @Description : 去妮玛的 json 格式,我就是要用 yaml 写 i18n
8 | */
9 | // plugins/vite-plugin-parse-yaml.js
10 | import fs from 'fs';
11 | import yaml from 'js-yaml';
12 | import { resolve } from 'path';
13 |
14 | export default function vitePluginYamlI18n(options = {}) {
15 | // Default options with a fallback
16 | const DefaultOptions = {
17 | inDir: 'src/i18n',
18 | outDir: 'dist/i18n',
19 | };
20 |
21 | const finalOptions = { ...DefaultOptions, ...options };
22 |
23 | return {
24 | name: 'vite-plugin-yaml-i18n',
25 | buildStart() {
26 | console.log('🌈 Parse I18n: YAML to JSON..');
27 | const inDir = finalOptions.inDir;
28 | const outDir = finalOptions.outDir
29 |
30 | if (!fs.existsSync(outDir)) {
31 | fs.mkdirSync(outDir, { recursive: true });
32 | }
33 |
34 | //Parse yaml file, output to json
35 | const files = fs.readdirSync(inDir);
36 | for (const file of files) {
37 | if (file.endsWith('.yaml') || file.endsWith('.yml')) {
38 | console.log(`-- Parsing ${file}`)
39 | //检查是否有同名的json文件
40 | const jsonFile = file.replace(/\.(yaml|yml)$/, '.json');
41 | if (files.includes(jsonFile)) {
42 | console.log(`---- File ${jsonFile} already exists, skipping...`);
43 | continue;
44 | }
45 | try {
46 | const filePath = resolve(inDir, file);
47 | const fileContents = fs.readFileSync(filePath, 'utf8');
48 | const parsed = yaml.load(fileContents);
49 | const jsonContent = JSON.stringify(parsed, null, 2);
50 | const outputFilePath = resolve(outDir, file.replace(/\.(yaml|yml)$/, '.json'));
51 | console.log(`---- Writing to ${outputFilePath}`);
52 | fs.writeFileSync(outputFilePath, jsonContent);
53 | } catch (error) {
54 | this.error(`---- Error parsing YAML file ${file}: ${error.message}`);
55 | }
56 | }
57 | }
58 | },
59 | };
60 | }
61 |
--------------------------------------------------------------------------------
/src/libs/const.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2024 by frostime. All Rights Reserved.
3 | * @Author : frostime
4 | * @Date : 2024-06-08 20:36:30
5 | * @FilePath : /src/libs/const.ts
6 | * @LastEditTime : 2024-06-08 20:48:06
7 | * @Description :
8 | */
9 |
10 |
11 | export const BlockType2NodeType: {[key in BlockType]: string} = {
12 | d: 'NodeDocument',
13 | p: 'NodeParagraph',
14 | query_embed: 'NodeBlockQueryEmbed',
15 | l: 'NodeList',
16 | i: 'NodeListItem',
17 | h: 'NodeHeading',
18 | iframe: 'NodeIFrame',
19 | tb: 'NodeThematicBreak',
20 | b: 'NodeBlockquote',
21 | s: 'NodeSuperBlock',
22 | c: 'NodeCodeBlock',
23 | widget: 'NodeWidget',
24 | t: 'NodeTable',
25 | html: 'NodeHTMLBlock',
26 | m: 'NodeMathBlock',
27 | av: 'NodeAttributeView',
28 | audio: 'NodeAudio'
29 | }
30 |
31 |
32 | export const NodeIcons = {
33 | NodeAttributeView: {
34 | icon: "iconDatabase"
35 | },
36 | NodeAudio: {
37 | icon: "iconRecord"
38 | },
39 | NodeBlockQueryEmbed: {
40 | icon: "iconSQL"
41 | },
42 | NodeBlockquote: {
43 | icon: "iconQuote"
44 | },
45 | NodeCodeBlock: {
46 | icon: "iconCode"
47 | },
48 | NodeDocument: {
49 | icon: "iconFile"
50 | },
51 | NodeHTMLBlock: {
52 | icon: "iconHTML5"
53 | },
54 | NodeHeading: {
55 | icon: "iconHeadings",
56 | subtypes: {
57 | h1: { icon: "iconH1" },
58 | h2: { icon: "iconH2" },
59 | h3: { icon: "iconH3" },
60 | h4: { icon: "iconH4" },
61 | h5: { icon: "iconH5" },
62 | h6: { icon: "iconH6" }
63 | }
64 | },
65 | NodeIFrame: {
66 | icon: "iconLanguage"
67 | },
68 | NodeList: {
69 | subtypes: {
70 | o: { icon: "iconOrderedList" },
71 | t: { icon: "iconCheck" },
72 | u: { icon: "iconList" }
73 | }
74 | },
75 | NodeListItem: {
76 | icon: "iconListItem"
77 | },
78 | NodeMathBlock: {
79 | icon: "iconMath"
80 | },
81 | NodeParagraph: {
82 | icon: "iconParagraph"
83 | },
84 | NodeSuperBlock: {
85 | icon: "iconSuper"
86 | },
87 | NodeTable: {
88 | icon: "iconTable"
89 | },
90 | NodeThematicBreak: {
91 | icon: "iconLine"
92 | },
93 | NodeVideo: {
94 | icon: "iconVideo"
95 | },
96 | NodeWidget: {
97 | icon: "iconBoth"
98 | }
99 | };
100 |
--------------------------------------------------------------------------------
/src/types/index.d.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2024 by frostime. All Rights Reserved.
3 | * @Author : frostime
4 | * @Date : 2023-08-15 10:28:10
5 | * @FilePath : /src/types/index.d.ts
6 | * @LastEditTime : 2024-06-08 20:50:53
7 | * @Description : Frequently used data structures in SiYuan
8 | */
9 |
10 |
11 | type DocumentId = string;
12 | type BlockId = string;
13 | type NotebookId = string;
14 | type PreviousID = BlockId;
15 | type ParentID = BlockId | DocumentId;
16 |
17 | type Notebook = {
18 | id: NotebookId;
19 | name: string;
20 | icon: string;
21 | sort: number;
22 | closed: boolean;
23 | }
24 |
25 | type NotebookConf = {
26 | name: string;
27 | closed: boolean;
28 | refCreateSavePath: string;
29 | createDocNameTemplate: string;
30 | dailyNoteSavePath: string;
31 | dailyNoteTemplatePath: string;
32 | }
33 |
34 | type BlockType =
35 | | 'd'
36 | | 'p'
37 | | 'query_embed'
38 | | 'l'
39 | | 'i'
40 | | 'h'
41 | | 'iframe'
42 | | 'tb'
43 | | 'b'
44 | | 's'
45 | | 'c'
46 | | 'widget'
47 | | 't'
48 | | 'html'
49 | | 'm'
50 | | 'av'
51 | | 'audio';
52 |
53 |
54 | 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";
55 |
56 | type Block = {
57 | id: BlockId;
58 | parent_id?: BlockId;
59 | root_id: DocumentId;
60 | hash: string;
61 | box: string;
62 | path: string;
63 | hpath: string;
64 | name: string;
65 | alias: string;
66 | memo: string;
67 | tag: string;
68 | content: string;
69 | fcontent?: string;
70 | markdown: string;
71 | length: number;
72 | type: BlockType;
73 | subtype: BlockSubType;
74 | /** string of { [key: string]: string }
75 | * For instance: "{: custom-type=\"query-code\" id=\"20230613234017-zkw3pr0\" updated=\"20230613234509\"}"
76 | */
77 | ial?: string;
78 | sort: number;
79 | created: string;
80 | updated: string;
81 | }
82 |
83 | type doOperation = {
84 | action: string;
85 | data: string;
86 | id: BlockId;
87 | parentID: BlockId | DocumentId;
88 | previousID: BlockId;
89 | retData: null;
90 | }
91 |
92 | interface Window {
93 | siyuan: {
94 | config: any;
95 | notebooks: any;
96 | menus: any;
97 | dialogs: any;
98 | blockPanels: any;
99 | storage: any;
100 | user: any;
101 | ws: any;
102 | languages: any;
103 | emojis: any;
104 | };
105 | Lute: any;
106 | }
107 |
--------------------------------------------------------------------------------
/scripts/make_dev_copy.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2024 by frostime. All Rights Reserved.
3 | * @Author : frostime
4 | * @Date : 2025-07-13
5 | * @FilePath : /scripts/make_dev_copy.js
6 | * @LastEditTime : 2025-07-13
7 | * @Description : Copy plugin files to SiYuan plugins directory instead of creating symbolic links
8 | */
9 | import fs from 'fs';
10 | import path from 'path';
11 | import { log, error, getSiYuanDir, chooseTarget, getThisPluginName, copyDirectory } from './utils.js';
12 |
13 | let targetDir = `D:\\Notes\\Siyuan\\Achuan-2\\data\\plugins`;
14 | // let targetDir =`C:\\Users\\wangmin\\Documents\\siyuan_plugins_test\\data\\plugins`;
15 | // let targetDir =`C:\\Users\\wangmin\\Documents\\Project Code\\notebook\\data\\plugins`;
16 |
17 | /**
18 | * 1. Get the parent directory to install the plugin
19 | */
20 | log('>>> Try to visit constant "targetDir" in make_dev_copy.js...');
21 | if (targetDir === '') {
22 | log('>>> Constant "targetDir" is empty, try to get SiYuan directory automatically....');
23 | let res = await getSiYuanDir();
24 |
25 | if (!res || res.length === 0) {
26 | log('>>> Can not get SiYuan directory automatically, try to visit environment variable "SIYUAN_PLUGIN_DIR"....');
27 | let env = process.env?.SIYUAN_PLUGIN_DIR;
28 | if (env) {
29 | targetDir = env;
30 | log(`\tGot target directory from environment variable "SIYUAN_PLUGIN_DIR": ${targetDir}`);
31 | } else {
32 | error('\tCan not get SiYuan directory from environment variable "SIYUAN_PLUGIN_DIR", failed!');
33 | process.exit(1);
34 | }
35 | } else {
36 | targetDir = await chooseTarget(res);
37 | }
38 |
39 | log(`>>> Successfully got target directory: ${targetDir}`);
40 | }
41 | if (!fs.existsSync(targetDir)) {
42 | error(`Failed! Plugin directory not exists: "${targetDir}"`);
43 | error('Please set the plugin directory in scripts/make_dev_copy.js');
44 | process.exit(1);
45 | }
46 |
47 | /**
48 | * 2. The dev directory, which contains the compiled plugin code
49 | */
50 | const devDir = `${process.cwd()}/dev`;
51 | if (!fs.existsSync(devDir)) {
52 | error(`Failed! Dev directory not exists: "${devDir}"`);
53 | error('Please run "pnpm run build" or "pnpm run dev" first to generate the dev directory');
54 | process.exit(1);
55 | }
56 |
57 | /**
58 | * 3. The target directory to copy dev directory contents
59 | */
60 | const name = getThisPluginName();
61 | if (name === null) {
62 | process.exit(1);
63 | }
64 | const targetPath = `${targetDir}/${name}`;
65 |
66 | /**
67 | * 4. Create target directory if it doesn't exist
68 | */
69 | log(`>>> Ensuring target directory exists: ${targetPath}`);
70 | if (!fs.existsSync(targetPath)) {
71 | fs.mkdirSync(targetPath, { recursive: true });
72 | log(`Created directory: ${targetPath}`);
73 | } else {
74 | log(`Target directory already exists, will update files incrementally`);
75 | }
76 |
77 | /**
78 | * 5. Copy/update all contents from dev directory to target directory
79 | * This will only update changed files instead of deleting everything
80 | */
81 | copyDirectory(devDir, targetPath);
82 | log(`>>> Successfully synchronized all files to SiYuan plugins directory!`);
83 |
--------------------------------------------------------------------------------
/src/utils/sortConfig.ts:
--------------------------------------------------------------------------------
1 | import { Plugin } from "siyuan";
2 | import { t } from "./i18n";
3 | import { getFile, removeFile } from "../api";
4 | import { SETTINGS_FILE } from "../index";
5 |
6 | const SORT_CONFIG_FILE = 'data/storage/petal/siyuan-plugin-task-note-management/sort_config.json';
7 |
8 | export interface SortConfig {
9 | method: string;
10 | order: 'asc' | 'desc';
11 | }
12 |
13 | export async function loadSortConfig(plugin: Plugin): Promise
{
14 | try {
15 | const settings = await plugin.loadData(SETTINGS_FILE) || {};
16 |
17 | // 检查是否存在旧的 sort_config.json 文件,如果存在则导入并删除
18 | try {
19 | const oldSortContent = await getFile(SORT_CONFIG_FILE);
20 | if (oldSortContent && oldSortContent.code !== 404) {
21 | const oldSort = typeof oldSortContent === 'string' ? JSON.parse(oldSortContent) : oldSortContent;
22 | if (oldSort && typeof oldSort === 'object') {
23 | // 合并旧排序配置到新的 settings
24 | if (oldSort.method) settings.sortMethod = oldSort.method;
25 | if (oldSort.order) settings.sortOrder = oldSort.order;
26 | await plugin.saveData('reminder-settings.json', settings);
27 | // 删除旧文件
28 | await removeFile(SORT_CONFIG_FILE);
29 | console.log('成功导入并删除旧的 sort_config.json 文件');
30 | }
31 | }
32 | } catch (error) {
33 | // 如果文件不存在或其他错误,忽略
34 | console.log('旧的 sort_config.json 文件不存在或已处理');
35 | }
36 |
37 | return {
38 | method: settings.sortMethod || 'time',
39 | order: settings.sortOrder || 'asc'
40 | };
41 | } catch (error) {
42 | console.log('加载排序配置失败,使用默认配置:', error);
43 | return { method: 'time', order: 'asc' };
44 | }
45 | }
46 |
47 | export async function saveSortConfig(plugin: Plugin, method: string, order: 'asc' | 'desc' = 'asc'): Promise {
48 | try {
49 | const settings = await plugin.loadData(SETTINGS_FILE) || {};
50 | settings.sortMethod = method;
51 | settings.sortOrder = order;
52 | await plugin.saveData(SETTINGS_FILE, settings);
53 |
54 | console.log('排序配置保存成功:', { method, order });
55 |
56 | // 触发排序配置更新事件
57 | window.dispatchEvent(new CustomEvent('sortConfigUpdated', {
58 | detail: { method, order }
59 | }));
60 | } catch (error) {
61 | console.error('保存排序配置失败:', error);
62 | // 即使保存失败,仍然触发事件以保持界面同步
63 | window.dispatchEvent(new CustomEvent('sortConfigUpdated', {
64 | detail: { method, order }
65 | }));
66 | }
67 | }
68 |
69 | export function getSortMethodName(method: string, order: 'asc' | 'desc' = 'asc'): string {
70 | const methodNames = {
71 | 'time': t("sortByTime"),
72 | 'priority': t("sortByPriority"),
73 | 'title': t("sortByTitle"),
74 | 'created': t("sortByCreated")
75 | };
76 |
77 | const orderNames = {
78 | 'asc': t("ascending"),
79 | 'desc': t("descending")
80 | };
81 |
82 | const methodName = methodNames[method] || t("sortByTime");
83 | const orderName = orderNames[order] || t("ascending");
84 |
85 | return `${methodName}(${orderName})`;
86 | }
87 |
--------------------------------------------------------------------------------
/src/components/ProjectColorDialog.ts:
--------------------------------------------------------------------------------
1 | import { Dialog, showMessage } from "siyuan";
2 | import { t } from "../utils/i18n";
3 | import { Project, ProjectManager } from "../utils/projectManager";
4 | import { StatusManager } from "../utils/statusManager";
5 |
6 | export class ProjectColorDialog {
7 | private dialog: Dialog;
8 | private projectManager: ProjectManager;
9 | private statusManager: StatusManager;
10 | private onSave: () => void;
11 |
12 | constructor(onSave: () => void, plugin?: any) {
13 | this.projectManager = ProjectManager.getInstance(plugin);
14 | this.statusManager = StatusManager.getInstance(plugin);
15 | this.onSave = onSave;
16 | }
17 |
18 | public show() {
19 | this.dialog = new Dialog({
20 | title: t("setProjectColors"),
21 | content: ``,
22 | width: "520px",
23 | height: "600px",
24 | });
25 |
26 | const contentEl = this.dialog.element.querySelector("#project-color-dialog-content");
27 | this.renderContent(contentEl);
28 | }
29 |
30 | private async renderContent(container: Element) {
31 | await this.projectManager.initialize();
32 | const projectsByStatus = this.projectManager.getProjectsGroupedByStatus();
33 |
34 | let content = '';
35 | for (const statusId in projectsByStatus) {
36 | const projects = projectsByStatus[statusId];
37 | if (projects.length > 0) {
38 | const status = this.statusManager.getStatusById(statusId);
39 | const statusName = status ? status.name : t("uncategorized");
40 | content += `
41 |
42 |
43 | ${statusName} (${projects.length})
44 |
45 | ${projects.map(p => this.renderProjectItem(p)).join('')}
46 |
47 |
48 |
49 | `;
50 | }
51 | }
52 | container.innerHTML = content;
53 | this.addEventListeners(container);
54 | }
55 |
56 | private renderProjectItem(project: Project): string {
57 | const color = this.projectManager.getProjectColor(project.id);
58 | return `
59 |
60 |
${project.name}
61 |
62 |
63 |
64 |
65 | `;
66 | }
67 |
68 | private addEventListeners(container: Element) {
69 | container.querySelectorAll('input[type="color"]').forEach(input => {
70 | input.addEventListener('change', async (e) => {
71 | const target = e.target as HTMLInputElement;
72 | const projectId = (target.closest('.project-item') as HTMLElement).dataset.projectId;
73 | await this.projectManager.setProjectColor(projectId, target.value);
74 | showMessage(t("colorSetSuccess"));
75 | this.onSave();
76 | });
77 | });
78 | }
79 | }
--------------------------------------------------------------------------------
/src/libs/components/Form/form-input.svelte:
--------------------------------------------------------------------------------
1 |
33 |
34 | {#if type === "checkbox"}
35 |
36 |
44 | {:else if type === "textinput"}
45 |
46 |
56 | {:else if type === "textarea"}
57 |
63 | {:else if type === "number"}
64 |
74 | {:else if type === "button"}
75 |
76 |
87 | {:else if type === "select"}
88 |
89 |
102 | {:else if type == "slider"}
103 |
104 |
105 |
117 |
118 | {/if}
119 |
--------------------------------------------------------------------------------
/src/utils/pomodoroManager.ts:
--------------------------------------------------------------------------------
1 | import { PomodoroTimer } from "../components/PomodoroTimer";
2 |
3 | /**
4 | * 全局番茄钟管理器
5 | * 确保整个插件只有一个活动的番茄钟实例,避免重复创建
6 | */
7 | export class PomodoroManager {
8 | private static instance: PomodoroManager | null = null;
9 | private currentPomodoroTimer: PomodoroTimer | null = null;
10 |
11 | private constructor() { }
12 |
13 | /**
14 | * 获取全局单例实例
15 | */
16 | public static getInstance(): PomodoroManager {
17 | if (!PomodoroManager.instance) {
18 | PomodoroManager.instance = new PomodoroManager();
19 | }
20 | return PomodoroManager.instance;
21 | }
22 |
23 | /**
24 | * 获取当前活动的番茄钟实例
25 | */
26 | public getCurrentPomodoroTimer(): PomodoroTimer | null {
27 | return this.currentPomodoroTimer;
28 | }
29 |
30 | /**
31 | * 设置当前活动的番茄钟实例
32 | */
33 | public setCurrentPomodoroTimer(timer: PomodoroTimer | null): void {
34 | this.currentPomodoroTimer = timer;
35 | }
36 |
37 | /**
38 | * 检查是否有活动的番茄钟实例且窗口仍然存在
39 | */
40 | public hasActivePomodoroTimer(): boolean {
41 | return this.currentPomodoroTimer !== null && this.currentPomodoroTimer.isWindowActive();
42 | }
43 |
44 | /**
45 | * 获取当前番茄钟的状态(如果存在)
46 | */
47 | public getCurrentState(): any {
48 | if (this.currentPomodoroTimer && this.currentPomodoroTimer.isWindowActive()) {
49 | return this.currentPomodoroTimer.getCurrentState();
50 | }
51 | return null;
52 | }
53 |
54 | /**
55 | * 暂停当前番茄钟(如果存在且正在运行)
56 | */
57 | public pauseCurrentTimer(): boolean {
58 | if (this.currentPomodoroTimer && this.currentPomodoroTimer.isWindowActive()) {
59 | try {
60 | this.currentPomodoroTimer.pauseFromExternal();
61 | return true;
62 | } catch (error) {
63 | console.error('暂停当前番茄钟失败:', error);
64 | return false;
65 | }
66 | }
67 | return false;
68 | }
69 |
70 | /**
71 | * 恢复当前番茄钟的运行(如果存在且已暂停)
72 | */
73 | public resumeCurrentTimer(): boolean {
74 | if (this.currentPomodoroTimer && this.currentPomodoroTimer.isWindowActive()) {
75 | try {
76 | this.currentPomodoroTimer.resumeFromExternal();
77 | return true;
78 | } catch (error) {
79 | console.error('恢复番茄钟运行失败:', error);
80 | return false;
81 | }
82 | }
83 | return false;
84 | }
85 |
86 | /**
87 | * 关闭并清理当前番茄钟实例
88 | */
89 | public closeCurrentTimer(): void {
90 | if (this.currentPomodoroTimer) {
91 | try {
92 | // 检查窗口是否仍然活动,如果不活动则直接清理引用
93 | if (!this.currentPomodoroTimer.isWindowActive()) {
94 | this.currentPomodoroTimer = null;
95 | return;
96 | }
97 | this.currentPomodoroTimer.close();
98 | } catch (error) {
99 | console.error('关闭番茄钟实例失败:', error);
100 | }
101 | this.currentPomodoroTimer = null;
102 | }
103 | }
104 |
105 | /**
106 | * 销毁并清理当前番茄钟实例
107 | */
108 | public destroyCurrentTimer(): void {
109 | if (this.currentPomodoroTimer) {
110 | try {
111 | // 检查窗口是否仍然活动,如果不活动则直接清理引用
112 | if (!this.currentPomodoroTimer.isWindowActive()) {
113 | this.currentPomodoroTimer = null;
114 | return;
115 | }
116 | this.currentPomodoroTimer.destroy();
117 | } catch (error) {
118 | console.error('销毁番茄钟实例失败:', error);
119 | }
120 | this.currentPomodoroTimer = null;
121 | }
122 | }
123 |
124 | /**
125 | * 清理所有资源(在插件卸载时调用)
126 | */
127 | public cleanup(): void {
128 | this.destroyCurrentTimer();
129 | PomodoroManager.instance = null;
130 | }
131 |
132 | /**
133 | * 清理无效的番茄钟引用(窗口已关闭但引用还在)
134 | */
135 | public cleanupInactiveTimer(): void {
136 | if (this.currentPomodoroTimer && !this.currentPomodoroTimer.isWindowActive()) {
137 | this.currentPomodoroTimer = null;
138 | }
139 | }
140 | }
--------------------------------------------------------------------------------
/src/utils/calendarConfigManager.ts:
--------------------------------------------------------------------------------
1 | import { Plugin } from "siyuan";
2 | import { getFile, removeFile } from "../api";
3 | import { SETTINGS_FILE } from "../index";
4 |
5 | const CALENDAR_CONFIG_FILE = 'data/storage/petal/siyuan-plugin-task-note-management/calendar-config.json';
6 |
7 | export interface CalendarConfig {
8 | colorBy: 'category' | 'priority' | 'project';
9 | viewMode: 'dayGridMonth' | 'timeGridWeek' | 'timeGridDay';
10 | }
11 |
12 | export class CalendarConfigManager {
13 | private static instance: CalendarConfigManager;
14 | private config: CalendarConfig;
15 | private plugin: Plugin;
16 |
17 | private constructor(plugin: Plugin) {
18 | this.plugin = plugin;
19 | this.config = {
20 | colorBy: 'project', // 默认按项目上色
21 | viewMode: 'timeGridWeek' // 默认周视图
22 | };
23 | }
24 |
25 | public static getInstance(plugin: Plugin): CalendarConfigManager {
26 | if (!CalendarConfigManager.instance) {
27 | CalendarConfigManager.instance = new CalendarConfigManager(plugin);
28 | }
29 | return CalendarConfigManager.instance;
30 | }
31 |
32 | async initialize() {
33 | await this.loadConfig();
34 | }
35 |
36 | private async saveConfig() {
37 | try {
38 | const settings = await this.plugin.loadData(SETTINGS_FILE) || {};
39 | settings.calendarColorBy = this.config.colorBy;
40 | settings.calendarViewMode = this.config.viewMode;
41 | await this.plugin.saveData(SETTINGS_FILE, settings);
42 | } catch (error) {
43 | console.error('Failed to save calendar config:', error);
44 | throw error;
45 | }
46 | }
47 |
48 | private async loadConfig() {
49 | try {
50 | const settings = await this.plugin.loadData(SETTINGS_FILE) || {};
51 |
52 | // 检查是否存在旧的 calendar-config.json 文件,如果存在则导入并删除
53 | try {
54 | const oldCalendarContent = await getFile(CALENDAR_CONFIG_FILE);
55 | if (oldCalendarContent && oldCalendarContent.code !== 404) {
56 | const oldCalendar = typeof oldCalendarContent === 'string' ? JSON.parse(oldCalendarContent) : oldCalendarContent;
57 | if (oldCalendar && typeof oldCalendar === 'object') {
58 | // 合并旧日历配置到新的 settings
59 | if (oldCalendar.colorBy) settings.calendarColorBy = oldCalendar.colorBy;
60 | if (oldCalendar.viewMode) settings.calendarViewMode = oldCalendar.viewMode;
61 | await this.plugin.saveData(SETTINGS_FILE, settings);
62 | // 删除旧文件
63 | await removeFile(CALENDAR_CONFIG_FILE);
64 | console.log('成功导入并删除旧的 calendar-config.json 文件');
65 | }
66 | }
67 | } catch (error) {
68 | // 如果文件不存在或其他错误,忽略
69 | console.log('旧的 calendar-config.json 文件不存在或已处理');
70 | }
71 |
72 | this.config = {
73 | colorBy: settings.calendarColorBy || 'project',
74 | viewMode: settings.calendarViewMode || 'timeGridWeek'
75 | };
76 | } catch (error) {
77 | console.warn('Failed to load calendar config, using defaults:', error);
78 | this.config = { colorBy: 'project', viewMode: 'timeGridWeek' };
79 | try {
80 | await this.saveConfig();
81 | } catch (saveError) {
82 | console.error('Failed to create initial calendar config:', saveError);
83 | }
84 | }
85 | }
86 |
87 | public async setColorBy(colorBy: 'category' | 'priority' | 'project') {
88 | this.config.colorBy = colorBy;
89 | await this.saveConfig();
90 | }
91 |
92 | public getColorBy(): 'category' | 'priority' | 'project' {
93 | return this.config.colorBy;
94 | }
95 |
96 | public async setViewMode(viewMode: 'dayGridMonth' | 'timeGridWeek' | 'timeGridDay') {
97 | this.config.viewMode = viewMode;
98 | await this.saveConfig();
99 | }
100 |
101 | public getViewMode(): 'dayGridMonth' | 'timeGridWeek' | 'timeGridDay' {
102 | return this.config.viewMode;
103 | }
104 |
105 | public getConfig(): CalendarConfig {
106 | return { ...this.config };
107 | }
108 | }
--------------------------------------------------------------------------------
/src/utils/habitGroupManager.ts:
--------------------------------------------------------------------------------
1 | export interface HabitGroup {
2 | id: string;
3 | name: string;
4 | color?: string;
5 | icon?: string;
6 | order: number;
7 | createdAt: string;
8 | updatedAt: string;
9 | }
10 |
11 | export class HabitGroupManager {
12 | private static instance: HabitGroupManager;
13 | private groups: Map = new Map();
14 | private initialized: boolean = false;
15 |
16 | private constructor() { }
17 |
18 | static getInstance(): HabitGroupManager {
19 | if (!HabitGroupManager.instance) {
20 | HabitGroupManager.instance = new HabitGroupManager();
21 | }
22 | return HabitGroupManager.instance;
23 | }
24 |
25 | async initialize() {
26 | if (this.initialized) return;
27 |
28 | try {
29 | // 使用 API 从文件读取数据
30 | const { readHabitGroupData } = await import('../api');
31 | const groupsArray: HabitGroup[] = await readHabitGroupData();
32 |
33 | this.groups.clear();
34 | if (Array.isArray(groupsArray)) {
35 | groupsArray.forEach(group => {
36 | // 兼容旧数据,如果没有order则默认为0
37 | if (group.order === undefined) {
38 | group.order = 0;
39 | }
40 | this.groups.set(group.id, group);
41 | });
42 | }
43 | this.initialized = true;
44 | } catch (error) {
45 | console.error('初始化习惯分组管理器失败:', error);
46 | this.groups.clear();
47 | this.initialized = true;
48 | }
49 | }
50 |
51 | async saveGroups() {
52 | try {
53 | // 使用 API 保存到文件
54 | const { writeHabitGroupData } = await import('../api');
55 | const groupsArray = Array.from(this.groups.values());
56 | await writeHabitGroupData(groupsArray);
57 | } catch (error) {
58 | console.error('保存习惯分组失败:', error);
59 | throw error;
60 | }
61 | }
62 |
63 | getAllGroups(): HabitGroup[] {
64 | return Array.from(this.groups.values()).sort((a, b) => {
65 | if (a.order !== b.order) {
66 | return a.order - b.order;
67 | }
68 | return a.createdAt.localeCompare(b.createdAt);
69 | });
70 | }
71 |
72 | getGroupById(id: string): HabitGroup | undefined {
73 | return this.groups.get(id);
74 | }
75 |
76 | async addGroup(group: Omit): Promise {
77 | const now = new Date().toISOString();
78 |
79 | // 计算最大order
80 | let maxOrder = -1;
81 | this.groups.forEach(g => {
82 | if (g.order > maxOrder) maxOrder = g.order;
83 | });
84 |
85 | const newGroup: HabitGroup = {
86 | ...group,
87 | id: `habit-group-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
88 | order: maxOrder + 1,
89 | createdAt: now,
90 | updatedAt: now
91 | };
92 |
93 | this.groups.set(newGroup.id, newGroup);
94 | await this.saveGroups();
95 | return newGroup;
96 | }
97 |
98 | async updateGroup(id: string, updates: Partial>): Promise {
99 | const group = this.groups.get(id);
100 | if (!group) {
101 | throw new Error(`分组不存在: ${id}`);
102 | }
103 |
104 | const updatedGroup: HabitGroup = {
105 | ...group,
106 | ...updates,
107 | updatedAt: new Date().toISOString()
108 | };
109 |
110 | this.groups.set(id, updatedGroup);
111 | await this.saveGroups();
112 | }
113 |
114 | async updateGroupOrder(groupIds: string[]): Promise {
115 | let changed = false;
116 | groupIds.forEach((id, index) => {
117 | const group = this.groups.get(id);
118 | if (group && group.order !== index) {
119 | group.order = index;
120 | group.updatedAt = new Date().toISOString();
121 | changed = true;
122 | }
123 | });
124 |
125 | if (changed) {
126 | await this.saveGroups();
127 | }
128 | }
129 |
130 | async deleteGroup(id: string): Promise {
131 | if (!this.groups.has(id)) {
132 | throw new Error(`分组不存在: ${id}`);
133 | }
134 |
135 | this.groups.delete(id);
136 | await this.saveGroups();
137 | }
138 |
139 | groupExists(name: string, excludeId?: string): boolean {
140 | return Array.from(this.groups.values()).some(
141 | group => group.name === name && group.id !== excludeId
142 | );
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/src/utils/statusManager.ts:
--------------------------------------------------------------------------------
1 | import { STATUSES_DATA_FILE } from "../index";
2 |
3 | export interface Status {
4 | id: string;
5 | name: string;
6 | icon?: string;
7 | isArchived: boolean; // 标识是否是归档状态
8 | }
9 |
10 | const DEFAULT_STATUSES: Status[] = [
11 | { id: 'active', name: '正在进行', icon: '⏳', isArchived: false },
12 | { id: 'someday', name: '未来也许', icon: '💭', isArchived: false },
13 | { id: 'archived', name: '已归档', icon: '📥', isArchived: true }
14 | ];
15 |
16 | export class StatusManager {
17 | private static instance: StatusManager;
18 | private statuses: Status[] = [];
19 | private plugin: any;
20 |
21 | private constructor(plugin: any) {
22 | this.plugin = plugin;
23 | }
24 |
25 | public static getInstance(plugin?: any): StatusManager {
26 | if (!StatusManager.instance) {
27 | StatusManager.instance = new StatusManager(plugin);
28 | }
29 | return StatusManager.instance;
30 | }
31 |
32 | public async initialize(): Promise {
33 | try {
34 | await this.loadStatuses();
35 | } catch (error) {
36 | console.error('初始化状态失败:', error);
37 | this.statuses = [...DEFAULT_STATUSES];
38 | await this.saveStatuses();
39 | }
40 | }
41 |
42 | public async loadStatuses(): Promise {
43 | try {
44 | const content = await this.plugin.loadData(STATUSES_DATA_FILE);
45 | if (!content) {
46 | console.log('状态文件不存在,创建默认状态');
47 | this.statuses = [...DEFAULT_STATUSES];
48 | await this.saveStatuses();
49 | return this.statuses;
50 | }
51 |
52 | const statusesData = content;
53 |
54 | if (Array.isArray(statusesData) && statusesData.length > 0) {
55 | this.statuses = statusesData;
56 | } else {
57 | console.log('状态数据无效,使用默认状态');
58 | this.statuses = [...DEFAULT_STATUSES];
59 | await this.saveStatuses();
60 | }
61 | } catch (error) {
62 | console.warn('加载状态文件失败,使用默认状态:', error);
63 | this.statuses = [...DEFAULT_STATUSES];
64 | await this.saveStatuses();
65 | }
66 |
67 | return this.statuses;
68 | }
69 |
70 | public async saveStatuses(): Promise {
71 | try {
72 | await this.plugin.saveData(STATUSES_DATA_FILE, this.statuses);
73 | } catch (error) {
74 | console.error('保存状态失败:', error);
75 | throw error;
76 | }
77 | }
78 |
79 | public getStatuses(): Status[] {
80 | return [...this.statuses];
81 | }
82 |
83 | public getStatusById(id: string): Status | undefined {
84 | return this.statuses.find(s => s.id === id);
85 | }
86 |
87 | public async addStatus(status: Omit): Promise {
88 | const newStatus: Status = {
89 | ...status,
90 | id: `status_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`,
91 | isArchived: false
92 | };
93 |
94 | this.statuses.push(newStatus);
95 | await this.saveStatuses();
96 | return newStatus;
97 | }
98 |
99 | public async updateStatus(id: string, updates: Partial>): Promise {
100 | const index = this.statuses.findIndex(s => s.id === id);
101 | if (index === -1) {
102 | return false;
103 | }
104 |
105 | this.statuses[index] = { ...this.statuses[index], ...updates };
106 | await this.saveStatuses();
107 | return true;
108 | }
109 |
110 | public async deleteStatus(id: string): Promise {
111 | const index = this.statuses.findIndex(s => s.id === id);
112 | if (index === -1) {
113 | return false;
114 | }
115 |
116 | // 不允许删除归档状态
117 | if (this.statuses[index].isArchived) {
118 | return false;
119 | }
120 |
121 | this.statuses.splice(index, 1);
122 | await this.saveStatuses();
123 | return true;
124 | }
125 |
126 | public async resetToDefault(): Promise {
127 | this.statuses = [...DEFAULT_STATUSES];
128 | await this.saveStatuses();
129 | }
130 |
131 | public async reorderStatuses(reorderedStatuses: Status[]): Promise {
132 | if (!Array.isArray(reorderedStatuses)) {
133 | throw new Error('Reordered statuses must be an array');
134 | }
135 |
136 | if (reorderedStatuses.length !== this.statuses.length) {
137 | throw new Error('Reordered statuses count does not match');
138 | }
139 |
140 | const currentIds = new Set(this.statuses.map(s => s.id));
141 | const reorderedIds = new Set(reorderedStatuses.map(s => s.id));
142 |
143 | if (currentIds.size !== reorderedIds.size ||
144 | ![...currentIds].every(id => reorderedIds.has(id))) {
145 | throw new Error('Reordered status IDs do not match');
146 | }
147 |
148 | this.statuses = [...reorderedStatuses];
149 | await this.saveStatuses();
150 | }
151 | }
--------------------------------------------------------------------------------
/scripts/update_version.js:
--------------------------------------------------------------------------------
1 | // const fs = require('fs');
2 | // const path = require('path');
3 | // const readline = require('readline');
4 | import fs from 'node:fs';
5 | import path from 'node:path';
6 | import readline from 'node:readline';
7 |
8 | // Utility to read JSON file
9 | function readJsonFile(filePath) {
10 | return new Promise((resolve, reject) => {
11 | fs.readFile(filePath, 'utf8', (err, data) => {
12 | if (err) return reject(err);
13 | try {
14 | const jsonData = JSON.parse(data);
15 | resolve(jsonData);
16 | } catch (e) {
17 | reject(e);
18 | }
19 | });
20 | });
21 | }
22 |
23 | // Utility to write JSON file
24 | function writeJsonFile(filePath, jsonData) {
25 | return new Promise((resolve, reject) => {
26 | fs.writeFile(filePath, JSON.stringify(jsonData, null, 2), 'utf8', (err) => {
27 | if (err) return reject(err);
28 | resolve();
29 | });
30 | });
31 | }
32 |
33 | // Utility to prompt the user for input
34 | function promptUser(query) {
35 | const rl = readline.createInterface({
36 | input: process.stdin,
37 | output: process.stdout
38 | });
39 | return new Promise((resolve) => rl.question(query, (answer) => {
40 | rl.close();
41 | resolve(answer);
42 | }));
43 | }
44 |
45 | // Function to parse the version string
46 | function parseVersion(version) {
47 | const [major, minor, patch] = version.split('.').map(Number);
48 | return { major, minor, patch };
49 | }
50 |
51 | // Function to auto-increment version parts
52 | function incrementVersion(version, type) {
53 | let { major, minor, patch } = parseVersion(version);
54 |
55 | switch (type) {
56 | case 'major':
57 | major++;
58 | minor = 0;
59 | patch = 0;
60 | break;
61 | case 'minor':
62 | minor++;
63 | patch = 0;
64 | break;
65 | case 'patch':
66 | patch++;
67 | break;
68 | default:
69 | break;
70 | }
71 |
72 | return `${major}.${minor}.${patch}`;
73 | }
74 |
75 | // Main script
76 | (async function () {
77 | try {
78 | const pluginJsonPath = path.join(process.cwd(), 'plugin.json');
79 | const packageJsonPath = path.join(process.cwd(), 'package.json');
80 |
81 | // Read both JSON files
82 | const pluginData = await readJsonFile(pluginJsonPath);
83 | const packageData = await readJsonFile(packageJsonPath);
84 |
85 | // Get the current version from both files (assuming both have the same version)
86 | const currentVersion = pluginData.version || packageData.version;
87 | console.log(`\n🌟 Current version: \x1b[36m${currentVersion}\x1b[0m\n`);
88 |
89 | // Calculate potential new versions for auto-update
90 | const newPatchVersion = incrementVersion(currentVersion, 'patch');
91 | const newMinorVersion = incrementVersion(currentVersion, 'minor');
92 | const newMajorVersion = incrementVersion(currentVersion, 'major');
93 |
94 | // Prompt the user with formatted options
95 | console.log('🔄 How would you like to update the version?\n');
96 | console.log(` 1️⃣ Auto update \x1b[33mpatch\x1b[0m version (new version: \x1b[32m${newPatchVersion}\x1b[0m)`);
97 | console.log(` 2️⃣ Auto update \x1b[33mminor\x1b[0m version (new version: \x1b[32m${newMinorVersion}\x1b[0m)`);
98 | console.log(` 3️⃣ Auto update \x1b[33mmajor\x1b[0m version (new version: \x1b[32m${newMajorVersion}\x1b[0m)`);
99 | console.log(` 4️⃣ Input version \x1b[33mmanually\x1b[0m`);
100 | // Press 0 to skip version update
101 | console.log(' 0️⃣ Quit without updating\n');
102 |
103 | const updateChoice = await promptUser('👉 Please choose (1/2/3/4): ');
104 |
105 | let newVersion;
106 |
107 | switch (updateChoice.trim()) {
108 | case '1':
109 | newVersion = newPatchVersion;
110 | break;
111 | case '2':
112 | newVersion = newMinorVersion;
113 | break;
114 | case '3':
115 | newVersion = newMajorVersion;
116 | break;
117 | case '4':
118 | newVersion = await promptUser('✍️ Please enter the new version (in a.b.c format): ');
119 | break;
120 | case '0':
121 | console.log('\n🛑 Skipping version update.');
122 | return;
123 | default:
124 | console.log('\n❌ Invalid option, no version update.');
125 | return;
126 | }
127 |
128 | // Update the version in both plugin.json and package.json
129 | pluginData.version = newVersion;
130 | packageData.version = newVersion;
131 |
132 | // Write the updated JSON back to files
133 | await writeJsonFile(pluginJsonPath, pluginData);
134 | await writeJsonFile(packageJsonPath, packageData);
135 |
136 | console.log(`\n✅ Version successfully updated to: \x1b[32m${newVersion}\x1b[0m\n`);
137 |
138 | } catch (error) {
139 | console.error('❌ Error:', error);
140 | }
141 | })();
142 |
--------------------------------------------------------------------------------
/src/libs/dialog.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2024 by frostime. All Rights Reserved.
3 | * @Author : frostime
4 | * @Date : 2024-03-23 21:37:33
5 | * @FilePath : /src/libs/dialog.ts
6 | * @LastEditTime : 2024-10-16 14:31:04
7 | * @Description : Kits about dialogs
8 | */
9 | import { Dialog } from "siyuan";
10 | import { type SvelteComponent } from "svelte";
11 |
12 | export const inputDialog = (args: {
13 | title: string, placeholder?: string, defaultText?: string,
14 | confirm?: (text: string) => void, cancel?: () => void,
15 | width?: string, height?: string
16 | }) => {
17 | const dialog = new Dialog({
18 | title: args.title,
19 | content: `
22 |
23 |
24 |
25 |
`,
26 | width: args.width ?? "520px",
27 | height: args.height
28 | });
29 | const target: HTMLTextAreaElement = dialog.element.querySelector(".b3-dialog__content>div.ft__breakword>textarea");
30 | const btnsElement = dialog.element.querySelectorAll(".b3-button");
31 | btnsElement[0].addEventListener("click", () => {
32 | if (args?.cancel) {
33 | args.cancel();
34 | }
35 | dialog.destroy();
36 | });
37 | btnsElement[1].addEventListener("click", () => {
38 | if (args?.confirm) {
39 | args.confirm(target.value);
40 | }
41 | dialog.destroy();
42 | });
43 | };
44 |
45 | export const inputDialogSync = async (args: {
46 | title: string, placeholder?: string, defaultText?: string,
47 | width?: string, height?: string
48 | }) => {
49 | return new Promise((resolve) => {
50 | let newargs = {
51 | ...args, confirm: (text) => {
52 | resolve(text);
53 | }, cancel: () => {
54 | resolve(null);
55 | }
56 | };
57 | inputDialog(newargs);
58 | });
59 | }
60 |
61 |
62 | interface IConfirmDialogArgs {
63 | title: string;
64 | content: string | HTMLElement;
65 | confirm?: (ele?: HTMLElement) => void;
66 | cancel?: (ele?: HTMLElement) => void;
67 | width?: string;
68 | height?: string;
69 | }
70 |
71 | export const confirmDialog = (args: IConfirmDialogArgs) => {
72 | const { title, content, confirm, cancel, width, height } = args;
73 |
74 | const dialog = new Dialog({
75 | title,
76 | content: `
80 |
81 |
82 |
83 |
`,
84 | width: width,
85 | height: height
86 | });
87 |
88 | const target: HTMLElement = dialog.element.querySelector(".b3-dialog__content>div.ft__breakword");
89 | if (typeof content === "string") {
90 | target.innerHTML = content;
91 | } else {
92 | target.appendChild(content);
93 | }
94 |
95 | const btnsElement = dialog.element.querySelectorAll(".b3-button");
96 | btnsElement[0].addEventListener("click", () => {
97 | if (cancel) {
98 | cancel(target);
99 | }
100 | dialog.destroy();
101 | });
102 | btnsElement[1].addEventListener("click", () => {
103 | if (confirm) {
104 | confirm(target);
105 | }
106 | dialog.destroy();
107 | });
108 | };
109 |
110 |
111 | export const confirmDialogSync = async (args: IConfirmDialogArgs) => {
112 | return new Promise((resolve) => {
113 | let newargs = {
114 | ...args, confirm: (ele: HTMLElement) => {
115 | resolve(ele);
116 | }, cancel: (ele: HTMLElement) => {
117 | resolve(ele);
118 | }
119 | };
120 | confirmDialog(newargs);
121 | });
122 | };
123 |
124 |
125 | export const simpleDialog = (args: {
126 | title: string, ele: HTMLElement | DocumentFragment,
127 | width?: string, height?: string,
128 | callback?: () => void;
129 | }) => {
130 | const dialog = new Dialog({
131 | title: args.title,
132 | content: ``,
133 | width: args.width,
134 | height: args.height,
135 | destroyCallback: args.callback
136 | });
137 | dialog.element.querySelector(".dialog-content").appendChild(args.ele);
138 | return {
139 | dialog,
140 | close: dialog.destroy.bind(dialog)
141 | };
142 | }
143 |
144 |
145 | export const svelteDialog = (args: {
146 | title: string, constructor: (container: HTMLElement) => SvelteComponent,
147 | width?: string, height?: string,
148 | callback?: () => void;
149 | }) => {
150 | let container = document.createElement('div')
151 | container.style.display = 'contents';
152 | let component = args.constructor(container);
153 | const { dialog, close } = simpleDialog({
154 | ...args, ele: container, callback: () => {
155 | component.$destroy();
156 | if (args.callback) args.callback();
157 | }
158 | });
159 | return {
160 | component,
161 | dialog,
162 | close
163 | }
164 | }
165 |
--------------------------------------------------------------------------------
/scripts/utils.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2024 by frostime. All Rights Reserved.
3 | * @Author : frostime
4 | * @Date : 2024-09-06 17:42:57
5 | * @FilePath : /scripts/utils.js
6 | * @LastEditTime : 2024-09-06 19:23:12
7 | * @Description :
8 | */
9 | // common.js
10 | import fs from 'fs';
11 | import path from 'node:path';
12 | import http from 'node:http';
13 | import readline from 'node:readline';
14 |
15 | // Logging functions
16 | export const log = (info) => console.log(`\x1B[36m%s\x1B[0m`, info);
17 | export const error = (info) => console.log(`\x1B[31m%s\x1B[0m`, info);
18 |
19 | // HTTP POST headers
20 | export const POST_HEADER = {
21 | "Content-Type": "application/json",
22 | };
23 |
24 | // Fetch function compatible with older Node.js versions
25 | export async function myfetch(url, options) {
26 | return new Promise((resolve, reject) => {
27 | let req = http.request(url, options, (res) => {
28 | let data = '';
29 | res.on('data', (chunk) => {
30 | data += chunk;
31 | });
32 | res.on('end', () => {
33 | resolve({
34 | ok: true,
35 | status: res.statusCode,
36 | json: () => JSON.parse(data)
37 | });
38 | });
39 | });
40 | req.on('error', (e) => {
41 | reject(e);
42 | });
43 | req.end();
44 | });
45 | }
46 |
47 | /**
48 | * Fetch SiYuan workspaces from port 6806
49 | * @returns {Promise