Skip to content

Commit 129a9c2

Browse files
Enhance SYLink processing for improved plugin and block handling (#17843)
* 🎨 Enhance SYLink processing for improved plugin handling and block fetching * 🎨 Refactor SYLink processing and enhance block info parsing for improved functionality * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * 🎨 Refactor SYLink processing to SiYuan URI for improved clarity and functionality * 🎨 Rename SY protocol block info interface to SiYuan URI block info and update related parsing function for consistency * fix: invert condition for SiYuan URI protocol validation in processSiYuanUri function --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
1 parent 983d7ac commit 129a9c2

8 files changed

Lines changed: 211 additions & 116 deletions

File tree

app/src/boot/onGetConfig.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import {sendGlobalShortcut} from "./globalEvent/keydown";
2727
import {closeWindow} from "../window/closeWin";
2828
import {correctHotkey} from "./globalEvent/commonHotkey";
2929
import {recordBeforeResizeTop} from "../protyle/util/resize";
30-
import {processSYLink} from "../editor/openLink";
30+
import {processSiYuanUri} from "../editor/openLink";
3131
import {getAllEditor} from "../layout/getAll";
3232

3333
export const onGetConfig = (isStart: boolean, app: App) => {
@@ -162,7 +162,7 @@ export const initWindow = async (app: App) => {
162162
});
163163
if (!isWindow()) {
164164
ipcRenderer.on(Constants.SIYUAN_OPEN_URL, (event, url) => {
165-
processSYLink(app, url);
165+
processSiYuanUri(app, url);
166166
});
167167
}
168168
ipcRenderer.on(Constants.SIYUAN_OPEN_FILE, (event, data) => {

app/src/editor/openLink.ts

Lines changed: 112 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {getIdFromSYProtocol, isLocalPath, isSYProtocol, pathPosix} from "../util/pathName";
1+
import {isLocalPath, isSiYuanUriProtocol, parseSiYuanUriBlockInfo, pathPosix} from "../util/pathName";
22
/// #if !BROWSER
33
import {shell, ipcRenderer} from "electron";
44
/// #endif
@@ -8,63 +8,19 @@ import {Constants} from "../constants";
88
import {openAsset, openBy, openFile, openFileById} from "./util";
99
/// #endif
1010
import {showMessage} from "../dialog/message";
11-
import {openByMobile} from "../protyle/util/compatibility";
12-
import {App} from "../index";
11+
import {isInIOS, isInAndroid, isInHarmony} from "../protyle/util/compatibility";
1312
import {fetchPost} from "../util/fetch";
1413
import {checkFold} from "../util/noRelyPCFunction";
1514
import {openMobileFileById} from "../mobile/editor";
1615

17-
export const processSYLink = (app: App, url: string) => {
18-
let urlObj: URL;
19-
try {
20-
urlObj = new URL(url);
21-
if (urlObj.protocol !== "siyuan:") {
22-
return false;
23-
}
24-
} catch (error) {
25-
return false;
26-
}
27-
if (urlObj && urlObj.hostname === "plugins") {
28-
const pluginNameType = urlObj.pathname.split("/")[1];
29-
if (!pluginNameType) {
30-
return false;
31-
}
32-
app.plugins.find(plugin => {
33-
if (pluginNameType.startsWith(plugin.name)) {
34-
// siyuan://plugins/plugin-name/foo?bar=baz
35-
plugin.eventBus.emit("open-siyuan-url-plugin", {url});
16+
import type {App} from "../index";
3617

37-
/// #if !MOBILE
38-
// https://github.com/siyuan-note/siyuan/pull/9256
39-
if (pluginNameType.split("/")[0] !== plugin.name) {
40-
// siyuan://plugins/plugin-samplecustom_tab?title=自定义页签&icon=iconFace&data={"text": "This is the custom plugin tab I opened via protocol."}
41-
let data = urlObj.searchParams.get("data");
42-
try {
43-
data = JSON.parse(data || "{}");
44-
} catch (e) {
45-
console.log("Error open plugin tab with protocol:", e);
46-
}
47-
openFile({
48-
app,
49-
custom: {
50-
title: urlObj.searchParams.get("title"),
51-
icon: urlObj.searchParams.get("icon"),
52-
data,
53-
id: pluginNameType
54-
},
55-
});
56-
}
57-
/// #endif
58-
return true;
59-
}
60-
});
61-
return true;
62-
}
63-
if (urlObj && isSYProtocol(url)) {
64-
const id = getIdFromSYProtocol(url);
65-
const focus = urlObj.searchParams.get("focus") === "1";
66-
window.siyuan.editorIsFullscreen = urlObj.searchParams.get("fullscreen") === "1";
67-
fetchPost("/api/block/checkBlockExist", {id}, existResponse => {
18+
export const processSiYuanUriBlocks = (app: App, uriObj: URL): boolean => {
19+
const blockInfo = parseSiYuanUriBlockInfo(uriObj);
20+
if (blockInfo != null) {
21+
const {id, focus} = blockInfo;
22+
window.siyuan.editorIsFullscreen = blockInfo.fullscreen;
23+
fetchPost("/api/block/checkBlockExist", { id }, existResponse => {
6824
if (existResponse.data) {
6925
checkFold(id, (zoomIn) => {
7026
/// #if !MOBILE
@@ -84,7 +40,7 @@ export const processSYLink = (app: App, url: string) => {
8440
}
8541
app.plugins.forEach(plugin => {
8642
plugin.eventBus.emit("open-siyuan-url-block", {
87-
url,
43+
url: uriObj.href,
8844
id,
8945
focus,
9046
exist: existResponse.data,
@@ -96,6 +52,75 @@ export const processSYLink = (app: App, url: string) => {
9652
return false;
9753
};
9854

55+
export const processSiYuanUriPlugins = (app: App, uriObj: URL): boolean => {
56+
const pluginNameOrTabType: string | null = (() => {
57+
const name = uriObj.pathname.split("/")[1];
58+
if (!name) {
59+
return null;
60+
}
61+
try {
62+
return decodeURIComponent(name);
63+
} catch (error) {
64+
return null;
65+
}
66+
})();
67+
68+
if (!pluginNameOrTabType) {
69+
return false;
70+
}
71+
72+
const plugin = app.plugins.find(plugin => pluginNameOrTabType === plugin.name);
73+
if (plugin) {
74+
// siyuan://plugins/plugin-name/foo?bar=baz
75+
plugin.eventBus.emit("open-siyuan-url-plugin", { url: uriObj.href });
76+
} else {
77+
// siyuan://plugins/plugin-samplecustom_tab?title=自定义页签&icon=iconFace&data={"text": "This is the custom plugin tab I opened via protocol."}
78+
/// #if !MOBILE
79+
// https://github.com/siyuan-note/siyuan/pull/9256
80+
const data = (() => {
81+
try {
82+
return JSON.parse(uriObj.searchParams.get("data") || "{}");
83+
} catch (e) {
84+
console.log("Error open plugin tab with protocol:", e);
85+
return undefined;
86+
}
87+
})();
88+
// id 不存在时无副作用
89+
openFile({
90+
app,
91+
custom: {
92+
title: uriObj.searchParams.get("title") ?? pluginNameOrTabType,
93+
icon: uriObj.searchParams.get("icon") ?? "iconPlugin",
94+
data,
95+
id: pluginNameOrTabType
96+
},
97+
});
98+
/// #endif
99+
}
100+
return true;
101+
};
102+
103+
export const processSiYuanUri = (app: App, uri: string) => {
104+
let uriObj: URL;
105+
try {
106+
uriObj = new URL(uri);
107+
if (!isSiYuanUriProtocol(uriObj)) {
108+
return false;
109+
}
110+
} catch (error) {
111+
return false;
112+
}
113+
switch (uriObj.hostname) {
114+
case "blocks":
115+
return processSiYuanUriBlocks(app, uriObj);
116+
case "plugins":
117+
return processSiYuanUriPlugins(app, uriObj);
118+
default:
119+
break;
120+
}
121+
return false;
122+
};
123+
99124
export const openLink = (protyle: IProtyle, aLink: string, event?: MouseEvent, ctrlIsPressed = false) => {
100125
let linkAddress = Lute.UnEscapeHTMLStr(aLink);
101126
let pdfParams;
@@ -164,3 +189,36 @@ export const openLink = (protyle: IProtyle, aLink: string, event?: MouseEvent, c
164189
}
165190
/// #endif
166191
};
192+
193+
export const openByMobile = (uri: string) => {
194+
if (!uri) {
195+
return;
196+
}
197+
/// #if MOBILE
198+
if (processSiYuanUri(window.siyuan.ws.app, uri)) {
199+
return;
200+
}
201+
/// #endif
202+
if (isInIOS()) {
203+
if (uri.startsWith("assets/")) {
204+
// iOS 16.7 之前的版本,uri 需要 encodeURIComponent
205+
window.webkit.messageHandlers.openLink.postMessage(location.origin + "/assets/" + encodeURIComponent(uri.replace("assets/", "")));
206+
} else if (uri.startsWith("/")) {
207+
// 导出 zip 返回的是已经 encode 过的,因此不能再 encode
208+
window.webkit.messageHandlers.openLink.postMessage(location.origin + uri);
209+
} else {
210+
try {
211+
new URL(uri);
212+
window.webkit.messageHandlers.openLink.postMessage(uri);
213+
} catch (e) {
214+
window.webkit.messageHandlers.openLink.postMessage("https://" + uri);
215+
}
216+
}
217+
} else if (isInAndroid()) {
218+
window.JSAndroid.openExternal(uri);
219+
} else if (isInHarmony()) {
220+
window.JSHarmony.openExternal(uri);
221+
} else {
222+
window.open(uri);
223+
}
224+
};

app/src/menus/commonMenuItem.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ import {
1111
isInHarmony,
1212
isInIOS,
1313
isInMobileApp,
14-
openByMobile,
1514
saveExportFile,
1615
writeText
1716
} from "../protyle/util/compatibility";
17+
import {openByMobile} from "../editor/openLink";
1818
import {fetchPost, fetchSyncPost} from "../util/fetch";
1919
import {hideMessage, showMessage} from "../dialog/message";
2020
import {Dialog} from "../dialog";

app/src/plugin/platformUtils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import * as compatibility from "../protyle/util/compatibility";
33
import {ipcRenderer} from "electron";
44
import {Constants} from "../constants";
55
/// #endif
6-
export const openByMobile = compatibility.openByMobile;
6+
export {openByMobile} from "../editor/openLink";
77
export const readText = compatibility.readText;
88
export const writeText = compatibility.writeText;
99
export const copyPlainText = compatibility.copyPlainText;

app/src/protyle/preview/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import {isOnlyMeta, openByMobile, writeText} from "../util/compatibility";
1+
import {isOnlyMeta, writeText} from "../util/compatibility";
22
import {focusByRange} from "../util/selection";
3+
import {openByMobile} from "../../editor/openLink";
34
import {showMessage} from "../../dialog/message";
45
import {isLocalPath, pathPosix} from "../../util/pathName";
56
import {previewDocImage} from "./image";

app/src/protyle/util/compatibility.ts

Lines changed: 0 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,6 @@ import {Constants} from "../../constants";
44
/// #if !BROWSER
55
import {ipcRenderer} from "electron";
66
/// #endif
7-
/// #if MOBILE
8-
import {processSYLink} from "../../editor/openLink";
9-
/// #endif
107
import {getDefaultSubType, getDefaultType} from "../../search/getDefault";
118
import {hideMessage, showMessage} from "../../dialog/message";
129

@@ -66,39 +63,6 @@ export const getTextSiyuanFromTextHTML = (html: string) => {
6663
};
6764
};
6865

69-
export const openByMobile = (uri: string) => {
70-
if (!uri) {
71-
return;
72-
}
73-
/// #if MOBILE
74-
if (processSYLink(window.siyuan.ws.app, uri)) {
75-
return;
76-
}
77-
/// #endif
78-
if (isInIOS()) {
79-
if (uri.startsWith("assets/")) {
80-
// iOS 16.7 之前的版本,uri 需要 encodeURIComponent
81-
window.webkit.messageHandlers.openLink.postMessage(location.origin + "/assets/" + encodeURIComponent(uri.replace("assets/", "")));
82-
} else if (uri.startsWith("/")) {
83-
// 导出 zip 返回的是已经 encode 过的,因此不能再 encode
84-
window.webkit.messageHandlers.openLink.postMessage(location.origin + uri);
85-
} else {
86-
try {
87-
new URL(uri);
88-
window.webkit.messageHandlers.openLink.postMessage(uri);
89-
} catch (e) {
90-
window.webkit.messageHandlers.openLink.postMessage("https://" + uri);
91-
}
92-
}
93-
} else if (isInAndroid()) {
94-
window.JSAndroid.openExternal(uri);
95-
} else if (isInHarmony()) {
96-
window.JSHarmony.openExternal(uri);
97-
} else {
98-
window.open(uri);
99-
}
100-
};
101-
10266
export const saveExportFile = async (uri: string, msgId?: string) => {
10367
if (!uri) {
10468
return;
@@ -701,4 +665,3 @@ export const initNativeDialogOverride = () => {
701665
};
702666
};
703667
/// #endif
704-

app/src/types/index.d.ts

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1288,8 +1288,6 @@ interface IKernelPluginRpcError {
12881288
data?: any;
12891289
}
12901290

1291-
1292-
12931291
interface IKernelPluginRpc {
12941292
/**
12951293
* 通过 {@link Proxy} 实现的动态方法调用,插件开发者可以直接调用 `call.方法名(params)` 来调用内核插件暴露的方法,无需关心 JSON-RPC 的细节
@@ -1316,3 +1314,27 @@ interface IKernelPluginRpc {
13161314
*/
13171315
unbind: (method: TJsonRpcMethod, handler: TJsonRpcHandler<void>) => void;
13181316
}
1317+
1318+
/**
1319+
* SiYuan URI 块信息接口,用于描述通过 SiYuan URI 协议传递的块信息
1320+
*/
1321+
interface ISiYuanUriBlockInfo {
1322+
/**
1323+
* 块 ID
1324+
*/
1325+
id: string;
1326+
1327+
/**
1328+
* 是否聚焦该块
1329+
*
1330+
* @defaultValue false
1331+
*/
1332+
focus: boolean;
1333+
1334+
/**
1335+
* 是否全屏显示该块
1336+
*
1337+
* @defaultValue false
1338+
*/
1339+
fullscreen: boolean;
1340+
}

0 commit comments

Comments
 (0)