generated from mirrors/plugin-sample-vite-svelte
Initial commit
This commit is contained in:
commit
04f54e248a
42 changed files with 4538 additions and 0 deletions
478
src/api.ts
Normal file
478
src/api.ts
Normal file
|
|
@ -0,0 +1,478 @@
|
|||
/**
|
||||
* Copyright (c) 2023 frostime. All rights reserved.
|
||||
* https://github.com/frostime/sy-plugin-template-vite
|
||||
*
|
||||
* See API Document in [API.md](https://github.com/siyuan-note/siyuan/blob/master/API.md)
|
||||
* API 文档见 [API_zh_CN.md](https://github.com/siyuan-note/siyuan/blob/master/API_zh_CN.md)
|
||||
*/
|
||||
|
||||
import { fetchPost, fetchSyncPost, IWebSocketData } from "siyuan";
|
||||
|
||||
|
||||
export async function request(url: string, data: any) {
|
||||
let response: IWebSocketData = await fetchSyncPost(url, data);
|
||||
let res = response.code === 0 ? response.data : null;
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
// **************************************** Noteboook ****************************************
|
||||
|
||||
|
||||
export async function lsNotebooks(): Promise<IReslsNotebooks> {
|
||||
let url = '/api/notebook/lsNotebooks';
|
||||
return request(url, '');
|
||||
}
|
||||
|
||||
|
||||
export async function openNotebook(notebook: NotebookId) {
|
||||
let url = '/api/notebook/openNotebook';
|
||||
return request(url, { notebook: notebook });
|
||||
}
|
||||
|
||||
|
||||
export async function closeNotebook(notebook: NotebookId) {
|
||||
let url = '/api/notebook/closeNotebook';
|
||||
return request(url, { notebook: notebook });
|
||||
}
|
||||
|
||||
|
||||
export async function renameNotebook(notebook: NotebookId, name: string) {
|
||||
let url = '/api/notebook/renameNotebook';
|
||||
return request(url, { notebook: notebook, name: name });
|
||||
}
|
||||
|
||||
|
||||
export async function createNotebook(name: string): Promise<Notebook> {
|
||||
let url = '/api/notebook/createNotebook';
|
||||
return request(url, { name: name });
|
||||
}
|
||||
|
||||
|
||||
export async function removeNotebook(notebook: NotebookId) {
|
||||
let url = '/api/notebook/removeNotebook';
|
||||
return request(url, { notebook: notebook });
|
||||
}
|
||||
|
||||
|
||||
export async function getNotebookConf(notebook: NotebookId): Promise<IResGetNotebookConf> {
|
||||
let data = { notebook: notebook };
|
||||
let url = '/api/notebook/getNotebookConf';
|
||||
return request(url, data);
|
||||
}
|
||||
|
||||
|
||||
export async function setNotebookConf(notebook: NotebookId, conf: NotebookConf): Promise<NotebookConf> {
|
||||
let data = { notebook: notebook, conf: conf };
|
||||
let url = '/api/notebook/setNotebookConf';
|
||||
return request(url, data);
|
||||
}
|
||||
|
||||
|
||||
// **************************************** File Tree ****************************************
|
||||
export async function createDocWithMd(notebook: NotebookId, path: string, markdown: string): Promise<DocumentId> {
|
||||
let data = {
|
||||
notebook: notebook,
|
||||
path: path,
|
||||
markdown: markdown,
|
||||
};
|
||||
let url = '/api/filetree/createDocWithMd';
|
||||
return request(url, data);
|
||||
}
|
||||
|
||||
|
||||
export async function renameDoc(notebook: NotebookId, path: string, title: string): Promise<DocumentId> {
|
||||
let data = {
|
||||
doc: notebook,
|
||||
path: path,
|
||||
title: title
|
||||
};
|
||||
let url = '/api/filetree/renameDoc';
|
||||
return request(url, data);
|
||||
}
|
||||
|
||||
|
||||
export async function removeDoc(notebook: NotebookId, path: string) {
|
||||
let data = {
|
||||
notebook: notebook,
|
||||
path: path,
|
||||
};
|
||||
let url = '/api/filetree/removeDoc';
|
||||
return request(url, data);
|
||||
}
|
||||
|
||||
|
||||
export async function moveDocs(fromPaths: string[], toNotebook: NotebookId, toPath: string) {
|
||||
let data = {
|
||||
fromPaths: fromPaths,
|
||||
toNotebook: toNotebook,
|
||||
toPath: toPath
|
||||
};
|
||||
let url = '/api/filetree/moveDocs';
|
||||
return request(url, data);
|
||||
}
|
||||
|
||||
|
||||
export async function getHPathByPath(notebook: NotebookId, path: string): Promise<string> {
|
||||
let data = {
|
||||
notebook: notebook,
|
||||
path: path
|
||||
};
|
||||
let url = '/api/filetree/getHPathByPath';
|
||||
return request(url, data);
|
||||
}
|
||||
|
||||
|
||||
export async function getHPathByID(id: BlockId): Promise<string> {
|
||||
let data = {
|
||||
id: id
|
||||
};
|
||||
let url = '/api/filetree/getHPathByID';
|
||||
return request(url, data);
|
||||
}
|
||||
|
||||
|
||||
export async function getIDsByHPath(notebook: NotebookId, path: string): Promise<BlockId[]> {
|
||||
let data = {
|
||||
notebook: notebook,
|
||||
path: path
|
||||
};
|
||||
let url = '/api/filetree/getIDsByHPath';
|
||||
return request(url, data);
|
||||
}
|
||||
|
||||
// **************************************** Asset Files ****************************************
|
||||
|
||||
export async function upload(assetsDirPath: string, files: any[]): Promise<IResUpload> {
|
||||
let form = new FormData();
|
||||
form.append('assetsDirPath', assetsDirPath);
|
||||
for (let file of files) {
|
||||
form.append('file[]', file);
|
||||
}
|
||||
let url = '/api/asset/upload';
|
||||
return request(url, form);
|
||||
}
|
||||
|
||||
// **************************************** Block ****************************************
|
||||
type DataType = "markdown" | "dom";
|
||||
export async function insertBlock(
|
||||
dataType: DataType, data: string,
|
||||
nextID?: BlockId, previousID?: BlockId, parentID?: BlockId
|
||||
): Promise<IResdoOperations[]> {
|
||||
let payload = {
|
||||
dataType: dataType,
|
||||
data: data,
|
||||
nextID: nextID,
|
||||
previousID: previousID,
|
||||
parentID: parentID
|
||||
}
|
||||
let url = '/api/block/insertBlock';
|
||||
return request(url, payload);
|
||||
}
|
||||
|
||||
|
||||
export async function prependBlock(dataType: DataType, data: string, parentID: BlockId | DocumentId): Promise<IResdoOperations[]> {
|
||||
let payload = {
|
||||
dataType: dataType,
|
||||
data: data,
|
||||
parentID: parentID
|
||||
}
|
||||
let url = '/api/block/prependBlock';
|
||||
return request(url, payload);
|
||||
}
|
||||
|
||||
|
||||
export async function appendBlock(dataType: DataType, data: string, parentID: BlockId | DocumentId): Promise<IResdoOperations[]> {
|
||||
let payload = {
|
||||
dataType: dataType,
|
||||
data: data,
|
||||
parentID: parentID
|
||||
}
|
||||
let url = '/api/block/appendBlock';
|
||||
return request(url, payload);
|
||||
}
|
||||
|
||||
|
||||
export async function updateBlock(dataType: DataType, data: string, id: BlockId): Promise<IResdoOperations[]> {
|
||||
let payload = {
|
||||
dataType: dataType,
|
||||
data: data,
|
||||
id: id
|
||||
}
|
||||
let url = '/api/block/updateBlock';
|
||||
return request(url, payload);
|
||||
}
|
||||
|
||||
|
||||
export async function deleteBlock(id: BlockId): Promise<IResdoOperations[]> {
|
||||
let data = {
|
||||
id: id
|
||||
}
|
||||
let url = '/api/block/deleteBlock';
|
||||
return request(url, data);
|
||||
}
|
||||
|
||||
|
||||
export async function moveBlock(id: BlockId, previousID?: PreviousID, parentID?: ParentID): Promise<IResdoOperations[]> {
|
||||
let data = {
|
||||
id: id,
|
||||
previousID: previousID,
|
||||
parentID: parentID
|
||||
}
|
||||
let url = '/api/block/moveBlock';
|
||||
return request(url, data);
|
||||
}
|
||||
|
||||
|
||||
export async function foldBlock(id: BlockId) {
|
||||
let data = {
|
||||
id: id
|
||||
}
|
||||
let url = '/api/block/foldBlock';
|
||||
return request(url, data);
|
||||
}
|
||||
|
||||
|
||||
export async function unfoldBlock(id: BlockId) {
|
||||
let data = {
|
||||
id: id
|
||||
}
|
||||
let url = '/api/block/unfoldBlock';
|
||||
return request(url, data);
|
||||
}
|
||||
|
||||
|
||||
export async function getBlockKramdown(id: BlockId): Promise<IResGetBlockKramdown> {
|
||||
let data = {
|
||||
id: id
|
||||
}
|
||||
let url = '/api/block/getBlockKramdown';
|
||||
return request(url, data);
|
||||
}
|
||||
|
||||
|
||||
export async function getChildBlocks(id: BlockId): Promise<IResGetChildBlock[]> {
|
||||
let data = {
|
||||
id: id
|
||||
}
|
||||
let url = '/api/block/getChildBlocks';
|
||||
return request(url, data);
|
||||
}
|
||||
|
||||
export async function transferBlockRef(fromID: BlockId, toID: BlockId, refIDs: BlockId[]) {
|
||||
let data = {
|
||||
fromID: fromID,
|
||||
toID: toID,
|
||||
refIDs: refIDs
|
||||
}
|
||||
let url = '/api/block/transferBlockRef';
|
||||
return request(url, data);
|
||||
}
|
||||
|
||||
// **************************************** Attributes ****************************************
|
||||
export async function setBlockAttrs(id: BlockId, attrs: { [key: string]: string }) {
|
||||
let data = {
|
||||
id: id,
|
||||
attrs: attrs
|
||||
}
|
||||
let url = '/api/attr/setBlockAttrs';
|
||||
return request(url, data);
|
||||
}
|
||||
|
||||
|
||||
export async function getBlockAttrs(id: BlockId): Promise<{ [key: string]: string }> {
|
||||
let data = {
|
||||
id: id
|
||||
}
|
||||
let url = '/api/attr/getBlockAttrs';
|
||||
return request(url, data);
|
||||
}
|
||||
|
||||
// **************************************** SQL ****************************************
|
||||
|
||||
export async function sql(sql: string): Promise<any[]> {
|
||||
let sqldata = {
|
||||
stmt: sql,
|
||||
};
|
||||
let url = '/api/query/sql';
|
||||
return request(url, sqldata);
|
||||
}
|
||||
|
||||
export async function getBlockByID(blockId: string): Promise<Block> {
|
||||
let sqlScript = `select * from blocks where id ='${blockId}'`;
|
||||
let data = await sql(sqlScript);
|
||||
return data[0];
|
||||
}
|
||||
|
||||
// **************************************** Template ****************************************
|
||||
|
||||
export async function render(id: DocumentId, path: string): Promise<IResGetTemplates> {
|
||||
let data = {
|
||||
id: id,
|
||||
path: path
|
||||
}
|
||||
let url = '/api/template/render';
|
||||
return request(url, data);
|
||||
}
|
||||
|
||||
|
||||
export async function renderSprig(template: string): Promise<string> {
|
||||
let url = '/api/template/renderSprig';
|
||||
return request(url, { template: template });
|
||||
}
|
||||
|
||||
// **************************************** File ****************************************
|
||||
|
||||
export async function getFile(path: string): Promise<any> {
|
||||
let data = {
|
||||
path: path
|
||||
}
|
||||
let url = '/api/file/getFile';
|
||||
return new Promise((resolve, _) => {
|
||||
fetchPost(url, data, (content: any) => {
|
||||
resolve(content)
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* fetchPost will secretly convert data into json, this func merely return Blob
|
||||
* @param endpoint
|
||||
* @returns
|
||||
*/
|
||||
export const getFileBlob = async (path: string): Promise<Blob | null> => {
|
||||
const endpoint = '/api/file/getFile'
|
||||
let response = await fetch(endpoint, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
path: path
|
||||
})
|
||||
});
|
||||
if (!response.ok) {
|
||||
return null;
|
||||
}
|
||||
let data = await response.blob();
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
export async function putFile(path: string, isDir: boolean, file: any) {
|
||||
let form = new FormData();
|
||||
form.append('path', path);
|
||||
form.append('isDir', isDir.toString());
|
||||
// Copyright (c) 2023, terwer.
|
||||
// https://github.com/terwer/siyuan-plugin-importer/blob/v1.4.1/src/api/kernel-api.ts
|
||||
form.append('modTime', Math.floor(Date.now() / 1000).toString());
|
||||
form.append('file', file);
|
||||
let url = '/api/file/putFile';
|
||||
return request(url, form);
|
||||
}
|
||||
|
||||
export async function removeFile(path: string) {
|
||||
let data = {
|
||||
path: path
|
||||
}
|
||||
let url = '/api/file/removeFile';
|
||||
return request(url, data);
|
||||
}
|
||||
|
||||
|
||||
|
||||
export async function readDir(path: string): Promise<IResReadDir> {
|
||||
let data = {
|
||||
path: path
|
||||
}
|
||||
let url = '/api/file/readDir';
|
||||
return request(url, data);
|
||||
}
|
||||
|
||||
|
||||
// **************************************** Export ****************************************
|
||||
|
||||
export async function exportMdContent(id: DocumentId): Promise<IResExportMdContent> {
|
||||
let data = {
|
||||
id: id
|
||||
}
|
||||
let url = '/api/export/exportMdContent';
|
||||
return request(url, data);
|
||||
}
|
||||
|
||||
export async function exportResources(paths: string[], name: string): Promise<IResExportResources> {
|
||||
let data = {
|
||||
paths: paths,
|
||||
name: name
|
||||
}
|
||||
let url = '/api/export/exportResources';
|
||||
return request(url, data);
|
||||
}
|
||||
|
||||
// **************************************** Convert ****************************************
|
||||
|
||||
export type PandocArgs = string;
|
||||
export async function pandoc(args: PandocArgs[]) {
|
||||
let data = {
|
||||
args: args
|
||||
}
|
||||
let url = '/api/convert/pandoc';
|
||||
return request(url, data);
|
||||
}
|
||||
|
||||
// **************************************** Notification ****************************************
|
||||
|
||||
// /api/notification/pushMsg
|
||||
// {
|
||||
// "msg": "test",
|
||||
// "timeout": 7000
|
||||
// }
|
||||
export async function pushMsg(msg: string, timeout: number = 7000) {
|
||||
let payload = {
|
||||
msg: msg,
|
||||
timeout: timeout
|
||||
};
|
||||
let url = "/api/notification/pushMsg";
|
||||
return request(url, payload);
|
||||
}
|
||||
|
||||
export async function pushErrMsg(msg: string, timeout: number = 7000) {
|
||||
let payload = {
|
||||
msg: msg,
|
||||
timeout: timeout
|
||||
};
|
||||
let url = "/api/notification/pushErrMsg";
|
||||
return request(url, payload);
|
||||
}
|
||||
|
||||
// **************************************** Network ****************************************
|
||||
export async function forwardProxy(
|
||||
url: string, method: string = 'GET', payload: any = {},
|
||||
headers: any[] = [], timeout: number = 7000, contentType: string = "text/html"
|
||||
): Promise<IResForwardProxy> {
|
||||
let data = {
|
||||
url: url,
|
||||
method: method,
|
||||
timeout: timeout,
|
||||
contentType: contentType,
|
||||
headers: headers,
|
||||
payload: payload
|
||||
}
|
||||
let url1 = '/api/network/forwardProxy';
|
||||
return request(url1, data);
|
||||
}
|
||||
|
||||
|
||||
// **************************************** System ****************************************
|
||||
|
||||
export async function bootProgress(): Promise<IResBootProgress> {
|
||||
return request('/api/system/bootProgress', {});
|
||||
}
|
||||
|
||||
|
||||
export async function version(): Promise<string> {
|
||||
return request('/api/system/version', {});
|
||||
}
|
||||
|
||||
|
||||
export async function currentTime(): Promise<number> {
|
||||
return request('/api/system/currentTime', {});
|
||||
}
|
||||
63
src/hello.svelte
Normal file
63
src/hello.svelte
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
<!--
|
||||
Copyright (c) 2024 by frostime. All Rights Reserved.
|
||||
Author : frostime
|
||||
Date : 2023-11-19 12:30:45
|
||||
FilePath : /src/hello.svelte
|
||||
LastEditTime : 2024-10-16 14:37:50
|
||||
Description :
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { onDestroy, onMount } from "svelte";
|
||||
// import { version } from "@/api";
|
||||
import { showMessage, fetchPost, Protyle } from "siyuan";
|
||||
|
||||
export let app;
|
||||
export let blockID: string;
|
||||
|
||||
let time: string = "";
|
||||
|
||||
let divProtyle: HTMLDivElement;
|
||||
let protyle: any;
|
||||
|
||||
onMount(async () => {
|
||||
// ver = await version();
|
||||
fetchPost("/api/system/currentTime", {}, (response) => {
|
||||
time = new Date(response.data).toString();
|
||||
});
|
||||
if (blockID) {
|
||||
protyle = await initProtyle();
|
||||
} else {
|
||||
divProtyle.innerHTML = "Please open a document first";
|
||||
}
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
showMessage("Hello panel closed");
|
||||
protyle?.destroy();
|
||||
});
|
||||
|
||||
async function initProtyle() {
|
||||
return new Protyle(app, divProtyle, {
|
||||
blockId: blockID
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="b3-dialog__content">
|
||||
<div>appId:</div>
|
||||
<div class="fn__hr"></div>
|
||||
<div class="plugin-sample__time">${app?.appId}</div>
|
||||
<div class="fn__hr"></div>
|
||||
<div class="fn__hr"></div>
|
||||
<div>API demo:</div>
|
||||
<div class="fn__hr" />
|
||||
<div class="plugin-sample__time">
|
||||
System current time: <span id="time">{time}</span>
|
||||
</div>
|
||||
<div class="fn__hr" />
|
||||
<div class="fn__hr" />
|
||||
<div>Protyle demo: id = {blockID}</div>
|
||||
<div class="fn__hr" />
|
||||
<div id="protyle" style="height: 360px;" bind:this={divProtyle}/>
|
||||
</div>
|
||||
|
||||
0
src/index.scss
Normal file
0
src/index.scss
Normal file
1011
src/index.ts
Normal file
1011
src/index.ts
Normal file
File diff suppressed because it is too large
Load diff
118
src/libs/components/Form/form-input.svelte
Normal file
118
src/libs/components/Form/form-input.svelte
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
<script lang="ts">
|
||||
import { createEventDispatcher } from "svelte";
|
||||
export let type: string; // Setting Type
|
||||
export let key: string;
|
||||
export let value: any;
|
||||
|
||||
// Optional parameters
|
||||
export let placeholder: string = "";
|
||||
export let options: { [key: string | number]: string } = {};
|
||||
export let slider: {
|
||||
min: number;
|
||||
max: number;
|
||||
step: number;
|
||||
} = { min: 0, max: 100, step: 1 };
|
||||
export let button: {
|
||||
label: string;
|
||||
callback?: () => void;
|
||||
} = { label: value, callback: () => {} };
|
||||
export let fnSize: boolean = true; // If the form input is used within setting panel context, it is usually given a fixed width by a class named "fn__size200".
|
||||
export let style: string = ""; // Custom style
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
function click() {
|
||||
button?.callback();
|
||||
dispatch("click", { key: key });
|
||||
}
|
||||
|
||||
function changed() {
|
||||
dispatch("changed", { key: key, value: value });
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if type === "checkbox"}
|
||||
<!-- Checkbox -->
|
||||
<input
|
||||
class="b3-switch fn__flex-center"
|
||||
id={key}
|
||||
type="checkbox"
|
||||
bind:checked={value}
|
||||
on:change={changed}
|
||||
style={style}
|
||||
/>
|
||||
{:else if type === "textinput"}
|
||||
<!-- Text Input -->
|
||||
<input
|
||||
class:b3-text-field={true}
|
||||
class:fn__flex-center={true}
|
||||
class:fn__size200={fnSize}
|
||||
id={key}
|
||||
{placeholder}
|
||||
bind:value={value}
|
||||
on:change={changed}
|
||||
style={style}
|
||||
/>
|
||||
{:else if type === "textarea"}
|
||||
<textarea
|
||||
class="b3-text-field fn__block"
|
||||
style={`resize: vertical; height: 10em; white-space: nowrap; ${style}`}
|
||||
bind:value={value}
|
||||
on:change={changed}
|
||||
/>
|
||||
{:else if type === "number"}
|
||||
<input
|
||||
class:b3-text-field={true}
|
||||
class:fn__flex-center={true}
|
||||
class:fn__size200={fnSize}
|
||||
id={key}
|
||||
type="number"
|
||||
bind:value={value}
|
||||
on:change={changed}
|
||||
style={style}
|
||||
/>
|
||||
{:else if type === "button"}
|
||||
<!-- Button Input -->
|
||||
<button
|
||||
class:b3-button={true}
|
||||
class:b3-button--outline={true}
|
||||
class:fn__flex-center={true}
|
||||
class:fn__size200={fnSize}
|
||||
id={key}
|
||||
on:click={click}
|
||||
style={style}
|
||||
>
|
||||
{button.label}
|
||||
</button>
|
||||
{:else if type === "select"}
|
||||
<!-- Dropdown select -->
|
||||
<select
|
||||
class:b3-select={true}
|
||||
class:fn__flex-center={true}
|
||||
class:fn__size200={fnSize}
|
||||
id="iconPosition"
|
||||
bind:value={value}
|
||||
on:change={changed}
|
||||
style={style}
|
||||
>
|
||||
{#each Object.entries(options) as [value, text]}
|
||||
<option {value}>{text}</option>
|
||||
{/each}
|
||||
</select>
|
||||
{:else if type == "slider"}
|
||||
<!-- Slider -->
|
||||
<div class="b3-tooltips b3-tooltips__n" aria-label={value}>
|
||||
<input
|
||||
class:b3-slider={true}
|
||||
class:fn__size200={fnSize}
|
||||
id="fontSize"
|
||||
min={slider.min}
|
||||
max={slider.max}
|
||||
step={slider.step}
|
||||
type="range"
|
||||
bind:value={value}
|
||||
on:change={changed}
|
||||
style={style}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
53
src/libs/components/Form/form-wrap.svelte
Normal file
53
src/libs/components/Form/form-wrap.svelte
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
<!--
|
||||
Copyright (c) 2024 by frostime. All Rights Reserved.
|
||||
Author : frostime
|
||||
Date : 2024-06-01 20:03:50
|
||||
FilePath : /src/libs/components/item-wrap.svelte
|
||||
LastEditTime : 2024-07-19 15:28:57
|
||||
Description : The setting item container
|
||||
-->
|
||||
<script lang="ts">
|
||||
export let title: string; // Displayint Setting Title
|
||||
export let description: string; // Displaying Setting Text
|
||||
export let direction: 'row' | 'column' = 'column';
|
||||
</script>
|
||||
|
||||
{#if direction === "row"}
|
||||
<div class="item-wrap b3-label" data-key="CustomCSS">
|
||||
<div class="fn__block">
|
||||
<span class="title">{title}</span>
|
||||
<div class="b3-label__text">{@html description}</div>
|
||||
<div class="fn__hr"></div>
|
||||
<div style="display: flex; flex-direction: column; gap: 5px; position: relative;">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="item-wrap fn__flex b3-label config__item">
|
||||
<div class="fn__flex-1">
|
||||
<span class="title">{title}</span>
|
||||
<div class="b3-label__text">
|
||||
{@html description}
|
||||
</div>
|
||||
</div>
|
||||
<span class="fn__space" />
|
||||
<slot />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
span.title {
|
||||
font-weight: bold;
|
||||
color: var(--b3-theme-primary)
|
||||
}
|
||||
.item-wrap.b3-label {
|
||||
box-shadow: none !important;
|
||||
padding-bottom: 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.item-wrap.b3-label:not(:last-child) {
|
||||
border-bottom: 1px solid var(--b3-border-color);
|
||||
}
|
||||
</style>
|
||||
6
src/libs/components/Form/index.ts
Normal file
6
src/libs/components/Form/index.ts
Normal file
|
|
@ -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 };
|
||||
3
src/libs/components/b3-typography.svelte
Normal file
3
src/libs/components/b3-typography.svelte
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<div class="item__readme b3-typography">
|
||||
<slot/>
|
||||
</div>
|
||||
51
src/libs/components/setting-panel.svelte
Normal file
51
src/libs/components/setting-panel.svelte
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
<!--
|
||||
Copyright (c) 2023 by frostime All Rights Reserved.
|
||||
Author : frostime
|
||||
Date : 2023-07-01 19:23:50
|
||||
FilePath : /src/libs/components/setting-panel.svelte
|
||||
LastEditTime : 2024-08-09 21:41:07
|
||||
Description :
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import Form from './Form';
|
||||
|
||||
export let group: string;
|
||||
export let settingItems: ISettingItem[];
|
||||
export let display: boolean = true;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
function onClick( {detail}) {
|
||||
dispatch("click", { key: detail.key });
|
||||
}
|
||||
function onChanged( {detail}) {
|
||||
dispatch("changed", {group: group, ...detail});
|
||||
}
|
||||
|
||||
$: fn__none = display ? "" : "fn__none";
|
||||
|
||||
</script>
|
||||
|
||||
<div class="config__tab-container {fn__none}" data-name={group}>
|
||||
<slot />
|
||||
{#each settingItems as item (item.key)}
|
||||
<Form.Wrap
|
||||
title={item.title}
|
||||
description={item.description}
|
||||
direction={item?.direction}
|
||||
>
|
||||
<Form.Input
|
||||
type={item.type}
|
||||
key={item.key}
|
||||
bind:value={item.value}
|
||||
placeholder={item?.placeholder}
|
||||
options={item?.options}
|
||||
slider={item?.slider}
|
||||
button={item?.button}
|
||||
on:click={onClick}
|
||||
on:changed={onChanged}
|
||||
/>
|
||||
</Form.Wrap>
|
||||
{/each}
|
||||
</div>
|
||||
99
src/libs/const.ts
Normal file
99
src/libs/const.ts
Normal file
|
|
@ -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"
|
||||
}
|
||||
};
|
||||
164
src/libs/dialog.ts
Normal file
164
src/libs/dialog.ts
Normal file
|
|
@ -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: `<div class="b3-dialog__content">
|
||||
<div class="ft__breakword"><textarea class="b3-text-field fn__block" style="height: 100%;" placeholder=${args?.placeholder ?? ''}>${args?.defaultText ?? ''}</textarea></div>
|
||||
</div>
|
||||
<div class="b3-dialog__action">
|
||||
<button class="b3-button b3-button--cancel">${window.siyuan.languages.cancel}</button><div class="fn__space"></div>
|
||||
<button class="b3-button b3-button--text" id="confirmDialogConfirmBtn">${window.siyuan.languages.confirm}</button>
|
||||
</div>`,
|
||||
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<string>((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: `<div class="b3-dialog__content">
|
||||
<div class="ft__breakword">
|
||||
</div>
|
||||
</div>
|
||||
<div class="b3-dialog__action">
|
||||
<button class="b3-button b3-button--cancel">${window.siyuan.languages.cancel}</button><div class="fn__space"></div>
|
||||
<button class="b3-button b3-button--text" id="confirmDialogConfirmBtn">${window.siyuan.languages.confirm}</button>
|
||||
</div>`,
|
||||
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<HTMLElement>((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: `<div class="dialog-content" style="display: flex; height: 100%;"/>`,
|
||||
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
|
||||
}
|
||||
}
|
||||
43
src/libs/index.d.ts
vendored
Normal file
43
src/libs/index.d.ts
vendored
Normal file
|
|
@ -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;
|
||||
}
|
||||
48
src/libs/promise-pool.ts
Normal file
48
src/libs/promise-pool.ts
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
export default class PromiseLimitPool<T> {
|
||||
private maxConcurrent: number;
|
||||
private currentRunning = 0;
|
||||
private queue: (() => void)[] = [];
|
||||
private promises: Promise<T>[] = [];
|
||||
|
||||
constructor(maxConcurrent: number) {
|
||||
this.maxConcurrent = maxConcurrent;
|
||||
}
|
||||
|
||||
add(fn: () => Promise<T>): void {
|
||||
const promise = new Promise<T>((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<T[]> {
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
397
src/libs/setting-utils.ts
Normal file
397
src/libs/setting-utils.ts
Normal file
|
|
@ -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<string, ISettingUtilsItem> = new Map();
|
||||
elements: Map<string, HTMLElement> = 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);
|
||||
}
|
||||
}
|
||||
139
src/setting-example.svelte
Normal file
139
src/setting-example.svelte
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
<script lang="ts">
|
||||
import { showMessage } from "siyuan";
|
||||
import SettingPanel from "./libs/components/setting-panel.svelte";
|
||||
|
||||
let groups: string[] = ["🌈 Group 1", "✨ Group 2"];
|
||||
let focusGroup = groups[0];
|
||||
|
||||
const group1Items: ISettingItem[] = [
|
||||
{
|
||||
type: 'checkbox',
|
||||
title: 'checkbox',
|
||||
description: 'checkbox',
|
||||
key: 'a',
|
||||
value: true
|
||||
},
|
||||
{
|
||||
type: 'textinput',
|
||||
title: 'text',
|
||||
description: 'This is a text',
|
||||
key: 'b',
|
||||
value: 'This is a text',
|
||||
placeholder: 'placeholder'
|
||||
},
|
||||
{
|
||||
type: 'textarea',
|
||||
title: 'textarea',
|
||||
description: 'This is a textarea',
|
||||
key: 'b2',
|
||||
value: 'This is a textarea',
|
||||
placeholder: 'placeholder',
|
||||
direction: 'row'
|
||||
},
|
||||
{
|
||||
type: 'select',
|
||||
title: 'select',
|
||||
description: 'select',
|
||||
key: 'c',
|
||||
value: 'x',
|
||||
options: {
|
||||
x: 'x',
|
||||
y: 'y',
|
||||
z: 'z'
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
const group2Items: ISettingItem[] = [
|
||||
{
|
||||
type: 'button',
|
||||
title: 'button',
|
||||
description: 'This is a button',
|
||||
key: 'e',
|
||||
value: 'Click Button',
|
||||
button: {
|
||||
label: 'Click Me',
|
||||
callback: () => {
|
||||
showMessage('Hello, world!');
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'slider',
|
||||
title: 'slider',
|
||||
description: 'slider',
|
||||
key: 'd',
|
||||
value: 50,
|
||||
slider: {
|
||||
min: 0,
|
||||
max: 100,
|
||||
step: 1
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
/********** Events **********/
|
||||
interface ChangeEvent {
|
||||
group: string;
|
||||
key: string;
|
||||
value: any;
|
||||
}
|
||||
|
||||
const onChanged = ({ detail }: CustomEvent<ChangeEvent>) => {
|
||||
if (detail.group === groups[0]) {
|
||||
// setting.set(detail.key, detail.value);
|
||||
//Please add your code here
|
||||
//Udpate the plugins setting data, don't forget to call plugin.save() for data persistence
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="fn__flex-1 fn__flex config__panel">
|
||||
<ul class="b3-tab-bar b3-list b3-list--background">
|
||||
{#each groups as group}
|
||||
<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
|
||||
<li
|
||||
data-name="editor"
|
||||
class:b3-list-item--focus={group === focusGroup}
|
||||
class="b3-list-item"
|
||||
on:click={() => {
|
||||
focusGroup = group;
|
||||
}}
|
||||
on:keydown={() => {}}
|
||||
>
|
||||
<span class="b3-list-item__text">{group}</span>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
<div class="config__tab-wrap">
|
||||
<SettingPanel
|
||||
group={groups[0]}
|
||||
settingItems={group1Items}
|
||||
display={focusGroup === groups[0]}
|
||||
on:changed={onChanged}
|
||||
on:click={({ detail }) => { console.debug("Click:", detail.key); }}
|
||||
>
|
||||
<div class="fn__flex b3-label">
|
||||
💡 This is our default settings.
|
||||
</div>
|
||||
</SettingPanel>
|
||||
<SettingPanel
|
||||
group={groups[1]}
|
||||
settingItems={group2Items}
|
||||
display={focusGroup === groups[1]}
|
||||
on:changed={onChanged}
|
||||
on:click={({ detail }) => { console.debug("Click:", detail.key); }}
|
||||
>
|
||||
</SettingPanel>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.config__panel {
|
||||
height: 100%;
|
||||
}
|
||||
.config__panel > ul > li {
|
||||
padding-left: 1rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
65
src/types/api.d.ts
vendored
Normal file
65
src/types/api.d.ts
vendored
Normal file
|
|
@ -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;
|
||||
}
|
||||
|
||||
106
src/types/index.d.ts
vendored
Normal file
106
src/types/index.d.ts
vendored
Normal file
|
|
@ -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;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue