diff --git a/apps/build-docs/package.json b/apps/build-docs/package.json index 2b6abad8f3..c173c96f19 100644 --- a/apps/build-docs/package.json +++ b/apps/build-docs/package.json @@ -25,6 +25,6 @@ "fs-extra": "11.3.4", "js-yaml": "4.1.1", "typedoc": "0.28.18", - "typedoc-plugin-missing-exports": "4.1.2" + "typedoc-plugin-missing-exports": "4.1.3" } } diff --git a/apps/client/package.json b/apps/client/package.json index dfac77848f..ed5aa67f66 100644 --- a/apps/client/package.json +++ b/apps/client/package.json @@ -34,14 +34,14 @@ "@triliumnext/highlightjs": "workspace:*", "@triliumnext/share-theme": "workspace:*", "@triliumnext/split.js": "workspace:*", - "@univerjs/preset-sheets-conditional-formatting": "0.19.0", - "@univerjs/preset-sheets-core": "0.19.0", - "@univerjs/preset-sheets-data-validation": "0.19.0", - "@univerjs/preset-sheets-filter": "0.19.0", - "@univerjs/preset-sheets-find-replace": "0.19.0", - "@univerjs/preset-sheets-note": "0.19.0", - "@univerjs/preset-sheets-sort": "0.19.0", - "@univerjs/presets": "0.19.0", + "@univerjs/preset-sheets-conditional-formatting": "0.20.0", + "@univerjs/preset-sheets-core": "0.20.0", + "@univerjs/preset-sheets-data-validation": "0.20.0", + "@univerjs/preset-sheets-filter": "0.20.0", + "@univerjs/preset-sheets-find-replace": "0.20.0", + "@univerjs/preset-sheets-note": "0.20.0", + "@univerjs/preset-sheets-sort": "0.20.0", + "@univerjs/presets": "0.20.0", "@zumer/snapdom": "2.7.0", "autocomplete.js": "0.38.1", "bootstrap": "5.3.8", @@ -65,7 +65,7 @@ "mermaid": "11.14.0", "mind-elixir": "5.10.0", "panzoom": "9.4.4", - "preact": "10.29.0", + "preact": "10.29.1", "react-i18next": "17.0.2", "react-window": "2.2.7", "reveal.js": "6.0.0", diff --git a/apps/client/src/desktop.ts b/apps/client/src/desktop.ts index 6f22d3fc7b..bb1f859809 100644 --- a/apps/client/src/desktop.ts +++ b/apps/client/src/desktop.ts @@ -54,7 +54,7 @@ function initOnElectron() { const currentWindow = electronRemote.getCurrentWindow(); const style = window.getComputedStyle(document.body); - initDarkOrLightMode(style); + initDarkOrLightMode(); initTransparencyEffects(style, currentWindow); initFullScreenDetection(currentWindow); @@ -119,11 +119,11 @@ function initTransparencyEffects(style: CSSStyleDeclaration, currentWindow: Elec * * @param style the root CSS element to read variables from. */ -function initDarkOrLightMode(style: CSSStyleDeclaration) { +function initDarkOrLightMode() { let themeSource: typeof nativeTheme.themeSource = "system"; - const themeStyle = style.getPropertyValue("--theme-style"); - if (style.getPropertyValue("--theme-style-auto") !== "true" && (themeStyle === "light" || themeStyle === "dark")) { + const themeStyle = window.glob.getThemeStyle(); + if (themeStyle !== "auto") { themeSource = themeStyle; } diff --git a/apps/client/src/index.ts b/apps/client/src/index.ts index 292138ddc7..48a463273f 100644 --- a/apps/client/src/index.ts +++ b/apps/client/src/index.ts @@ -1,3 +1,5 @@ +import { getThemeStyle } from "./services/theme"; + async function bootstrap() { showSplash(); await setupGlob(); @@ -39,6 +41,7 @@ async function setupGlob() { activeDialog: null, device: json.device || getDevice() }; + window.glob.getThemeStyle = getThemeStyle; } function getDevice() { @@ -76,31 +79,65 @@ async function loadBootstrapCss() { } } -function loadStylesheets() { - const { device, assetPath, themeCssUrl, themeUseNextAsBase } = window.glob; +type StylesheetRef = { + href: string; + media?: string; +}; - const cssToLoad: string[] = []; - if (device !== "print") { - cssToLoad.push(`${assetPath}/stylesheets/ckeditor-theme.css`); - cssToLoad.push(`api/fonts`); - cssToLoad.push(`${assetPath}/stylesheets/theme-light.css`); - if (themeCssUrl) { - cssToLoad.push(themeCssUrl); - } - if (themeUseNextAsBase === "next") { - cssToLoad.push(`${assetPath}/stylesheets/theme-next.css`); - } else if (themeUseNextAsBase === "next-dark") { - cssToLoad.push(`${assetPath}/stylesheets/theme-next-dark.css`); - } else if (themeUseNextAsBase === "next-light") { - cssToLoad.push(`${assetPath}/stylesheets/theme-next-light.css`); - } - cssToLoad.push(`${assetPath}/stylesheets/style.css`); +function getConfiguredThemeStylesheets(stylesheetsPath: string, theme: string, customThemeCssUrl?: string) { + if (theme === "auto") { + return [{ href: `${stylesheetsPath}/theme-dark.css`, media: "(prefers-color-scheme: dark)" }]; } - for (const href of cssToLoad) { + if (theme === "dark") { + return [{ href: `${stylesheetsPath}/theme-dark.css` }]; + } + + if (theme === "next") { + return [ + { href: `${stylesheetsPath}/theme-next-light.css` }, + { href: `${stylesheetsPath}/theme-next-dark.css`, media: "(prefers-color-scheme: dark)" } + ]; + } + + if (theme === "next-light") { + return [{ href: `${stylesheetsPath}/theme-next-light.css` }]; + } + + if (theme === "next-dark") { + return [{ href: `${stylesheetsPath}/theme-next-dark.css` }]; + } + + if (theme !== "light" && customThemeCssUrl) { + return [{ href: customThemeCssUrl }]; + } + + return []; +} + +function loadStylesheets() { + const { device, assetPath, theme, themeBase, customThemeCssUrl } = window.glob; + const stylesheetsPath = `${assetPath}/stylesheets`; + + const cssToLoad: StylesheetRef[] = []; + if (device !== "print") { + cssToLoad.push({ href: `${stylesheetsPath}/ckeditor-theme.css` }); + cssToLoad.push({ href: `api/fonts` }); + cssToLoad.push({ href: `${stylesheetsPath}/theme-light.css` }); + cssToLoad.push(...getConfiguredThemeStylesheets(stylesheetsPath, theme, customThemeCssUrl)); + if (themeBase) { + cssToLoad.push(...getConfiguredThemeStylesheets(stylesheetsPath, themeBase)); + } + cssToLoad.push({ href: `${stylesheetsPath}/style.css` }); + } + + for (const { href, media } of cssToLoad) { const linkEl = document.createElement("link"); linkEl.href = href; linkEl.rel = "stylesheet"; + if (media) { + linkEl.media = media; + } document.head.appendChild(linkEl); } } diff --git a/apps/client/src/services/theme.ts b/apps/client/src/services/theme.ts new file mode 100644 index 0000000000..9aa42c22ba --- /dev/null +++ b/apps/client/src/services/theme.ts @@ -0,0 +1,35 @@ +export function getThemeStyle(): "auto" | "light" | "dark" { + const configuredTheme = window.glob?.theme; + if (configuredTheme === "auto" || configuredTheme === "next") { + return "auto"; + } + + if (configuredTheme === "light" || configuredTheme === "dark") { + return configuredTheme; + } + + if (configuredTheme === "next-light") { + return "light"; + } + + if (configuredTheme === "next-dark") { + return "dark"; + } + + const style = window.getComputedStyle(document.body); + const themeStyle = style.getPropertyValue("--theme-style"); + if (style.getPropertyValue("--theme-style-auto") !== "true" && (themeStyle === "light" || themeStyle === "dark")) { + return themeStyle as "light" | "dark"; + } + + return "auto"; +} + +export function getEffectiveThemeStyle(): "light" | "dark" { + const themeStyle = getThemeStyle(); + if (themeStyle === "auto") { + return window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"; + } + + return themeStyle === "dark" ? "dark" : "light"; +} diff --git a/apps/client/src/stylesheets/theme-next.css b/apps/client/src/stylesheets/theme-next.css deleted file mode 100644 index 1c8a7d810d..0000000000 --- a/apps/client/src/stylesheets/theme-next.css +++ /dev/null @@ -1,11 +0,0 @@ -/* Import the light color scheme. - * This is the base color scheme, always active and overridden by the dark - * color scheme stylesheet when necessary. */ -@import url(./theme-next-light.css); - -/* Import the dark color scheme when the system preference is set to dark mode */ -@import url(./theme-next-dark.css) (prefers-color-scheme: dark); - -:root { - --theme-style-auto: true; -} diff --git a/apps/client/src/stylesheets/theme.css b/apps/client/src/stylesheets/theme.css deleted file mode 100644 index e99702d325..0000000000 --- a/apps/client/src/stylesheets/theme.css +++ /dev/null @@ -1,11 +0,0 @@ -/* Import the light color scheme. - * This is the base color scheme, always active and overridden by the dark - * color scheme stylesheet when necessary. */ -@import url(./theme-light.css); - -/* Import the dark color scheme when the system preference is set to dark mode */ -@import url(./theme-dark.css) (prefers-color-scheme: dark); - -:root { - --theme-style-auto: true; -} diff --git a/apps/client/src/types-lib.d.ts b/apps/client/src/types-lib.d.ts index 4f942b8cb6..8d3c9296d1 100644 --- a/apps/client/src/types-lib.d.ts +++ b/apps/client/src/types-lib.d.ts @@ -66,6 +66,7 @@ declare module "preact" { interface ElectronWebViewElement extends JSX.HTMLAttributes { src: string; class: string; + key?: string | number; } interface IntrinsicElements { diff --git a/apps/client/src/types.d.ts b/apps/client/src/types.d.ts index 545ff8e029..df29fae664 100644 --- a/apps/client/src/types.d.ts +++ b/apps/client/src/types.d.ts @@ -23,6 +23,7 @@ interface CustomGlobals extends BootstrapDefinition { getReferenceLinkTitle: (href: string) => Promise; getReferenceLinkTitleSync: (href: string) => string; getActiveContextNote: () => FNote | null; + getThemeStyle: () => "auto" | "light" | "dark"; ESLINT: Library; appContext: AppContext; froca: Froca; diff --git a/apps/client/src/widgets/note_map/NoteMap.tsx b/apps/client/src/widgets/note_map/NoteMap.tsx index 12e15202d2..2677e4aa59 100644 --- a/apps/client/src/widgets/note_map/NoteMap.tsx +++ b/apps/client/src/widgets/note_map/NoteMap.tsx @@ -1,18 +1,21 @@ -import { useEffect, useMemo, useRef, useState } from "preact/hooks"; import "./NoteMap.css"; -import { getThemeStyle, MapType, NoteMapWidgetMode, rgb2hex } from "./utils"; -import { RefObject } from "preact"; -import FNote from "../../entities/fnote"; -import { useElementSize, useNoteLabel } from "../react/hooks"; + import ForceGraph from "force-graph"; +import { RefObject } from "preact"; +import { useEffect, useMemo, useRef, useState } from "preact/hooks"; + +import appContext from "../../components/app_context"; +import FNote from "../../entities/fnote"; +import link_context_menu from "../../menus/link_context_menu"; +import hoisted_note from "../../services/hoisted_note"; +import { t } from "../../services/i18n"; +import { getEffectiveThemeStyle } from "../../services/theme"; +import ActionButton from "../react/ActionButton"; +import { useElementSize, useNoteLabel } from "../react/hooks"; +import Slider from "../react/Slider"; import { loadNotesAndRelations, NoteMapLinkObject, NoteMapNodeObject, NotesAndRelationsData } from "./data"; import { CssData, setupRendering } from "./rendering"; -import ActionButton from "../react/ActionButton"; -import { t } from "../../services/i18n"; -import link_context_menu from "../../menus/link_context_menu"; -import appContext from "../../components/app_context"; -import Slider from "../react/Slider"; -import hoisted_note from "../../services/hoisted_note"; +import { MapType, NoteMapWidgetMode, rgb2hex } from "./utils"; interface NoteMapProps { note: FNote; @@ -40,9 +43,9 @@ export default function NoteMap({ note, widgetMode, parentRef }: NoteMapProps) { return hoisted_note.getHoistedNoteId(); } else if (mapRootIdLabel) { return mapRootIdLabel; - } else { - return appContext.tabManager.getActiveContext()?.parentNoteId ?? null; } + return appContext.tabManager.getActiveContext()?.parentNoteId ?? null; + }, [ note ]); // Build the note graph instance. @@ -67,7 +70,7 @@ export default function NoteMap({ note, widgetMode, parentRef }: NoteMapProps) { noteIdToSizeMap: notesAndRelations.noteIdToSizeMap, cssData, notesAndRelations, - themeStyle: getThemeStyle(), + themeStyle: getEffectiveThemeStyle(), widgetMode, mapType }); @@ -113,7 +116,7 @@ export default function NoteMap({ note, widgetMode, parentRef }: NoteMapProps) { node.fx = undefined; node.fy = undefined; } - }) + }); }, [ fixNodes, mapType ]); return ( @@ -159,7 +162,7 @@ function MapTypeSwitcher({ icon, text, type, currentMapType, setMapType }: { onClick={() => setMapType(type)} frame /> - ) + ); } function getCssData(container: HTMLElement, styleResolver: HTMLElement): CssData { @@ -170,5 +173,5 @@ function getCssData(container: HTMLElement, styleResolver: HTMLElement): CssData fontFamily: containerStyle.fontFamily, textColor: rgb2hex(containerStyle.color), mutedTextColor: rgb2hex(styleResolverStyle.color) - } + }; } diff --git a/apps/client/src/widgets/note_map/utils.ts b/apps/client/src/widgets/note_map/utils.ts index d551ea235b..923b5cb001 100644 --- a/apps/client/src/widgets/note_map/utils.ts +++ b/apps/client/src/widgets/note_map/utils.ts @@ -27,7 +27,3 @@ export function generateColorFromString(str: string, themeStyle: "light" | "dark return color; } -export function getThemeStyle() { - const documentStyle = window.getComputedStyle(document.documentElement); - return documentStyle.getPropertyValue("--theme-style")?.trim() as "light" | "dark"; -} diff --git a/apps/client/src/widgets/react/hooks.tsx b/apps/client/src/widgets/react/hooks.tsx index 6bae391f56..a6f9c4595f 100644 --- a/apps/client/src/widgets/react/hooks.tsx +++ b/apps/client/src/widgets/react/hooks.tsx @@ -1385,7 +1385,7 @@ export function useGetContextDataFrom( } export function useColorScheme() { - const themeStyle = getThemeStyle(); + const themeStyle = window.glob.getThemeStyle(); const defaultValue = themeStyle === "auto" ? (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) : themeStyle === "dark"; const [ prefersDark, setPrefersDark ] = useState(defaultValue); @@ -1400,12 +1400,3 @@ export function useColorScheme() { return prefersDark ? "dark" : "light"; } - -function getThemeStyle() { - const style = window.getComputedStyle(document.body); - const themeStyle = style.getPropertyValue("--theme-style"); - if (style.getPropertyValue("--theme-style-auto") !== "true" && (themeStyle === "light" || themeStyle === "dark")) { - return themeStyle as "light" | "dark"; - } - return "auto"; -} diff --git a/apps/client/src/widgets/type_widgets/WebView.tsx b/apps/client/src/widgets/type_widgets/WebView.tsx index bbf170933d..4d45295e87 100644 --- a/apps/client/src/widgets/type_widgets/WebView.tsx +++ b/apps/client/src/widgets/type_widgets/WebView.tsx @@ -57,6 +57,7 @@ function DesktopWebView({ src, ntxId }: { src: string, ntxId: string | null | un return ; } @@ -80,6 +81,7 @@ function BrowserWebView({ src, ntxId }: { src: string, ntxId: string | null | un return