mirror of
https://github.com/siyuan-note/siyuan.git
synced 2026-02-18 04:58:06 +01:00
PWA support (desktop & mobile) (#8012)
* 🎨 Improve return to feature of the auth page * 🎨 PWA support * Update manifest.webmanifest * 🎨 add `service-worker.js` * Update service-worker.js * Update service-worker.js * Update service-worker.js
This commit is contained in:
parent
e1d3789ced
commit
93e4bb1adf
17 changed files with 816 additions and 30 deletions
|
|
@ -6,6 +6,7 @@
|
|||
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, viewport-fit=cover, user-scalable=no">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
||||
<link rel="manifest" href="/manifest.webmanifest">
|
||||
<link rel="apple-touch-icon" href="../../icon.png">
|
||||
<style id="editorFontSize" type="text/css"></style>
|
||||
<style id="editorAttr" type="text/css"></style>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, height=device-height, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, viewport-fit=cover">
|
||||
<link rel="manifest" href="/manifest.webmanifest">
|
||||
<style id="editorFontSize" type="text/css"></style>
|
||||
</head>
|
||||
<body class="fn__flex-column">
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import {BlockPanel} from "./Panel";
|
||||
import {hasClosestBlock, hasClosestByAttribute, hasClosestByClassName} from "../protyle/util/hasClosest";
|
||||
import {fetchSyncPost} from "../util/fetch";
|
||||
import {getIdFromSiyuanUrl} from "../util/functions";
|
||||
import {hideTooltip, showTooltip} from "../dialog/tooltip";
|
||||
|
||||
let popoverTargetElement: HTMLElement;
|
||||
|
|
@ -199,7 +200,7 @@ export const showPopover = async () => {
|
|||
}
|
||||
} else if (popoverTargetElement.getAttribute("data-type")?.split(" ").includes("a")) {
|
||||
// 以思源协议开头的链接
|
||||
ids = [popoverTargetElement.getAttribute("data-href").substr(16, 22)];
|
||||
ids = [getIdFromSiyuanUrl(popoverTargetElement.getAttribute("data-href"))];
|
||||
} else {
|
||||
// pdf
|
||||
let targetId;
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import {renderSnippet} from "../config/util/snippets";
|
|||
import {openFileById} from "../editor/util";
|
||||
import {focusByRange} from "../protyle/util/selection";
|
||||
import {exitSiYuan} from "../dialog/processSystem";
|
||||
import {getSearch, isWindow} from "../util/functions";
|
||||
import {getSearch, isWindow, isSiyuanUrl, getIdFromSiyuanUrl} from "../util/functions";
|
||||
import {initStatus} from "../layout/status";
|
||||
import {showMessage} from "../dialog/message";
|
||||
import {replaceLocalPath} from "../editor/rename";
|
||||
|
|
@ -212,10 +212,10 @@ export const initWindow = () => {
|
|||
});
|
||||
if (!isWindow()) {
|
||||
ipcRenderer.on(Constants.SIYUAN_OPENURL, (event, url) => {
|
||||
if (!/^siyuan:\/\/blocks\/\d{14}-\w{7}/.test(url)) {
|
||||
if (!isSiyuanUrl(url)) {
|
||||
return;
|
||||
}
|
||||
const id = url.substr(16, 22);
|
||||
const id = getIdFromSiyuanUrl(url);
|
||||
fetchPost("/api/block/checkBlockExist", {id}, existResponse => {
|
||||
if (existResponse.data) {
|
||||
openFileById({
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ export abstract class Constants {
|
|||
public static readonly ASSETS_ADDRESS: string = "https://assets.b3logfile.com/siyuan/";
|
||||
public static readonly PROTYLE_CDN: string = "/stage/protyle";
|
||||
public static readonly UPLOAD_ADDRESS: string = "/upload";
|
||||
public static readonly SERVICE_WORKER_PATH: string = "/service-worker.js";
|
||||
|
||||
// drop 事件
|
||||
public static readonly SIYUAN_DROP_FILE: string = "application/siyuan-file";
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import {getCurrentWindow} from "@electron/remote";
|
|||
/// #endif
|
||||
import {hideMessage, showMessage} from "./message";
|
||||
import {Dialog} from "./index";
|
||||
import {isMobile} from "../util/functions";
|
||||
import {isMobile, redirectToCheckAuth} from "../util/functions";
|
||||
import {confirmDialog} from "./confirmDialog";
|
||||
import {escapeHtml} from "../util/escape";
|
||||
import {getWorkspaceName} from "../util/noRelyPCFunction";
|
||||
|
|
@ -21,7 +21,7 @@ export const lockScreen = () => {
|
|||
}
|
||||
/// #if BROWSER
|
||||
fetchPost("/api/system/logoutAuth", {}, () => {
|
||||
window.location.href = `/check-auth?url=${window.location.href}`;
|
||||
redirectToCheckAuth();
|
||||
});
|
||||
/// #else
|
||||
ipcRenderer.send(Constants.SIYUAN_SEND_WINDOWS, {cmd: "lockscreen"});
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import {addScript, addScriptSync} from "./protyle/util/addScript";
|
|||
import {genUUID} from "./util/genID";
|
||||
import {fetchGet, fetchPost} from "./util/fetch";
|
||||
import {addBaseURL, setNoteBook} from "./util/pathName";
|
||||
import {registerServiceWorker} from "./util/serviceWorker";
|
||||
import {openFileById} from "./editor/util";
|
||||
import {
|
||||
bootSync,
|
||||
|
|
@ -25,11 +26,18 @@ import {resizeDrag} from "./layout/util";
|
|||
import {getAllTabs} from "./layout/getAll";
|
||||
import {getLocalStorage} from "./protyle/util/compatibility";
|
||||
import {updateEditModeElement} from "./layout/topBar";
|
||||
import {getSearch} from "./util/functions";
|
||||
import {getSearch, isSiyuanUrl, getIdFromSiyuanUrl} from "./util/functions";
|
||||
import {hideAllElements} from "./protyle/ui/hideElements";
|
||||
|
||||
class App {
|
||||
constructor() {
|
||||
/// #if BROWSER
|
||||
registerServiceWorker(`${Constants.SERVICE_WORKER_PATH}?v=${Constants.SIYUAN_VERSION}`);
|
||||
/// #endif
|
||||
/// #if MOBILE
|
||||
registerServiceWorker(`${Constants.SERVICE_WORKER_PATH}?v=${Constants.SIYUAN_VERSION}`);
|
||||
/// #endif
|
||||
|
||||
addScriptSync(`${Constants.PROTYLE_CDN}/js/lute/lute.min.js?v=${Constants.SIYUAN_VERSION}`, "protyleLuteScript");
|
||||
addScript(`${Constants.PROTYLE_CDN}/js/protyle-html.js?v=${Constants.SIYUAN_VERSION}`, "protyleWcHtmlScript");
|
||||
addBaseURL();
|
||||
|
|
@ -172,9 +180,9 @@ class App {
|
|||
|
||||
new App();
|
||||
window.openFileByURL = (openURL) => {
|
||||
if (openURL && /^siyuan:\/\/blocks\/\d{14}-\w{7}/.test(openURL)) {
|
||||
if (openURL && isSiyuanUrl(openURL)) {
|
||||
openFileById({
|
||||
id: openURL.substr(16, 22),
|
||||
id: getIdFromSiyuanUrl(openURL),
|
||||
action:getSearch("focus", openURL) === "1" ? [Constants.CB_GET_ALL, Constants.CB_GET_FOCUS] : [Constants.CB_GET_FOCUS, Constants.CB_GET_CONTEXT]
|
||||
});
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ import {saveScroll} from "../protyle/scroll/saveScroll";
|
|||
import {pdfResize} from "../asset/renderAssets";
|
||||
import {Backlink} from "./dock/Backlink";
|
||||
import {openFileById} from "../editor/util";
|
||||
import {getSearch, isWindow} from "../util/functions";
|
||||
import {getSearch, isWindow, isSiyuanUrl, isWebSiyuanUrl, getIdFromSiyuanUrl, getIdFromWebSiyuanUrl} from "../util/functions";
|
||||
import {showMessage} from "../dialog/message";
|
||||
import {setTabPosition} from "../window/setHeader";
|
||||
|
||||
|
|
@ -365,15 +365,35 @@ export const JSONToLayout = (isStart: boolean) => {
|
|||
});
|
||||
}
|
||||
|
||||
// PWA 捕获 siyuan://
|
||||
const searchParams = new URLSearchParams(window.location.search);
|
||||
const url = searchParams.get("url");
|
||||
if (isSiyuanUrl(url) || isWebSiyuanUrl(url)) {
|
||||
searchParams.delete("url");
|
||||
switch (true) {
|
||||
case isSiyuanUrl(url):
|
||||
searchParams.set("id", getIdFromSiyuanUrl(url));
|
||||
break;
|
||||
case isWebSiyuanUrl(url):
|
||||
searchParams.set("id", getIdFromWebSiyuanUrl(url));
|
||||
break;
|
||||
}
|
||||
|
||||
const focus = getSearch("focus", url);
|
||||
if (focus) {
|
||||
searchParams.set("focus", focus);
|
||||
}
|
||||
}
|
||||
|
||||
// 支持通过 URL 查询字符串参数 `id` 和 `focus` 跳转到 Web 端指定块 https://github.com/siyuan-note/siyuan/pull/7086
|
||||
const openId = getSearch("id");
|
||||
const openId = searchParams.get("id");
|
||||
if (openId) {
|
||||
// 启动时 layout 中有该文档,该文档还原会在此之后,因此需有延迟
|
||||
setTimeout(() => {
|
||||
openFileById({
|
||||
id: openId,
|
||||
action: [Constants.CB_GET_FOCUS, Constants.CB_GET_CONTEXT],
|
||||
zoomIn: getSearch("focus") === "1"
|
||||
zoomIn: searchParams.get("focus") === "1"
|
||||
});
|
||||
}, Constants.TIMEOUT_BLOCKLOAD);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import {goBack} from "./util/MobileBackFoward";
|
|||
import {hideKeyboardToolbar, showKeyboardToolbar} from "./util/keyboardToolbar";
|
||||
import {getLocalStorage} from "../protyle/util/compatibility";
|
||||
import {openMobileFileById} from "./editor";
|
||||
import {getSearch} from "../util/functions";
|
||||
import {getSearch, isSiyuanUrl, getIdFromSiyuanUrl} from "../util/functions";
|
||||
import {initRightMenu} from "./menu";
|
||||
import {openChangelog} from "../boot/openChangelog";
|
||||
|
||||
|
|
@ -90,8 +90,8 @@ window.showKeyboardToolbar = (height) => {
|
|||
};
|
||||
window.hideKeyboardToolbar = hideKeyboardToolbar;
|
||||
window.openFileByURL = (openURL) => {
|
||||
if (openURL && /^siyuan:\/\/blocks\/\d{14}-\w{7}/.test(openURL)) {
|
||||
openMobileFileById(openURL.substr(16, 22),
|
||||
if (openURL && isSiyuanUrl(openURL)) {
|
||||
openMobileFileById(getIdFromSiyuanUrl(openURL),
|
||||
getSearch("focus", openURL) === "1" ? [Constants.CB_GET_ALL, Constants.CB_GET_FOCUS] : [Constants.CB_GET_FOCUS, Constants.CB_GET_CONTEXT]);
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,3 +46,27 @@ export const isFileAnnotation = (text: string) => {
|
|||
export const looseJsonParse = (text: string) => {
|
||||
return Function(`"use strict";return (${text})`)();
|
||||
};
|
||||
|
||||
/* redirect to auth page */
|
||||
export const redirectToCheckAuth = (to: string = window.location.href) => {
|
||||
const url = new URL(window.location.origin);
|
||||
url.pathname = '/check-auth';
|
||||
url.searchParams.set('to', to);
|
||||
window.location.href = url.href;
|
||||
}
|
||||
|
||||
export const isSiyuanUrl = (url: string) => {
|
||||
return /^siyuan:\/\/blocks\/\d{14}-\w{7}/.test(url);
|
||||
}
|
||||
|
||||
export const isWebSiyuanUrl = (url: string) => {
|
||||
return /^web\+siyuan:\/\/blocks\/\d{14}-\w{7}/.test(url);
|
||||
}
|
||||
|
||||
export const getIdFromSiyuanUrl = (url: string) => {
|
||||
return url.substring(16, 16 + 22);
|
||||
}
|
||||
|
||||
export const getIdFromWebSiyuanUrl = (url: string) => {
|
||||
return url.substring(20, 20 + 22);
|
||||
}
|
||||
|
|
|
|||
32
app/src/util/serviceWorker.ts
Normal file
32
app/src/util/serviceWorker.ts
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
// https://github.com/siyuan-note/siyuan/pull/8012
|
||||
export const registerServiceWorker = (scriptURL: string) => {
|
||||
if (window.navigator.serviceWorker) {
|
||||
// REF https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration
|
||||
window.navigator.serviceWorker
|
||||
.register(scriptURL, {
|
||||
scope: "./",
|
||||
type: "module",
|
||||
}).then(registration => {
|
||||
if (registration.installing) {
|
||||
console.debug("Service worker installing");
|
||||
} else if (registration.waiting) {
|
||||
console.debug("Service worker installed");
|
||||
} else if (registration.active) {
|
||||
console.debug("Service worker active");
|
||||
}
|
||||
registration.update();
|
||||
}).catch(e => {
|
||||
console.debug(`Registration failed with ${e}`);
|
||||
});
|
||||
|
||||
// REF https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerGlobalScope/message_event
|
||||
window.navigator.serviceWorker.addEventListener("message", event => {
|
||||
// event is a MessageEvent object
|
||||
console.debug("client: onmessage", event);
|
||||
});
|
||||
|
||||
window.navigator.serviceWorker.ready.then(registration => {
|
||||
registration.active.postMessage("client: post message");
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
import {exportLayout, getInstanceById} from "../layout/util";
|
||||
import {Tab} from "../layout/Tab";
|
||||
import {fetchPost} from "../util/fetch";
|
||||
import {redirectToCheckAuth} from "../util/functions";
|
||||
|
||||
const closeTab = (ipcData: IWebSocketData) => {
|
||||
const tab = getInstanceById(ipcData.data);
|
||||
|
|
@ -16,7 +17,7 @@ export const onWindowsMsg = (ipcData: IWebSocketData) => {
|
|||
case "lockscreen":
|
||||
exportLayout(false, () => {
|
||||
fetchPost("/api/system/logoutAuth", {}, () => {
|
||||
window.location.href = `/check-auth?url=${window.location.href}`;
|
||||
redirectToCheckAuth();
|
||||
});
|
||||
}, false, false);
|
||||
break;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue