From 34fe4a6d626771812f4ead325baec5b79ddc684b Mon Sep 17 00:00:00 2001 From: Trang Doan Date: Fri, 11 Apr 2025 18:27:47 -0400 Subject: [PATCH 1/9] current progress --- apps/roam/src/components/GitHubSync.tsx | 103 +++++++----------- .../src/components/settings/NodeConfig.tsx | 60 ++++++++-- apps/roam/src/utils/getBlockIdIfExists.ts | 8 ++ apps/roam/src/utils/getDiscourseNodes.ts | 7 ++ 4 files changed, 109 insertions(+), 69 deletions(-) create mode 100644 apps/roam/src/utils/getBlockIdIfExists.ts diff --git a/apps/roam/src/components/GitHubSync.tsx b/apps/roam/src/components/GitHubSync.tsx index f71b5e4a6..da5e5311a 100644 --- a/apps/roam/src/components/GitHubSync.tsx +++ b/apps/roam/src/components/GitHubSync.tsx @@ -48,8 +48,7 @@ import { } from "roamjs-components/components/ConfigPanels/types"; import CustomPanel from "roamjs-components/components/ConfigPanels/CustomPanel"; import getShallowTreeByParentUid from "roamjs-components/queries/getShallowTreeByParentUid"; -import CommentsQuery from "./GitHubSyncCommentsQuery"; -import getSubTree from "roamjs-components/util/getSubTree"; + import isFlagEnabled from "~/utils/isFlagEnabled"; const CommentUidCache = new Set(); @@ -110,22 +109,40 @@ const getRoamCommentsContainerUid = async ({ extensionAPI: OnloadArgs["extensionAPI"]; }) => { const pageTitle = getPageTitleByPageUid(pageUid); - const configUid = getPageUidByPageTitle(CONFIG_PAGE); - const configTree = getBasicTreeByParentUid(configUid); - const queryNode = getSubTree({ - tree: configTree, - key: "Comments Block", - }); - if (!queryNode) { + + // Find the node type that matches this page + const discourseNodes = getDiscourseNodes(); + let matchingNode; + + for (const node of discourseNodes) { + if (node.githubSync) { + const isMatch = matchDiscourseNode({ + format: node.format || "", + specification: node.specification || [], + text: node.text || "", + title: pageTitle, + }); + + if (isMatch) { + matchingNode = node; + break; + } + } + } + + if (!matchingNode || !matchingNode.githubCommentsQueryUid) { renderToast({ id: "github-issue-comments", - content: `Comments Block query not set. Set it in ${CONFIG_PAGE}`, + content: + "Comments Block query not set. Configure it in the Discourse Graph settings.", }); return; } + + // Use the node-specific query const results = await runQuery({ extensionAPI, - parentUid: queryNode.uid, + parentUid: matchingNode.githubCommentsQueryUid, inputs: { NODETEXT: pageTitle, NODEUID: pageUid }, }); @@ -250,30 +267,22 @@ export const insertNewCommentsFromGitHub = async ({ }); } }; + export const isGitHubSyncPage = (pageTitle: string) => { - if (!enabled) return; - const gitHubNodeResult = window.roamAlphaAPI.data.fast.q(`[:find - (pull ?node [:block/string]) - :where - [?roamjsgithub-sync :node/title "roam/js/github-sync"] - [?node :block/page ?roamjsgithub-sync] - [?p :block/children ?node] - (or [?p :block/string ?p-String] - [?p :node/title ?p-String]) - [(clojure.string/includes? ?p-String "Node Select")] - ]`) as [PullBlock][]; - const nodeText = gitHubNodeResult[0]?.[0]?.[":block/string"] || ""; - if (!nodeText) return; + // Only check pages if the feature is globally enabled + if (!enabled) return false; const discourseNodes = getDiscourseNodes(); - const selectedNode = discourseNodes.find((node) => node.text === nodeText); - const isPageTypeOfNode = matchDiscourseNode({ - format: selectedNode?.format || "", - specification: selectedNode?.specification || [], - text: selectedNode?.text || "", - title: pageTitle, - }); - return isPageTypeOfNode; + return discourseNodes.some( + (node) => + node.githubSync && + matchDiscourseNode({ + format: node.format || "", + specification: node.specification || [], + text: node.text || "", + title: pageTitle, + }), + ); }; export const renderGitHubSyncPage = async ({ @@ -944,36 +953,6 @@ const initializeGitHubSync = async (onloadArgs: OnloadArgs) => { }, }, } as Field, - { - // @ts-ignore - Panel: SelectPanel, - title: "Node Select", - description: - "Select the node type to sync with GitHub Issues", - options: { - items: [ - "None", - ...getDiscourseNodes() - .map((node) => node.text) - .filter((text) => text !== "Block"), - ], - }, - defaultValue: "None", - }, - // @ts-ignore - { - Panel: CustomPanel, - title: "Comments Block", - description: - "Where comments are synced to. This will fire when the node is loaded. You have access to ':in NODETEXT' and ':in NODEUID' as variables for the current node.", - options: { - component: ({ uid }) => - React.createElement(CommentsQuery, { - parentUid: uid, - onloadArgs, - }), - }, - } as Field, ], }, ], diff --git a/apps/roam/src/components/settings/NodeConfig.tsx b/apps/roam/src/components/settings/NodeConfig.tsx index 91112aae4..260bea9d1 100644 --- a/apps/roam/src/components/settings/NodeConfig.tsx +++ b/apps/roam/src/components/settings/NodeConfig.tsx @@ -12,6 +12,8 @@ import DiscourseNodeAttributes from "./DiscourseNodeAttributes"; import DiscourseNodeCanvasSettings from "./DiscourseNodeCanvasSettings"; import DiscourseNodeIndex from "./DiscourseNodeIndex"; import { OnloadArgs } from "roamjs-components/types"; +import CommentsQuery from "../GitHubSyncCommentsQuery"; +import { getBlockUidIfExists } from "~/utils/getBlockIdIfExists"; const NodeConfig = ({ node, @@ -31,7 +33,10 @@ const NodeConfig = ({ const templateUid = getUid("Template"); const overlayUid = getUid("Overlay"); const canvasUid = getUid("Canvas"); - const graphOverviewUid = getUid("Graph Overview"); + const graphOverviewUid = getBlockUidIfExists(node.type, "Graph Overview"); + const githubSyncUid = getBlockUidIfExists(node.type, "GitHub Sync"); + + const githubCommentsFormatUid = getUid("Comments Block"); const attributeNode = getSubTree({ parentUid: node.type, key: "Attributes", @@ -45,12 +50,13 @@ const NodeConfig = ({ onChange={(id) => setSelectedTabId(id)} selectedTabId={selectedTabId} renderActiveTabPanelOnly={true} + className="discourse-node-tabs overflow-x-auto" > +
+
+
+
+
+
} /> + +
+

+ GitHub integration allows you to sync {node.text} pages with + GitHub Issues. When enabled, you can: +

+
    +
  • Send pages to GitHub as issues
  • +
  • Import and sync comments from GitHub
  • +
  • Configure where comments appear in the page
  • +
+
+ + +
+ +
+
+ } + /> ); diff --git a/apps/roam/src/utils/getBlockIdIfExists.ts b/apps/roam/src/utils/getBlockIdIfExists.ts new file mode 100644 index 000000000..3964aac7d --- /dev/null +++ b/apps/roam/src/utils/getBlockIdIfExists.ts @@ -0,0 +1,8 @@ +import getBasicTreeByParentUid from "roamjs-components/queries/getBasicTreeByParentUid"; +import toFlexRegex from "roamjs-components/util/toFlexRegex"; + +export const getBlockUidIfExists = (parentUid: string, key: string): string => { + const tree = getBasicTreeByParentUid(parentUid); + const node = tree.find((s) => toFlexRegex(key).test(s.text.trim())); + return node?.uid || ""; +}; diff --git a/apps/roam/src/utils/getDiscourseNodes.ts b/apps/roam/src/utils/getDiscourseNodes.ts index 2879ee76a..ac1c945ab 100644 --- a/apps/roam/src/utils/getDiscourseNodes.ts +++ b/apps/roam/src/utils/getDiscourseNodes.ts @@ -25,6 +25,8 @@ export type DiscourseNode = { graphOverview?: boolean; description?: string; template?: InputTextNode[]; + githubSync?: boolean; + githubCommentsQueryUid?: string; }; const DEFAULT_NODES: DiscourseNode[] = [ @@ -95,6 +97,11 @@ const getDiscourseNodes = (relations = getDiscourseRelations()) => { ), graphOverview: children.filter((c) => c.text === "Graph Overview").length > 0, + githubSync: children.filter((c) => c.text === "GitHub Sync").length > 0, + githubCommentsQueryUid: getSubTree({ + tree: children, + key: "Comments Block", + }).uid, description: getSettingValueFromTree({ tree: children, key: "description", From 00118edbd47a7071c658beebd2e3ec4c3c679d77 Mon Sep 17 00:00:00 2001 From: Trang Doan Date: Mon, 14 Apr 2025 12:43:57 -0400 Subject: [PATCH 2/9] improve in UI: if sync is turned off then also turn off the comments configuration --- apps/roam/src/components/GitHubSync.tsx | 8 ------ .../src/components/settings/NodeConfig.tsx | 26 +++++++++++++------ 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/apps/roam/src/components/GitHubSync.tsx b/apps/roam/src/components/GitHubSync.tsx index da5e5311a..fb86590a2 100644 --- a/apps/roam/src/components/GitHubSync.tsx +++ b/apps/roam/src/components/GitHubSync.tsx @@ -110,7 +110,6 @@ const getRoamCommentsContainerUid = async ({ }) => { const pageTitle = getPageTitleByPageUid(pageUid); - // Find the node type that matches this page const discourseNodes = getDiscourseNodes(); let matchingNode; @@ -131,15 +130,9 @@ const getRoamCommentsContainerUid = async ({ } if (!matchingNode || !matchingNode.githubCommentsQueryUid) { - renderToast({ - id: "github-issue-comments", - content: - "Comments Block query not set. Configure it in the Discourse Graph settings.", - }); return; } - // Use the node-specific query const results = await runQuery({ extensionAPI, parentUid: matchingNode.githubCommentsQueryUid, @@ -269,7 +262,6 @@ export const insertNewCommentsFromGitHub = async ({ }; export const isGitHubSyncPage = (pageTitle: string) => { - // Only check pages if the feature is globally enabled if (!enabled) return false; const discourseNodes = getDiscourseNodes(); diff --git a/apps/roam/src/components/settings/NodeConfig.tsx b/apps/roam/src/components/settings/NodeConfig.tsx index 260bea9d1..625b2eadd 100644 --- a/apps/roam/src/components/settings/NodeConfig.tsx +++ b/apps/roam/src/components/settings/NodeConfig.tsx @@ -43,6 +43,9 @@ const NodeConfig = ({ }); const [selectedTabId, setSelectedTabId] = useState("main"); + const [isGithubSyncEnabled, setIsGithubSyncEnabled] = useState( + node.githubSync || false, + ); return ( <> @@ -189,20 +192,27 @@ const NodeConfig = ({ parentUid={node.type} uid={githubSyncUid} value={node.githubSync} + options={{ + onChange: (checked) => setIsGithubSyncEnabled(checked), + }} /> -
-
+ )}
} /> From c796dbec8eeb822115fce6d0d9846851be600087 Mon Sep 17 00:00:00 2001 From: Trang Doan Date: Mon, 21 Apr 2025 16:55:12 -0400 Subject: [PATCH 3/9] address PR comments --- apps/roam/src/components/GitHubSync.tsx | 76 ++--------- .../src/components/settings/NodeConfig.tsx | 118 ++++++++++++++---- apps/roam/src/utils/getBlockIdIfExists.ts | 8 -- apps/roam/src/utils/getDiscourseNodes.ts | 20 ++- .../utils/initializeObserversAndListeners.ts | 5 +- 5 files changed, 125 insertions(+), 102 deletions(-) delete mode 100644 apps/roam/src/utils/getBlockIdIfExists.ts diff --git a/apps/roam/src/components/GitHubSync.tsx b/apps/roam/src/components/GitHubSync.tsx index fb86590a2..4d37d2b58 100644 --- a/apps/roam/src/components/GitHubSync.tsx +++ b/apps/roam/src/components/GitHubSync.tsx @@ -5,7 +5,7 @@ import React, { useRef, useState, } from "react"; -import getDiscourseNodes from "~/utils/getDiscourseNodes"; +import getDiscourseNodes, { DiscourseNode } from "~/utils/getDiscourseNodes"; import matchDiscourseNode from "~/utils/matchDiscourseNode"; import { OnloadArgs, PullBlock, RoamBasicNode } from "roamjs-components/types"; import { Button, Card, Classes, Dialog, Tag } from "@blueprintjs/core"; @@ -48,7 +48,6 @@ import { } from "roamjs-components/components/ConfigPanels/types"; import CustomPanel from "roamjs-components/components/ConfigPanels/CustomPanel"; import getShallowTreeByParentUid from "roamjs-components/queries/getShallowTreeByParentUid"; - import isFlagEnabled from "~/utils/isFlagEnabled"; const CommentUidCache = new Set(); @@ -104,32 +103,15 @@ const getPageGitHubPropsDetails = (pageUid: string) => { const getRoamCommentsContainerUid = async ({ pageUid, extensionAPI, + matchingNode, }: { pageUid: string; extensionAPI: OnloadArgs["extensionAPI"]; + matchingNode?: DiscourseNode; }) => { const pageTitle = getPageTitleByPageUid(pageUid); - const discourseNodes = getDiscourseNodes(); - let matchingNode; - - for (const node of discourseNodes) { - if (node.githubSync) { - const isMatch = matchDiscourseNode({ - format: node.format || "", - specification: node.specification || [], - text: node.text || "", - title: pageTitle, - }); - - if (isMatch) { - matchingNode = node; - break; - } - } - } - - if (!matchingNode || !matchingNode.githubCommentsQueryUid) { + if (!matchingNode?.githubCommentsQueryUid || !matchingNode) { return; } @@ -144,9 +126,11 @@ const getRoamCommentsContainerUid = async ({ export const insertNewCommentsFromGitHub = async ({ pageUid, extensionAPI, + matchingNode, }: { pageUid: string; extensionAPI: OnloadArgs["extensionAPI"]; + matchingNode?: DiscourseNode; }) => { const getCommentsOnPage = (pageUid: string) => { const query = `[:find @@ -181,6 +165,7 @@ export const insertNewCommentsFromGitHub = async ({ const commentsContainerUid = await getRoamCommentsContainerUid({ pageUid, extensionAPI, + matchingNode, }); const gitHubAccessToken = localStorageGet("github-oauth"); @@ -262,10 +247,10 @@ export const insertNewCommentsFromGitHub = async ({ }; export const isGitHubSyncPage = (pageTitle: string) => { - if (!enabled) return false; + if (!enabled) return null; const discourseNodes = getDiscourseNodes(); - return discourseNodes.some( + return discourseNodes.find( (node) => node.githubSync && matchDiscourseNode({ @@ -281,10 +266,12 @@ export const renderGitHubSyncPage = async ({ title, h1, onloadArgs, + matchingNode, }: { title: string; h1: HTMLHeadingElement; onloadArgs: OnloadArgs; + matchingNode: DiscourseNode; }) => { const extensionAPI = onloadArgs.extensionAPI; const pageUid = getPageUidByPageTitle(title); @@ -292,6 +279,7 @@ export const renderGitHubSyncPage = async ({ const commentsContainerUid = await getRoamCommentsContainerUid({ pageUid, extensionAPI, + matchingNode, }); const commentHeaderEl = document.querySelector( `.rm-block__input[id$="${commentsContainerUid}"]`, @@ -914,43 +902,6 @@ const initializeGitHubSync = async (onloadArgs: OnloadArgs) => { const unloads = new Set<() => void>(); const toggle = async (flag: boolean) => { if (flag && !enabled) { - const { observer: configObserver } = await createConfigObserver({ - title: "roam/js/github-sync", - config: { - tabs: [ - { - id: "home", - fields: [ - // @ts-ignore - { - title: "Docs", - description: `More information about the GitHub Sync Feature.`, - Panel: CustomPanel, - options: { - component: () => { - return ( -
-

- For more information about the GitHub Sync feature, - visit the GitHub page: -

- - GitHub Sync Documentation - -
- ); - }, - }, - } as Field, - ], - }, - ], - }, - }); - const commentObserver = createBlockObserver({ onBlockLoad: (b) => { const { blockUid } = getUids(b); @@ -984,7 +935,6 @@ const initializeGitHubSync = async (onloadArgs: OnloadArgs) => { }, }); - unloads.add(() => configObserver?.disconnect()); unloads.add(() => commentObserver.forEach((o) => o.disconnect())); unloads.add(() => CommentUidCache.clear()); unloads.add(() => CommentContainerUidCache.clear()); @@ -997,7 +947,6 @@ const initializeGitHubSync = async (onloadArgs: OnloadArgs) => { await toggle(isFlagEnabled(SETTING)); return toggle; }; - export const toggleGitHubSync = async ( flag: boolean, onloadArgs: OnloadArgs, @@ -1007,3 +956,4 @@ export const toggleGitHubSync = async ( }; export default initializeGitHubSync; + diff --git a/apps/roam/src/components/settings/NodeConfig.tsx b/apps/roam/src/components/settings/NodeConfig.tsx index 625b2eadd..0d34ddd61 100644 --- a/apps/roam/src/components/settings/NodeConfig.tsx +++ b/apps/roam/src/components/settings/NodeConfig.tsx @@ -4,16 +4,23 @@ import FlagPanel from "roamjs-components/components/ConfigPanels/FlagPanel"; import SelectPanel from "roamjs-components/components/ConfigPanels/SelectPanel"; import BlocksPanel from "roamjs-components/components/ConfigPanels/BlocksPanel"; import TextPanel from "roamjs-components/components/ConfigPanels/TextPanel"; -import { getSubTree } from "roamjs-components/util"; +import { getSubTree, setInputSetting } from "roamjs-components/util"; import Description from "roamjs-components/components/Description"; -import { Label, Tabs, Tab, TabId } from "@blueprintjs/core"; +import { + Label, + Tabs, + Tab, + TabId, + Checkbox, + Icon, + Tooltip, +} from "@blueprintjs/core"; import DiscourseNodeSpecification from "./DiscourseNodeSpecification"; import DiscourseNodeAttributes from "./DiscourseNodeAttributes"; import DiscourseNodeCanvasSettings from "./DiscourseNodeCanvasSettings"; import DiscourseNodeIndex from "./DiscourseNodeIndex"; import { OnloadArgs } from "roamjs-components/types"; import CommentsQuery from "../GitHubSyncCommentsQuery"; -import { getBlockUidIfExists } from "~/utils/getBlockIdIfExists"; const NodeConfig = ({ node, @@ -33,10 +40,13 @@ const NodeConfig = ({ const templateUid = getUid("Template"); const overlayUid = getUid("Overlay"); const canvasUid = getUid("Canvas"); - const graphOverviewUid = getBlockUidIfExists(node.type, "Graph Overview"); - const githubSyncUid = getBlockUidIfExists(node.type, "GitHub Sync"); + const graphOverviewUid = getUid("Graph Overview"); + const githubSyncUid = getUid("GitHub Sync"); + const githubCommentsFormatUid = getSubTree({ + parentUid: githubSyncUid || "", + key: "Comments Block", + }).uid; - const githubCommentsFormatUid = getUid("Comments Block"); const attributeNode = getSubTree({ parentUid: node.type, key: "Attributes", @@ -46,6 +56,9 @@ const NodeConfig = ({ const [isGithubSyncEnabled, setIsGithubSyncEnabled] = useState( node.githubSync || false, ); + const [isGraphOverviewEnabled, setIsGraphOverviewEnabled] = useState( + node.graphOverview || false, + ); return ( <> @@ -158,14 +171,42 @@ const NodeConfig = ({ panel={
- +
+ { + const target = e.target as HTMLInputElement; + setIsGraphOverviewEnabled(target.checked); + if (target.checked) { + setInputSetting({ + blockUid: node.type, + key: "Graph Overview", + value: "true", + }); + } else { + setInputSetting({ + blockUid: node.type, + key: "Graph Overview", + value: "false", + }); + } + }} + > + Graph Overview + + + + +
} /> @@ -173,7 +214,7 @@ const NodeConfig = ({ id="github" title="GitHub" panel={ -
+

GitHub integration allows you to sync {node.text} pages with @@ -185,20 +226,43 @@ const NodeConfig = ({

  • Configure where comments appear in the page
  • - setIsGithubSyncEnabled(checked), - }} - /> +
    + { + const target = e.target as HTMLInputElement; + setIsGithubSyncEnabled(target.checked); + if (target.checked) { + setInputSetting({ + blockUid: githubSyncUid, + key: "Enabled", + value: "true", + }); + } else { + setInputSetting({ + blockUid: githubSyncUid, + key: "Enabled", + value: "false", + }); + } + }} + > + GitHub Sync Enabled + + + + +
    {isGithubSyncEnabled && ( -
    +
    }