* feat: svelte example for setting pannel

* Must destroy svelte

* onDestroy

* Make hello easy

* 拷贝官方的代码功能

* Use hello both in tab and dialog

* fix: destroy dock
This commit is contained in:
Frostime 2023-05-20 17:40:15 +08:00 committed by GitHub
parent 18fc66f0f6
commit 0f049cc461
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 460 additions and 83 deletions

23
src/dock.svelte Normal file
View file

@ -0,0 +1,23 @@
<script lang="ts">
import { adaptHotkey } from "siyuan";
export let text: string;
</script>
<div class="fn__flex-1 fn__flex-column">
<div class="block__icons">
<div class="block__logo">
<svg><use xlink:href="#iconEmoji" /></svg>
Custom Dock
</div>
<span class="fn__flex-1 fn__space" />
<span
data-type="min"
class="block__icon b3-tooltips b3-tooltips__sw"
aria-label="Min ${adaptHotkey('⌘W')}"
><svg><use xlink:href="#iconMin" /></svg></span
>
</div>
<div class="fn__flex-1 plugin-sample__custom-dock">
{text}
</div>
</div>

View file

@ -3,68 +3,60 @@
* https://github.com/frostime/sy-plugin-template-vite * https://github.com/frostime/sy-plugin-template-vite
--> -->
<script lang="ts"> <script lang="ts">
import { onMount } from "svelte"; import { onDestroy, onMount } from "svelte";
import { version } from "./api"; import { version } from "./api";
import { showMessage } from "siyuan";
export let name: string; export let name: string;
export let i18n: any;
let time; let time;
let ver; let ver;
let intv1 = setInterval(async () => {
time = new Date();
}, 1000);
onMount(async () => { onMount(async () => {
time = new Date(); time = new Date();
ver = await version(); ver = await version();
}); });
$: time_str = new Date(time).toLocaleTimeString(); /**
* You must call this function when the component is destroyed.
*/
onDestroy(() => {
showMessage("Hello panel closed");
clearInterval(intv1);
});
setInterval(async () => { $: time_str = new Date(time).toLocaleTimeString();
time = new Date();
}, 1000);
</script> </script>
<div id="hello"> <div id="hello">
<div class="row"> <div class="fn__flex">
<div class="col left"> <div class="fn__flex-1">
<h2>Hello {name} v{ver}</h2> <h2>Hello {name} v{ver}</h2>
</div> </div>
<div class="col right"> <div class="fn__flex-1 b3-label__text __text-right">
{time_str} {time_str}
</div> </div>
</div> </div>
<br /> <div>
<p>{@html i18n.makesure}</p>
</div>
<p>
使用这个模板之前,请阅读<a
href="https://github.com/siyuan-note/plugin-sample">官方教程</a
>, 确保自己已经理解了插件的基本开发流程。
</p>
<p>
Before using this template, please read the <a
href="https://github.com/siyuan-note/plugin-sample">offical sample</a
>, make sure that you've known about the pipeline for plugin developing.
</p>
</div> </div>
<style lang="scss"> <style lang="scss">
#hello { #hello {
margin: 1rem; margin: 20px;
text-align: left; div {
margin-bottom: 10px;
} }
.row {
display: flex;
flex-direction: row;
.col {
flex: 1;
} }
.left { .__text-right {
text-align: left;
}
.right {
text-align: right; text-align: right;
} }
}
</style> </style>

View file

@ -1,3 +1,12 @@
{ {
"name": "SiYuan" "addTopBarIcon": "Add a top bar icon by plugin",
"cancel": "Cancel",
"save": "Save",
"byeMenu": "Bye, Menu!",
"helloPlugin": "Hello, Plugin!",
"byePlugin": "Bye, Plugin!",
"name": "SiYuan",
"hello": {
"makesure": "Before using this template, please read the <a href=\"https://github.com/siyuan-note/plugin-sample\">offical sample</a>, make sure that you've known about the pipeline for plugin developing."
}
} }

View file

@ -1,3 +1,12 @@
{ {
"name": "思源" "addTopBarIcon": "使用插件添加一个顶栏按钮",
"cancel": "取消",
"save": "保存",
"byeMenu": "再见,菜单!",
"helloPlugin": "你好,插件!",
"byePlugin": "再见,插件!",
"name": "思源",
"hello": {
"makesure": "使用这个模板之前,请阅读<a href=\"https://github.com/siyuan-note/plugin-sample\">官方教程</a>, 确保自己已经理解了插件的基本开发流程。"
}
} }

View file

@ -2,37 +2,205 @@
* Copyright (c) 2023 frostime. All rights reserved. * Copyright (c) 2023 frostime. All rights reserved.
* https://github.com/frostime/sy-plugin-template-vite * https://github.com/frostime/sy-plugin-template-vite
*/ */
import { Plugin, showMessage, Dialog } from "siyuan" import { Plugin, showMessage, confirm, Dialog, Menu, isMobile, openTab } from "siyuan";
import Hello from "./hello.svelte" import "./index.scss";
import "./index.scss"
import HelloExample from "./hello.svelte";
import DockExample from "./dock.svelte";
import SettingPannel from "./libs/setting-panel.svelte";
const STORAGE_NAME = "menu-config";
const TAB_TYPE = "custom_tab";
const DOCK_TYPE = "dock_tab";
export default class SamplePlugin extends Plugin { export default class SamplePlugin extends Plugin {
counter: { [key: string]: number } = {
hello: 0,
};
private customTab: () => any;
async onload() { async onload() {
console.log("onload"); showMessage("Hello SiYuan Plugin");
showMessage("Hello World"); console.log(this.i18n.helloPlugin);
this.addTopBar(
{ const topBarElement = this.addTopBar({
icon: "iconEmoji", icon: "iconEmoji",
"title": "Hello SiYuan", title: this.i18n.addTopBarIcon,
"callback": () => { position: "left",
callback: () => {
this.addMenu(topBarElement.getBoundingClientRect());
}
});
let div = document.createElement("div");
new HelloExample({
target: div,
props: {
name: this.i18n.name,
i18n: this.i18n.hello
}
});
this.customTab = this.addTab({
type: TAB_TYPE,
init() {
this.element.appendChild(div);
console.log(this.element);
},
destroy() {
console.log("destroy tab:", TAB_TYPE);
}
});
this.addDock({
config: {
position: "LeftBottom",
size: { width: 200, height: 0 },
icon: "iconEmoji",
title: "Custom Dock",
},
data: {
text: "This is my custom dock"
},
type: DOCK_TYPE,
init() {
this.component = new DockExample({
target: this.element,
props: {
text: this.data.text,
}
});
},
destroy() {
console.log("destroy dock:", DOCK_TYPE);
this.component.$destroy();
}
});
}
private wsEvent({ detail }: any) {
console.log(detail);
}
private async addMenu(rect: DOMRect) {
const menu = new Menu("topBarSample", () => {
console.log(this.i18n.byeMenu);
});
menu.addItem({
icon: "iconHelp",
label: "Confirm",
click() {
confirm("Confirm", "Is this a confirm?", () => {
showMessage("confirm");
}, () => {
showMessage("cancel");
});
}
});
menu.addItem({
icon: "iconFeedback",
label: "Message",
click: () => {
showMessage(this.i18n.helloPlugin);
}
});
menu.addItem({
icon: "iconInfo",
label: "Dialog",
click: () => this.openHelloInDialog()
});
menu.addItem({
icon: "iconLayoutBottom",
label: "Open Tab",
click: () => {
openTab({
custom: {
icon: "iconEmoji",
title: "Custom Tab",
data: {
text: "This is my custom tab",
},
fn: this.customTab
},
});
}
});
menu.addItem({
icon: "iconTrashcan",
label: "Remove Data",
click: () => {
this.removeData(STORAGE_NAME);
}
});
menu.addItem({
icon: "iconSelect",
label: "On ws-main",
click: () => {
this.eventBus.on("ws-main", this.wsEvent);
}
});
menu.addItem({
icon: "iconClose",
label: "Off ws-main",
click: () => {
this.eventBus.off("ws-main", this.wsEvent);
}
});
menu.addSeparator();
menu.addItem({
icon: "iconSparkles",
label: this.data[STORAGE_NAME] || "Readonly",
type: "readonly",
});
if (isMobile()) {
menu.fullscreen();
} else {
menu.open({
x: rect.right,
y: rect.bottom,
isLeft: true,
});
}
}
openSetting(): void {
let dialog = new Dialog({
title: "SettingPannel",
content: `<div id="SettingPanel"></div>`,
width: "600px",
destroyCallback: (options) => {
console.log("destroyCallback", options);
//You must destroy the component when the dialog is closed
pannel.$destroy();
}
});
let pannel = new SettingPannel({
target: dialog.element.querySelector("#SettingPanel"),
});
}
private openHelloInDialog() {
this.counter.hello++;
let dialog = new Dialog({ let dialog = new Dialog({
title: "Hello World", title: "Hello World",
content: `<div id="helloPanel"></div>`, content: `<div id="helloPanel"></div>`,
destroyCallback(options) {
//You must destroy the component when the dialog is closed
hello.$destroy();
},
}); });
new Hello({ let hello = new HelloExample({
target: dialog.element.querySelector("#helloPanel"), target: dialog.element.querySelector("#helloPanel"),
props: { props: {
name: this.i18n.name, name: this.i18n.name,
i18n: this.i18n.hello
} }
}); });
} }
}
)
}
async onunload() { async onunload() {
showMessage("Goodbye World"); showMessage("Goodbye SiYuan Plugin");
console.log("onunload"); console.log("onunload");
} }
} }

8
src/libs/b3-list.svelte Normal file
View file

@ -0,0 +1,8 @@
<ul class="b3-list b3-list--background">
<li class="b3-list-item">
<svg class="b3-list-item__graphic"><use xlink:href="#iconEdit"></use></svg>
<span class="b3-list-item__text">
<slot></slot>
</span>
</li>
</ul>

View file

@ -0,0 +1,90 @@
<script lang="ts">
import { createEventDispatcher } from "svelte";
export let type: string; // Setting Type
export let title: string; // Displayint Setting Title
export let text: string; // Displaying Setting Text
export let settingKey: string;
export let settingValue: any;
//Optional
export let placeholder: string = ""; // Use it if type is input
export let options: { [key: string]: string } = {}; // Use it if type is select
export let slider: {
min: number;
max: number;
step: number;
} = {min: 0, max: 100, step: 1}; // Use it if type is slider
const dispatch = createEventDispatcher();
function clicked() {
dispatch("clicked");
}
function changed() {
dispatch("changed", { key: settingKey, value: settingValue });
}
</script>
<label class="fn__flex b3-label">
<div class="fn__flex-1">
{title}
<div class="b3-label__text">
{text}
</div>
</div>
<span class="fn__space" />
<!-- <slot /> -->
{#if type === "checkbox"}
<!-- Checkbox -->
<input
class="b3-switch fn__flex-center"
id={settingKey}
type="checkbox"
bind:checked={settingValue}
on:change={changed}
/>
{:else if type === "input"}
<!-- Text Input -->
<input
class="b3-text-field fn__flex-center fn__size200"
id={settingKey}
{placeholder}
bind:value={settingValue}
on:change={changed}
/>
{:else if type === "button"}
<!-- Button Input -->
<button
class="b3-button b3-button--outline fn__flex-center fn__size200"
id={settingKey}
on:click={clicked}
>
{settingValue}
</button>
{:else if type === "select"}
<!-- Dropdown select -->
<select
class="b3-select fn__flex-center fn__size200"
id="iconPosition"
bind:value={settingValue}
on:change={changed}
>
{#each Object.entries(options) as [value, text]}
<option {value}>{text}</option>
{/each}
</select>
{:else if type == "slider"}
<!-- Slider -->
<input
class="b3-slider fn__size200"
id="fontSize"
min="{slider.min}"
max="{slider.max}"
step="{slider.step}"
type="range"
bind:value={settingValue}
on:change={changed}
/>
{/if}
</label>

View file

@ -0,0 +1,99 @@
<script>
import SettingItem from "./setting-item.svelte";
import { showMessage } from "siyuan";
import { onMount, onDestroy } from 'svelte';
onMount(() => {
showMessage("Setting panel opened");
});
onDestroy(() => {
showMessage("Setting panel closed");
});
</script>
<!--
You can use this template to quickly create a setting panel,
with the same UI style in SiYuan
-->
<div class="config__tab-container">
<div data-type="Header" class="fn__flex b3-label">
<div class="fn_flex-1">
<h4>This setting panel is provided by a svelte component</h4>
<div class="b3-label__text">
<span class="fn__flex-1">
See:
<pre style="display: inline">/lib/setting-pannel.svelte</pre>
</span>
</div>
</div>
</div>
<SettingItem
type="checkbox"
title="Checkbox"
text="This is a checkbox"
settingKey="Checkbox"
settingValue={true}
on:changed={(event) => {
showMessage(
`Checkbox changed: ${event.detail.key} = ${event.detail.value}`
);
}}
/>
<SettingItem
type="input"
title="Input"
text="This is an input"
settingKey="Input"
settingValue=""
placeholder="Input something"
on:changed={(event) => {
showMessage(
`Input changed: ${event.detail.key} = ${event.detail.value}`
);
}}
/>
<SettingItem
type="button"
title="Button"
text="This is a button"
settingKey="Button"
settingValue="Click me"
on:clicked={() => {
showMessage("Button clicked");
}}
/>
<SettingItem
type="select"
title="Select"
text="This is a select"
settingKey="Select"
settingValue="left"
options={{
left: "Left",
center: "Center",
right: "Right",
}}
on:changed={(event) => {
showMessage(
`Select changed: ${event.detail.key} = ${event.detail.value}`
);
}}
/>
<SettingItem
type="slider"
title="Slide"
text="This is a slide"
settingKey="Slide"
settingValue={50}
slider={{
min: 0,
max: 100,
step: 1,
}}
on:changed={(event) => {
showMessage(
`Slide changed: ${event.detail.key} = ${event.detail.value}`
);
}}
/>
</div>

23
src/siyuan.d.ts vendored
View file

@ -1,26 +1,5 @@
/* /*
* Copyright (c) 2023, Terwer . All rights reserved. * Copyright (c) 2023, SiYuan, frostime, Terwer . All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Terwer designates this
* particular file as subject to the "Classpath" exception as provided
* by Terwer in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Terwer, Shenzhen, Guangdong, China, youweics@163.com
* or visit www.terwer.space if you need additional information or have any
* questions.
*/ */
declare module "siyuan" { declare module "siyuan" {

2
src/sy-dtype.d.ts vendored
View file

@ -4,7 +4,7 @@
*/ */
/** /**
* * Frequently used data structures in SiYuan
*/ */
declare module "sy-dtype" { declare module "sy-dtype" {