diff options
author | Brian Lester <[email protected]> | 2021-08-02 20:28:48 -0400 |
---|---|---|
committer | Brian Lester <[email protected]> | 2021-08-02 21:26:22 -0400 |
commit | 528e2ce90f365c8d399410ee9cfb7a05f1ca638a (patch) | |
tree | a91efe8fe155bc98fa6c98e9104ebfcd9ba204fe | |
parent | fbb566a4547cfae5f449d82ca3d565ca3c395a7f (diff) |
Process org-roam-bibtex citations links differently depending on if they
have an associated node.
It is easy to imagine how overlapping citations could clue one into
connections about otherwise un-related work and generate insights.
However, `org-roam-ui` currently only shows citation links when the
`target` of the link has an associated node, that is, a node with a
matching `:ROAM_REFS:` property. Often one doesn't have a note for every
single thing they cite, and requiring them to create an empty note is a
bad user experience and something that would often be forgotten. Without
having citations links without associated nodes, connections in a users
notes will most likely be missed.
This change splits the handling of `org-roam-bibtex` citations links into
two categories, a citation link (when the target of the link does not
have an associated node, a node with a matching `:ROAM_REFS:`) and a
reference link (when the target of the link does have a node). Reasoning
for the naming is that a citation in a paper is generally just a link to
it, there is no extra commentary from you when that link if followed,
while a reference sound more like something you would refer to for
information, i.e. the notes you took in the attached node. I am willing
to flip the names if we want though.
Citations and References are customizable separately, with new menu
items for node/link color and dashed links added for Reference links.
A new filter option has also been added, the new switch `Citations
without note files` will remove all Citation nodes (and links to them)
when activated.
On the emacs side, this in implemented by using a new link type keyword,
`"ref"`, for any cite link where the target has an underlying node (a
node with a matching `:ROAM_REFS:` property). Links with this new type
are styled according to the new Ref styles in the UI. Any cite link where
the target does _not_ have an underlying node is left as a `"cite"` link.
Additionally, this set of link targets without associated nodes is used
to create fake nodes. These fake nodes have the `ref` key (the link target)
as their `id`, `file`, `title`, and `.properties.ROAM_REFS`. Their `level`
is set to `0`, and a new property, `FILELESS`, is set to `t`. Their id is
used for actually connecting links in the UI, and the new
`.properties.FILELESS` is used to styling a Citation vs a Reference node.
Theoretically, if we wanted to depend on `org-roam-bibtex`, and pay the
cost of look up citation information in the bibliography, we could use
the citation key to look up an actual title in the bibtex bibliography
during node creation, but that seems like it could get heavy for large
numbers of citations.
-rw-r--r-- | components/config.ts | 6 | ||||
-rw-r--r-- | components/tweaks.tsx | 45 | ||||
-rw-r--r-- | org-roam-ui.el | 14 | ||||
-rw-r--r-- | pages/index.tsx | 41 |
4 files changed, 92 insertions, 14 deletions
diff --git a/components/config.ts b/components/config.ts index 0544b58..5cc7775 100644 --- a/components/config.ts +++ b/components/config.ts @@ -34,6 +34,7 @@ export const initialPhysics = { export const initialFilter = { orphans: false, parents: true, + fileless_cites: false, tags: [], nodes: [], links: [], @@ -86,6 +87,11 @@ export const initialVisuals = { citeGapLength: 15, citeLinkColor: 'gray.600', citeNodeColor: 'black', + refDashes: true, + refDashLength: 35, + refGapLength: 15, + refLinkColor: 'gray.200', + refNodeColor: 'black', } export interface TagColors { diff --git a/components/tweaks.tsx b/components/tweaks.tsx index cb1fec0..ff7054f 100644 --- a/components/tweaks.tsx +++ b/components/tweaks.tsx @@ -233,6 +233,15 @@ export const Tweaks = (props: TweakProps) => { isChecked={filter.parents} ></Switch> </Flex> + <Flex justifyContent="space-between"> + <Text>Citations without note files</Text> + <Switch + onChange={() => { + setFilter({ ...filter, fileless_cites: !filter.fileless_cites }) + }} + isChecked={filter.fileless_cites} + ></Switch> + </Flex> </VStack> <Accordion padding={0} allowToggle allowMultiple paddingLeft={3}> <AccordionItem> @@ -762,6 +771,42 @@ export const Tweaks = (props: TweakProps) => { value={'citeLinkColor'} visValue={visuals.citeLinkColor} /> + + <EnableSection + label="Dash ref links" + infoText="Add dashes to citation links, whose target has a note, made with org-roam-bibtex" + value={visuals.refDashes} + onChange={() => setVisuals({ ...visuals, refDashes: !visuals.refDashes })} + > + <SliderWithInfo + label="Dash length" + value={visuals.refDashLength / 10} + onChange={(value) => + setVisuals({ ...visuals, refDashLength: value * 10 }) + } + /> + <SliderWithInfo + label="Gap length" + value={visuals.refGapLength / 5} + onChange={(value) => setVisuals({ ...visuals, refGapLength: value * 5 })} + /> + </EnableSection> + <ColorMenu + colorList={colorList} + label="Reference node color" + visuals={visuals} + setVisuals={setVisuals} + value={'refNodeColor'} + visValue={visuals.refNodeColor} + /> + <ColorMenu + colorList={colorList} + label="Referencelink color" + visuals={visuals} + setVisuals={setVisuals} + value={'refLinkColor'} + visValue={visuals.refLinkColor} + /> <Box> <Flex alignItems="center" justifyContent="space-between"> <Text>Labels</Text> diff --git a/org-roam-ui.el b/org-roam-ui.el index 5ac7eac..368d788 100644 --- a/org-roam-ui.el +++ b/org-roam-ui.el @@ -157,6 +157,9 @@ This serves the web-build and API over HTTP." (org-roam-ui--send-graphdata)) ) +(defun org-roam-ui--create-fake-node (ref) + (list ref ref ref 0 `(("ROAM_REFS" . ,(format "cite:%s" ref)) ("FILELESS" . t)) 'nil)) + (defun org-roam-ui--send-graphdata () "Get roam data, make JSON, send through websocket to org-roam-ui." (let* ((nodes-columns [id file title level properties ,(funcall group-concat tag (emacsql-escape-raw \, ))]) @@ -174,14 +177,17 @@ This serves the web-build and API over HTTP." :from links :left :outer :join refs :on (= links:dest refs:ref) :where (or (= links:type "id") (= links:type "cite"))])) - ;; Convert any cite links that have nodes with associated refs to a - ;; standard id link while removing the 'nil `refs:node-id' from all - ;; other links + ;; Convert any cite links that have nodes with associated refs to an + ;; id based link of type `ref' while removing the 'nil `refs:node-id' + ;; from all other links (links-db-rows (seq-map (lambda (l) (pcase-let ((`(,source ,dest ,type ,node-id) l)) (if node-id - (list source node-id "cite") + (list source node-id "ref") (list source dest type)))) links-db-rows)) + (links-with-empty-refs (seq-filter (lambda (l) (equal (nth 2 l) "cite")) links-db-rows)) + (fake-nodes (delete-dups (seq-map (lambda (l) (org-roam-ui--create-fake-node (nth 1 l))) links-with-empty-refs))) + (nodes-db-rows (append nodes-db-rows fake-nodes)) (response `((nodes . ,(mapcar (apply-partially #'org-roam-ui-sql-to-alist (append nodes-names nil)) nodes-db-rows)) (links . ,(mapcar (apply-partially #'org-roam-ui-sql-to-alist '(source target type)) links-db-rows)) (tags . ,(seq-mapcat #'seq-reverse (org-roam-db-query [:select :distinct tag :from tags])))))) diff --git a/pages/index.tsx b/pages/index.tsx index 9e46f45..7d947e0 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -70,6 +70,14 @@ export default function Home() { return <GraphPage /> } +function normalizeLinkEnds(link: OrgRoamLink | LinkObject): [string, string] { + // we need to cover both because force-graph modifies the original data + // but if we supply the original data on each render, the graph will re-render sporadically + const sourceId = typeof link.source === 'object' ? (link.source.id! as string) : (link.source as string) + const targetId = typeof link.target === 'object' ? (link.target.id! as string) : (link.target as string) + return [sourceId, targetId] +} + export function GraphPage() { const [physics, setPhysics] = usePersistantState('physics', initialPhysics) const [filter, setFilter] = usePersistantState('filter', initialFilter) @@ -416,6 +424,9 @@ export const Graph = forwardRef(function (props: GraphProps, graphRef: any) { if (filter.tags.length && node.tags.length) { return !filter.tags.some((tag) => node.tags.indexOf(tag) > -1) } + if (filter.fileless_cites && node.properties.FILELESS) { + return false + } if (!filter.orphans) { return true @@ -434,14 +445,16 @@ export const Graph = forwardRef(function (props: GraphProps, graphRef: any) { const filteredNodeIds = filteredNodes.map((node) => node.id as string) const filteredLinks = graphData.links.filter((link) => { - if (filter.tags.length) { - const sourceId = typeof link.source === 'object' ? link.source.id! : (link.source as string) - const targetId = typeof link.target === 'object' ? link.target.id! : (link.target as string) - return ( + const [sourceId, targetId] = normalizeLinkEnds(link) + if ( + !( filteredNodeIds.includes(sourceId as string) && filteredNodeIds.includes(targetId as string) ) + ) { + return false } + const linkRoam = link as OrgRoamLink return filter.parents || linkRoam.type !== 'parent' }) @@ -464,8 +477,7 @@ export const Graph = forwardRef(function (props: GraphProps, graphRef: any) { const scopedLinks = filteredGraphData.filteredLinks.filter((link) => { // we need to cover both because force-graph modifies the original data // but if we supply the original data on each render, the graph will re-render sporadically - const sourceId = typeof link.source === 'object' ? link.source.id! : (link.source as string) - const targetId = typeof link.target === 'object' ? link.target.id! : (link.target as string) + const [sourceId, targetId] = normalizeLinkEnds(link) return ( scopedNodeIds.includes(sourceId as string) && scopedNodeIds.includes(targetId as string) ) @@ -651,9 +663,12 @@ export const Graph = forwardRef(function (props: GraphProps, graphRef: any) { const tagColor = tagColors[node.tags.filter((tag) => tagColors[tag])[0]] return getThemeColor(tagColor) } - if (visuals.citeNodeColor && node.properties.ROAM_REFS) { + if (visuals.citeNodeColor && node.properties.ROAM_REFS && node.properties.FILELESS) { return getThemeColor(visuals.citeNodeColor) } + if (visuals.refNodeColor && node.properties.ROAM_REFS) { + return getThemeColor(visuals.refNodeColor) + } if (!needsHighlighting) { return getThemeColor(getNodeColorById(node.id as string)) } @@ -783,6 +798,9 @@ export const Graph = forwardRef(function (props: GraphProps, graphRef: any) { const linkWasHighlighted = isLinkRelatedToNode(link, lastHoverNode.current) const needsHighlighting = linkIsHighlighted || linkWasHighlighted const roamLink = link as OrgRoamLink + if (visuals.refLinkColor && roamLink.type === 'ref') { + return getThemeColor(visuals.refLinkColor) + } if (visuals.citeLinkColor && roamLink.type === 'cite') { return getThemeColor(visuals.citeLinkColor) } @@ -870,10 +888,13 @@ export const Graph = forwardRef(function (props: GraphProps, graphRef: any) { {...graphCommonProps} linkLineDash={(link) => { const linkArg = link as OrgRoamLink - if (!visuals.citeDashes || linkArg.type !== 'cite') { - return null + if (visuals.citeDashes && linkArg.type === 'cite') { + return [visuals.citeDashLength, visuals.citeGapLength] + } + if (visuals.refDashes && linkArg.type == 'ref') { + return [visuals.refDashLength, visuals.refGapLength] } - return [visuals.citeDashLength, visuals.citeGapLength] + return null }} /> )} |