⬆️ Update Svelte to 5

This commit is contained in:
frostime 2025-08-16 17:03:37 +08:00
parent 3972fc1e46
commit ba404a6648
9 changed files with 172 additions and 103 deletions

View file

@ -16,7 +16,7 @@
"make-install": "vite build && node --no-warnings ./scripts/make_install.js" "make-install": "vite build && node --no-warnings ./scripts/make_install.js"
}, },
"devDependencies": { "devDependencies": {
"@sveltejs/vite-plugin-svelte": "^3.1.0", "@sveltejs/vite-plugin-svelte": "^4.0.0",
"@tsconfig/svelte": "^4.0.1", "@tsconfig/svelte": "^4.0.1",
"@types/node": "^20.3.0", "@types/node": "^20.3.0",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
@ -27,10 +27,10 @@
"rollup-plugin-livereload": "^2.0.5", "rollup-plugin-livereload": "^2.0.5",
"sass": "^1.63.3", "sass": "^1.63.3",
"siyuan": "1.1.2", "siyuan": "1.1.2",
"svelte": "^4.2.20", "svelte": "^5.0.0",
"ts-node": "^10.9.1", "ts-node": "^10.9.1",
"typescript": "^5.1.3", "typescript": "^5.5.0",
"vite": "^5.2.9", "vite": "^5.4.4",
"vite-plugin-static-copy": "^1.0.2", "vite-plugin-static-copy": "^1.0.2",
"vite-plugin-zip-pack": "^1.0.5" "vite-plugin-zip-pack": "^1.0.5"
} }

View file

@ -11,13 +11,19 @@
// import { version } from "@/api"; // import { version } from "@/api";
import { showMessage, fetchPost, Protyle } from "siyuan"; import { showMessage, fetchPost, Protyle } from "siyuan";
export let app; interface Props {
export let blockID: string; app: any;
blockID: string;
}
let time: string = ""; let { app, blockID }: Props = $props();
let divProtyle: HTMLDivElement; let time: string = $state("");
// let ver: string = "";
let divProtyle: HTMLDivElement = $state();
let protyle: any; let protyle: any;
// let blockID: string = $state('');
onMount(async () => { onMount(async () => {
// ver = await version(); // ver = await version();
@ -50,14 +56,14 @@
<div class="fn__hr"></div> <div class="fn__hr"></div>
<div class="fn__hr"></div> <div class="fn__hr"></div>
<div>API demo:</div> <div>API demo:</div>
<div class="fn__hr" /> <div class="fn__hr"></div>
<div class="plugin-sample__time"> <div class="plugin-sample__time">
System current time: <span id="time">{time}</span> System current time: <span id="time">{time}</span>
</div> </div>
<div class="fn__hr" /> <div class="fn__hr"></div>
<div class="fn__hr" /> <div class="fn__hr"></div>
<div>Protyle demo: id = {blockID}</div> <div>Protyle demo: id = {blockID}</div>
<div class="fn__hr" /> <div class="fn__hr"></div>
<div id="protyle" style="height: 360px;" bind:this={divProtyle}/> <div id="protyle" style="height: 360px;" bind:this={divProtyle}></div>
</div> </div>

View file

@ -23,7 +23,7 @@ import {
getModelByDockType, getModelByDockType,
getAllEditor, getAllEditor,
Files, Files,
platformUtils, // platformUtils,
openSetting, openSetting,
openAttributePanel, openAttributePanel,
saveLayout saveLayout
@ -36,6 +36,7 @@ import SettingExample from "@/setting-example.svelte";
import { SettingUtils } from "./libs/setting-utils"; import { SettingUtils } from "./libs/setting-utils";
import { svelteDialog } from "./libs/dialog"; import { svelteDialog } from "./libs/dialog";
import { mount, unmount } from "svelte";
const STORAGE_NAME = "menu-config"; const STORAGE_NAME = "menu-config";
const TAB_TYPE = "custom_tab"; const TAB_TYPE = "custom_tab";
@ -84,7 +85,7 @@ export default class PluginSample extends Plugin {
this.custom = this.addTab({ this.custom = this.addTab({
type: TAB_TYPE, type: TAB_TYPE,
init() { init() {
app = new HelloExample({ app = mount(HelloExample, {
target: tabDiv, target: tabDiv,
props: { props: {
app: this.app, app: this.app,
@ -98,7 +99,8 @@ export default class PluginSample extends Plugin {
console.log("before destroy tab:", TAB_TYPE); console.log("before destroy tab:", TAB_TYPE);
}, },
destroy() { destroy() {
app?.$destroy(); // app?.$destroy();
app && unmount(app);
console.log("destroy tab:", TAB_TYPE); console.log("destroy tab:", TAB_TYPE);
} }
}); });
@ -415,19 +417,28 @@ export default class PluginSample extends Plugin {
* A custom setting pannel provided by svelte * A custom setting pannel provided by svelte
*/ */
openSetting(): void { openSetting(): void {
let dialog = new Dialog({ // let dialog = new Dialog({
// title: "SettingPannel",
// content: `<div id="SettingPanel" style="height: 100%;"></div>`,
// width: "800px",
// destroyCallback: (options) => {
// console.log("destroyCallback", options);
// //You'd better destroy the component when the dialog is closed
// unmount(pannel);
// }
// });
// let pannel = mount(SettingExample, {
// target: dialog.element.querySelector("#SettingPanel"),
// });
svelteDialog({
title: "SettingPannel", title: "SettingPannel",
content: `<div id="SettingPanel" style="height: 100%;"></div>`,
width: "800px", width: "800px",
destroyCallback: (options) => { height: "35rem",
console.log("destroyCallback", options); component: SettingExample,
//You'd better destroy the component when the dialog is closed props: {
pannel.$destroy(); app: this.app,
} }
}); });
let pannel = new SettingExample({
target: dialog.element.querySelector("#SettingPanel"),
});
} }
private eventBusPaste(event: any) { private eventBusPaste(event: any) {
@ -471,14 +482,10 @@ export default class PluginSample extends Plugin {
svelteDialog({ svelteDialog({
title: `SiYuan ${Constants.SIYUAN_VERSION}`, title: `SiYuan ${Constants.SIYUAN_VERSION}`,
width: this.isMobile ? "92vw" : "720px", width: this.isMobile ? "92vw" : "720px",
constructor: (container: HTMLElement) => { component: HelloExample,
return new HelloExample({ props: {
target: container, app: this.app,
props: { blockID: docId
app: this.app,
blockID: docId
}
});
} }
}); });
} }

View file

@ -1,33 +1,47 @@
<script lang="ts"> <script lang="ts">
import { createEventDispatcher } from "svelte";
export let type: string; // Setting Type
export let key: string;
export let value: any;
// Optional parameters // Optional parameters
export let placeholder: string = ""; interface Props {
export let options: { [key: string | number]: string } = {}; type: string;
export let slider: { key: string;
value: any;
placeholder?: string;
options?: { [key: string | number]: string };
slider?: {
min: number; min: number;
max: number; max: number;
step: number; step: number;
} = { min: 0, max: 100, step: 1 }; };
export let button: { button?: {
label: string; label: string;
callback?: () => void; 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". fnSize?: boolean;
export let style: string = ""; // Custom style style?: string;
onclick?: (detail: {key: string}) => void;
const dispatch = createEventDispatcher(); onchanged?: (detail: {key: string, value: any}) => void;
function click() {
button?.callback();
dispatch("click", { key: key });
} }
function changed() { let {
dispatch("changed", { key: key, value: value }); type,
key,
value = $bindable(),
placeholder = "",
options = {},
slider = { min: 0, max: 100, step: 1 },
button = { label: value, callback: () => {} },
fnSize = true,
style = "",
onclick,
onchanged
}: Props = $props();
function handleClick() {
button?.callback();
onclick?.({ key: key });
}
function handleChanged() {
onchanged?.({ key: key, value: value });
} }
</script> </script>
@ -38,7 +52,7 @@
id={key} id={key}
type="checkbox" type="checkbox"
bind:checked={value} bind:checked={value}
on:change={changed} onchange={handleChanged}
style={style} style={style}
/> />
{:else if type === "textinput"} {:else if type === "textinput"}
@ -50,7 +64,7 @@
id={key} id={key}
{placeholder} {placeholder}
bind:value={value} bind:value={value}
on:change={changed} onchange={handleChanged}
style={style} style={style}
/> />
{:else if type === "textarea"} {:else if type === "textarea"}
@ -58,8 +72,8 @@
class="b3-text-field fn__block" class="b3-text-field fn__block"
style={`resize: vertical; height: 10em; white-space: nowrap; ${style}`} style={`resize: vertical; height: 10em; white-space: nowrap; ${style}`}
bind:value={value} bind:value={value}
on:change={changed} onchange={handleChanged}
/> ></textarea>
{:else if type === "number"} {:else if type === "number"}
<input <input
class:b3-text-field={true} class:b3-text-field={true}
@ -68,7 +82,7 @@
id={key} id={key}
type="number" type="number"
bind:value={value} bind:value={value}
on:change={changed} onchange={handleChanged}
style={style} style={style}
/> />
{:else if type === "button"} {:else if type === "button"}
@ -79,7 +93,7 @@
class:fn__flex-center={true} class:fn__flex-center={true}
class:fn__size200={fnSize} class:fn__size200={fnSize}
id={key} id={key}
on:click={click} onclick={handleClick}
style={style} style={style}
> >
{button.label} {button.label}
@ -92,7 +106,7 @@
class:fn__size200={fnSize} class:fn__size200={fnSize}
id="iconPosition" id="iconPosition"
bind:value={value} bind:value={value}
on:change={changed} onchange={handleChanged}
style={style} style={style}
> >
{#each Object.entries(options) as [value, text]} {#each Object.entries(options) as [value, text]}
@ -111,7 +125,7 @@
step={slider.step} step={slider.step}
type="range" type="range"
bind:value={value} bind:value={value}
on:change={changed} onchange={handleChanged}
style={style} style={style}
/> />
</div> </div>

View file

@ -7,9 +7,19 @@
Description : The setting item container Description : The setting item container
--> -->
<script lang="ts"> <script lang="ts">
export let title: string; // Displayint Setting Title interface Props {
export let description: string; // Displaying Setting Text title: string;
export let direction: 'row' | 'column' = 'column'; description: string;
direction?: 'row' | 'column';
children?: import('svelte').Snippet;
}
let {
title,
description,
direction = 'column',
children
}: Props = $props();
</script> </script>
{#if direction === "row"} {#if direction === "row"}
@ -19,7 +29,7 @@
<div class="b3-label__text">{@html description}</div> <div class="b3-label__text">{@html description}</div>
<div class="fn__hr"></div> <div class="fn__hr"></div>
<div style="display: flex; flex-direction: column; gap: 5px; position: relative;"> <div style="display: flex; flex-direction: column; gap: 5px; position: relative;">
<slot /> {@render children?.()}
</div> </div>
</div> </div>
</div> </div>
@ -31,8 +41,8 @@
{@html description} {@html description}
</div> </div>
</div> </div>
<span class="fn__space" /> <span class="fn__space"></span>
<slot /> {@render children?.()}
</div> </div>
{/if} {/if}

View file

@ -1,3 +1,8 @@
<script>
/** @type {{children?: import('svelte').Snippet}} */
let { children } = $props();
</script>
<div class="item__readme b3-typography"> <div class="item__readme b3-typography">
<slot/> {@render children?.()}
</div> </div>

View file

@ -7,28 +7,42 @@
Description : Description :
--> -->
<script lang="ts"> <script lang="ts">
import { createEventDispatcher } from "svelte";
import Form from './Form'; import Form from './Form';
export let group: string; interface Props {
export let settingItems: ISettingItem[]; group: string;
export let display: boolean = true; settingItems: ISettingItem[];
display?: boolean;
const dispatch = createEventDispatcher(); children?: import('svelte').Snippet;
onclick?: (detail: {key: string}) => void;
function onClick( {detail}) { onchanged?: (detail: {group: string, key: string, value: any}) => void;
dispatch("click", { key: detail.key });
}
function onChanged( {detail}) {
dispatch("changed", {group: group, ...detail});
} }
$: fn__none = display ? "" : "fn__none"; let {
group,
settingItems: _settingItems,
display = true,
children,
onclick,
onchanged
}: Props = $props();
// 使用 $state 让 settingItems 变成响应式
let settingItems = $state(_settingItems);
function handleClick(detail: {key: string}) {
onclick?.(detail);
}
function handleChanged(detail: {key: string, value: any}) {
onchanged?.({group: group, key: detail.key, value: detail.value});
}
let fn__none = $derived(display ? "" : "fn__none");
</script> </script>
<div class="config__tab-container {fn__none}" data-name={group}> <div class="config__tab-container {fn__none}" data-name={group}>
<slot /> {@render children?.()}
{#each settingItems as item (item.key)} {#each settingItems as item (item.key)}
<Form.Wrap <Form.Wrap
title={item.title} title={item.title}
@ -43,8 +57,8 @@
options={item?.options} options={item?.options}
slider={item?.slider} slider={item?.slider}
button={item?.button} button={item?.button}
on:click={onClick} onclick={handleClick}
on:changed={onChanged} onchanged={handleChanged}
/> />
</Form.Wrap> </Form.Wrap>
{/each} {/each}

View file

@ -3,11 +3,11 @@
* @Author : frostime * @Author : frostime
* @Date : 2024-03-23 21:37:33 * @Date : 2024-03-23 21:37:33
* @FilePath : /src/libs/dialog.ts * @FilePath : /src/libs/dialog.ts
* @LastEditTime : 2024-10-16 14:31:04 * @LastEditTime : 2025-08-16 15:39:48
* @Description : Kits about dialogs * @Description : Kits about dialogs
*/ */
import { Dialog } from "siyuan"; import { Dialog } from "siyuan";
import { type SvelteComponent } from "svelte"; import { Component, mount, unmount } from "svelte";
export const inputDialog = (args: { export const inputDialog = (args: {
title: string, placeholder?: string, defaultText?: string, title: string, placeholder?: string, defaultText?: string,
@ -143,21 +143,34 @@ export const simpleDialog = (args: {
export const svelteDialog = (args: { export const svelteDialog = (args: {
title: string, constructor: (container: HTMLElement) => SvelteComponent, title: string,
width?: string, height?: string, component: Component<any>, // Svelte 5 component constructor
props?: Record<string, any>,
width?: string,
height?: string,
callback?: () => void; callback?: () => void;
}) => { }) => {
let container = document.createElement('div') let container = document.createElement('div')
container.style.display = 'contents'; container.style.display = 'contents';
let component = args.constructor(container);
// 内部处理 mount
let componentInstance = mount(args.component, {
target: container,
props: args.props || {}
});
const { dialog, close } = simpleDialog({ const { dialog, close } = simpleDialog({
...args, ele: container, callback: () => { ...args,
component.$destroy(); ele: container,
callback: () => {
// 内部处理 unmount
unmount(componentInstance);
if (args.callback) args.callback(); if (args.callback) args.callback();
} }
}); });
return { return {
component, component: componentInstance,
dialog, dialog,
close close
} }

View file

@ -3,7 +3,7 @@
import SettingPanel from "./libs/components/setting-panel.svelte"; import SettingPanel from "./libs/components/setting-panel.svelte";
let groups: string[] = ["🌈 Group 1", "✨ Group 2"]; let groups: string[] = ["🌈 Group 1", "✨ Group 2"];
let focusGroup = groups[0]; let focusGroup = $state(groups[0]);
const group1Items: ISettingItem[] = [ const group1Items: ISettingItem[] = [
{ {
@ -79,7 +79,7 @@
value: any; value: any;
} }
const onChanged = ({ detail }: CustomEvent<ChangeEvent>) => { const onChanged = (detail: ChangeEvent) => {
if (detail.group === groups[0]) { if (detail.group === groups[0]) {
// setting.set(detail.key, detail.value); // setting.set(detail.key, detail.value);
//Please add your code here //Please add your code here
@ -91,15 +91,15 @@
<div class="fn__flex-1 fn__flex config__panel"> <div class="fn__flex-1 fn__flex config__panel">
<ul class="b3-tab-bar b3-list b3-list--background"> <ul class="b3-tab-bar b3-list b3-list--background">
{#each groups as group} {#each groups as group}
<!-- svelte-ignore a11y-no-noninteractive-element-interactions --> <!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
<li <li
data-name="editor" data-name="editor"
class:b3-list-item--focus={group === focusGroup} class:b3-list-item--focus={group === focusGroup}
class="b3-list-item" class="b3-list-item"
on:click={() => { onclick={() => {
focusGroup = group; focusGroup = group;
}} }}
on:keydown={() => {}} onkeydown={() => {}}
> >
<span class="b3-list-item__text">{group}</span> <span class="b3-list-item__text">{group}</span>
</li> </li>
@ -110,8 +110,8 @@
group={groups[0]} group={groups[0]}
settingItems={group1Items} settingItems={group1Items}
display={focusGroup === groups[0]} display={focusGroup === groups[0]}
on:changed={onChanged} onchanged={onChanged}
on:click={({ detail }) => { console.debug("Click:", detail.key); }} onclick={(detail) => { console.debug("Click:", detail.key); }}
> >
<div class="fn__flex b3-label"> <div class="fn__flex b3-label">
💡 This is our default settings. 💡 This is our default settings.
@ -121,8 +121,8 @@
group={groups[1]} group={groups[1]}
settingItems={group2Items} settingItems={group2Items}
display={focusGroup === groups[1]} display={focusGroup === groups[1]}
on:changed={onChanged} onchanged={onChanged}
on:click={({ detail }) => { console.debug("Click:", detail.key); }} onclick={(detail) => { console.debug("Click:", detail.key); }}
> >
</SettingPanel> </SettingPanel>
</div> </div>