Skip to content

Instantly share code, notes, and snippets.

@thesved
Last active October 12, 2020 17:33
Show Gist options
  • Save thesved/808f921c73ca06f2e00d37a006b71d41 to your computer and use it in GitHub Desktop.
Save thesved/808f921c73ca06f2e00d37a006b71d41 to your computer and use it in GitHub Desktop.
Roam Research Auto Enclose for quotes et al
/*
* Viktor's Roam Native Autoenclose PoC
* version: 0.3
* author: @ViktorTabori
*
* How to install it:
* - go to page [[roam/js]]
* - create a node with: {{[[roam/js]]}}
* - create a clode block under it, and change its type from clojure to javascript
* - allow the running of the javascript on the {{[[roam/js]]}} node
*/
window.ViktorEnclose = window.ViktorEnclose || (function(){
var started = false;
start();
return {
isStarted: ()=>started,
start: start,
stop: stop,
};
function start() {
if (started) return;
started = !started;
document.addEventListener('keydown', process);
console.log('** auto enclose installed **');
}
function stop() {
if (!started) return;
started = !started;
document.removeEventListener('keydown', process);
console.log('** auto enclose STOPPED **');
}
async function process(e) {
// no ` character since now it seems to be supported by Roam
var autoEnclose = "'\"";
var autoComplete = "\""; // no autocomplete without selection for ' because of `it's` for example
var replace = [[/[„“”"]/g, '"'], [/[‘’']/g, "'"]];
var delKeys = ";Delete;Backspace;";
// we only modify text within a textarea
var t = e.target;
if (t.tagName != 'TEXTAREA') return;
// replace different quotes
var key = e.key;
replace.forEach(function(r){
key = key.replace(r[0], r[1]);
});
// we listen only for autoEnclose characters
if (autoEnclose.concat(autoComplete).indexOf(key) == -1 && delKeys.indexOf(';'+key+';') == -1) return;
// save current selection
var range = {start:t.selectionStart, end:t.selectionEnd, key:key, value:t.value, selection:t.value.substr(t.selectionStart,t.selectionEnd-t.selectionStart)};
// wait for text change
await sleep(20);
// process text
var ret;
// selected text is auto enclosed
if (range.start != range.end && autoEnclose.indexOf(range.key) > -1) {
ret = {elem:t, value:insert(t.value, range.start+1, range.selection+range.key), start:range.start+1, end:range.start+1+range.selection.length};
}
// auto complete when next char is a whitespace
if (range.start == range.end && autoComplete.indexOf(range.key) > -1 && (range.start == range.value.length || (range.value[range.start]||'').match(/\W/))) {
ret = {elem:t, value:insert(t.value, range.start+1, range.key), start:range.start+1, end:range.start+1};
}
// delete autoenclose characters symetrically
var direction = range.Key == "Delete"?0:-1;
if (range.start == range.end && delKeys.indexOf(range.key) > -1 && autoEnclose.indexOf(range.value[range.start+direction]) > -1 && range.value[range.start+direction] == range.value[range.start-(direction+1)]) {
ret = {elem:t, value:insert(t.value, range.start-1, '', 1), start:range.start-1, end:range.start-1};
}
if (ret) {
// replace quote-like characters
replace.forEach(function(r){
ret.value = ret.value.replace(r[0], r[1]);
});
await setValue(ret.elem, ret.value, ret.start, ret.end);
}
}
// bypass react setter, set text to textarea
async function setValue(elem, value, selectStart=0, selectEnd=0) {
Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype,"value").set.call(elem,value);
await sleep(20);
elem.selectionStart = selectStart;
elem.selectionEnd = selectEnd;
await sleep(20);
elem.dispatchEvent(new Event('input', {bubbles: true, cancelable: true }));
}
// insert text into an another at a given position
function insert(str, index, add, del=0) {
return str.slice(0, index) + add + str.slice(index + del);
}
function sleep(millis) {
return new Promise(function(resolve){setTimeout(resolve, millis||20)});
}
})();
@thesved
Copy link
Author

thesved commented Jun 7, 2020

No worries, either

  • when Roam is loaded, open up developer console (Mac: cmd + alt + i, Win: ctr + alt + i), and after selecting console copy - paste - hit enter
  • or if you like a little challenge, create a minimal chrome extension: https://gist.github.com/danharper/8364399

@pinusm
Copy link

pinusm commented Jul 12, 2020

another noob question..
What would be the procedure for using this in [[roam/js]]?
I tried pasting the script there in a code block, but it doesn't work.
Is it possible?

@thesved
Copy link
Author

thesved commented Jul 20, 2020

another noob question..
What would be the procedure for using this in [[roam/js]]?
I tried pasting the script there in a code block, but it doesn't work.
Is it possible?

You could try this one:

  • go to page [[roam/js]]
  • create a node with: {{[[roam/js]]}}
  • create a clode block under it, and change its type from clojure to javascript
  • allow the running of the javascript on the {{[[roam/js]]}} node

Let me know how it goes.

@pinusm
Copy link

pinusm commented Jul 23, 2020

I was missing the second step (the {{[[roam/js]]}} top node )..
Now it works!
Thanks!!

@thesved
Copy link
Author

thesved commented Jul 25, 2020

Awesome, thank you for letting me know!

@tfiers
Copy link

tfiers commented Oct 12, 2020

FYI: Roam now has built-in auto-enclose for backticks (`), so the backticks in the config strings on lines 24-25 oughta be deleted for there to be no conflict. The single and double quotes (and any additional custom delimiters you add) can stay.

Thanks for this script btw @thesved, I use it daily :)

@thesved
Copy link
Author

thesved commented Oct 12, 2020

Cheers @tfiers, I used a newer version of this and forgot to update here.

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