diff --git a/apps/client/src/widgets/note_title.tsx b/apps/client/src/widgets/note_title.tsx index b2f8e68697..f886d9f7db 100644 --- a/apps/client/src/widgets/note_title.tsx +++ b/apps/client/src/widgets/note_title.tsx @@ -26,6 +26,7 @@ export default function NoteTitleWidget(props: {className?: string}) { || note === undefined || (note.isProtected && !protected_session_holder.isProtectedSessionAvailable()) || isLaunchBarConfig(note.noteId) + || note.noteId.startsWith("_help_") || viewScope?.viewMode !== "default"; setReadOnly(isReadOnly); }, [ note, note?.noteId, note?.isProtected, viewScope?.viewMode ]); diff --git a/apps/client/src/widgets/react/hooks.tsx b/apps/client/src/widgets/react/hooks.tsx index 2dd5de4d5f..7616b9d9b1 100644 --- a/apps/client/src/widgets/react/hooks.tsx +++ b/apps/client/src/widgets/react/hooks.tsx @@ -1383,3 +1383,28 @@ export function useGetContextDataFrom( return data; } +export function useColorScheme() { + const themeStyle = getThemeStyle(); + const defaultValue = themeStyle === "auto" ? (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) : themeStyle === "dark"; + const [ prefersDark, setPrefersDark ] = useState(defaultValue); + + useEffect(() => { + if (themeStyle !== "auto") return; + const mediaQueryList = window.matchMedia("(prefers-color-scheme: dark)"); + const listener = (e: MediaQueryListEvent) => setPrefersDark(e.matches); + + mediaQueryList.addEventListener("change", listener); + return () => mediaQueryList.removeEventListener("change", listener); + }, [ themeStyle ]); + + 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/scroll_padding.tsx b/apps/client/src/widgets/scroll_padding.tsx index 549e53f441..e277ee86a1 100644 --- a/apps/client/src/widgets/scroll_padding.tsx +++ b/apps/client/src/widgets/scroll_padding.tsx @@ -8,6 +8,7 @@ export default function ScrollPadding() { const [height, setHeight] = useState(10); const isEnabled = ["text", "code"].includes(note?.type ?? "") && viewScope?.viewMode === "default" + && note?.isContentAvailable() && !note?.isTriliumSqlite(); const refreshHeight = () => { diff --git a/apps/client/src/widgets/type_widgets/MindMap.tsx b/apps/client/src/widgets/type_widgets/MindMap.tsx index b55a6e89d6..ac545b49fb 100644 --- a/apps/client/src/widgets/type_widgets/MindMap.tsx +++ b/apps/client/src/widgets/type_widgets/MindMap.tsx @@ -6,12 +6,12 @@ import "./MindMap.css"; import nodeMenu from "@mind-elixir/node-menu"; import { DISPLAYABLE_LOCALE_IDS } from "@triliumnext/commons"; import { snapdom } from "@zumer/snapdom"; -import { default as VanillaMindElixir,MindElixirData, MindElixirInstance, Operation, Options } from "mind-elixir"; +import { default as VanillaMindElixir,MindElixirData, MindElixirInstance, Operation, Options, THEME as LIGHT_THEME, DARK_THEME } from "mind-elixir"; import { HTMLAttributes, RefObject } from "preact"; import { useCallback, useEffect, useRef } from "preact/hooks"; import utils from "../../services/utils"; -import { useEditorSpacedUpdate, useNoteLabelBoolean, useSyncedRef, useTriliumEvent, useTriliumEvents, useTriliumOption } from "../react/hooks"; +import { useColorScheme, useEditorSpacedUpdate, useNoteLabelBoolean, useSyncedRef, useTriliumEvent, useTriliumEvents, useTriliumOption } from "../react/hooks"; import { refToJQuerySelector } from "../react/react_utils"; import { TypeWidgetProps } from "./type_widget"; @@ -85,9 +85,11 @@ export default function MindMap({ note, ntxId, noteContext }: TypeWidgetProps) { }, onContentChange: (content) => { let newContent: MindElixirData; + if (content) { try { newContent = JSON.parse(content) as MindElixirData; + delete newContent.theme; // The theme is managed internally by the widget, so we remove it from the loaded content to avoid inconsistencies. } catch (e) { console.warn(e); console.debug("Wrong JSON content: ", content); @@ -151,6 +153,7 @@ function MindElixir({ containerRef: externalContainerRef, containerProps, apiRef const containerRef = useSyncedRef(externalContainerRef, null); const apiRef = useRef(null); const [ locale ] = useTriliumOption("locale"); + const colorScheme = useColorScheme(); function reinitialize() { if (!containerRef.current) return; @@ -158,7 +161,8 @@ function MindElixir({ containerRef: externalContainerRef, containerProps, apiRef const mind = new VanillaMindElixir({ el: containerRef.current, locale: LOCALE_MAPPINGS[locale as DISPLAYABLE_LOCALE_IDS] ?? undefined, - editable + editable, + theme: LIGHT_THEME }); if (editable) { @@ -179,6 +183,14 @@ function MindElixir({ containerRef: externalContainerRef, containerProps, apiRef }; }, []); + // React to theme changes. + useEffect(() => { + if (!apiRef.current) return; + const newTheme = colorScheme === "dark" ? DARK_THEME : LIGHT_THEME; + if (apiRef.current.theme === newTheme) return; // Avoid unnecessary theme changes, which can be expensive to render. + apiRef.current.changeTheme(newTheme); + }, [ colorScheme ]); + useEffect(() => { const data = apiRef.current?.getData(); reinitialize(); diff --git a/apps/client/src/widgets/type_widgets/canvas/Canvas.tsx b/apps/client/src/widgets/type_widgets/canvas/Canvas.tsx index 6a5ea93772..3c22af9c76 100644 --- a/apps/client/src/widgets/type_widgets/canvas/Canvas.tsx +++ b/apps/client/src/widgets/type_widgets/canvas/Canvas.tsx @@ -1,7 +1,7 @@ import { Excalidraw } from "@excalidraw/excalidraw"; import { TypeWidgetProps } from "../type_widget"; import "@excalidraw/excalidraw/index.css"; -import { useNoteLabelBoolean, useTriliumOption } from "../../react/hooks"; +import { useColorScheme, useNoteLabelBoolean, useTriliumOption } from "../../react/hooks"; import { useCallback, useMemo, useRef } from "preact/hooks"; import { type ExcalidrawImperativeAPI, type AppState } from "@excalidraw/excalidraw/types"; import options from "../../../services/options"; @@ -19,12 +19,9 @@ window.EXCALIDRAW_ASSET_PATH = `${window.location.pathname}/node_modules/@excali export default function Canvas({ note, noteContext }: TypeWidgetProps) { const apiRef = useRef(null); const [ isReadOnly ] = useNoteLabelBoolean(note, "readOnly"); - const themeStyle = useMemo(() => { - const documentStyle = window.getComputedStyle(document.documentElement); - return documentStyle.getPropertyValue("--theme-style")?.trim() as AppState["theme"]; - }, []); + const colorScheme = useColorScheme(); const [ locale ] = useTriliumOption("locale"); - const persistence = useCanvasPersistence(note, noteContext, apiRef, themeStyle, isReadOnly); + const persistence = useCanvasPersistence(note, noteContext, apiRef, colorScheme, isReadOnly); /** Use excalidraw's native zoom instead of the global zoom. */ const onWheel = useCallback((e: MouseEvent) => { @@ -54,7 +51,7 @@ export default function Canvas({ note, noteContext }: TypeWidgetProps) {
apiRef.current = api} - theme={themeStyle} + theme={colorScheme} viewModeEnabled={isReadOnly || options.is("databaseReadonly")} zenModeEnabled={false} isCollaborating={false}