diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml
index 7e4626e..e681e96 100644
--- a/.github/workflows/release-please.yml
+++ b/.github/workflows/release-please.yml
@@ -14,7 +14,7 @@ permissions:
env:
PACKAGE_NAME: jupyter-client
- PACKAGE_VERSION: 0.2.0
+ PACKAGE_VERSION: 0.2.1
jobs:
release-please:
diff --git a/package.json b/package.json
index 2e8d76a..2079bf5 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "jupyter-client",
"private": true,
- "version": "0.2.0",
+ "version": "0.2.1",
"type": "module",
"scripts": {
"dev": "vite",
@@ -14,18 +14,18 @@
},
"devDependencies": {
"@jupyter-lsp/theme-vscode": "3.0.0-rc.0",
- "@jupyterlab/services": "^7.0.6",
- "@jupyterlab/ui-components": "^4.0.6",
+ "@jupyterlab/services": "^7.0.7",
+ "@jupyterlab/ui-components": "^4.0.7",
"@sveltejs/vite-plugin-svelte": "^2.4.6",
"@tsconfig/svelte": "^5.0.2",
"less": "^4.2.0",
"strip-ansi": "^7.1.0",
- "svelte": "^4.2.0",
+ "svelte": "^4.2.2",
"svelte-check": "^2.10.3",
"svelte-preprocess-less": "^0.4.0",
"tslib": "^2.6.2",
"typescript": "^5.2.2",
- "vite": "^4.4.9",
+ "vite": "^4.5.0",
"xterm": "^5.3.0",
"xterm-addon-fit": "^0.7.0"
},
diff --git a/public/i18n/en_US.json b/public/i18n/en_US.json
index 939ff87..fc83d9e 100644
--- a/public/i18n/en_US.json
+++ b/public/i18n/en_US.json
@@ -1,5 +1,14 @@
{
"commands": {
+ "openJupyterBrowser": {
+ "text": "Open Jupyter in Browser"
+ },
+ "openJupyterTab": {
+ "text": "Open Jupyter in New Tab"
+ },
+ "openJupyterWindow": {
+ "text": "Open Jupyter in New Window"
+ },
"runSelectedCells": {
"text": "Run selected cells"
},
@@ -197,14 +206,14 @@
"title": "Code execution"
},
"globalTab": {
- "enable": {
- "description": "Enable to attempt to establish connection with Jupyter service",
- "title": "Enable Jupyter client"
- },
"delay": {
"description": "The delay time when calling the Jupyter kernel language service to get context help and code suggestions.
Unit: milliseconds",
"title": "Language service call delay"
},
+ "enable": {
+ "description": "Enable to attempt to establish connection with Jupyter service",
+ "title": "Enable Jupyter client"
+ },
"title": "Global"
},
"importTab": {
diff --git a/public/i18n/zh_CHT.json b/public/i18n/zh_CHT.json
index 45327b3..80e414b 100644
--- a/public/i18n/zh_CHT.json
+++ b/public/i18n/zh_CHT.json
@@ -1,5 +1,14 @@
{
"commands": {
+ "openJupyterBrowser": {
+ "text": "在瀏覽器中打開 Jupyter"
+ },
+ "openJupyterTab": {
+ "text": "在新頁籤中打開 Jupyter"
+ },
+ "openJupyterWindow": {
+ "text": "在新窗口中打開 Jupyter"
+ },
"runSelectedCells": {
"text": "運行所選代碼塊"
},
@@ -197,14 +206,14 @@
"title": "代碼運行"
},
"globalTab": {
- "enable": {
- "description": "開啟後將嘗試與 Jupyter 服務建立連接",
- "title": "啟用 Jupyter 客戶端"
- },
"delay": {
"description": "調用 Jupyter 內核語言服務獲取上下文幫助與代碼建議時的延時時間
單位:毫秒",
"title": "語言服務調用延時"
},
+ "enable": {
+ "description": "開啟後將嘗試與 Jupyter 服務建立連接",
+ "title": "啟用 Jupyter 客戶端"
+ },
"title": "全局"
},
"importTab": {
diff --git a/public/i18n/zh_CN.json b/public/i18n/zh_CN.json
index 217f45c..08dc67d 100644
--- a/public/i18n/zh_CN.json
+++ b/public/i18n/zh_CN.json
@@ -1,5 +1,14 @@
{
"commands": {
+ "openJupyterBrowser": {
+ "text": "在浏览器中打开 Jupyter"
+ },
+ "openJupyterTab": {
+ "text": "在新页签中打开 Jupyter"
+ },
+ "openJupyterWindow": {
+ "text": "在新窗口中打开 Jupyter"
+ },
"runSelectedCells": {
"text": "运行所选代码块"
},
@@ -197,14 +206,14 @@
"title": "代码运行"
},
"globalTab": {
- "enable": {
- "description": "开启后将尝试与 Jupyter 服务建立连接",
- "title": "启用 Jupyter 客户端"
- },
"delay": {
"description": "调用 Jupyter 内核语言服务获取上下文帮助与代码建议时的延时时间
单位:毫秒",
"title": "语言服务调用延时"
},
+ "enable": {
+ "description": "开启后将尝试与 Jupyter 服务建立连接",
+ "title": "启用 Jupyter 客户端"
+ },
"title": "全局"
},
"importTab": {
diff --git a/public/plugin.json b/public/plugin.json
index 5972800..9b5d3e9 100644
--- a/public/plugin.json
+++ b/public/plugin.json
@@ -2,8 +2,26 @@
"name": "jupyter-client",
"author": "Zuoqiu Yingyi",
"url": "https://github.com/Zuoqiu-Yingyi/siyuan-plugin-jupyter-client",
- "version": "0.2.0",
- "minAppVersion": "2.10.3",
+ "version": "0.2.1",
+ "minAppVersion": "2.10.13",
+ "keywords": [
+ "代码",
+ "程式碼",
+ "执行代码",
+ "執行程式碼",
+ "code",
+ "execute",
+ "jupyter",
+ "notebook",
+ "lab",
+ "hub"
+ ],
+ "backends": [
+ "all"
+ ],
+ "frontends": [
+ "all"
+ ],
"displayName": {
"default": "Jupyter Client",
"zh_CN": "Jupyter 客户端",
diff --git a/src/index.ts b/src/index.ts
index ca6b6ab..a7c2d7a 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -47,6 +47,7 @@ import icon_jupyter_client_session_notebook from "./assets/symbols/icon-jupyter-
import * as sdk from "@siyuan-community/siyuan-sdk";
import Item from "@workspace/components/siyuan/menu/Item.svelte"
+import JupyterTab from "@workspace/components/siyuan/tab/IframeTab.svelte";
import Settings from "./components/Settings.svelte";
import JupyterDock from "./components/JupyterDock.svelte";
import JupyterInspectDock from "./components/JupyterInspectDock.svelte";
@@ -80,6 +81,7 @@ import { toUint8Array } from "@workspace/utils/misc/base64";
import { encode } from "@workspace/utils/misc/base64";
import { select } from "@workspace/utils/dom/selection";
import { replaceRangeWithText } from "@workspace/utils/dom/range";
+import { openWindow } from "@workspace/utils/window/open";
import uuid from "@workspace/utils/misc/uuid";
import CONSTANTS from "./constants";
@@ -114,7 +116,8 @@ import type {
IClickEditorContentEvent,
IClickEditorTitleIconEvent,
IDestroyProtyleEvent,
- ILoadedProtyleEvent,
+ ILoadedProtyleStaticEvent,
+ ISwitchProtyleEvent,
} from "@workspace/types/siyuan/events";
import type { THandlersWrapper } from "@workspace/utils/worker/bridge";
import type { WorkerHandlers } from "./workers/jupyter";
@@ -134,9 +137,14 @@ export type TMenuContext = IBlockMenuContext | {
isMultiBlock: false,
id: BlockID,
};
+export interface IJupyterTab extends siyuan.ITabModel {
+ component?: InstanceType;
+}
export default class JupyterClientPlugin extends siyuan.Plugin {
static readonly GLOBAL_CONFIG_NAME = "global-config";
+ public static readonly CUSTOM_TAB_TYPE_JUPYTER = "-jupyter-tab";
+
static readonly EDIT_KEYBOARD_EVENT_STATUS_INSPECT: IKeyboardStatus = {
type: "keyup",
altKey: false,
@@ -160,7 +168,12 @@ export default class JupyterClientPlugin extends siyuan.Plugin {
public readonly logger: InstanceType;
public readonly client: InstanceType;
+ protected readonly TOP_BAR_MENU_ID: string;
protected readonly SETTINGS_DIALOG_ID: string;
+ protected readonly CUSTOM_TAB_ID_JUPYTER: string;
+
+ protected readonly jupyterTab: ReturnType;
+ protected topBarButton?: HTMLElement; // 顶部菜单栏按钮
public config: IConfig = DEFAULT_CONFIG;
protected worker?: InstanceType; // worker
@@ -207,7 +220,10 @@ export default class JupyterClientPlugin extends siyuan.Plugin {
this.logger = new Logger(this.name);
this.client = new sdk.Client(undefined, "fetch");
+ this.TOP_BAR_MENU_ID = `${this.name}-top-bar-menu`;
this.SETTINGS_DIALOG_ID = `${this.name}-settings-dialog`;
+ this.CUSTOM_TAB_ID_JUPYTER = `${this.name}${JupyterClientPlugin.CUSTOM_TAB_TYPE_JUPYTER}`;
+
this.handlers = {
gotoBlock: {
this: this,
@@ -238,6 +254,29 @@ export default class JupyterClientPlugin extends siyuan.Plugin {
},
} as const;
+ /* 注册页签 */
+ this.jupyterTab = this.addTab({
+ type: JupyterClientPlugin.CUSTOM_TAB_TYPE_JUPYTER,
+ init() {
+ // plugin.logger.debug("tab-init");
+ // plugin.logger.debug(this);
+
+ const tab: IJupyterTab = this;
+ tab.component = new JupyterTab({
+ target: tab.element,
+ props: {
+ ...tab.data,
+ },
+ });
+ },
+ destroy() {
+ // plugin.logger.debug("tab-destroy");
+
+ const tab: IJupyterTab = this;
+ tab.component?.$destroy();
+ },
+ });
+
/**
* 注册自定义 HTMLElement 组件
* REF: https://developer.mozilla.org/zh-CN/docs/Web/API/CustomElementRegistry
@@ -426,6 +465,28 @@ export default class JupyterClientPlugin extends siyuan.Plugin {
},
});
+ this.addCommand({ // 在新页签中打开 Jupyter
+ langKey: "open-jupyter-tab",
+ langText: this.i18n.commands.openJupyterTab.text,
+ hotkey: "", // 默认快捷键
+ customHotkey: "", // 自定义快捷键
+ callback: this.openJupyterTab,
+ });
+ this.addCommand({ // 在浏览器中打开 Jupyter
+ langKey: "open-jupyter-browser",
+ langText: this.i18n.commands.openJupyterBrowser.text,
+ hotkey: "", // 默认快捷键
+ customHotkey: "", // 自定义快捷键
+ callback: this.openJupyterBrowser,
+ });
+ this.addCommand({ // 在新窗口中打开 Jupyter
+ langKey: "open-jupyter-window",
+ langText: this.i18n.commands.openJupyterWindow.text,
+ hotkey: "", // 默认快捷键
+ customHotkey: "", // 自定义快捷键
+ callback: this.openJupyterWindow,
+ });
+
/* 加载数据 */
this.loadData(JupyterClientPlugin.GLOBAL_CONFIG_NAME)
.then(config => {
@@ -461,19 +522,55 @@ export default class JupyterClientPlugin extends siyuan.Plugin {
this.eventBus.on("click-editortitleicon", this.blockMenuEventListener);
this.eventBus.on("click-blockicon", this.blockMenuEventListener);
this.eventBus.on("click-editorcontent", this.clickEditorContentEventListener);
- this.eventBus.on("loaded-protyle", this.loadedProtyleEventListener);
+ this.eventBus.on("loaded-protyle-static", this.loadedProtyleEventListener);
+ this.eventBus.on("switch-protyle", this.loadedProtyleEventListener);
this.eventBus.off("destroy-protyle", this.destroyProtyleEventListener);
});
}
onLayoutReady(): void {
+ /* 添加菜单项 */
+ this.topBarButton = this.addTopBar({
+ icon: "icon-jupyter-client",
+ title: this.displayName,
+ position: "right",
+ callback: e => {
+ const menu = new siyuan.Menu(this.TOP_BAR_MENU_ID);
+ menu.addItem({
+ icon: "iconLayout",
+ label: this.i18n.commands.openJupyterTab.text,
+ click: this.openJupyterTab,
+ });
+ menu.addItem({
+ icon: "iconOpen",
+ label: this.i18n.commands.openJupyterBrowser.text,
+ click: this.openJupyterBrowser,
+ });
+ menu.addItem({
+ icon: "iconOpenWindow",
+ label: this.i18n.commands.openJupyterWindow.text,
+ click: this.openJupyterWindow,
+ });
+ if (FLAG_MOBILE) {
+ menu.fullscreen();
+ }
+ else {
+ menu.open({
+ x: globalThis.siyuan?.coordinates?.pageX ?? 0,
+ y: globalThis.siyuan?.coordinates?.pageY ?? 0,
+ isLeft: true,
+ });
+ }
+ },
+ });
}
onunload(): void {
this.eventBus.off("click-editortitleicon", this.blockMenuEventListener);
this.eventBus.off("click-blockicon", this.blockMenuEventListener);
this.eventBus.off("click-editorcontent", this.clickEditorContentEventListener);
- this.eventBus.off("loaded-protyle", this.loadedProtyleEventListener);
+ this.eventBus.off("loaded-protyle-static", this.loadedProtyleEventListener);
+ this.eventBus.off("switch-protyle", this.loadedProtyleEventListener);
this.eventBus.off("destroy-protyle", this.destroyProtyleEventListener);
for (const objectURL of this.kernelName2logoObjectURL.values()) {
@@ -787,12 +884,12 @@ export default class JupyterClientPlugin extends siyuan.Plugin {
const pathname = (() => {
switch (true) {
- case "logo-svg" in spec.resources:
- return spec.resources["logo-svg"];
- case "logo-32x32" in spec.resources:
- return spec.resources["logo-32x32"];
case "logo-64x64" in spec.resources:
return spec.resources["logo-64x64"];
+ case "logo-32x32" in spec.resources:
+ return spec.resources["logo-32x32"];
+ case "logo-svg" in spec.resources:
+ return spec.resources["logo-svg"];
default:
if (Object.keys(spec.resources).length > 0) {
return Object.values(spec.resources)[0];
@@ -1592,8 +1689,8 @@ export default class JupyterClientPlugin extends siyuan.Plugin {
const menu = new siyuan.Menu(message.header.msg_id, () => {
this.complating = false;
- if (menu.menu.element.lastElementChild instanceof HTMLElement) {
- menu.menu.element.lastElementChild.style.maxHeight = "";
+ if (menu.element.lastElementChild instanceof HTMLElement) {
+ menu.element.lastElementChild.style.maxHeight = "";
}
});
@@ -1605,9 +1702,9 @@ export default class JupyterClientPlugin extends siyuan.Plugin {
menu.open(options);
/* 调整菜单位置 */
- const menu_rect = menu.menu.element.getBoundingClientRect();
+ const menu_rect = menu.element.getBoundingClientRect();
if (menu_rect.y !== options.y) {
- const items_element = menu.menu.element.lastElementChild;
+ const items_element = menu.element.lastElementChild;
if (items_element instanceof HTMLElement) {
const max_height = globalThis.innerHeight - options.y - 32;
@@ -2072,9 +2169,9 @@ export default class JupyterClientPlugin extends siyuan.Plugin {
}
/* 编辑器加载事件监听器 */
- protected readonly loadedProtyleEventListener = async (e: ILoadedProtyleEvent) => {
+ protected readonly loadedProtyleEventListener = async (e: ILoadedProtyleStaticEvent | ISwitchProtyleEvent) => {
// this.logger.debug(e);
- const protyle = e.detail;
+ const protyle = e.detail.protyle;
/* 更新内核状态 */
if (!this.doc2session.has(protyle.block.rootID!)) { // 当前文档未连接会话
@@ -2256,4 +2353,46 @@ export default class JupyterClientPlugin extends siyuan.Plugin {
sessions,
});
}
+
+ /**
+ * 在新页签中打开 Jupyter
+ */
+ protected readonly openJupyterTab = async () => {
+ siyuan.openTab({
+ app: this.app,
+ custom: {
+ icon: "icon-jupyter-client",
+ title: "Jupyter",
+ id: this.CUSTOM_TAB_ID_JUPYTER,
+ data: {
+ src: this.baseUrl,
+ title: "Jupyter",
+ },
+ },
+ keepCursor: false,
+ removeCurrentTab: false,
+ });
+ };
+
+ /**
+ * 在浏览器中打开 Jupyter
+ */
+ protected readonly openJupyterBrowser = async () => {
+ globalThis.open(this.baseUrl);
+ };
+
+ /**
+ * 在新窗口中打开 Jupyter
+ */
+ protected readonly openJupyterWindow = async () => {
+ openWindow({
+ url: this.baseUrl,
+ base: {
+ center: true,
+ },
+ extra: {
+ enableMenuBar: true,
+ },
+ });
+ };
};