import {Constants} from "../constants";
import {addScript} from "../protyle/util/addScript";
import {addStyle} from "../protyle/util/addStyle";
/// #if !MOBILE
import {getAllModels} from "../layout/getAll";
import {exportLayout} from "../layout/util";
/// #endif
import {fetchPost} from "./fetch";
import {isInAndroid, isInHarmony, isInIOS, isIPad, isIPhone, isMac, isWin11} from "../protyle/util/compatibility";
export const loadAssets = (data: Config.IAppearance) => {
const htmlElement = document.getElementsByTagName("html")[0];
htmlElement.setAttribute("lang", window.siyuan.config.appearance.lang);
htmlElement.setAttribute("data-theme-mode", getThemeMode());
htmlElement.setAttribute("data-light-theme", window.siyuan.config.appearance.themeLight);
htmlElement.setAttribute("data-dark-theme", window.siyuan.config.appearance.themeDark);
const OSTheme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
if (window.siyuan.config.appearance.modeOS && (
(window.siyuan.config.appearance.mode === 1 && OSTheme === "light") ||
(window.siyuan.config.appearance.mode === 0 && OSTheme === "dark")
)) {
fetchPost("/api/system/setAppearanceMode", {mode: OSTheme === "light" ? 0 : 1});
window.siyuan.config.appearance.mode = (OSTheme === "light" ? 0 : 1);
}
const defaultStyleElement = document.getElementById("themeDefaultStyle");
const defaultThemeAddress = `/appearance/themes/${data.mode === 1 ? "midnight" : "daylight"}/theme.css?v=${Constants.SIYUAN_VERSION}`;
if (defaultStyleElement) {
if (!defaultStyleElement.getAttribute("href").startsWith(defaultThemeAddress)) {
const newStyleElement = document.createElement("link");
// 等待新样式表加载完成再移除旧样式表
new Promise((resolve) => {
newStyleElement.rel = "stylesheet";
newStyleElement.href = defaultThemeAddress;
newStyleElement.onload = resolve;
defaultStyleElement.parentNode.insertBefore(newStyleElement, defaultStyleElement);
}).then(() => {
defaultStyleElement.remove();
newStyleElement.id = "themeDefaultStyle";
});
}
} else {
addStyle(defaultThemeAddress, "themeDefaultStyle");
}
const styleElement = document.getElementById("themeStyle");
if ((data.mode === 1 && data.themeDark !== "midnight") || (data.mode === 0 && data.themeLight !== "daylight")) {
const themeAddress = `/appearance/themes/${data.mode === 1 ? data.themeDark : data.themeLight}/theme.css?v=${data.themeVer}`;
if (styleElement) {
if (!styleElement.getAttribute("href").startsWith(themeAddress)) {
styleElement.setAttribute("href", themeAddress);
}
} else {
addStyle(themeAddress, "themeStyle");
}
} else if (styleElement) {
styleElement.remove();
}
/// #if !MOBILE
getAllModels().graph.forEach(item => {
item.searchGraph(false);
});
const pdfTheme = window.siyuan.config.appearance.mode === 0 ? window.siyuan.storage[Constants.LOCAL_PDFTHEME].light :
window.siyuan.storage[Constants.LOCAL_PDFTHEME].dark;
document.querySelectorAll(".pdf__outer").forEach(item => {
const darkElement = item.querySelector("#pdfDark");
const lightElement = item.querySelector("#pdfLight");
if (pdfTheme === "dark") {
item.classList.add("pdf__outer--dark");
lightElement.classList.remove("toggled");
darkElement.classList.add("toggled");
} else {
item.classList.remove("pdf__outer--dark");
lightElement.classList.add("toggled");
darkElement.classList.remove("toggled");
}
});
/// #endif
/// #if BROWSER
if (!window.webkit?.messageHandlers && !window.JSAndroid && !window.JSHarmony &&
("serviceWorker" in window.navigator) && ("caches" in window) && ("fetch" in window) && navigator.serviceWorker) {
document.head.insertAdjacentHTML("afterbegin", ``);
}
/// #endif
setCodeTheme();
const themeScriptElement = document.getElementById("themeScript");
const themeScriptAddress = `/appearance/themes/${data.mode === 1 ? data.themeDark : data.themeLight}/theme.js?v=${data.themeVer}`;
if (themeScriptElement) {
if (!themeScriptElement.getAttribute("src").startsWith(themeScriptAddress)) {
themeScriptElement.remove();
addScript(themeScriptAddress, "themeScript");
}
} else {
addScript(themeScriptAddress, "themeScript");
}
// load icons
const isBuiltInIcon = ["ant", "material"].includes(data.icon);
const iconScriptElement = document.getElementById("iconScript");
const iconDefaultScriptElement = document.getElementById("iconDefaultScript");
// 不能使用 data.iconVer,因为其他主题也需要加载默认图标,此时 data.iconVer 为其他图标的版本号
const iconDefaultURL = `/appearance/icons/${isBuiltInIcon ? data.icon : "material"}/icon.js?v=${Constants.SIYUAN_VERSION}`;
const iconThirdURL = `/appearance/icons/${data.icon}/icon.js?v=${data.iconVer}`;
if ((isBuiltInIcon && iconDefaultScriptElement && iconDefaultScriptElement.getAttribute("src").startsWith(iconDefaultURL)) ||
(!isBuiltInIcon && iconScriptElement && iconScriptElement.getAttribute("src").startsWith(iconThirdURL))) {
// 第三方图标切换到 material
if (isBuiltInIcon) {
iconScriptElement?.remove();
Array.from(document.body.children).forEach((item) => {
if (item.tagName === "svg" &&
!item.getAttribute("data-name") &&
!["iconsMaterial", "iconsAnt"].includes(item.id)) {
item.remove();
}
});
}
return;
}
if (iconDefaultScriptElement && !iconDefaultScriptElement.getAttribute("src").startsWith(iconDefaultURL)) {
iconDefaultScriptElement.remove();
if (data.icon === "ant") {
document.querySelectorAll("#iconsMaterial").forEach(item => {
item.remove();
});
} else {
document.querySelectorAll("#iconsAnt").forEach(item => {
item.remove();
});
}
}
addScript(iconDefaultURL, "iconDefaultScript").then(() => {
iconScriptElement?.remove();
if (!isBuiltInIcon) {
addScript(iconThirdURL, "iconScript").then(() => {
Array.from(document.body.children).forEach((item, index) => {
if (item.tagName === "svg" &&
index !== 0 &&
!item.getAttribute("data-name") &&
!["iconsMaterial", "iconsAnt"].includes(item.id)) {
item.remove();
}
});
});
}
});
};
export const initAssets = () => {
const loadingElement = document.getElementById("loading");
if (loadingElement) {
setTimeout(() => {
loadingElement.remove();
}, 160);
}
updateMobileTheme(window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light");
window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", event => {
const OSTheme = event.matches ? "dark" : "light";
updateMobileTheme(OSTheme);
if (!window.siyuan.config.appearance.modeOS) {
return;
}
if ((window.siyuan.config.appearance.mode === 0 && OSTheme === "light") ||
(window.siyuan.config.appearance.mode === 1 && OSTheme === "dark")) {
return;
}
fetchPost("/api/system/setAppearanceMode", {
mode: OSTheme === "light" ? 0 : 1
}, async response => {
if (window.siyuan.config.appearance.themeJS) {
if (window.destroyTheme) {
try {
await window.destroyTheme();
window.destroyTheme = undefined;
document.getElementById("themeScript").remove();
} catch (e) {
console.error("destroyTheme error: " + e);
}
} else {
/// #if !MOBILE
exportLayout({
cb() {
window.location.reload();
},
errorExit: false,
});
/// #else
window.location.reload();
/// #endif
return;
}
}
window.siyuan.config.appearance = response.data.appearance;
loadAssets(response.data.appearance);
});
});
};
export const setInlineStyle = async (set = true, servePath = "../../../") => {
let style;
// Emojis Reset: 字体中包含了 emoji,需重置
// Emojis Additional: 苹果/win11 字体中没有的 emoji
if (isMac() || isIPad() || isIPhone()) {
style = `@font-face {
font-family: "Emojis Additional";
src: url(${servePath}appearance/fonts/Noto-COLRv1-2.047/Noto-COLRv1.woff2) format("woff2");
unicode-range: U+1fae9, U+1fac6, U+1fabe, U+1fadc, U+e50a, U+1fa89, U+1fadf, U+1f1e6-1f1ff, U+1fa8f;
}
@font-face {
font-family: "Emojis Reset";
src: local("Apple Color Emoji"),
local("Segoe UI Emoji"),
local("Segoe UI Symbol");
unicode-range: U+21a9, U+21aa, U+2122, U+2194-2199, U+23cf, U+25b6, U+25c0, U+25fb, U+25fc, U+25aa, U+25ab, U+2600-2603,
U+260e, U+2611, U+261d, U+2639, U+263a, U+2640, U+2642, U+2660, U+2663, U+2665, U+2666, U+2668, U+267b, U+26aa, U+26ab,
U+2702, U+2708, U+2934, U+2935, U+1f170, U+1f171, U+1f17e, U+1f17f, U+1f202, U+1f21a, U+1f22f, U+1f232-1f23a, U+1f250,
U+1f251, U+1fae4, U+2049, U+203c, U+3030, U+303d, U+24c2, U+26a0, U+26a1, U+26be, U+27a1, U+2b05-2b07, U+3297, U+3299, U+a9, U+ae;
size-adjust: 115%;
}
@font-face {
font-family: "Emojis";
src: local("Apple Color Emoji"),
local("Segoe UI Emoji"),
local("Segoe UI Symbol");
size-adjust: 115%;
}`;
} else if (await isWin11()) {
// Win11 Browser
style = `@font-face {
font-family: "Emojis Additional";
src: url(${servePath}appearance/fonts/Noto-COLRv1-2.047/Noto-COLRv1.woff2) format("woff2");
unicode-range: U+1fae9, U+1fac6, U+1fabe, U+1fadc, U+e50a, U+1fa89, U+1fadf, U+1f1e6-1f1ff, U+1f3f4, U+e0067, U+e0062,
U+e0065, U+e006e, U+e007f, U+e0073, U+e0063, U+e0074, U+e0077, U+e006c;
size-adjust: 85%;
}
@font-face {
font-family: "Emojis Reset";
src: local("Segoe UI Emoji"),
local("Segoe UI Symbol");
unicode-range: U+263a, U+21a9, U+2642, U+303d, U+2197, U+2198, U+2199, U+2196, U+2195, U+2194, U+2660, U+2665, U+2666,
U+2663, U+3030, U+21aa, U+25b6, U+25c0, U+2640, U+203c, U+a9, U+ae, U+2122;
size-adjust: 85%;
}
@font-face {
font-family: "Emojis";
src: local("Segoe UI Emoji"),
local("Segoe UI Symbol");
size-adjust: 85%;
}`;
} else {
style = `@font-face {
font-family: "Emojis Reset";
src: url(${servePath}appearance/fonts/Noto-COLRv1-2.047/Noto-COLRv1.woff2) format("woff2");
unicode-range: U+1f170-1f171, U+1f17e, U+1f17f, U+1f21a, U+1f22f, U+1f232-1f23a, U+1f250, U+1f251, U+1f32b, U+1f3bc,
U+1f411, U+1f42d, U+1f42e, U+1f431, U+1f435, U+1f441, U+1f4a8, U+1f4ab, U+1f525, U+1f600-1f60d, U+1f60f-1f623,
U+1f625-1f62b, U+1f62d-1f63f, U+1F643, U+1F640, U+1f79, U+1f8f, U+1fa79, U+1fae4, U+1fae9, U+1fac6, U+1fabe, U+1fadf,
U+200d, U+203c, U+2049, U+2122, U+2139, U+2194-2199, U+21a9, U+21aa, U+23cf, U+25aa, U+25ab, U+25b6, U+25c0, U+25fb-25fe,
U+2611, U+2615, U+2618, U+261d, U+2620, U+2622, U+2623, U+2626, U+262a, U+262e, U+2638-263a, U+2640, U+2642, U+2648-2653,
U+265f, U+2660, U+2663, U+2665, U+2666, U+267b, U+267e, U+267f, U+2692-2697, U+2699, U+269b, U+269c, U+26a0, U+26a1,
U+26a7, U+26aa, U+26ab, U+26b0, U+26b1, U+2702, U+2708, U+2709, U+270c, U+270d, U+2712, U+2714, U+2716, U+271d, U+2733,
U+2734, U+2744, U+2747, U+2763, U+2764, U+2934-2935, U+3030, U+303d, U+3297, U+3299, U+fe0f, U+e50a, U+a9, U+ae;
size-adjust: 92%;
}
@font-face {
font-family: "Emojis";
src: url(${servePath}appearance/fonts/Noto-COLRv1-2.047/Noto-COLRv1.woff2) format("woff2"),
local("Segoe UI Emoji"),
local("Segoe UI Symbol"),
local("Apple Color Emoji"),
local("Twemoji Mozilla"),
local("Noto Color Emoji"),
local("Android Emoji"),
local("EmojiSymbols");
size-adjust: 92%;
}`;
}
style += `\n:root { --b3-font-size-editor: ${window.siyuan.config.editor.fontSize}px }
.b3-typography code:not(.hljs), .protyle-wysiwyg span[data-type~=code] { font-variant-ligatures: ${window.siyuan.config.editor.codeLigatures ? "normal" : "none"} }${window.siyuan.config.editor.justify ? "\n.protyle-wysiwyg [data-node-id] { text-align: justify }" : ""}`;
if (window.siyuan.config.editor.rtl) {
style += `\n.protyle-title__input,
.protyle-wysiwyg .p,
.protyle-wysiwyg .code-block .hljs,
.protyle-wysiwyg .table,
.protyle-wysiwyg .render-node protyle-html,
.protyle-wysiwyg .render-node > div[spin="1"],
.protyle-wysiwyg [data-type="NodeHeading"] {direction: rtl}
.protyle-wysiwyg [data-node-id].li > .protyle-action {
right: 0;
left: auto;
direction: rtl;
}
.protyle-wysiwyg [data-node-id].li > [data-node-id] {
margin-right: 34px;
margin-left: 0;
}
.protyle-wysiwyg [data-node-id].li::before {
right: 17px;
left: auto;
}
.b3-typography table:not([style*="text-align: left"]) {
margin-left: auto;
}`;
}
if (window.siyuan.config.editor.fontFamily) {
style += `\n.b3-typography:not(.b3-typography--default), .protyle-wysiwyg, .protyle-title {font-family: "Emojis Additional", "Emojis Reset", "${window.siyuan.config.editor.fontFamily}", var(--b3-font-family)}`;
}
// pad 端菜单移除显示,如工作空间
if ("ontouchend" in document) {
style += "\n.b3-menu .b3-menu__action {opacity: 0.68;}";
}
if (set) {
const siyuanStyle = document.getElementById("siyuanStyle");
if (siyuanStyle) {
siyuanStyle.innerHTML = style;
} else {
document.querySelector("#pluginsStyle").insertAdjacentHTML("beforebegin", ``);
}
}
return style;
};
export const setCodeTheme = (cdn = Constants.PROTYLE_CDN) => {
const protyleHljsStyle = document.getElementById("protyleHljsStyle") as HTMLLinkElement;
let css;
if (window.siyuan.config.appearance.mode === 0) {
css = window.siyuan.config.appearance.codeBlockThemeLight;
if (!Constants.SIYUAN_CONFIG_APPEARANCE_LIGHT_CODE.includes(css)) {
css = "default";
}
} else {
css = window.siyuan.config.appearance.codeBlockThemeDark;
if (!Constants.SIYUAN_CONFIG_APPEARANCE_DARK_CODE.includes(css)) {
css = "github-dark";
}
}
const href = `${cdn}/js/highlight.js/styles/${css}.min.css?v=11.11.1`;
if (!protyleHljsStyle) {
addStyle(href, "protyleHljsStyle");
} else if (!protyleHljsStyle.href.includes(href)) {
protyleHljsStyle.remove();
addStyle(href, "protyleHljsStyle");
}
};
export const setMode = (modeElementValue: number) => {
/// #if !MOBILE
let mode = modeElementValue;
if (modeElementValue === 2) {
if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
mode = 1;
} else {
mode = 0;
}
}
fetchPost("/api/setting/setAppearance", Object.assign({}, window.siyuan.config.appearance, {
mode,
modeOS: modeElementValue === 2,
}));
/// #endif
};
const rgba2hex = (rgba: string) => {
if (rgba.startsWith("#")) {
return rgba;
}
let a: any;
const rgb: any = rgba.replace(/\s/g, "").match(/^rgba?\((\d+),(\d+),(\d+),?([^,\s)]+)?/i);
const alpha = (rgb && rgb[4] || "").trim();
let hex = rgb ?
(rgb[1] | 1 << 8).toString(16).slice(1) +
(rgb[2] | 1 << 8).toString(16).slice(1) +
(rgb[3] | 1 << 8).toString(16).slice(1) : rgba;
if (alpha !== "") {
a = alpha;
} else {
a = 0o1;
}
a = ((a * 255) | 1 << 8).toString(16).slice(1);
hex = hex + a;
return hex;
};
const updateMobileTheme = (OSTheme: string) => {
if (isInIOS() || isInAndroid() || isInHarmony()) {
setTimeout(() => {
const backgroundColor = rgba2hex(getComputedStyle(document.body).getPropertyValue("--b3-theme-background").trim());
let mode = window.siyuan.config.appearance.mode;
if (window.siyuan.config.appearance.modeOS) {
if (OSTheme === "dark") {
mode = 1;
} else {
mode = 0;
}
}
if (isInIOS()) {
window.webkit.messageHandlers.changeStatusBar.postMessage((backgroundColor || (mode === 0 ? "#fff" : "#1e1e1e")) + " " + mode);
} else if (isInAndroid()) {
window.JSAndroid.changeStatusBarColor(backgroundColor, mode);
} else if (isInHarmony()) {
window.JSHarmony.changeStatusBarColor(backgroundColor, mode);
}
}, 500); // 移动端需要加载完才可以获取到颜色
}
};
export const getThemeMode = () => {
const OSTheme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
if (window.siyuan.config.appearance.modeOS) {
return OSTheme;
} else {
return window.siyuan.config.appearance.mode === 0 ? "light" : "dark";
}
};