siyuan/app/src/protyle/util/compatibility.ts

429 lines
15 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import {focusByRange} from "./selection";
import {fetchPost} from "../../util/fetch";
import {Constants} from "../../constants";
export const openByMobile = (uri: string) => {
if (!uri) {
return;
}
if (isInIOS()) {
if (uri.startsWith("assets/")) {
// iOS 16.7 之前的版本uri 需要 encodeURIComponent
window.webkit.messageHandlers.openLink.postMessage(location.origin + "/assets/" + encodeURIComponent(uri.replace("assets/", "")));
} else if (uri.startsWith("/")) {
// 导出 zip 返回的是已经 encode 过的,因此不能再 encode
window.webkit.messageHandlers.openLink.postMessage(location.origin + uri);
} else {
try {
new URL(uri);
window.webkit.messageHandlers.openLink.postMessage(uri);
} catch (e) {
window.webkit.messageHandlers.openLink.postMessage("https://" + uri);
}
}
} else if (isInAndroid()) {
window.JSAndroid.openExternal(uri);
} else if (isInHarmony()) {
window.JSHarmony.openExternal(uri);
} else {
window.open(uri);
}
};
export const exportByMobile = (uri: string) => {
if (!uri) {
return;
}
if (isInIOS()) {
openByMobile(uri);
} else if (isInAndroid()) {
window.JSAndroid.exportByDefault(uri);
} else if (isInHarmony()) {
window.JSHarmony.exportByDefault(uri);
} else {
window.open(uri);
}
};
export const readText = () => {
if (isInAndroid()) {
return window.JSAndroid.readClipboard();
} else if (isInHarmony()) {
return window.JSHarmony.readClipboard();
}
return navigator.clipboard.readText();
};
export const readClipboard = async () => {
const text: {
textHTML?: string,
textPlain?: string,
files?: File[],
} = {textPlain: "", textHTML: ""};
try {
const clipboardContents = await navigator.clipboard.read();
for (const item of clipboardContents) {
if (item.types.includes("text/html")) {
const blob = await item.getType("text/html");
text.textHTML = await blob.text();
}
if (item.types.includes("text/plain")) {
const blob = await item.getType("text/plain");
text.textPlain = await blob.text();
}
if (item.types.includes("image/png")) {
const blob = await item.getType("image/png");
text.files = [new File([blob], "image.png", {type: "image/png", lastModified: Date.now()})];
}
}
return text;
} catch (e) {
if (isInAndroid()) {
text.textPlain = window.JSAndroid.readClipboard();
text.textHTML = window.JSAndroid.readHTMLClipboard();
} else if (isInHarmony()) {
text.textPlain = window.JSHarmony.readClipboard();
text.textHTML = window.JSHarmony.readHTMLClipboard();
}
return text;
}
};
export const writeText = (text: string) => {
let range: Range;
if (getSelection().rangeCount > 0) {
range = getSelection().getRangeAt(0).cloneRange();
}
try {
// navigator.clipboard.writeText 抛出异常不进入 catch这里需要先处理移动端复制
if (isInAndroid()) {
window.JSAndroid.writeClipboard(text);
return;
}
if (isInHarmony()) {
window.JSHarmony.writeClipboard(text);
return;
}
if (isInIOS()) {
window.webkit.messageHandlers.setClipboard.postMessage(text);
return;
}
navigator.clipboard.writeText(text);
} catch (e) {
if (isInIOS()) {
window.webkit.messageHandlers.setClipboard.postMessage(text);
} else if (isInAndroid()) {
window.JSAndroid.writeClipboard(text);
} else if (isInHarmony()) {
window.JSHarmony.writeClipboard(text);
} else {
const textElement = document.createElement("textarea");
textElement.value = text;
textElement.style.position = "fixed"; //avoid scrolling to bottom
document.body.appendChild(textElement);
textElement.focus();
textElement.select();
document.execCommand("copy");
document.body.removeChild(textElement);
if (range) {
focusByRange(range);
}
}
}
};
export const copyPlainText = async (text: string) => {
text = text.replace(new RegExp(Constants.ZWSP, "g"), ""); // `复制纯文本` 时移除所有零宽空格 https://github.com/siyuan-note/siyuan/issues/6674
await writeText(text);
};
// 用户 iPhone 点击延迟/需要双击的处理
export const getEventName = () => {
if (isIPhone()) {
return "touchstart";
} else {
return "click";
}
};
export const isOnlyMeta = (event: KeyboardEvent | MouseEvent) => {
if (isMac()) {
// mac
if (event.metaKey && !event.ctrlKey) {
return true;
}
return false;
} else {
if (!event.metaKey && event.ctrlKey) {
return true;
}
return false;
}
};
export const isNotCtrl = (event: KeyboardEvent | MouseEvent) => {
if (!event.metaKey && !event.ctrlKey) {
return true;
}
return false;
};
export const isHuawei = () => {
return window.siyuan.config.system.osPlatform.toLowerCase().indexOf("huawei") > -1;
};
export const isDisabledFeature = (feature: string): boolean => {
return window.siyuan.config.system.disabledFeatures?.indexOf(feature) > -1;
};
export const isIPhone = () => {
return navigator.userAgent.indexOf("iPhone") > -1;
};
export const isSafari = () => {
const userAgent = navigator.userAgent;
return userAgent.includes("Safari") && !userAgent.includes("Chrome") && !userAgent.includes("Chromium");
};
export const isIPad = () => {
return navigator.userAgent.indexOf("iPad") > -1;
};
export const isMac = () => {
return navigator.platform.toUpperCase().indexOf("MAC") > -1;
};
export const isWin11 = async () => {
if (!(navigator as any).userAgentData || !(navigator as any).userAgentData.getHighEntropyValues) {
return false;
}
const ua = await (navigator as any).userAgentData.getHighEntropyValues(["platformVersion"]);
if ((navigator as any).userAgentData.platform === "Windows") {
if (parseInt(ua.platformVersion.split(".")[0]) >= 13) {
return true;
}
}
return false;
};
export const isWindows = () => {
return navigator.platform.toUpperCase().indexOf("WIN") > -1;
};
export const isInAndroid = () => {
return window.siyuan.config.system.container === "android" && window.JSAndroid;
};
export const isInIOS = () => {
return window.siyuan.config.system.container === "ios" && window.webkit?.messageHandlers;
};
export const isInHarmony = () => {
return window.siyuan.config.system.container === "harmony" && window.JSHarmony;
};
export const updateHotkeyAfterTip = (hotkey: string) => {
if (hotkey) {
return " " + updateHotkeyTip(hotkey);
}
return "";
};
// MacWindows 快捷键展示
export const updateHotkeyTip = (hotkey: string) => {
if (isMac()) {
return hotkey;
}
const KEY_MAP = new Map(Object.entries({
"⌘": "Ctrl",
"⌃": "Ctrl",
"⇧": "Shift",
"⌥": "Alt",
"⇥": "Tab",
"⌫": "Backspace",
"⌦": "Delete",
"↩": "Enter",
}));
const keys = [];
if ((hotkey.indexOf("⌘") > -1 || hotkey.indexOf("⌃") > -1)) keys.push(KEY_MAP.get("⌘"));
if (hotkey.indexOf("⇧") > -1) keys.push(KEY_MAP.get("⇧"));
if (hotkey.indexOf("⌥") > -1) keys.push(KEY_MAP.get("⌥"));
// 不能去最后一个,需匹配 F2
const lastKey = hotkey.replace(/⌘|⇧|⌥|⌃/g, "");
if (lastKey) {
keys.push(KEY_MAP.get(lastKey) || lastKey);
}
return keys.join("+");
};
export const getLocalStorage = (cb: () => void) => {
fetchPost("/api/storage/getLocalStorage", undefined, (response) => {
window.siyuan.storage = response.data;
// 历史数据迁移
const defaultStorage: any = {};
defaultStorage[Constants.LOCAL_SEARCHASSET] = {
keys: [],
col: "",
row: "",
layout: 0,
method: 0,
types: {},
sort: 0,
k: "",
};
defaultStorage[Constants.LOCAL_SEARCHUNREF] = {
col: "",
row: "",
layout: 0,
};
Constants.SIYUAN_ASSETS_SEARCH.forEach(type => {
defaultStorage[Constants.LOCAL_SEARCHASSET].types[type] = true;
});
defaultStorage[Constants.LOCAL_SEARCHKEYS] = {
keys: [],
replaceKeys: [],
col: "",
row: "",
layout: 0,
colTab: "",
rowTab: "",
layoutTab: 0
};
defaultStorage[Constants.LOCAL_PDFTHEME] = {
light: "light",
dark: "dark",
annoColor: "var(--b3-pdf-background1)"
};
defaultStorage[Constants.LOCAL_LAYOUTS] = []; // {name: "", layout:{}, time: number, filespaths: IFilesPath[]}
defaultStorage[Constants.LOCAL_AI] = []; // {name: "", memo: ""}
defaultStorage[Constants.LOCAL_PLUGIN_DOCKS] = {}; // { pluginName: {dockId: IPluginDockTab}}
defaultStorage[Constants.LOCAL_PLUGINTOPUNPIN] = [];
defaultStorage[Constants.LOCAL_OUTLINE] = {keepExpand: true};
defaultStorage[Constants.LOCAL_FILEPOSITION] = {}; // {id: IScrollAttr}
defaultStorage[Constants.LOCAL_DIALOGPOSITION] = {}; // {id: IPosition}
defaultStorage[Constants.LOCAL_HISTORY] = {
notebookId: "%",
type: 0,
operation: "all",
sideWidth: "256px",
sideDocWidth: "256px",
sideDiffWidth: "256px",
};
defaultStorage[Constants.LOCAL_FLASHCARD] = {
fullscreen: false
};
defaultStorage[Constants.LOCAL_BAZAAR] = {
theme: "0",
template: "0",
icon: "0",
widget: "0",
};
defaultStorage[Constants.LOCAL_EXPORTWORD] = {removeAssets: false, mergeSubdocs: false};
defaultStorage[Constants.LOCAL_EXPORTPDF] = {
landscape: false,
marginType: "0",
scale: 1,
pageSize: "A4",
removeAssets: true,
keepFold: false,
mergeSubdocs: false,
watermark: false
};
defaultStorage[Constants.LOCAL_EXPORTIMG] = {
keepFold: false,
watermark: false
};
defaultStorage[Constants.LOCAL_DOCINFO] = {
id: "",
};
defaultStorage[Constants.LOCAL_IMAGES] = {
file: "1f4c4",
note: "1f5c3",
folder: "1f4d1"
};
defaultStorage[Constants.LOCAL_EMOJIS] = {
currentTab: "emoji"
};
defaultStorage[Constants.LOCAL_FONTSTYLES] = [];
defaultStorage[Constants.LOCAL_FILESPATHS] = []; // IFilesPath[]
defaultStorage[Constants.LOCAL_SEARCHDATA] = {
page: 1,
sort: 0,
group: 0,
hasReplace: false,
method: 0,
hPath: "",
idPath: [],
k: "",
r: "",
types: {
document: window.siyuan.config.search.document,
heading: window.siyuan.config.search.heading,
list: window.siyuan.config.search.list,
listItem: window.siyuan.config.search.listItem,
codeBlock: window.siyuan.config.search.codeBlock,
htmlBlock: window.siyuan.config.search.htmlBlock,
mathBlock: window.siyuan.config.search.mathBlock,
table: window.siyuan.config.search.table,
blockquote: window.siyuan.config.search.blockquote,
superBlock: window.siyuan.config.search.superBlock,
paragraph: window.siyuan.config.search.paragraph,
embedBlock: window.siyuan.config.search.embedBlock,
databaseBlock: window.siyuan.config.search.databaseBlock,
},
replaceTypes: Object.assign({}, Constants.SIYUAN_DEFAULT_REPLACETYPES),
};
defaultStorage[Constants.LOCAL_ZOOM] = 1;
defaultStorage[Constants.LOCAL_MOVE_PATH] = {keys: [], k: ""};
[Constants.LOCAL_EXPORTIMG, Constants.LOCAL_SEARCHKEYS, Constants.LOCAL_PDFTHEME, Constants.LOCAL_BAZAAR,
Constants.LOCAL_EXPORTWORD, Constants.LOCAL_EXPORTPDF, Constants.LOCAL_DOCINFO, Constants.LOCAL_FONTSTYLES,
Constants.LOCAL_SEARCHDATA, Constants.LOCAL_ZOOM, Constants.LOCAL_LAYOUTS, Constants.LOCAL_AI,
Constants.LOCAL_PLUGINTOPUNPIN, Constants.LOCAL_SEARCHASSET, Constants.LOCAL_FLASHCARD,
Constants.LOCAL_DIALOGPOSITION, Constants.LOCAL_SEARCHUNREF, Constants.LOCAL_HISTORY,
Constants.LOCAL_OUTLINE, Constants.LOCAL_FILEPOSITION, Constants.LOCAL_FILESPATHS, Constants.LOCAL_IMAGES,
Constants.LOCAL_PLUGIN_DOCKS, Constants.LOCAL_EMOJIS, Constants.LOCAL_MOVE_PATH].forEach((key) => {
if (typeof response.data[key] === "string") {
try {
const parseData = JSON.parse(response.data[key]);
if (typeof parseData === "number") {
// https://github.com/siyuan-note/siyuan/issues/8852 Object.assign 会导致 number to Number
window.siyuan.storage[key] = parseData;
} else {
window.siyuan.storage[key] = Object.assign(defaultStorage[key], parseData);
}
} catch (e) {
window.siyuan.storage[key] = defaultStorage[key];
}
} else if (typeof response.data[key] === "undefined") {
window.siyuan.storage[key] = defaultStorage[key];
}
});
// 搜索数据添加 replaceTypes 兼容
if (!window.siyuan.storage[Constants.LOCAL_SEARCHDATA].replaceTypes ||
Object.keys(window.siyuan.storage[Constants.LOCAL_SEARCHDATA].replaceTypes).length === 0) {
window.siyuan.storage[Constants.LOCAL_SEARCHDATA].replaceTypes = Object.assign({}, Constants.SIYUAN_DEFAULT_REPLACETYPES);
}
cb();
});
};
export const setStorageVal = (key: string, val: any, cb?: () => void) => {
if (window.siyuan.config.readonly) {
return;
}
fetchPost("/api/storage/setLocalStorageVal", {
app: Constants.SIYUAN_APPID,
key,
val,
}, () => {
if (cb) {
cb();
}
});
};