summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBrian Lester <[email protected]>2021-08-02 20:28:48 -0400
committerBrian Lester <[email protected]>2021-08-02 21:26:22 -0400
commit528e2ce90f365c8d399410ee9cfb7a05f1ca638a (patch)
treea91efe8fe155bc98fa6c98e9104ebfcd9ba204fe
parentfbb566a4547cfae5f449d82ca3d565ca3c395a7f (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.ts6
-rw-r--r--components/tweaks.tsx45
-rw-r--r--org-roam-ui.el14
-rw-r--r--pages/index.tsx41
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
}}
/>
)}