Skip to content

Instantly share code, notes, and snippets.

@azlen
Last active April 15, 2024 14:06
Show Gist options
  • Star 126 You must be signed in to star a gist
  • Fork 7 You must be signed in to fork a gist
  • Save azlen/cc8d543f0e46e17d978e705650df0e9e to your computer and use it in GitHub Desktop.
Save azlen/cc8d543f0e46e17d978e705650df0e9e to your computer and use it in GitHub Desktop.
All Paths Lead to Roam
/*
* credit to Dhrumil Shah (@wandcrafting) and Robert Haisfield (@RobertHaisfield)
* for the original concept which was part of their RoamGames submission
* and can be found at: https://www.figma.com/file/5shwLdUCHxSaPNEO7pazbe/
*
*/
/* ======= OPTIONS ======== */
/* note: if you change these, reload the page to see the effect */
// BULLET
let scale = 2;
let bulletColor = '#FF0099';
// LINES
let showLines = true;
let lineWidth = 2;
let lineColor = bulletColor;
let borderRadius = 5;
// HIGHLIGHT REFS
let highlightRefs = true;
let refColor = bulletColor;
/* ======= LIBRARIES ======== */
/*
* arrive.js
* v2.4.1
* https://github.com/uzairfarooq/arrive
* MIT licensed
*
* Copyright (c) 2014-2017 Uzair Farooq
*/
var Arrive=function(e,t,n){"use strict";function r(e,t,n){l.addMethod(t,n,e.unbindEvent),l.addMethod(t,n,e.unbindEventWithSelectorOrCallback),l.addMethod(t,n,e.unbindEventWithSelectorAndCallback)}function i(e){e.arrive=f.bindEvent,r(f,e,"unbindArrive"),e.leave=d.bindEvent,r(d,e,"unbindLeave")}if(e.MutationObserver&&"undefined"!=typeof HTMLElement){var o=0,l=function(){var t=HTMLElement.prototype.matches||HTMLElement.prototype.webkitMatchesSelector||HTMLElement.prototype.mozMatchesSelector||HTMLElement.prototype.msMatchesSelector;return{matchesSelector:function(e,n){return e instanceof HTMLElement&&t.call(e,n)},addMethod:function(e,t,r){var i=e[t];e[t]=function(){return r.length==arguments.length?r.apply(this,arguments):"function"==typeof i?i.apply(this,arguments):n}},callCallbacks:function(e,t){t&&t.options.onceOnly&&1==t.firedElems.length&&(e=[e[0]]);for(var n,r=0;n=e[r];r++)n&&n.callback&&n.callback.call(n.elem,n.elem);t&&t.options.onceOnly&&1==t.firedElems.length&&t.me.unbindEventWithSelectorAndCallback.call(t.target,t.selector,t.callback)},checkChildNodesRecursively:function(e,t,n,r){for(var i,o=0;i=e[o];o++)n(i,t,r)&&r.push({callback:t.callback,elem:i}),i.childNodes.length>0&&l.checkChildNodesRecursively(i.childNodes,t,n,r)},mergeArrays:function(e,t){var n,r={};for(n in e)e.hasOwnProperty(n)&&(r[n]=e[n]);for(n in t)t.hasOwnProperty(n)&&(r[n]=t[n]);return r},toElementsArray:function(t){return n===t||"number"==typeof t.length&&t!==e||(t=[t]),t}}}(),c=function(){var e=function(){this._eventsBucket=[],this._beforeAdding=null,this._beforeRemoving=null};return e.prototype.addEvent=function(e,t,n,r){var i={target:e,selector:t,options:n,callback:r,firedElems:[]};return this._beforeAdding&&this._beforeAdding(i),this._eventsBucket.push(i),i},e.prototype.removeEvent=function(e){for(var t,n=this._eventsBucket.length-1;t=this._eventsBucket[n];n--)if(e(t)){this._beforeRemoving&&this._beforeRemoving(t);var r=this._eventsBucket.splice(n,1);r&&r.length&&(r[0].callback=null)}},e.prototype.beforeAdding=function(e){this._beforeAdding=e},e.prototype.beforeRemoving=function(e){this._beforeRemoving=e},e}(),a=function(t,r){var i=new c,o=this,a={fireOnAttributesModification:!1};return i.beforeAdding(function(n){var i,l=n.target;(l===e.document||l===e)&&(l=document.getElementsByTagName("html")[0]),i=new MutationObserver(function(e){r.call(this,e,n)});var c=t(n.options);i.observe(l,c),n.observer=i,n.me=o}),i.beforeRemoving(function(e){e.observer.disconnect()}),this.bindEvent=function(e,t,n){t=l.mergeArrays(a,t);for(var r=l.toElementsArray(this),o=0;o<r.length;o++)i.addEvent(r[o],e,t,n)},this.unbindEvent=function(){var e=l.toElementsArray(this);i.removeEvent(function(t){for(var r=0;r<e.length;r++)if(this===n||t.target===e[r])return!0;return!1})},this.unbindEventWithSelectorOrCallback=function(e){var t,r=l.toElementsArray(this),o=e;t="function"==typeof e?function(e){for(var t=0;t<r.length;t++)if((this===n||e.target===r[t])&&e.callback===o)return!0;return!1}:function(t){for(var i=0;i<r.length;i++)if((this===n||t.target===r[i])&&t.selector===e)return!0;return!1},i.removeEvent(t)},this.unbindEventWithSelectorAndCallback=function(e,t){var r=l.toElementsArray(this);i.removeEvent(function(i){for(var o=0;o<r.length;o++)if((this===n||i.target===r[o])&&i.selector===e&&i.callback===t)return!0;return!1})},this},s=function(){function e(e){var t={attributes:!1,childList:!0,subtree:!0};return e.fireOnAttributesModification&&(t.attributes=!0),t}function t(e,t){e.forEach(function(e){var n=e.addedNodes,i=e.target,o=[];null!==n&&n.length>0?l.checkChildNodesRecursively(n,t,r,o):"attributes"===e.type&&r(i,t,o)&&o.push({callback:t.callback,elem:i}),l.callCallbacks(o,t)})}function r(e,t){return l.matchesSelector(e,t.selector)&&(e._id===n&&(e._id=o++),-1==t.firedElems.indexOf(e._id))?(t.firedElems.push(e._id),!0):!1}var i={fireOnAttributesModification:!1,onceOnly:!1,existing:!1};f=new a(e,t);var c=f.bindEvent;return f.bindEvent=function(e,t,r){n===r?(r=t,t=i):t=l.mergeArrays(i,t);var o=l.toElementsArray(this);if(t.existing){for(var a=[],s=0;s<o.length;s++)for(var u=o[s].querySelectorAll(e),f=0;f<u.length;f++)a.push({callback:r,elem:u[f]});if(t.onceOnly&&a.length)return r.call(a[0].elem,a[0].elem);setTimeout(l.callCallbacks,1,a)}c.call(this,e,t,r)},f},u=function(){function e(){var e={childList:!0,subtree:!0};return e}function t(e,t){e.forEach(function(e){var n=e.removedNodes,i=[];null!==n&&n.length>0&&l.checkChildNodesRecursively(n,t,r,i),l.callCallbacks(i,t)})}function r(e,t){return l.matchesSelector(e,t.selector)}var i={};d=new a(e,t);var o=d.bindEvent;return d.bindEvent=function(e,t,r){n===r?(r=t,t=i):t=l.mergeArrays(i,t),o.call(this,e,t,r)},d},f=new s,d=new u;t&&i(t.fn),i(HTMLElement.prototype),i(NodeList.prototype),i(HTMLCollection.prototype),i(HTMLDocument.prototype),i(Window.prototype);var h={};return r(f,h,"unbindAllArrive"),r(d,h,"unbindAllLeave"),h}}(window,"undefined"==typeof jQuery?null:jQuery,void 0);
/* ======= CODE ======== */
// Add custom style tag to document for CSS customization
let style = document.createElement('style');
let baseStyle = `
.path-highlighted .rm-bullet .rm-bullet__inner,
.path-highlighted .rm-bullet .rm-bullet__inner--user-icon {
background-color: ${bulletColor} !important;
transform: scale(${scale});
}
`
if(highlightRefs) {
baseStyle += `
.path-highlighted + .roam-block .rm-page-ref,
.path-highlighted + .roam-block .rm-alias {
color: ${refColor};
}
.path-highlighted + .roam-block .rm-block-ref {
border-bottom-color: ${refColor};
}
`
}
style.textContent = baseStyle
document.body.appendChild(style);
// Remove classes when block is unfocused
document.leave('textarea.rm-block-input', function(el) {
let bullets = [].slice.call(document.querySelectorAll('.path-highlighted'));
bullets.forEach(function(bullet) {
bullet.classList.remove('path-highlighted');
})
})
// Show highlighted bullets + path when block is focused
let pathid = 0;
document.arrive('textarea.rm-block-input', function(el) {
let bullets = [];
let block = el.closest('.roam-block-container');
// iterate through parents and get bullet elements
while(block) {
bullets.push(block.querySelector('.controls'));
block = block.parentElement.closest('.roam-block-container')
}
// bullet styles cannot be applied directly to the element because they are pseudotags
// so we'll create a style string and add it to the style tag we created earlier
let bulletStyle = ''
bullets.forEach(function(bullet, i) {
let lastBullet = i > 0 ? bullets[i-1] : null
// give each bullet a special path identifier so that we can target it directly
if(!bullet.dataset.pathidentifier) { bullet.dataset.pathidentifier = pathid++ }
if(lastBullet != null && showLines == true) {
let bboxA = lastBullet.getBoundingClientRect()
let bboxB = bullet.getBoundingClientRect()
bulletStyle += `
.path-highlighted[data-pathidentifier="${bullet.dataset.pathidentifier}"] .bp3-popover-target::before {
content: "";
position: absolute;
top: 10px; left: 6px;
width: ${bboxA.x-bboxB.x}px; height: ${bboxA.y-bboxB.y}px;
border: ${lineWidth}px solid ${lineColor};
border-right: none;
border-top: none;
border-bottom-left-radius: ${borderRadius}px;
pointer-events: none;
z-index: 11 !important;
}
`
}
// highlight bullet
bullet.classList.add('path-highlighted')
})
// set content of style tag to include both the base styles from before and the bullet styles we just generated
style.textContent = baseStyle + bulletStyle;
})
@ErikPlachta
Copy link

Amazing, thank you.

@azlen
Copy link
Author

azlen commented Feb 13, 2021

Hey all, seems there was a problem where the class on the bullets would change when you have "edit icons" turned on

The latest code should fix the issue!

@mahadikprasad15
Copy link

mahadikprasad15 commented Feb 13, 2021 via email

@agoodbear
Copy link

Fantastic work~~~~

@orkhan10
Copy link

orkhan10 commented Feb 19, 2021

Am I the only one having this problem where I can't expand and collapse blocks other than the one I am currently editing? See video:

https://drive.google.com/file/d/14S5L2kYmnZET8VyuWhzOw2FufSUTe9TB/view?usp=sharing

@azlen
Copy link
Author

azlen commented Feb 26, 2021

@orkhan10 Thanks for pointing out the problem with expand/collapse—this should be fixed in the latest version if you grab the latest code

@orkhan10
Copy link

@orkhan10 Thanks for pointing out the problem with expand/collapse—this should be fixed in the latest version if you grab the latest code

Lovely, thank you!

@agoodbear
Copy link

CleanShot_2021-02-27_at_12_57_24_2x

Is there a way to fix that the bulletpath line not completely overlaping the previous line? I hope the bulletpath line overlap the previous vertical line~~ Thanks~~~

@kvistgaard
Copy link

I'm using the standard theme. It used to work fine for a few days and now the vertical lines appear above the path lines
image

@abhayprasanna
Copy link

This was fixed by adding : z-index: 11 !important; to the .path-highlighted class (line 105). They added z-index of 10 to .rm-multibar

@agoodbear
Copy link

This was fixed by adding : z-index: 11 !important; to the .path-highlighted class (line 105). They added z-index of 10 to .rm-multibar

It works~~~Thanks

@zzzgithubzzz
Copy link

Wow, wonderfully useful addition to Roam. Thank you.

@kauderk
Copy link

kauderk commented Mar 23, 2021

I want to offset them vertically a few pixel.
Where should I do it?

@digitecture
Copy link

How do we get the new bullets to appear below the existing bullet to draw attention to it rather than cover it over with the new color?

@jacobtfisher
Copy link

Great work -- thanks for this! Is there any way to make it play nice with headings? It looks great whenever there are no headings applied, but lines are off when they are.

screenshot_2021-04-25 at 13 10 11@2x

@Mannixhu
Copy link

Screenshot_2021-07-20_14-23-30

some error info "#object[TypeError TypeError: document.arrive(...) is not a function]" (Roam: 0.8.7-d82d431a chrome: 91.0.4472.114)

@LudwigWS
Copy link

LudwigWS commented Sep 7, 2021

awesome

@zzzgithubzzz
Copy link

This was fixed by adding : z-index: 11 !important; to the .path-highlighted class (line 105). They added z-index of 10 to .rm-multibar

This worked 👍 🙏

@mickm3n
Copy link

mickm3n commented Sep 11, 2021

Thanks!! I really love it!!

@DRschulman
Copy link

I've been seeing this feature popping up in Roam videos all over the place and couldn't figure out where they got it! So happy I found it. This is fabulous! Thank you.

@DRschulman
Copy link

Hi @abhayprasanna this looks and works fabulous on Drac Pro!

@abhayprasanna
Copy link

Hi @abhayprasanna this looks and works fabulous on Drac Pro!

Indeed!

@maugermika2
Copy link

Hi, I know I'm writing a bit late but my bullet does not get highlighted. I tried resetting my user settings and deleting my css, nothing works. Thanks for your help !

@8bitgentleman
Copy link

Hey @azlen , are you planning on bringing this incredible plugin to the new official Roam plugin marketplace? If not would you be comfortable passing the reins to me or someone in the Roam community to keep it updated?

@paulovieira
Copy link

Hello @azlen! This plugin was re-implemented as a Roam Extension (in Roam Depot). The new code is here: https://github.com/paulovieira/roam-reference-path

The original code was re-written but the essence of the original idea is the same (using the ::before css pseudo-selectors and getBoundingClientRect to compute the correct width/height for the box).

I'm happy to transfer my repository to you if you want. Let me know.

More details here: Roam-Research/roam-depot#48

@JasperGeh
Copy link

Hi @paulovieira,

I compared the original code from azlen and your roamdepot implementation and saw that the horizontal line from the multibar to the bullet is moved up a few px in your version and doesn't "enter" the bullet in the middle.

@paulovieira
Copy link

paulovieira commented Sep 8, 2022

Hi @paulovieira,

I compared the original code from azlen and your roamdepot implementation and saw that the horizontal line
from the multibar to the bullet is moved up a few px in your version and doesn't "enter" the bullet in the middle.

@JasperGeh, thanks for looking into this! The line was indeed slightly out of place when using a line width of 2px. This is now corrected in the latest update submitted to roam depot. The problem should be solved when the update is accepted (you can confirm it is updated when you see "Version: 2" instead of "Version: 1" in roam depot).

By the way, to have the new extension look exactly like the original, the settings must be configured like so:

Screenshot_2022-09-07_15-45-43_options

@DRschulman
Copy link

DRschulman commented Oct 11, 2022 via email

@paulovieira
Copy link

@maugermika2 @DRschulman This extension is now available in Roam Depot. It has several improvements over the original implementation (the code in this gist) and is now the simplest way to install.

@Julin2003
Copy link

this is so beautiful, thank u!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment