From 02ffabd3842803a02029fa017474fab08a491b93 Mon Sep 17 00:00:00 2001 From: Michael Gartner Date: Fri, 27 Dec 2024 09:12:14 -0600 Subject: [PATCH 1/5] finish integration --- apps/roam/package.json | 2 +- apps/roam/src/components/DiscourseContext.tsx | 49 ++ apps/roam/src/components/LivePreview.tsx | 7 - apps/roam/src/components/ReferenceContext.tsx | 88 ---- .../src/components/canvas/LabelDialog.tsx | 2 +- apps/roam/src/components/index.ts | 1 - .../src/data/defaultDiscourseRelations.ts | 38 ++ apps/roam/src/discourseGraphsMode.ts | 454 ------------------ apps/roam/src/index.ts | 105 ++-- apps/roam/src/settings/configPages.ts | 144 ++++++ .../utils/getPlainTitleFromSpecification.ts | 24 + apps/roam/src/utils/graphViewNodeStyling.ts | 61 +++ .../src/utils/initializeDiscourseNodes.ts | 36 ++ .../utils/initializeObserversAndListeners.ts | 164 +++++++ apps/roam/src/utils/predefinedSelections.ts | 28 ++ apps/roam/src/utils/refreshConfigTree.ts | 2 +- apps/roam/src/utils/registerSmartBlock.ts | 4 +- .../utils/renderLinkedReferenceAdditions.ts | 51 ++ apps/roam/src/utils/setQueryPages.ts | 18 + package-lock.json | 8 +- 20 files changed, 650 insertions(+), 636 deletions(-) delete mode 100644 apps/roam/src/components/ReferenceContext.tsx create mode 100644 apps/roam/src/utils/getPlainTitleFromSpecification.ts create mode 100644 apps/roam/src/utils/graphViewNodeStyling.ts create mode 100644 apps/roam/src/utils/initializeDiscourseNodes.ts create mode 100644 apps/roam/src/utils/initializeObserversAndListeners.ts create mode 100644 apps/roam/src/utils/renderLinkedReferenceAdditions.ts create mode 100644 apps/roam/src/utils/setQueryPages.ts diff --git a/apps/roam/package.json b/apps/roam/package.json index 59b2a577c..deca7ed44 100644 --- a/apps/roam/package.json +++ b/apps/roam/package.json @@ -33,7 +33,7 @@ "react-draggable": "^4.4.5", "react-in-viewport": "^1.0.0-alpha.20", "react-vertical-timeline-component": "^3.5.2", - "roamjs-components": "^0.84.0", + "roamjs-components": "^0.84.1", "signia-react": "^0.1.1" }, "overrides": { diff --git a/apps/roam/src/components/DiscourseContext.tsx b/apps/roam/src/components/DiscourseContext.tsx index 058257850..5a25f32d8 100644 --- a/apps/roam/src/components/DiscourseContext.tsx +++ b/apps/roam/src/components/DiscourseContext.tsx @@ -21,6 +21,12 @@ import { Result } from "roamjs-components/types/query-builder"; import nanoId from "nanoid"; import getDiscourseContextResults from "../utils/getDiscourseContextResults"; import ResultsView from "./ResultsView/ResultsView"; +import { getPageTitleValueByHtmlElement } from "roamjs-components/dom"; +import getPageUidByPageTitle from "roamjs-components/queries/getPageUidByPageTitle"; +import renderWithUnmount from "roamjs-components/util/renderWithUnmount"; +import isDiscourseNode from "~/utils/isDiscourseNode"; +import CanvasReferences from "./canvas/CanvasReferences"; +import { OnloadArgs } from "roamjs-components/types/native"; export type DiscourseContextResults = Awaited< ReturnType @@ -457,4 +463,47 @@ const useDebounce = (value: T, delay: number): T => { return debouncedValue; }; +export const observerCallback = async ( + div: HTMLDivElement, + args: OnloadArgs, +) => { + const isMainWindow = !!div.closest(".roam-article"); + const uid = isMainWindow + ? await window.roamAlphaAPI.ui.mainWindow.getOpenPageOrBlockUid() + : getPageUidByPageTitle(getPageTitleValueByHtmlElement(div)); + if ( + uid && + isDiscourseNode(uid) && + !div.getAttribute("data-roamjs-discourse-context") + ) { + div.setAttribute("data-roamjs-discourse-context", "true"); + const parent = div.firstElementChild; + if (parent) { + const insertBefore = parent.firstElementChild; + + const p = document.createElement("div"); + parent.insertBefore(p, insertBefore); + renderWithUnmount( + React.createElement(DiscourseContext, { + uid, + results: [], + args, + }), + p, + args, + ); + + const canvasP = document.createElement("div"); + parent.insertBefore(canvasP, insertBefore); + renderWithUnmount( + React.createElement(CanvasReferences, { + uid, + }), + canvasP, + args, + ); + } + } +}; + export default DiscourseContext; diff --git a/apps/roam/src/components/LivePreview.tsx b/apps/roam/src/components/LivePreview.tsx index eb8a56aa6..2c36bc0f3 100644 --- a/apps/roam/src/components/LivePreview.tsx +++ b/apps/roam/src/components/LivePreview.tsx @@ -10,8 +10,6 @@ import React, { import ReactDOM from "react-dom"; import getChildrenLengthByPageUid from "roamjs-components/queries/getChildrenLengthByPageUid"; import getPageUidByPageTitle from "roamjs-components/queries/getPageUidByPageTitle"; -import { render as renderReferenceContext } from "./ReferenceContext"; -import isDiscourseNode from "../utils/isDiscourseNode"; const sizes = [300, 400, 500, 600]; @@ -50,11 +48,6 @@ const TooltipContent = ({ containerRef.current.parentElement.style.padding = "0"; newIsEmpty = false; } - if (isDiscourseNode(uid) && containerRef.current) { - const refs = renderReferenceContext({ title: tag }); - containerRef.current.appendChild(refs); - newIsEmpty = newIsEmpty && !refs.childElementCount; - } setIsEmpty(newIsEmpty); }, [uid, containerRef, numChildren, tag, setIsEmpty]); return ( diff --git a/apps/roam/src/components/ReferenceContext.tsx b/apps/roam/src/components/ReferenceContext.tsx deleted file mode 100644 index e98664ee2..000000000 --- a/apps/roam/src/components/ReferenceContext.tsx +++ /dev/null @@ -1,88 +0,0 @@ -import React, { useEffect, useMemo, useRef, useState } from "react"; -import ReactDOM from "react-dom"; -import type { PullBlock } from "roamjs-components/types/native"; - -type Props = { title: string }; - -const Content = ({ title, uid }: { title: string; uid: string }) => { - const sectionRef = useRef(null); - useEffect(() => { - const el = sectionRef.current; - if (el) - window.roamAlphaAPI.ui.components.renderBlock({ - el, - uid, - }); - }, [uid]); - return ( -
-
{title}
-
-
- ); -}; - -const ContextContent = ({ title }: Props) => { - const queryResults = useMemo( - () => - ( - window.roamAlphaAPI.data.fast.q( - `[:find (pull ?pr [:node/title]) (pull ?r [:block/uid :block/children :create/time]) :where [?p :node/title "${title}"] [?r :block/refs ?p] [?r :block/page ?pr]]`, - ) as [PullBlock, PullBlock][] - ) - .filter( - ([, { [":block/children"]: children = [] }]) => !!children.length, - ) - .sort( - ([, { [":create/time"]: a = 0 }], [, { [":create/time"]: b = 0 }]) => - a - b, - ), - [title], - ); - return ( - <> - {queryResults.map( - ([{ [":node/title"]: title = "" }, { [":block/uid"]: uid = "" }]) => ( - - ), - )} - - ); -}; - -const ReferenceContext = ({ title }: Props) => { - const [caretShown, setCaretShown] = useState(false); - const [caretOpen, setCaretOpen] = useState(false); - return ( - <> -
setCaretShown(true)} - onMouseLeave={() => setCaretShown(false)} - style={{ marginBottom: 4 }} - > - setCaretOpen(!caretOpen)} - /> -
-
- Reference Context -
-
- {caretOpen && } - - ); -}; - -export const render = (props: Props) => { - const container = document.createElement("div"); - ReactDOM.render(, container); - return container; -}; - -export default ReferenceContext; diff --git a/apps/roam/src/components/canvas/LabelDialog.tsx b/apps/roam/src/components/canvas/LabelDialog.tsx index a64505c8d..7971915ba 100644 --- a/apps/roam/src/components/canvas/LabelDialog.tsx +++ b/apps/roam/src/components/canvas/LabelDialog.tsx @@ -17,7 +17,7 @@ import { RoamOverlayProps } from "roamjs-components/util/renderOverlay"; import { Result } from "~/utils/types"; import AutocompleteInput from "roamjs-components/components/AutocompleteInput"; import { DiscourseContextType } from "./Tldraw"; -import { getPlainTitleFromSpecification } from "../../discourseGraphsMode"; +import { getPlainTitleFromSpecification } from "~/utils/getPlainTitleFromSpecification"; import isLiveBlock from "roamjs-components/queries/isLiveBlock"; import getPageTitleByPageUid from "roamjs-components/queries/getPageTitleByPageUid"; import getTextByBlockUid from "roamjs-components/queries/getTextByBlockUid"; diff --git a/apps/roam/src/components/index.ts b/apps/roam/src/components/index.ts index b6aaf09d3..f4c1df4ba 100644 --- a/apps/roam/src/components/index.ts +++ b/apps/roam/src/components/index.ts @@ -16,7 +16,6 @@ export { default as QueryDrawer } from "./QueryDrawer"; export { default as QueryEditor } from "./QueryEditor"; export { default as QueryBuilder } from "./QueryBuilder"; export { default as QueryPagesPanel } from "./settings/QueryPagesPanel"; -export { default as ReferenceContext } from "./ReferenceContext"; export { default as ResizableDrawer } from "./ResizableDrawer"; export { default as ResultsView } from "./ResultsView/ResultsView"; export { default as Timeline } from "./ResultsView/views/Timeline"; diff --git a/apps/roam/src/data/defaultDiscourseRelations.ts b/apps/roam/src/data/defaultDiscourseRelations.ts index 5eaa0ddae..48a3bd482 100644 --- a/apps/roam/src/data/defaultDiscourseRelations.ts +++ b/apps/roam/src/data/defaultDiscourseRelations.ts @@ -35,6 +35,16 @@ const DEFAULT_RELATION_VALUES: InputTextNode[] = [ { text: "is a", children: [{ text: "destination" }] }, ], }, + { + text: "Node Positions", + children: [ + { text: "0", children: [{ text: "100 57" }] }, + { text: "1", children: [{ text: "100 208" }] }, + { text: "2", children: [{ text: "100 345" }] }, + { text: "source", children: [{ text: "281 57" }] }, + { text: "destination", children: [{ text: "281 345" }] }, + ], + }, ], }, ], @@ -125,6 +135,20 @@ const DEFAULT_RELATION_VALUES: InputTextNode[] = [ }, ], }, + { + text: "Node Positions", + children: [ + { text: "0", children: [{ text: "250 325" }] }, + { text: "1", children: [{ text: "100 325" }] }, + { text: "2", children: [{ text: "100 200" }] }, + { text: "3", children: [{ text: "250 200" }] }, + { text: "4", children: [{ text: "400 200" }] }, + { text: "5", children: [{ text: "100 75" }] }, + { text: "6", children: [{ text: "250 75" }] }, + { text: "source", children: [{ text: "400 325" }] }, + { text: "destination", children: [{ text: "400 75" }] }, + ], + }, ], }, ], @@ -215,6 +239,20 @@ const DEFAULT_RELATION_VALUES: InputTextNode[] = [ }, ], }, + { + text: "Node Positions", + children: [ + { text: "0", children: [{ text: "250 325" }] }, + { text: "1", children: [{ text: "100 325" }] }, + { text: "2", children: [{ text: "100 200" }] }, + { text: "3", children: [{ text: "250 200" }] }, + { text: "4", children: [{ text: "400 200" }] }, + { text: "5", children: [{ text: "100 75" }] }, + { text: "6", children: [{ text: "250 75" }] }, + { text: "source", children: [{ text: "400 325" }] }, + { text: "destination", children: [{ text: "400 75" }] }, + ], + }, ], }, ], diff --git a/apps/roam/src/discourseGraphsMode.ts b/apps/roam/src/discourseGraphsMode.ts index 0b3e1e792..ba682c200 100644 --- a/apps/roam/src/discourseGraphsMode.ts +++ b/apps/roam/src/discourseGraphsMode.ts @@ -22,12 +22,10 @@ import refreshConfigTree from "./utils/refreshConfigTree"; import getBasicTreeByParentUid from "roamjs-components/queries/getBasicTreeByParentUid"; import getSettingValueFromTree from "roamjs-components/util/getSettingValueFromTree"; import { render } from "./components/DiscourseNodeMenu"; -import { render as renderReferenceContext } from "./components/ReferenceContext"; import DiscourseContext from "./components/DiscourseContext"; import createHTMLObserver from "roamjs-components/dom/createHTMLObserver"; import getPageUidByPageTitle from "roamjs-components/queries/getPageUidByPageTitle"; import isDiscourseNode from "./utils/isDiscourseNode"; -import isFlagEnabled from "./utils/isFlagEnabled"; import addStyle from "roamjs-components/dom/addStyle"; import { registerSelection } from "./utils/predefinedSelections"; import deriveNodeAttribute from "./utils/deriveDiscourseNodeAttribute"; @@ -39,7 +37,6 @@ import createPage from "roamjs-components/writes/createPage"; import INITIAL_NODE_VALUES from "./data/defaultDiscourseNodes"; import CanvasReferences from "./components/canvas/CanvasReferences"; import { render as renderGraphOverviewExport } from "./components/ExportDiscourseContext"; -import { Condition, QBClause } from "./utils/types"; import styles from "./styles/discourseGraphStyles.css"; import { DISCOURSE_CONFIG_PAGE_TITLE } from "./settings/configPages"; import { formatHexColor } from "./components/settings/DiscourseNodeCanvasSettings"; @@ -49,462 +46,11 @@ import { previewPageRefHandler, } from "./utils/pageRefObserverHandlers"; -export const getPlainTitleFromSpecification = ({ - specification, - text, -}: { - specification: Condition[]; - text: string; -}) => { - // Assumptions: - // - Conditions are properly ordered - // - There is a 'has title' condition somewhere - const titleCondition = specification.find( - (s): s is QBClause => - s.type === "clause" && s.relation === "has title" && s.source === text, - ); - if (!titleCondition) return ""; - return titleCondition.target - .replace(/^\/(\^)?/, "") - .replace(/(\$)?\/$/, "") - .replace(/\\\[/g, "[") - .replace(/\\\]/g, "]") - .replace(/\(\.[\*\+](\?)?\)/g, ""); -}; - const initializeDiscourseGraphsMode = async (args: OnloadArgs) => { const unloads = new Set<() => void>(); - window.roamjs.version = { - ...window.roamjs.version, - ["discourse-graph"]: args.extension.version, - }; - unloads.add(function removeVersion() { - delete window.roamjs.version.discourseGraph; - unloads.delete(removeVersion); - }); - - const style = addStyle(styles); - unloads.add(function removeStyle() { - style.remove(); - unloads.delete(removeStyle); - }); - - const { pageUid, observer } = await createConfigObserver({ - title: DISCOURSE_CONFIG_PAGE_TITLE, - config: { - tabs: [ - { - id: "home", - fields: [ - { - title: "trigger", - description: - "The trigger to create the node menu. Must refresh after editing", - defaultValue: "\\", - // @ts-ignore - Panel: TextPanel, - }, - // @ts-ignore - { - title: "disable sidebar open", - description: - "Disable opening new nodes in the sidebar when created", - Panel: FlagPanel, - } as Field, - // @ts-ignore - { - title: "preview", - description: - "Whether or not to display page previews when hovering over page refs", - Panel: FlagPanel, - options: { - onChange: onPageRefObserverChange(previewPageRefHandler), - }, - } as Field, - ], - }, - { - id: "grammar", - fields: [ - // @ts-ignore - { - title: "nodes", - Panel: CustomPanel, - description: "The types of nodes in your discourse graph", - options: { - component: DiscourseNodeConfigPanel, - }, - } as Field, - // @ts-ignore - { - title: "relations", - Panel: CustomPanel, - description: "The types of relations in your discourse graph", - defaultValue: DEFAULT_RELATION_VALUES, - options: { - component: DiscourseRelationConfigPanel, - }, - } as Field, - // @ts-ignore - { - title: "overlay", - Panel: FlagPanel, - // description: - // "Whether to overlay discourse context information over node references", - description: "Currently disabled. Being reworked.", - disabled: true, - options: { - onChange: (val) => { - onPageRefObserverChange((s) => - overlayPageRefHandler(s, args), - )(val); - }, - }, - } as Field, - ], - }, - { - id: "export", - fields: [ - { - title: "max filename length", - // @ts-ignore - Panel: NumberPanel, - description: - "Set the maximum name length for markdown file exports", - defaultValue: 64, - }, - { - title: "remove special characters", - // @ts-ignore - Panel: FlagPanel, - description: - "Whether or not to remove the special characters in a file name", - }, - { - title: "simplified filename", - // @ts-ignore - Panel: FlagPanel, - description: - "For discourse nodes, extract out the {content} from the page name to become the file name", - }, - { - title: "frontmatter", - // @ts-ignore - Panel: MultiTextPanel, - description: - "Specify all the lines that should go to the Frontmatter of the markdown file", - }, - { - title: "resolve block references", - // @ts-ignore - Panel: FlagPanel, - description: - "Replaces block references in the markdown content with the block's content", - }, - { - title: "resolve block embeds", - // @ts-ignore - Panel: FlagPanel, - description: - "Replaces block embeds in the markdown content with the block's content tree", - }, - // @ts-ignore - { - title: "link type", - Panel: SelectPanel, - description: "How to format links that appear in your export.", - options: { - items: ["alias", "wikilinks", "roam url"], - }, - } as Field, - { - title: "append referenced node", - // @ts-ignore - Panel: FlagPanel, - description: - "If a referenced node is defined in a node's format, it will be appended to the discourse context", - }, - ], - }, - ], - }, - }); - unloads.add(function configObserverDisconnect() { - observer?.disconnect(); - unloads.delete(configObserverDisconnect); - }); refreshConfigTree(); - const nodes = getDiscourseNodes().filter(excludeDefaultNodes); - if (nodes.length === 0) { - await Promise.all( - INITIAL_NODE_VALUES.map( - (n) => - getPageUidByPageTitle(`discourse-graph/nodes/${n.text}`) || - createPage({ - title: `discourse-graph/nodes/${n.text}`, - uid: n.type, - tree: [ - { text: "Format", children: [{ text: n.format || "" }] }, - { text: "Shortcut", children: [{ text: n.shortcut || "" }] }, - { text: "Graph Overview" }, - { - text: "Canvas", - children: [ - { - text: "color", - children: [{ text: n.canvasSettings?.color || "" }], - }, - ], - }, - ], - }), - ), - ); - } - - const hashChangeListener = (e: HashChangeEvent) => { - if ( - e.oldURL.endsWith(pageUid) || - getDiscourseNodes().some(({ type }) => e.oldURL.endsWith(type)) - ) { - refreshConfigTree(); - } - }; - window.addEventListener("hashchange", hashChangeListener); - unloads.add(function removeHashChangeListener() { - window.removeEventListener("hashchange", hashChangeListener); - unloads.delete(removeHashChangeListener); - }); - - const unregisterDatalog = refreshConfigTree().concat([ - registerSelection({ - test: /^(.*)-(.*)$/, - pull: ({ returnNode }) => `(pull ?${returnNode} [:node/title])`, - mapper: () => { - return `This selection is deprecated. Define a Node Attribute and use \`discourse:attribute\` instead.`; - }, - }), - registerSelection({ - test: /^discourse:(.*)$/, - pull: ({ returnNode }) => `(pull ?${returnNode} [:block/uid])`, - mapper: (r, key) => { - const attribute = key.substring("discourse:".length); - const uid = r[":block/uid"] || ""; - return deriveNodeAttribute({ uid, attribute }); - }, - }), - registerSelection({ - test: /^\s*type\s*$/i, - pull: ({ returnNode }) => - `(pull ?${returnNode} [:node/title :block/string])`, - mapper: (r) => { - const title = r[":node/title"] || ""; - return ( - getDiscourseNodes().find((n) => - matchDiscourseNode({ - ...n, - title, - }), - )?.text || (r[":block/string"] ? "block" : "page") - ); - }, - }), - ]); - unloads.add(function unregisterAllDatalog() { - unregisterDatalog.forEach((u) => u()); - unloads.delete(unregisterAllDatalog); - }); - const configTree = getBasicTreeByParentUid(pageUid); - - const trigger = getSettingValueFromTree({ - tree: configTree, - key: "trigger", - defaultValue: "\\", - }).trim(); - const keydownListener = (e: KeyboardEvent) => { - if (e.key === trigger) { - const target = e.target as HTMLElement; - if ( - target.tagName === "TEXTAREA" && - target.classList.contains("rm-block-input") - ) { - render({ textarea: target as HTMLTextAreaElement }); - e.preventDefault(); - e.stopPropagation(); - } - } - }; - document.addEventListener("keydown", keydownListener); - unloads.add(function removeKeydownListener() { - document.removeEventListener("keydown", keydownListener); - unloads.delete(removeKeydownListener); - }); - - const discourseContextObserver = createHTMLObserver({ - tag: "DIV", - useBody: true, - className: "rm-reference-main", - callback: async (d: HTMLElement) => { - const isMain = !!d.closest(".roam-article"); - const uid = isMain - ? await window.roamAlphaAPI.ui.mainWindow.getOpenPageOrBlockUid() - : getPageUidByPageTitle(getPageTitleValueByHtmlElement(d)); - if ( - uid && - isDiscourseNode(uid) && - !d.getAttribute("data-roamjs-discourse-context") - ) { - d.setAttribute("data-roamjs-discourse-context", "true"); - const parent = d.firstElementChild; - if (parent) { - const insertBefore = parent.firstElementChild; - - const p = document.createElement("div"); - parent.insertBefore(p, insertBefore); - renderWithUnmount( - React.createElement(DiscourseContext, { - uid, - results: [], - args, - }), - p, - args, - ); - - const canvasP = document.createElement("div"); - parent.insertBefore(canvasP, insertBefore); - renderWithUnmount( - React.createElement(CanvasReferences, { - uid, - }), - canvasP, - args, - ); - } - } - }, - }); - - const graphOverviewExportObserver = createHTMLObserver({ - tag: "DIV", - className: "rm-graph-view-control-panel__main-options", - callback: (el) => { - const div = el as HTMLDivElement; - renderGraphOverviewExport(div); - }, - }); - - unloads.add(function removeObservers() { - graphOverviewExportObserver.disconnect(); - discourseContextObserver.disconnect(); - - unloads.delete(removeObservers); - }); - - const queryPages = args.extensionAPI.settings.get("query-pages"); - const queryPageArray = Array.isArray(queryPages) - ? queryPages - : typeof queryPages === "object" - ? [] - : typeof queryPages === "string" && queryPages - ? [queryPages] - : []; - if (!queryPageArray.includes("discourse-graph/queries/*")) { - args.extensionAPI.settings.set("query-pages", [ - ...queryPageArray, - "discourse-graph/queries/*", - ]); - } - unloads.add(function removeQueryPage() { - args.extensionAPI.settings.set( - "query-pages", - ( - (args.extensionAPI.settings.get("query-pages") as string[]) || [] - ).filter((s) => s !== "discourse-graph/queries/*"), - ); - unloads.delete(removeQueryPage); - }); - - type SigmaRenderer = { - setSetting: (settingName: string, value: any) => void; - getSetting: (settingName: string) => any; - }; - type nodeData = { - x: number; - y: number; - label: string; - size: number; - }; - - window.roamAlphaAPI.ui.graphView.wholeGraph.addCallback({ - label: "discourse-node-styling", - callback: ({ "sigma-renderer": sigma }) => { - const sig = sigma as SigmaRenderer; - const allNodes = getDiscourseNodes(); - const prefixColors = allNodes.map((n) => { - const formattedTitle = getPlainTitleFromSpecification({ - specification: n.specification, - text: n.text, - }); - const formattedBackgroundColor = formatHexColor(n.canvasSettings.color); - - return { - prefix: formattedTitle, - color: formattedBackgroundColor, - showInGraphOverview: n.graphOverview, - }; - }); - - const originalReducer = sig.getSetting("nodeReducer"); - sig.setSetting("nodeReducer", (id: string, nodeData: nodeData) => { - let modifiedData = originalReducer - ? originalReducer(id, nodeData) - : nodeData; - - const { label } = modifiedData; - - for (const { prefix, color, showInGraphOverview } of prefixColors) { - if (prefix && showInGraphOverview && label.startsWith(prefix)) { - return { - ...modifiedData, - color, - }; - } - } - - return modifiedData; - }); - }, - }); - unloads.add(function removeGraphViewCallback() { - window.roamAlphaAPI.ui.graphView.wholeGraph.removeCallback({ - label: "discourse-node-styling", - }); - unloads.delete(removeGraphViewCallback); - }); - if (isFlagEnabled("render references")) { - createHTMLObserver({ - className: "rm-sidebar-window", - tag: "div", - callback: (d) => { - const label = d.querySelector( - ".window-headers div span", - ); - if (label && label.innerText.startsWith("Outline")) { - const titleEl = - d.querySelector(".rm-title-display"); - const title = titleEl && getPageTitleValueByHtmlElement(titleEl); - if (title && isDiscourseNode(getPageUidByPageTitle(title))) { - const container = renderReferenceContext({ title }); - d.appendChild(container); - } - } - }, - }); - } return () => { unloads.forEach((u) => u()); unloads.clear(); diff --git a/apps/roam/src/index.ts b/apps/roam/src/index.ts index ccf4a12cb..2338469e8 100644 --- a/apps/roam/src/index.ts +++ b/apps/roam/src/index.ts @@ -1,45 +1,29 @@ -import { - addStyle, - createHTMLObserver, - createButtonObserver, - getPageTitleValueByHtmlElement, -} from "roamjs-components/dom"; -import { createBlock } from "roamjs-components/writes"; +import { addStyle } from "roamjs-components/dom"; import { render as renderToast } from "roamjs-components/components/Toast"; import { runExtension } from "roamjs-components/util"; -import { renderTldrawCanvas } from "./components/canvas/Tldraw"; -import { renderQueryPage, renderQueryBlock } from "./components/QueryBuilder"; import { QueryBuilderLoadedToast } from "./components/toastMessages"; import runQuery from "./utils/runQuery"; import isDiscourseNode from "./utils/isDiscourseNode"; import { fireQuerySync } from "./utils/fireQuery"; import parseQuery from "./utils/parseQuery"; +import refreshConfigTree from "./utils/refreshConfigTree"; -import initializeDiscourseGraphsMode from "./discourseGraphsMode"; import styles from "./styles/styles.css"; import settingsStyles from "./styles/settingsStyles.css"; +import discourseGraphStyles from "./styles/discourseGraphStyles.css"; import { registerCommandPaletteCommands } from "./settings/commandPalette"; import { createSettingsPanel } from "./settings/settingsPanel"; -import { renderNodeConfigPage } from "./settings/configPages"; -import { isCurrentPageCanvas as isCanvasPage } from "./utils/isCanvasPage"; -import { isDiscourseNodeConfigPage as isNodeConfigPage } from "./utils/isDiscourseNodeConfigPage"; -import { isQueryPage } from "./utils/isQueryPage"; import { listActiveQueries } from "./utils/listActiveQueries"; import { registerSmartBlock } from "./utils/registerSmartBlock"; -import isFlagEnabled from "./utils/isFlagEnabled"; - -import { - enablePageRefObserver, - addPageRefObserver, - getPageRefObserversSize, - previewPageRefHandler, - overlayPageRefHandler, -} from "./utils/pageRefObserverHandlers"; +import { initObservers } from "./utils/initializeObserversAndListeners"; +import { addGraphViewNodeStyling } from "./utils/graphViewNodeStyling"; +import { setQueryPages } from "./utils/setQueryPages"; +import initializeDiscourseNodes from "./utils/initializeDiscourseNodes"; export const DEFAULT_CANVAS_PAGE_FORMAT = "Canvas/*"; @@ -62,63 +46,20 @@ export default runExtension(async (onloadArgs) => { timeout: 500, }); } + const { extensionAPI } = onloadArgs; + const style = addStyle(styles); + const dgraphStyles = addStyle(discourseGraphStyles); const settingsStyle = addStyle(settingsStyles); - const cleanupDiscourseGraphs = - await initializeDiscourseGraphsMode(onloadArgs); - - // Observers and Listeners - const pageTitleObserver = createHTMLObserver({ - tag: "H1", - className: "rm-title-display", - callback: (e) => { - const h1 = e as HTMLHeadingElement; - const title = getPageTitleValueByHtmlElement(h1); - const props = { title, h1, onloadArgs }; - - if (isNodeConfigPage(title)) renderNodeConfigPage(props); - else if (isQueryPage(props)) renderQueryPage(props); - else if (isCanvasPage(props)) renderTldrawCanvas(props); - }, - }); - - const queryBlockObserver = createButtonObserver({ - attribute: "query-block", - render: (b) => renderQueryBlock(b, onloadArgs), - }); - - const pageActionListener = (( - e: CustomEvent<{ - action: string; - uid: string; - val: string; - onRefresh: () => void; - }>, - ) => { - if (!/page/i.test(e.detail.action)) return; - window.roamAlphaAPI.ui.mainWindow - .getOpenPageOrBlockUid() - .then((u) => u || window.roamAlphaAPI.util.dateToPageUid(new Date())) - .then((parentUid) => { - createBlock({ - parentUid, - order: Number.MAX_VALUE, - node: { text: `[[${e.detail.val}]]` }, - }); - }); - }) as EventListener; + const { observers, listeners } = await initObservers({ onloadArgs }); + const [pageActionListener, hashChangeListener, nodeMenuTriggerListener] = + listeners; document.addEventListener("roamjs:query-builder:action", pageActionListener); + window.addEventListener("hashchange", hashChangeListener); + document.addEventListener("keydown", nodeMenuTriggerListener); - if (isFlagEnabled("preview")) addPageRefObserver(previewPageRefHandler); - if (isFlagEnabled("grammar.overlay")) { - addPageRefObserver((s) => overlayPageRefHandler(s, onloadArgs)); - } - if (!!getPageRefObserversSize()) enablePageRefObserver(); - - // Window - // @ts-ignore window.roamjs.extension.queryBuilder = { runQuery: (parentUid: string) => runQuery({ parentUid, extensionAPI }).then( @@ -132,22 +73,30 @@ export default runExtension(async (onloadArgs) => { isDiscourseNode: isDiscourseNode, }; + initializeDiscourseNodes(); + refreshConfigTree(); + addGraphViewNodeStyling(); registerCommandPaletteCommands(onloadArgs); createSettingsPanel(onloadArgs); - registerSmartBlock(extensionAPI); + registerSmartBlock(onloadArgs); + setQueryPages(onloadArgs); return { - elements: [style, settingsStyle], - observers: [pageTitleObserver, queryBlockObserver], + elements: [style, settingsStyle, dgraphStyles], + observers: observers, unload: () => { window.roamjs.extension?.smartblocks?.unregisterCommand("QUERYBUILDER"); - cleanupDiscourseGraphs(); // @ts-ignore - tldraw throws a warning on multiple loads delete window[Symbol.for("__signia__")]; document.removeEventListener( "roamjs:query-builder:action", pageActionListener, ); + window.removeEventListener("hashchange", hashChangeListener); + document.removeEventListener("keydown", nodeMenuTriggerListener); + window.roamAlphaAPI.ui.graphView.wholeGraph.removeCallback({ + label: "discourse-node-styling", + }); }, }; }); diff --git a/apps/roam/src/settings/configPages.ts b/apps/roam/src/settings/configPages.ts index b5ee299fc..f1c7929ae 100644 --- a/apps/roam/src/settings/configPages.ts +++ b/apps/roam/src/settings/configPages.ts @@ -4,6 +4,8 @@ import FlagPanel from "roamjs-components/components/ConfigPanels/FlagPanel"; import SelectPanel from "roamjs-components/components/ConfigPanels/SelectPanel"; import TextPanel from "roamjs-components/components/ConfigPanels/TextPanel"; import BlocksPanel from "roamjs-components/components/ConfigPanels/BlocksPanel"; +import MultiTextPanel from "roamjs-components/components/ConfigPanels/MultiTextPanel"; +import NumberPanel from "roamjs-components/components/ConfigPanels/NumberPanel"; import { Field, CustomField, @@ -23,6 +25,15 @@ import { } from "~/components"; import getDiscourseNodes from "~/utils/getDiscourseNodes"; import { render as configPageRender } from "roamjs-components/components/ConfigPage"; +import DiscourseNodeConfigPanel from "~/components/settings/DiscourseNodeConfigPanel"; +import DiscourseRelationConfigPanel from "~/components/settings/DiscourseRelationConfigPanel"; +import DEFAULT_RELATION_VALUES from "~/data/defaultDiscourseRelations"; +import { + onPageRefObserverChange, + previewPageRefHandler, + overlayPageRefHandler, +} from "~/utils/pageRefObserverHandlers"; +import { ConfigTab } from "roamjs-components/components/ConfigPage"; export const DISCOURSE_CONFIG_PAGE_TITLE = "roam/js/discourse-graph"; export const NODE_CONFIG_PAGE_TITLE = "discourse-graph/nodes/"; @@ -145,3 +156,136 @@ export const renderNodeConfigPage = ({ renderNode(); } }; + +export const configPageTabs = (args: OnloadArgs): ConfigTab[] => [ + { + id: "home", + fields: [ + { + title: "trigger", + description: + "The trigger to create the node menu. Must refresh after editing", + defaultValue: "\\", + // @ts-ignore + Panel: TextPanel, + }, + // @ts-ignore + { + title: "disable sidebar open", + description: "Disable opening new nodes in the sidebar when created", + Panel: FlagPanel, + } as Field, + // @ts-ignore + { + title: "preview", + description: + "Whether or not to display page previews when hovering over page refs", + Panel: FlagPanel, + options: { + onChange: onPageRefObserverChange(previewPageRefHandler), + }, + } as Field, + ], + }, + { + id: "grammar", + fields: [ + // @ts-ignore + { + title: "nodes", + Panel: CustomPanel, + description: "The types of nodes in your discourse graph", + options: { + component: DiscourseNodeConfigPanel, + }, + } as Field, + // @ts-ignore + { + title: "relations", + Panel: CustomPanel, + description: "The types of relations in your discourse graph", + defaultValue: DEFAULT_RELATION_VALUES, + options: { + component: DiscourseRelationConfigPanel, + }, + } as Field, + // @ts-ignore + { + title: "overlay", + Panel: FlagPanel, + // description: + // "Whether to overlay discourse context information over node references", + description: "Currently disabled. Being reworked.", + disabled: true, + options: { + onChange: (val) => { + onPageRefObserverChange((s) => overlayPageRefHandler(s, args))(val); + }, + }, + } as Field, + ], + }, + { + id: "export", + fields: [ + { + title: "max filename length", + // @ts-ignore + Panel: NumberPanel, + description: "Set the maximum name length for markdown file exports", + defaultValue: 64, + }, + { + title: "remove special characters", + // @ts-ignore + Panel: FlagPanel, + description: + "Whether or not to remove the special characters in a file name", + }, + { + title: "simplified filename", + // @ts-ignore + Panel: FlagPanel, + description: + "For discourse nodes, extract out the {content} from the page name to become the file name", + }, + { + title: "frontmatter", + // @ts-ignore + Panel: MultiTextPanel, + description: + "Specify all the lines that should go to the Frontmatter of the markdown file", + }, + { + title: "resolve block references", + // @ts-ignore + Panel: FlagPanel, + description: + "Replaces block references in the markdown content with the block's content", + }, + { + title: "resolve block embeds", + // @ts-ignore + Panel: FlagPanel, + description: + "Replaces block embeds in the markdown content with the block's content tree", + }, + // @ts-ignore + { + title: "link type", + Panel: SelectPanel, + description: "How to format links that appear in your export.", + options: { + items: ["alias", "wikilinks", "roam url"], + }, + } as Field, + { + title: "append referenced node", + // @ts-ignore + Panel: FlagPanel, + description: + "If a referenced node is defined in a node's format, it will be appended to the discourse context", + }, + ], + }, +]; diff --git a/apps/roam/src/utils/getPlainTitleFromSpecification.ts b/apps/roam/src/utils/getPlainTitleFromSpecification.ts new file mode 100644 index 000000000..2ada76391 --- /dev/null +++ b/apps/roam/src/utils/getPlainTitleFromSpecification.ts @@ -0,0 +1,24 @@ +import { Condition, QBClause } from "./types"; + +export const getPlainTitleFromSpecification = ({ + specification, + text, +}: { + specification: Condition[]; + text: string; +}) => { + // Assumptions: + // - Conditions are properly ordered + // - There is a 'has title' condition somewhere + const titleCondition = specification.find( + (s): s is QBClause => + s.type === "clause" && s.relation === "has title" && s.source === text, + ); + if (!titleCondition) return ""; + return titleCondition.target + .replace(/^\/(\^)?/, "") + .replace(/(\$)?\/$/, "") + .replace(/\\\[/g, "[") + .replace(/\\\]/g, "]") + .replace(/\(\.[\*\+](\?)?\)/g, ""); +}; diff --git a/apps/roam/src/utils/graphViewNodeStyling.ts b/apps/roam/src/utils/graphViewNodeStyling.ts new file mode 100644 index 000000000..c98dd19c8 --- /dev/null +++ b/apps/roam/src/utils/graphViewNodeStyling.ts @@ -0,0 +1,61 @@ +import { formatHexColor } from "~/components/settings/DiscourseNodeCanvasSettings"; +import getDiscourseNodes from "./getDiscourseNodes"; +import { getPlainTitleFromSpecification } from "./getPlainTitleFromSpecification"; + +type SigmaRenderer = { + setSetting: (settingName: string, value: any) => void; + getSetting: (settingName: string) => any; +}; +type nodeData = { + x: number; + y: number; + label: string; + size: number; +}; + +export const addGraphViewNodeStyling = () => { + window.roamAlphaAPI.ui.graphView.wholeGraph.addCallback({ + label: "discourse-node-styling", + callback: ({ "sigma-renderer": sigma }) => { + const sig = sigma as SigmaRenderer; + graphViewNodeStyling({ sig }); + }, + }); +}; + +const graphViewNodeStyling = ({ sig }: { sig: SigmaRenderer }) => { + const allNodes = getDiscourseNodes(); + const prefixColors = allNodes.map((n) => { + const formattedTitle = getPlainTitleFromSpecification({ + specification: n.specification, + text: n.text, + }); + const formattedBackgroundColor = formatHexColor(n.canvasSettings.color); + + return { + prefix: formattedTitle, + color: formattedBackgroundColor, + showInGraphOverview: n.graphOverview, + }; + }); + + const originalReducer = sig.getSetting("nodeReducer"); + sig.setSetting("nodeReducer", (id: string, nodeData: nodeData) => { + let modifiedData = originalReducer + ? originalReducer(id, nodeData) + : nodeData; + + const { label } = modifiedData; + + for (const { prefix, color, showInGraphOverview } of prefixColors) { + if (prefix && showInGraphOverview && label.startsWith(prefix)) { + return { + ...modifiedData, + color, + }; + } + } + + return modifiedData; + }); +}; diff --git a/apps/roam/src/utils/initializeDiscourseNodes.ts b/apps/roam/src/utils/initializeDiscourseNodes.ts new file mode 100644 index 000000000..83ebc6f05 --- /dev/null +++ b/apps/roam/src/utils/initializeDiscourseNodes.ts @@ -0,0 +1,36 @@ +import getPageUidByPageTitle from "roamjs-components/queries/getPageUidByPageTitle"; +import { createPage } from "roamjs-components/writes"; +import INITIAL_NODE_VALUES from "~/data/defaultDiscourseNodes"; +import getDiscourseNodes, { excludeDefaultNodes } from "./getDiscourseNodes"; + +const initializeDiscourseNodes = async () => { + const nodes = getDiscourseNodes().filter(excludeDefaultNodes); + if (nodes.length === 0) { + await Promise.all( + INITIAL_NODE_VALUES.map( + (n) => + getPageUidByPageTitle(`discourse-graph/nodes/${n.text}`) || + createPage({ + title: `discourse-graph/nodes/${n.text}`, + uid: n.type, + tree: [ + { text: "Format", children: [{ text: n.format || "" }] }, + { text: "Shortcut", children: [{ text: n.shortcut || "" }] }, + { text: "Graph Overview" }, + { + text: "Canvas", + children: [ + { + text: "color", + children: [{ text: n.canvasSettings?.color || "" }], + }, + ], + }, + ], + }), + ), + ); + } +}; + +export default initializeDiscourseNodes; diff --git a/apps/roam/src/utils/initializeObserversAndListeners.ts b/apps/roam/src/utils/initializeObserversAndListeners.ts new file mode 100644 index 000000000..47e29715e --- /dev/null +++ b/apps/roam/src/utils/initializeObserversAndListeners.ts @@ -0,0 +1,164 @@ +import { + createHTMLObserver, + createButtonObserver, + getPageTitleValueByHtmlElement, +} from "roamjs-components/dom"; +import { createBlock } from "roamjs-components/writes"; +import { renderLinkedReferenceAdditions } from "~/utils/renderLinkedReferenceAdditions"; +import { createConfigObserver } from "roamjs-components/components/ConfigPage"; +import { renderTldrawCanvas } from "~/components/canvas/Tldraw"; +import { renderQueryPage, renderQueryBlock } from "~/components/QueryBuilder"; +import { + configPageTabs, + DISCOURSE_CONFIG_PAGE_TITLE, + renderNodeConfigPage, +} from "~/settings/configPages"; +import isFlagEnabled from "~/utils/isFlagEnabled"; +import { isCurrentPageCanvas as isCanvasPage } from "~/utils/isCanvasPage"; +import { isDiscourseNodeConfigPage as isNodeConfigPage } from "~/utils/isDiscourseNodeConfigPage"; +import { isQueryPage } from "~/utils/isQueryPage"; +import { + enablePageRefObserver, + addPageRefObserver, + getPageRefObserversSize, + previewPageRefHandler, +} from "~/utils/pageRefObserverHandlers"; +import getDiscourseNodes from "~/utils/getDiscourseNodes"; +import { OnloadArgs } from "roamjs-components/types"; +import refreshConfigTree from "~/utils/refreshConfigTree"; +import { render as renderGraphOverviewExport } from "~/components/ExportDiscourseContext"; +import getBasicTreeByParentUid from "roamjs-components/queries/getBasicTreeByParentUid"; +import { getSettingValueFromTree } from "roamjs-components/util"; +import { render as renderDiscourseNodeMenu } from "~/components/DiscourseNodeMenu"; + +export const initObservers = async ({ + onloadArgs, +}: { + onloadArgs: OnloadArgs; +}): Promise<{ + observers: MutationObserver[]; + listeners: EventListener[]; +}> => { + const pageTitleObserver = createHTMLObserver({ + tag: "H1", + className: "rm-title-display", + callback: (e) => { + const h1 = e as HTMLHeadingElement; + const title = getPageTitleValueByHtmlElement(h1); + const props = { title, h1, onloadArgs }; + + if (isNodeConfigPage(title)) renderNodeConfigPage(props); + else if (isQueryPage(props)) renderQueryPage(props); + else if (isCanvasPage(props)) renderTldrawCanvas(props); + }, + }); + + // TODO: sidebar references don't work + // TODO: contains roam query + const linkedReferencesObserver = createHTMLObserver({ + tag: "DIV", + useBody: true, + className: "rm-reference-main", + callback: async (el) => { + const div = el as HTMLDivElement; + await renderLinkedReferenceAdditions(div, onloadArgs); + }, + }); + + const queryBlockObserver = createButtonObserver({ + attribute: "query-block", + render: (b) => renderQueryBlock(b, onloadArgs), + }); + + const pageActionListener = (( + e: CustomEvent<{ + action: string; + uid: string; + val: string; + onRefresh: () => void; + }>, + ) => { + if (!/page/i.test(e.detail.action)) return; + window.roamAlphaAPI.ui.mainWindow + .getOpenPageOrBlockUid() + .then((u) => u || window.roamAlphaAPI.util.dateToPageUid(new Date())) + .then((parentUid) => { + createBlock({ + parentUid, + order: Number.MAX_VALUE, + node: { text: `[[${e.detail.val}]]` }, + }); + }); + }) as EventListener; + + const graphOverviewExportObserver = createHTMLObserver({ + tag: "DIV", + className: "rm-graph-view-control-panel__main-options", + callback: (el) => { + const div = el as HTMLDivElement; + renderGraphOverviewExport(div); + }, + }); + + if (isFlagEnabled("preview")) addPageRefObserver(previewPageRefHandler); + // if (isFlagEnabled("grammar.overlay")) { + // addPageRefObserver((s) => overlayPageRefHandler(s, onloadArgs)); + // } + if (!!getPageRefObserversSize()) enablePageRefObserver(); + + const { pageUid: configPageUid, observer: configPageObserver } = + await createConfigObserver({ + title: DISCOURSE_CONFIG_PAGE_TITLE, + config: { + tabs: configPageTabs(onloadArgs), + }, + }); + + const hashChangeListener = (e: Event) => { + const evt = e as HashChangeEvent; + // Attempt to refresh config navigating away from config page + // doesn't work if they update via sidebar + if ( + evt.oldURL.endsWith(configPageUid) || + getDiscourseNodes().some(({ type }) => evt.oldURL.endsWith(type)) + ) { + refreshConfigTree(); + } + }; + + const configTree = getBasicTreeByParentUid(configPageUid); + const trigger = getSettingValueFromTree({ + tree: configTree, + key: "trigger", + defaultValue: "\\", + }).trim(); + const nodeMenuTriggerListener = (e: Event) => { + const evt = e as KeyboardEvent; + if (evt.key === trigger) { + const target = evt.target as HTMLElement; + if ( + target.tagName === "TEXTAREA" && + target.classList.contains("rm-block-input") + ) { + renderDiscourseNodeMenu({ textarea: target as HTMLTextAreaElement }); + evt.preventDefault(); + evt.stopPropagation(); + } + } + }; + + return { + observers: [ + pageTitleObserver, + queryBlockObserver, + configPageObserver, + linkedReferencesObserver, + graphOverviewExportObserver, + ].filter((o): o is MutationObserver => !!o), + listeners: [ + pageActionListener, + hashChangeListener, + nodeMenuTriggerListener, + ], + }; +}; diff --git a/apps/roam/src/utils/predefinedSelections.ts b/apps/roam/src/utils/predefinedSelections.ts index ccdf826bb..a497eda8d 100644 --- a/apps/roam/src/utils/predefinedSelections.ts +++ b/apps/roam/src/utils/predefinedSelections.ts @@ -14,6 +14,9 @@ import updateBlock from "roamjs-components/writes/updateBlock"; import parseQuery from "./parseQuery"; import toCellValue from "./toCellValue"; import createBlock from "roamjs-components/writes/createBlock"; +import deriveNodeAttribute from "./deriveDiscourseNodeAttribute"; +import matchDiscourseNode from "./matchDiscourseNode"; +import getDiscourseNodes from "./getDiscourseNodes"; const ALIAS_TEST = /^node$/i; const REGEX_TEST = /\/([^}]*)\//; @@ -513,6 +516,31 @@ const predefinedSelections: PredefinedSelection[] = [ }, ], }, + { + test: /^discourse:(.*)$/, + pull: ({ returnNode }) => `(pull ?${returnNode} [:block/uid])`, + mapper: (r, key) => { + const attribute = key.substring("discourse:".length); + const uid = r[":block/uid"] || ""; + return deriveNodeAttribute({ uid, attribute }); + }, + }, + { + test: /^\s*type\s*$/i, + pull: ({ returnNode }) => + `(pull ?${returnNode} [:node/title :block/string])`, + mapper: (r) => { + const title = r[":node/title"] || ""; + return ( + getDiscourseNodes().find((n) => + matchDiscourseNode({ + ...n, + title, + }), + )?.text || (r[":block/string"] ? "block" : "page") + ); + }, + }, { test: /.*/, pull: ({ returnNode }) => `(pull ?${returnNode} [:block/uid])`, diff --git a/apps/roam/src/utils/refreshConfigTree.ts b/apps/roam/src/utils/refreshConfigTree.ts index 49aeea99e..178fc15ad 100644 --- a/apps/roam/src/utils/refreshConfigTree.ts +++ b/apps/roam/src/utils/refreshConfigTree.ts @@ -36,7 +36,7 @@ const refreshConfigTree = () => { ]; }), ); - return registerDiscourseDatalogTranslators(); + registerDiscourseDatalogTranslators(); }; export default refreshConfigTree; diff --git a/apps/roam/src/utils/registerSmartBlock.ts b/apps/roam/src/utils/registerSmartBlock.ts index 5d4a44a38..5ed784d68 100644 --- a/apps/roam/src/utils/registerSmartBlock.ts +++ b/apps/roam/src/utils/registerSmartBlock.ts @@ -7,7 +7,8 @@ import registerSmartBlocksCommand from "roamjs-components/util/registerSmartBloc import resolveQueryBuilderRef from "./resolveQueryBuilderRef"; import runQuery from "./runQuery"; -export const registerSmartBlock = (extensionAPI: OnloadArgs["extensionAPI"]) => +export const registerSmartBlock = (onloadArgs: OnloadArgs) => { + const { extensionAPI } = onloadArgs; registerSmartBlocksCommand({ text: "QUERYBUILDER", delayArgs: true, @@ -87,3 +88,4 @@ export const registerSmartBlock = (extensionAPI: OnloadArgs["extensionAPI"]) => }); }, }); +}; diff --git a/apps/roam/src/utils/renderLinkedReferenceAdditions.ts b/apps/roam/src/utils/renderLinkedReferenceAdditions.ts new file mode 100644 index 000000000..cc255f87f --- /dev/null +++ b/apps/roam/src/utils/renderLinkedReferenceAdditions.ts @@ -0,0 +1,51 @@ +import { createElement } from "react"; +import { getPageTitleValueByHtmlElement } from "roamjs-components/dom"; +import getPageUidByPageTitle from "roamjs-components/queries/getPageUidByPageTitle"; +import renderWithUnmount from "roamjs-components/util/renderWithUnmount"; +import { DiscourseContext } from "~/components"; +import CanvasReferences from "~/components/canvas/CanvasReferences"; +import isDiscourseNode from "./isDiscourseNode"; +import { OnloadArgs } from "roamjs-components/types"; + +export const renderLinkedReferenceAdditions = async ( + div: HTMLDivElement, + onloadArgs: OnloadArgs, +) => { + const isMainWindow = !!div.closest(".roam-article"); + const uid = isMainWindow + ? await window.roamAlphaAPI.ui.mainWindow.getOpenPageOrBlockUid() + : getPageUidByPageTitle(getPageTitleValueByHtmlElement(div)); + if ( + uid && + isDiscourseNode(uid) && + !div.getAttribute("data-roamjs-discourse-context") + ) { + div.setAttribute("data-roamjs-discourse-context", "true"); + const parent = div.firstElementChild; + if (parent) { + const insertBefore = parent.firstElementChild; + + const p = document.createElement("div"); + parent.insertBefore(p, insertBefore); + renderWithUnmount( + createElement(DiscourseContext, { + uid, + results: [], + onloadArgs, + }), + p, + onloadArgs, + ); + + const canvasP = document.createElement("div"); + parent.insertBefore(canvasP, insertBefore); + renderWithUnmount( + createElement(CanvasReferences, { + uid, + }), + canvasP, + onloadArgs, + ); + } + } +}; diff --git a/apps/roam/src/utils/setQueryPages.ts b/apps/roam/src/utils/setQueryPages.ts new file mode 100644 index 000000000..f851879e5 --- /dev/null +++ b/apps/roam/src/utils/setQueryPages.ts @@ -0,0 +1,18 @@ +import { OnloadArgs } from "roamjs-components/types"; + +export const setQueryPages = (onloadArgs: OnloadArgs) => { + const queryPages = onloadArgs.extensionAPI.settings.get("query-pages"); + const queryPageArray = Array.isArray(queryPages) + ? queryPages + : typeof queryPages === "object" + ? [] + : typeof queryPages === "string" && queryPages + ? [queryPages] + : []; + if (!queryPageArray.includes("discourse-graph/queries/*")) { + onloadArgs.extensionAPI.settings.set("query-pages", [ + ...queryPageArray, + "discourse-graph/queries/*", + ]); + } +}; diff --git a/package-lock.json b/package-lock.json index 16f124611..0d6770e73 100644 --- a/package-lock.json +++ b/package-lock.json @@ -37,7 +37,7 @@ "react-draggable": "^4.4.5", "react-in-viewport": "^1.0.0-alpha.20", "react-vertical-timeline-component": "^3.5.2", - "roamjs-components": "^0.84.0", + "roamjs-components": "^0.84.1", "signia-react": "^0.1.1" }, "devDependencies": { @@ -5699,9 +5699,9 @@ } }, "apps/roam/node_modules/roamjs-components": { - "version": "0.84.0", - "resolved": "https://registry.npmjs.org/roamjs-components/-/roamjs-components-0.84.0.tgz", - "integrity": "sha512-dsK2GAPszxAjkUZGPAhHnZbgU8T3IWXIH+l6bVry7oR5vJL7FxnXFNZF/n8iDNNiDM+o+9n3gQxCl5aL+N3rzg==", + "version": "0.84.1", + "resolved": "https://registry.npmjs.org/roamjs-components/-/roamjs-components-0.84.1.tgz", + "integrity": "sha512-/hICtZEbiWRyp9TFZUuckSSm+TThFqbsEfMaZvjHJHkkKYjHrFLSWZx3racw4ChmTFr4253WUJlth+szxH1PGQ==", "hasInstallScript": true, "license": "MIT", "dependencies": { From cad78b49378fb2e88e95d3f9fb48556e6a2947b3 Mon Sep 17 00:00:00 2001 From: Michael Gartner Date: Fri, 27 Dec 2024 09:24:54 -0600 Subject: [PATCH 2/5] move init up --- apps/roam/src/index.ts | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/apps/roam/src/index.ts b/apps/roam/src/index.ts index 2338469e8..59636f2ea 100644 --- a/apps/roam/src/index.ts +++ b/apps/roam/src/index.ts @@ -47,7 +47,13 @@ export default runExtension(async (onloadArgs) => { }); } - const { extensionAPI } = onloadArgs; + initializeDiscourseNodes(); + refreshConfigTree(); + addGraphViewNodeStyling(); + registerCommandPaletteCommands(onloadArgs); + createSettingsPanel(onloadArgs); + registerSmartBlock(onloadArgs); + setQueryPages(onloadArgs); const style = addStyle(styles); const dgraphStyles = addStyle(discourseGraphStyles); @@ -60,6 +66,7 @@ export default runExtension(async (onloadArgs) => { window.addEventListener("hashchange", hashChangeListener); document.addEventListener("keydown", nodeMenuTriggerListener); + const { extensionAPI } = onloadArgs; window.roamjs.extension.queryBuilder = { runQuery: (parentUid: string) => runQuery({ parentUid, extensionAPI }).then( @@ -73,14 +80,6 @@ export default runExtension(async (onloadArgs) => { isDiscourseNode: isDiscourseNode, }; - initializeDiscourseNodes(); - refreshConfigTree(); - addGraphViewNodeStyling(); - registerCommandPaletteCommands(onloadArgs); - createSettingsPanel(onloadArgs); - registerSmartBlock(onloadArgs); - setQueryPages(onloadArgs); - return { elements: [style, settingsStyle, dgraphStyles], observers: observers, From a02ffbde24333a3705e26bc62348c3060e1ea0c3 Mon Sep 17 00:00:00 2001 From: Michael Gartner Date: Fri, 27 Dec 2024 10:30:00 -0600 Subject: [PATCH 3/5] . --- apps/roam/src/components/DiscourseContext.tsx | 43 ------------- apps/roam/src/discourseGraphsMode.ts | 60 ------------------- 2 files changed, 103 deletions(-) delete mode 100644 apps/roam/src/discourseGraphsMode.ts diff --git a/apps/roam/src/components/DiscourseContext.tsx b/apps/roam/src/components/DiscourseContext.tsx index 5a25f32d8..b3599d901 100644 --- a/apps/roam/src/components/DiscourseContext.tsx +++ b/apps/roam/src/components/DiscourseContext.tsx @@ -463,47 +463,4 @@ const useDebounce = (value: T, delay: number): T => { return debouncedValue; }; -export const observerCallback = async ( - div: HTMLDivElement, - args: OnloadArgs, -) => { - const isMainWindow = !!div.closest(".roam-article"); - const uid = isMainWindow - ? await window.roamAlphaAPI.ui.mainWindow.getOpenPageOrBlockUid() - : getPageUidByPageTitle(getPageTitleValueByHtmlElement(div)); - if ( - uid && - isDiscourseNode(uid) && - !div.getAttribute("data-roamjs-discourse-context") - ) { - div.setAttribute("data-roamjs-discourse-context", "true"); - const parent = div.firstElementChild; - if (parent) { - const insertBefore = parent.firstElementChild; - - const p = document.createElement("div"); - parent.insertBefore(p, insertBefore); - renderWithUnmount( - React.createElement(DiscourseContext, { - uid, - results: [], - args, - }), - p, - args, - ); - - const canvasP = document.createElement("div"); - parent.insertBefore(canvasP, insertBefore); - renderWithUnmount( - React.createElement(CanvasReferences, { - uid, - }), - canvasP, - args, - ); - } - } -}; - export default DiscourseContext; diff --git a/apps/roam/src/discourseGraphsMode.ts b/apps/roam/src/discourseGraphsMode.ts deleted file mode 100644 index ba682c200..000000000 --- a/apps/roam/src/discourseGraphsMode.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { createConfigObserver } from "roamjs-components/components/ConfigPage"; -import { - CustomField, - Field, - FlagField, - SelectField, -} from "roamjs-components/components/ConfigPanels/types"; -import DiscourseNodeConfigPanel from "./components/settings/DiscourseNodeConfigPanel"; -import DiscourseRelationConfigPanel from "./components/settings/DiscourseRelationConfigPanel"; -import CustomPanel from "roamjs-components/components/ConfigPanels/CustomPanel"; -import TextPanel from "roamjs-components/components/ConfigPanels/TextPanel"; -import FlagPanel from "roamjs-components/components/ConfigPanels/FlagPanel"; -import NumberPanel from "roamjs-components/components/ConfigPanels/NumberPanel"; -import MultiTextPanel from "roamjs-components/components/ConfigPanels/MultiTextPanel"; -import SelectPanel from "roamjs-components/components/ConfigPanels/SelectPanel"; -import DEFAULT_RELATION_VALUES from "./data/defaultDiscourseRelations"; -import { OnloadArgs } from "roamjs-components/types"; -import getDiscourseNodes, { - excludeDefaultNodes, -} from "./utils/getDiscourseNodes"; -import refreshConfigTree from "./utils/refreshConfigTree"; -import getBasicTreeByParentUid from "roamjs-components/queries/getBasicTreeByParentUid"; -import getSettingValueFromTree from "roamjs-components/util/getSettingValueFromTree"; -import { render } from "./components/DiscourseNodeMenu"; -import DiscourseContext from "./components/DiscourseContext"; -import createHTMLObserver from "roamjs-components/dom/createHTMLObserver"; -import getPageUidByPageTitle from "roamjs-components/queries/getPageUidByPageTitle"; -import isDiscourseNode from "./utils/isDiscourseNode"; -import addStyle from "roamjs-components/dom/addStyle"; -import { registerSelection } from "./utils/predefinedSelections"; -import deriveNodeAttribute from "./utils/deriveDiscourseNodeAttribute"; -import matchDiscourseNode from "./utils/matchDiscourseNode"; -import getPageTitleValueByHtmlElement from "roamjs-components/dom/getPageTitleValueByHtmlElement"; -import React from "react"; -import renderWithUnmount from "roamjs-components/util/renderWithUnmount"; -import createPage from "roamjs-components/writes/createPage"; -import INITIAL_NODE_VALUES from "./data/defaultDiscourseNodes"; -import CanvasReferences from "./components/canvas/CanvasReferences"; -import { render as renderGraphOverviewExport } from "./components/ExportDiscourseContext"; -import styles from "./styles/discourseGraphStyles.css"; -import { DISCOURSE_CONFIG_PAGE_TITLE } from "./settings/configPages"; -import { formatHexColor } from "./components/settings/DiscourseNodeCanvasSettings"; -import { - onPageRefObserverChange, - overlayPageRefHandler, - previewPageRefHandler, -} from "./utils/pageRefObserverHandlers"; - -const initializeDiscourseGraphsMode = async (args: OnloadArgs) => { - const unloads = new Set<() => void>(); - - refreshConfigTree(); - - return () => { - unloads.forEach((u) => u()); - unloads.clear(); - }; -}; - -export default initializeDiscourseGraphsMode; From ed31c79934e43c8c03d5f1c912cf676e22054211 Mon Sep 17 00:00:00 2001 From: Michael Gartner Date: Fri, 27 Dec 2024 10:39:53 -0600 Subject: [PATCH 4/5] . --- apps/roam/src/utils/initializeObserversAndListeners.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/roam/src/utils/initializeObserversAndListeners.ts b/apps/roam/src/utils/initializeObserversAndListeners.ts index 47e29715e..bb1583202 100644 --- a/apps/roam/src/utils/initializeObserversAndListeners.ts +++ b/apps/roam/src/utils/initializeObserversAndListeners.ts @@ -53,8 +53,7 @@ export const initObservers = async ({ }, }); - // TODO: sidebar references don't work - // TODO: contains roam query + // TODO: contains roam query: https://github.com/DiscourseGraphs/discourse-graph/issues/39 const linkedReferencesObserver = createHTMLObserver({ tag: "DIV", useBody: true, From 0522d54ab9f92aeca6dfcfd337a194cbfdafce3a Mon Sep 17 00:00:00 2001 From: Michael Gartner Date: Fri, 27 Dec 2024 10:50:47 -0600 Subject: [PATCH 5/5] comment --- apps/roam/src/utils/initializeObserversAndListeners.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/roam/src/utils/initializeObserversAndListeners.ts b/apps/roam/src/utils/initializeObserversAndListeners.ts index bb1583202..783fc5d56 100644 --- a/apps/roam/src/utils/initializeObserversAndListeners.ts +++ b/apps/roam/src/utils/initializeObserversAndListeners.ts @@ -100,6 +100,7 @@ export const initObservers = async ({ }); if (isFlagEnabled("preview")) addPageRefObserver(previewPageRefHandler); + // TODO: grammar overlay being refactored // if (isFlagEnabled("grammar.overlay")) { // addPageRefObserver((s) => overlayPageRefHandler(s, onloadArgs)); // }