Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 78 additions & 53 deletions app/src/editor/openLink.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {getIdFromSYProtocol, isLocalPath, isSYProtocol, pathPosix} from "../util/pathName";
import {isLocalPath, parseSYProtocolBlockInfo, pathPosix} from "../util/pathName";
/// #if !BROWSER
import {shell, ipcRenderer} from "electron";
/// #endif
Expand All @@ -14,57 +14,12 @@ import {fetchPost} from "../util/fetch";
import {checkFold} from "../util/noRelyPCFunction";
import {openMobileFileById} from "../mobile/editor";

export const processSYLink = (app: App, url: string) => {
let urlObj: URL;
try {
urlObj = new URL(url);
if (urlObj.protocol !== "siyuan:") {
return false;
}
} catch (error) {
return false;
}
if (urlObj && urlObj.hostname === "plugins") {
const pluginNameType = urlObj.pathname.split("/")[1];
if (!pluginNameType) {
return false;
}
app.plugins.find(plugin => {
if (pluginNameType.startsWith(plugin.name)) {
// siyuan://plugins/plugin-name/foo?bar=baz
plugin.eventBus.emit("open-siyuan-url-plugin", {url});

/// #if !MOBILE
// https://github.com/siyuan-note/siyuan/pull/9256
if (pluginNameType.split("/")[0] !== plugin.name) {
// siyuan://plugins/plugin-samplecustom_tab?title=自定义页签&icon=iconFace&data={"text": "This is the custom plugin tab I opened via protocol."}
let data = urlObj.searchParams.get("data");
try {
data = JSON.parse(data || "{}");
} catch (e) {
console.log("Error open plugin tab with protocol:", e);
}
openFile({
app,
custom: {
title: urlObj.searchParams.get("title"),
icon: urlObj.searchParams.get("icon"),
data,
id: pluginNameType
},
});
}
/// #endif
return true;
}
});
return true;
}
if (urlObj && isSYProtocol(url)) {
const id = getIdFromSYProtocol(url);
const focus = urlObj.searchParams.get("focus") === "1";
window.siyuan.editorIsFullscreen = urlObj.searchParams.get("fullscreen") === "1";
fetchPost("/api/block/checkBlockExist", {id}, existResponse => {
export const processSYLinkBlocks = (app: App, urlObj: URL): boolean => {
const blockInfo = parseSYProtocolBlockInfo(urlObj);
if (blockInfo != null) {
const {id, focus} = blockInfo;
window.siyuan.editorIsFullscreen = blockInfo.fullscreen;
fetchPost("/api/block/checkBlockExist", { id }, existResponse => {
if (existResponse.data) {
checkFold(id, (zoomIn) => {
/// #if !MOBILE
Expand All @@ -84,7 +39,7 @@ export const processSYLink = (app: App, url: string) => {
}
app.plugins.forEach(plugin => {
plugin.eventBus.emit("open-siyuan-url-block", {
url,
url: urlObj.href,
id,
focus,
exist: existResponse.data,
Expand All @@ -96,6 +51,76 @@ export const processSYLink = (app: App, url: string) => {
return false;
};

export const processSYLinkPlugins = (app: App, urlObj: URL): boolean => {
const pluginNameOrTabType: string | null = (() => {
const name = urlObj.pathname.split("/")[1];
if (!name) {
return null;
}
try {
return decodeURIComponent(name);
} catch (error) {
return null;
}
})();

if (!pluginNameOrTabType) {
return false;
}

const plugin = app.plugins.find(plugin => pluginNameOrTabType === plugin.name);
if (plugin) {
// siyuan://plugins/plugin-name/foo?bar=baz
plugin.eventBus.emit("open-siyuan-url-plugin", {url: urlObj.href});
} else {
// siyuan://plugins/plugin-samplecustom_tab?title=自定义页签&icon=iconFace&data={"text": "This is the custom plugin tab I opened via protocol."}
/// #if !MOBILE
Comment thread
Zuoqiu-Yingyi marked this conversation as resolved.
// https://github.com/siyuan-note/siyuan/pull/9256
const data = (() => {
try {
return JSON.parse(urlObj.searchParams.get("data") || "{}");
} catch (e) {
console.log("Error open plugin tab with protocol:", e);
return undefined;
}
})();
// id 不存在时无副作用
openFile({
app,
custom: {
title: urlObj.searchParams.get("title") ?? pluginNameOrTabType,
icon: urlObj.searchParams.get("icon") ?? "iconPlugin",
data,
id: pluginNameOrTabType
},
});
/// #endif
}
return true;
};

export const processSYLink = (app: App, url: string) => {
let urlObj: URL;
try {
urlObj = new URL(url);
if (urlObj.protocol !== "siyuan:" && urlObj.protocol !== "web+siyuan:") {
return false;
}
} catch (error) {
return false;
}
switch (urlObj.hostname) {
case "blocks":
return processSYLinkBlocks(app, urlObj);
case "plugins":
return processSYLinkPlugins(app, urlObj);
default:
break;
}
return false;
};


export const openLink = (protyle: IProtyle, aLink: string, event?: MouseEvent, ctrlIsPressed = false) => {
let linkAddress = Lute.UnEscapeHTMLStr(aLink);
let pdfParams;
Expand Down
26 changes: 24 additions & 2 deletions app/src/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1257,8 +1257,6 @@ interface IKernelPluginRpcError {
data?: any;
}



interface IKernelPluginRpc {
/**
* 通过 {@link Proxy} 实现的动态方法调用,插件开发者可以直接调用 `call.方法名(params)` 来调用内核插件暴露的方法,无需关心 JSON-RPC 的细节
Expand All @@ -1285,3 +1283,27 @@ interface IKernelPluginRpc {
*/
unbind: (method: TJsonRpcMethod, handler: TJsonRpcHandler<void>) => void;
}

/**
* SY 协议块信息接口,用于描述通过 SY 协议传递的块信息
*/
interface ISYProtocolBlocksInfo {
/**
* 块 ID
*/
id: string;

/**
* 是否聚焦该块
*
* @defaultValue false
*/
focus: boolean;

/**
* 是否全屏显示该块
*
* @defaultValue false
*/
fullscreen: boolean;
}
67 changes: 50 additions & 17 deletions app/src/util/pathName.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,30 +26,63 @@ export const useShell = (cmd: "showItemInFolder" | "openPath", filePath: string)
/// #endif
};

/**
* parse siyuan://blocks/20221031001313-rk7sd0e?focus=1&fullscreen=1
* @param url - the siyuan block url to parse
* @return the block id and other info, or null if the url is not a valid siyuan block url
*/
Comment thread
Zuoqiu-Yingyi marked this conversation as resolved.
export const parseSYProtocolBlockInfo = (url: URL | string | null | undefined): ISYProtocolBlocksInfo | null => {
try {
if (url == null) return null;

const urlObj = new URL(url);
if (urlObj.protocol !== "siyuan:" && urlObj.protocol !== "web+siyuan:") {
return null;
}
if (urlObj.hostname === "blocks" && /^\/\d{14}-\w{7}/.test(urlObj.pathname)) {
return {
id: urlObj.pathname.substring(1, 1 + 22),
focus: urlObj.searchParams.get("focus") === "1",
fullscreen: urlObj.searchParams.get("fullscreen") === "1",
};
}
return null;
} catch (error) {
return null;
}
};

export const getIdZoomInByPath = () => {
const searchParams = new URLSearchParams(window.location.search);
const PWAURL = searchParams.get("url");
const data = {
id: "",
isZoomIn: false,
};
if (/^web\+siyuan:\/\/blocks\/\d{14}-\w{7}/.test(PWAURL)) {
// PWA 捕获 web+siyuan://blocks/20221031001313-rk7sd0e?focus=1
data.id = PWAURL.substring(20, 20 + 22);
data.isZoomIn = getSearch("focus", PWAURL) === "1";
window.siyuan.editorIsFullscreen = getSearch("fullscreen", PWAURL) === "1";
} else if (window.JSAndroid) {
// PAD 通过思源协议打开
const SYURL = window.JSAndroid.getBlockURL();
data.id = getIdFromSYProtocol(SYURL);
data.isZoomIn = getSearch("focus", SYURL) === "1";
window.siyuan.editorIsFullscreen = getSearch("fullscreen", SYURL) === "1";
} else {
// 支持通过 URL 查询字符串参数 `id` 和 `focus` 跳转到 Web 端指定块 https://github.com/siyuan-note/siyuan/pull/7086
data.id = searchParams.get("id");
data.isZoomIn = searchParams.get("focus") === "1";
window.siyuan.editorIsFullscreen = searchParams.get("fullscreen") === "1";

if (searchParams.has("url")) {
const dataInfo = parseSYProtocolBlockInfo(searchParams.get("url"));
if (dataInfo != null) {
data.id = dataInfo.id;
data.isZoomIn = dataInfo.focus;
window.siyuan.editorIsFullscreen = dataInfo.fullscreen;
return data;
}
}

if (window.JSAndroid) {
const dataInfo = parseSYProtocolBlockInfo(window.JSAndroid.getBlockURL());
if (dataInfo != null) {
data.id = dataInfo.id;
data.isZoomIn = dataInfo.focus;
window.siyuan.editorIsFullscreen = dataInfo.fullscreen;
return data;
}
}

// 支持通过 URL 查询字符串参数 `id` 和 `focus` 跳转到 Web 端指定块 https://github.com/siyuan-note/siyuan/pull/7086
data.id = searchParams.get("id") ?? "";
data.isZoomIn = searchParams.get("focus") === "1";
window.siyuan.editorIsFullscreen = searchParams.get("fullscreen") === "1";
return data;
};

Expand Down