From 0975a1bb68399508e726b2a1a3b400d21b75ee00 Mon Sep 17 00:00:00 2001 From: chenyu Date: Thu, 7 Aug 2025 19:36:28 +0800 Subject: [PATCH 1/3] fix for logseq db --- package.json | 4 ++-- src/Heatmap.tsx | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index a63756c..bc54dc3 100644 --- a/package.json +++ b/package.json @@ -8,11 +8,11 @@ }, "license": "MIT", "dependencies": { - "@logseq/libs": "0.0.6", + "@logseq/libs": "0.0.17", "@types/uuid": "^8.3.4", "date-fns": "^2.28.0", "react": "^18.1.0", - "react-calendar-heatmap": "^1.8.1", + "react-calendar-heatmap": "^1.10.0", "react-dom": "^18.1.0", "react-suspense-fetch": "^0.6.0", "react-tooltip": "^4.2.21", diff --git a/src/Heatmap.tsx b/src/Heatmap.tsx index c542f0c..e7c5b03 100644 --- a/src/Heatmap.tsx +++ b/src/Heatmap.tsx @@ -46,9 +46,8 @@ const useActivities = (startDate: string, endDate: string) => { [:find (pull ?p [*]) (count ?b) :where [?b :block/page ?p] - [?p :block/journal? true] [?p :block/journal-day ?d] - [?b :block/content ?c] + [?b :block/title ?c] [(clojure.string/blank? ?c) ?empty] [(not ?empty)] [(>= ?d ${formatAsParam(date0)})] @@ -272,7 +271,7 @@ export const Heatmap = React.forwardRef(({}, ref) => {
From 9645318e2047256271428542638b40bb3e394128 Mon Sep 17 00:00:00 2001 From: chenyu Date: Thu, 7 Aug 2025 20:07:22 +0800 Subject: [PATCH 2/3] fix for logseq db --- src/Heatmap.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Heatmap.tsx b/src/Heatmap.tsx index e7c5b03..347ef32 100644 --- a/src/Heatmap.tsx +++ b/src/Heatmap.tsx @@ -69,7 +69,7 @@ const useActivities = (startDate: string, endDate: string) => { const datum = { count: count ?? 0, date: formatAsDashed(date), - originalName: page["original-name"] as string, + originalName: page["name"] as string, }; return [datum.date, datum]; }) From 13e4092c75ffd55d619517e2f51f6c09e91b433c Mon Sep 17 00:00:00 2001 From: yuanli Date: Wed, 5 Aug 2026 17:29:37 +0800 Subject: [PATCH 3/3] fix: avoid cross-origin access in DB graphs Query journal data as scalar values instead of pulling full page entities, and use the Logseq theme API instead of reading top.document. This prevents the plugin iframe from throwing a SecurityError and leaving an input-blocking overlay in Logseq DB graphs. --- pnpm-lock.yaml | 55 ++++++++++++------------------ src/Heatmap.tsx | 91 ++++++++++++++++++++++++++----------------------- src/utils.ts | 26 ++++++++++---- 3 files changed, 90 insertions(+), 82 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7452d85..cd11972 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,7 +1,7 @@ lockfileVersion: 5.4 specifiers: - '@logseq/libs': 0.0.6 + '@logseq/libs': 0.0.17 '@semantic-release/changelog': 6.0.2 '@semantic-release/exec': 6.0.3 '@semantic-release/git': 10.0.1 @@ -19,7 +19,7 @@ specifiers: eslint: 8.33.0 eslint-plugin-react: 7.32.2 react: ^18.1.0 - react-calendar-heatmap: ^1.8.1 + react-calendar-heatmap: ^1.10.0 react-dom: ^18.1.0 react-error-boundary: ^3.1.4 react-suspense-fetch: ^0.6.0 @@ -33,11 +33,11 @@ specifiers: windicss: 3.5.6 dependencies: - '@logseq/libs': 0.0.6 + '@logseq/libs': 0.0.17 '@types/uuid': 8.3.4 date-fns: 2.28.0 react: 18.1.0 - react-calendar-heatmap: 1.8.1_react@18.1.0 + react-calendar-heatmap: 1.10.0_react@18.1.0 react-dom: 18.1.0_react@18.1.0 react-suspense-fetch: 0.6.0 react-tooltip: 4.2.21_ef5jwxihqo6n7gxfmzogljlgcm @@ -422,12 +422,13 @@ packages: '@jridgewell/sourcemap-codec': 1.4.14 dev: true - /@logseq/libs/0.0.6: - resolution: {integrity: sha512-0Fv+Wdno5m0EA9xW6tRRvoKNPAF5nznpi6A1bBjh8aq4XMt7rGvhHbsH+u84/nswCYlce3t2Q/ei8CRK8iWbdw==} + /@logseq/libs/0.0.17: + resolution: {integrity: sha512-SkzzAaocmrgeHYrCOaRyEqzPOxw3d0qVEZSrt9qVvXE4tuEgbvEHR8tzI1N5RjgAv+PDWuGPiP7/mhcXHpINEw==} dependencies: - csstype: 3.0.8 - debug: 4.3.1 - dompurify: 2.3.1 + csstype: 3.1.0 + debug: 4.3.4 + deepmerge: 4.3.1 + dompurify: 2.3.8 eventemitter3: 4.0.7 fast-deep-equal: 3.1.3 lodash-es: 4.17.21 @@ -1377,10 +1378,6 @@ packages: source-map: 0.6.1 dev: false - /csstype/3.0.8: - resolution: {integrity: sha512-jXKhWqXPmlUeoQnF/EhTtTl4C9SnrxSH/jZUih3jmO6lBKr99rP3/+FmrMj4EFpOXzMtXHAZkd3x0E6h6Fgflw==} - dev: false - /csstype/3.1.0: resolution: {integrity: sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==} @@ -1393,18 +1390,6 @@ packages: resolution: {integrity: sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==} dev: true - /debug/4.3.1: - resolution: {integrity: sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - dependencies: - ms: 2.1.2 - dev: false - /debug/4.3.4: resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} engines: {node: '>=6.0'} @@ -1415,7 +1400,6 @@ packages: optional: true dependencies: ms: 2.1.2 - dev: true /decamelize-keys/1.1.0: resolution: {integrity: sha512-ocLWuYzRPoS9bfiSdDd3cxvrzovVMZnRDVEzAs+hWIVXGDbHxWMECij2OBuyB/An0FFW/nLuq6Kv1i/YC5Qfzg==} @@ -1439,6 +1423,11 @@ packages: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} dev: true + /deepmerge/4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + dev: false + /define-properties/1.1.4: resolution: {integrity: sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==} engines: {node: '>= 0.4'} @@ -1486,8 +1475,8 @@ packages: esutils: 2.0.3 dev: true - /dompurify/2.3.1: - resolution: {integrity: sha512-xGWt+NHAQS+4tpgbOAI08yxW0Pr256Gu/FNE2frZVTbgrBUn8M7tz7/ktS/LZ2MHeGqz6topj0/xY+y8R5FBFw==} + /dompurify/2.3.8: + resolution: {integrity: sha512-eVhaWoVibIzqdGYjwsBWodIQIaXFSB+cKDf4cfxLMsK0xiud6SE+/WCVx/Xw/UwQsa4cS3T2eITcdtmTg2UKcw==} dev: false /dot-case/3.0.4: @@ -3311,7 +3300,7 @@ packages: dev: true /path/0.12.7: - resolution: {integrity: sha1-1NwqUGxM4hl+tIHr/NWzbAFAsQ8=} + resolution: {integrity: sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q==} dependencies: process: 0.11.10 util: 0.10.4 @@ -3358,7 +3347,7 @@ packages: dev: true /process/0.11.10: - resolution: {integrity: sha1-czIwDoQBYb2j5podHZGn1LwW8YI=} + resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} engines: {node: '>= 0.6.0'} dev: false @@ -3402,10 +3391,10 @@ packages: strip-json-comments: 2.0.1 dev: true - /react-calendar-heatmap/1.8.1_react@18.1.0: - resolution: {integrity: sha512-4Hbq/pDMJoCPzZnyIWFfHgokLlLXzKyGsDcMgNhYpi7zcKHcvsK9soLEPvhW2dBBqgDrQOSp/uG4wtifaDg4eQ==} + /react-calendar-heatmap/1.10.0_react@18.1.0: + resolution: {integrity: sha512-e5vcrzMWzKIF710egr1FpjWyuDEFeZm39nvV25muc8Wtqqi8iDOfqREELeQ9Wouqf9hhj939gq0i+iAxo7KdSw==} peerDependencies: - react: ^0.14.0 || ^15.0.0 || ^16.0.0 + react: '>=0.14.0' dependencies: memoize-one: 5.2.1 prop-types: 15.8.1 diff --git a/src/Heatmap.tsx b/src/Heatmap.tsx index 347ef32..7ec144f 100644 --- a/src/Heatmap.tsx +++ b/src/Heatmap.tsx @@ -10,13 +10,12 @@ import { ErrorBoundary, FallbackProps } from "react-error-boundary"; import * as React from "react"; import CalendarHeatmap from "react-calendar-heatmap"; import ReactTooltip from "react-tooltip"; -import { useMountedState, useWindowSize } from "react-use"; +import { useMountedState } from "react-use"; import "./Heatmap.css"; import { formatAsDashed, formatAsLocale, formatAsParam, - triggerIconName, parseJournalDate, useCurrentJournalDate, } from "./utils"; @@ -36,40 +35,61 @@ const useActivities = (startDate: string, endDate: string) => { const currentJournalDate = useCurrentJournalDate(); const [rawValue, setRawValue] = React.useState([]); + const [queryError, setQueryError] = React.useState(null); + + React.useEffect(() => { + let cancelled = false; - React.useLayoutEffect(() => { (async () => { - const date0 = new Date(startDate); - const date1 = new Date(endDate); + try { + const date0 = new Date(startDate); + const date1 = new Date(endDate); - const res: any[] = await logseq.DB.datascriptQuery(` - [:find (pull ?p [*]) (count ?b) - :where - [?b :block/page ?p] - [?p :block/journal-day ?d] - [?b :block/title ?c] - [(clojure.string/blank? ?c) ?empty] - [(not ?empty)] - [(>= ?d ${formatAsParam(date0)})] - [(<= ?d ${formatAsParam(date1)})]] - `); + // Return only scalar values. DB graphs expose pulled entity keys with + // namespaces (for example `block/journal-day`), so reading `name` and + // `journal-day` from a pulled object is both version-sensitive and much + // more expensive than asking Datascript for the values directly. + const res: any[] = await logseq.DB.datascriptQuery(` + [:find ?d ?name (count ?b) + :where + [?p :block/journal-day ?d] + [(>= ?d ${formatAsParam(date0)})] + [(<= ?d ${formatAsParam(date1)})] + [?p :block/name ?name] + [?b :block/page ?p] + [?b :block/title ?c] + [(clojure.string/blank? ?c) ?empty] + [(not ?empty)]] + `); - if (isMounted()) { - setRawValue(res); + if (!cancelled && isMounted()) { + setQueryError(null); + setRawValue(res); + } + } catch (error) { + if (!cancelled && isMounted()) { + setQueryError( + error instanceof Error ? error : new Error(String(error)) + ); + } } })(); + + return () => { + cancelled = true; + }; }, [startDate, endDate]); - return React.useMemo(() => { + const activities = React.useMemo(() => { const date0 = new Date(startDate); const date1 = new Date(endDate); const mapping = Object.fromEntries( - rawValue.map(([page, count]: any[]) => { - const date = parseJournalDate(page["journal-day"]); + rawValue.map(([journalDay, pageName, count]: any[]) => { + const date = parseJournalDate(journalDay); const datum = { count: count ?? 0, date: formatAsDashed(date), - originalName: page["name"] as string, + originalName: pageName as string, }; return [datum.date, datum]; }) @@ -100,6 +120,12 @@ const useActivities = (startDate: string, endDate: string) => { } return newValues; }, [rawValue, currentJournalDate]); + + if (queryError) { + throw queryError; + } + + return activities; }; type Datum = { @@ -245,33 +271,14 @@ const DateRange = ({ return null; }; -function useIconPosition() { - const windowSize = useWindowSize(); - return React.useMemo(() => { - let right = windowSize.width - 10; - let bottom = 20; - if (top?.document) { - const iconRect = top?.document - .querySelector(`.${triggerIconName}`) - ?.getBoundingClientRect(); - if (iconRect) { - right = iconRect.right; - bottom = iconRect.bottom; - } - } - return { right, bottom }; - }, [windowSize]); -} - export const Heatmap = React.forwardRef(({}, ref) => { const today = formatAsDashed(new Date()); const [range, setRange] = React.useState<[string, string] | null>(null); - const { bottom, right } = useIconPosition(); return (
diff --git a/src/utils.ts b/src/utils.ts index b502dec..2861a75 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -63,17 +63,29 @@ export const useThemeMode = () => { const isMounted = useMountedState(); const [mode, setMode] = React.useState<"dark" | "light">("light"); React.useEffect(() => { - setMode( - (top?.document - .querySelector("html") - ?.getAttribute("data-theme") as typeof mode) ?? - (matchMedia("prefers-color-scheme: dark").matches ? "dark" : "light") - ); - logseq.App.onThemeModeChanged((s) => { + let cancelled = false; + + logseq.App.getUserConfigs().then((config) => { + const configuredMode = config?.preferredThemeMode; + if ( + !cancelled && + isMounted() && + (configuredMode === "dark" || configuredMode === "light") + ) { + setMode(configuredMode); + } + }); + + const unsubscribe = logseq.App.onThemeModeChanged((s) => { if (isMounted()) { setMode(s.mode); } }); + + return () => { + cancelled = true; + unsubscribe?.(); + }; }, [isMounted]); return mode;