├── README.md ├── src.crx ├── src.pem └── src ├── content.js └── manifest.json /README.md: -------------------------------------------------------------------------------- 1 | this is the read me for a project to make a dictionary of auto-linking words in roam 2 | 3 | ---------------------------------------------------------------- 4 | #### Get the extension here: 5 | https://chrome.google.com/webstore/detail/roam-autolink/ilegcckbllooaoplapjfgkgjkjpbbjpe 6 | ---------------------------------------------------------------- 7 | ### USER INSTRUCTIONS 8 | * this currently a basic version of an automatic linking tool for roam 9 | 10 | * any time you type a word that is in your dictionary of saved words it will automatically make it a link 11 | 12 | * this is currently triggered by the SPACE BUTTON, notably this means that for now you will have to hit space before you hit enter to link words at the end of a line 13 | 14 | * so if we have "dog" saved as am autolink "my dog is the best" will become "my [[dog]] is the best" 15 | 16 | #### TO ADD A LINK 17 | * start a new line in roam 18 | 19 | * type your word on the new line wrapped in + signs like so 20 | 21 | +word or phrase+ 22 | 23 | #### TO ADD A HASHTAG LINK 24 | * start a new line in roam 25 | 26 | * type your word on the new line starting with + and ending with # like so 27 | 28 | +word or phrase# 29 | 30 | #### TO REMOVE A LINK 31 | 32 | * same as above but with - signs, like so 33 | 34 | -word or phrase- 35 | 36 | #### TO SEE WHAT WORDS YOU HAVE SET TO AUTOLINK 37 | * go to a new line in roam and type two plus signs, like so 38 | 39 | ++ 40 | * this will display all the links you currently have, like so 41 | links: word, example phrase. 42 | 43 | #### TO CLEAR THE DISPLAYED WORDS 44 | * if you have hit ++ and displayed your currently linked word you can type dash at the end of the line to clear it, like so 45 | 46 | links: word, example phrase.- 47 | 48 | ### END OF USER INSTRUCTIONS 49 | ------------------------------------------------------------------ 50 | 51 | ### TO DO/IDEAS 52 | [ ] CLEAN UP COMMENTED CODE, ADD ACTUAL COMMENTS 53 | 54 | [+] make search fire on any letter keystroke (possibly excluding + and -) rather than just space [DONE] 55 | 56 | [ ] make it so lines starting with + or - cannot trigger auto-link-recognition (maybe do't do this, adding links containing smaller links will cause other more difficult problems) 57 | 58 | [ ] make matching in autlink dictionary non-case-sensitive 59 | 60 | [x] make shortcut for clearing dictionary when listed out (possibly typing -- at the end of the string) [DONE] 61 | 62 | [ ] get autofill working for saved words and phrases, detect partial strings in dictionary (includes method?) and maybe interface with roams autofill somehow.. 63 | 64 | [ ] build checkbox/form style GUI to manage words. draw this first 65 | 66 | [ ] make syntax to add or remove words that doesn not require going to a new line (possibly something like [[word+]]) (this would also give the user access to roams autcomplete when adding a new link!!) 67 | 68 | [ ] add shortcut for displaying instructions (/autolink-i, or something) 69 | 70 | [ ] ability to add multiple new links at once, comma separated (for example +ok, yes, word, and phrase+) 71 | 72 | [ ] stop the words "link: ." from displaying when there are no linked words to display 73 | 74 | [ ] restructure dicitonary to set a local variable based on chrome storage so that chrome storage is not being called for every keystroke which could cause latency problems 75 | 76 | [] figure out why cursor stays inside word sometimes and fix it 77 | 78 | [] make a way/command to set all links to be autolinks 79 | 80 | . 81 | ------------------------------------------------------------------- 82 | old notes 83 | 84 | detect if url is roam [done] 85 | 86 | [x] detect keystrokes from user [done] 87 | 88 | if i can do those. 89 | 90 | add any word with [[]] around it to a dictionary(type thing) [working on / different approach maybe necessary] 91 | search the dictionary of words for all future words (every keystroke? every space so it only fires at the end of the word?) 92 | 93 | if it matches, inject the [[]] around the word (not sure if possible, but i think so) [DONE] 94 | 95 | 96 | -------------------------------------------------------------------------------- /src.crx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlakeAnd/link-dictionary/c1b002a9e19535ea5ddc4fd7ada2ee73aa0085e1/src.crx -------------------------------------------------------------------------------- /src.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC9bunPI8Jm6Pv/ 3 | A2Vs8QKiWITzen1wDN4ST732zQscgqkrKlx42gEeq1nM7rfAzFMGqrpRjx2z+1yS 4 | axZOMmYOeUvo/T+V1+RBRbW3X2m/d3LGpvratvYfW7AKV6ewIAER0w0hlDzAbSm0 5 | M1F2+pspiDjE5Tw+aH1YK9KO7kngvfdsubXn6fQnSOtPn7ueviM0WSXWp+MeJ9cZ 6 | 3azQq4kZ2mDtGIR2zf77cFphPiZ8L7ni6OOX+4gmGe711wydGlM6gcsK9EwOibp8 7 | JYzxz77RjQi1LsFFI81Coeck/xPPuggD4Qd5VOcSvU4UAG1uoMlUmX7EAXG8rS7n 8 | 4HP6ozuTAgMBAAECggEAO1AHFTx1GhPJE0fU8g9ka8CboECyFetxfPpK2IPK/tpK 9 | baGN4qkfwKKWM3xrPIq9VFEQlYjiHACQM6gkC8fr/IwuIH1q81ftuCJDi27shW65 10 | GUofMWwoFBl5PUPE1Xiv3QzgB6uKehW0ASAwWbiN7KsiW0QLYN8GoBzYiLhDmYJW 11 | iA5pJCa6YE376ZbdSc0tS4XHLoVI4Im3pcLADS55uMcOW18VB+AhzlUV/qgG7gIh 12 | K0lbWVls46irkN9y6T3MZRJKcrWhGaMi2OeCBHh0Onqoa/Uuf1QEhca7j5Yvsbw4 13 | SejBS3xbEngSoXszIxcYXlFtisOTZU4/MBO+UMQRAQKBgQD5PYNyq3jS+KGEJ6/J 14 | Vjj1reuXnoyWz0h7dTvPQIEsR1RrVTjE5Pdffsb9egw5SqSBmEh8/2Th/akl778o 15 | VQoFU5fl/oiAJxqGGVIyesOFzx6HcmgGuV69L2TbVHaoY98c2anZkAWzm+ZJzWS7 16 | rUwNkr2OH1Bd0Yt0Ntv1bgmE0QKBgQDCkigs2Qmmbx+djhpgqM0mB+/EdaFxkANJ 17 | FfseVmWPiFvAqpHLqLqoFt/Nb8DHF65cjZBy8Chi7Py4vH/XZeWEdaa7Xg+Hcmj1 18 | 9RxGQTjTq7cG6scydJVoYVhgRUh0GvbWumu8lGeSSy+UeYksPMMTHnSxg6Rs7wDv 19 | 6JKJeLWjIwKBgQDwSq4AT/Ec9ThDAUApDEe+FP+eHe3NN+rZnB0do0LmPZ7WHEOv 20 | EFCucLvIhXJjieMwTnEUkeXhO31oZcwWRmZy3DUGOG/BnfGkd6UXpeP7jcQRMeu5 21 | D96W5qqGCtibYC4q0m8+oevdTeCoJq2Hg3xfWaoG64m/6dZZJMrLxFrJAQKBgQCc 22 | T6Lq9KnmBZwWjVTvlmzJQtMCt6WtgA7Dpl8JrksFFnzvuZhLTxA1fbrqZf8vcvzm 23 | 6evECt/HKbCWEQl7WCcdVQ9Ps28yV12vSu0eG8O1eVweuHqzNCnbo2jGXqKodAkd 24 | 1MI8y9SxGKnu0/y5h08Iiw0glyt/QDs2gVdIDauOlQKBgQDYqavZxhZYaanosA0l 25 | 8S8Y0eCh2dIk5UoC5vr482jeFC2gRz9ePFAOBm3RsnKcNH7GtnwEirtStTw9FIu6 26 | rxdjIVan7HmLyi990o74AXU050iw6IMpP10BJsQZdFdo430gUMh2SFWTGiKjIRTF 27 | MAXO5D8rRbuELMfmeMJHAMjaNA== 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /src/content.js: -------------------------------------------------------------------------------- 1 | window.addEventListener('load', function () { 2 | 3 | var longest_link = get_link_at_start(); 4 | let text_key = null; 5 | let directions = `you can type - at the end of the directions to clear them away COMMANDS, TO ADD A LINK: start a new line and type +your word or phrase+ TO ADD A # STYLE LINK: start a new line and type +your word or phrase# TO REMOVE A LINK: start a new line and type -your word or phrase- SEE ALL LINKS SET TO AUTOLINK: start a new line in roam and type ++ TO CLEAR THE LIST OF AUTOLINKS: while the list of autolinks is displayed type a - at the end, for example "links: word, example phrase.-" will make the line blank. SHOW DIRECTIONS: start a new line and type autolink+ to bring back these directions`; 6 | 7 | document.onkeypress = function() { 8 | if (event.keyCode == 43){ 9 | handle_plus(); 10 | } 11 | else if (event.keyCode == 45){ 12 | handle_minus(); 13 | } 14 | else if (event.keyCode == 32){ 15 | text_key = event.key; 16 | handle_other(); 17 | } 18 | else if (event.keyCode == 35){ 19 | handle_hashtag(); 20 | } 21 | }; 22 | 23 | function handle_other(){ 24 | let str = document.activeElement.value; 25 | let inner_str = str.slice(1); 26 | if(str[0] != "+" && str[0] != "-"){ 27 | dictionary_exists("check for link"); 28 | } 29 | } 30 | 31 | function handle_hashtag() { 32 | let str = document.activeElement.value; 33 | let inner_str = str.slice(1); 34 | if(str[0] === "+" && inner_str.length > 0 && inner_str.includes(" ") === false){ 35 | event.preventDefault(); 36 | dictionary_exists("add # word"); 37 | } 38 | } 39 | 40 | function handle_plus () { 41 | let str = document.activeElement.value; 42 | let inner_str = str.slice(1); 43 | if(str === "+"){ 44 | event.preventDefault(); 45 | dictionary_exists("show dictionary"); 46 | } 47 | else if(str === "autolink"){ 48 | event.preventDefault(); 49 | dictionary_exists("show directions") 50 | } 51 | else if(str[0] === "+" && inner_str.length > 0){ 52 | event.preventDefault(); 53 | dictionary_exists("add word"); 54 | } 55 | 56 | } 57 | 58 | function handle_minus () { 59 | let str = document.activeElement.value; 60 | let inner_str = str.slice(1); 61 | if(str[0] === "-" && inner_str.length > 0){ 62 | dictionary_exists("remove word"); 63 | } 64 | else { 65 | dictionary_exists("hide dictionary") 66 | } 67 | } 68 | 69 | //checks if the dictionary has been created in the chrome api and calls functions based on the and a control string 70 | //this needs to be restructured so we are not calling the chrome api so much, something similar to the way the storage of longest link is handled 71 | function dictionary_exists (action) { 72 | let it_exists = false; 73 | chrome.storage.local.get(["dictionary"], function(result) { 74 | for(var key in result) { 75 | if(result.hasOwnProperty(key)){ 76 | it_exists = true; 77 | } 78 | } 79 | if(it_exists === true){ 80 | if(action === "show dictionary"){ 81 | show_dictionary(); 82 | } 83 | else if (action === "add word"){ 84 | add_word(); 85 | } 86 | else if (action === "remove word"){ 87 | remove_word(); 88 | } 89 | else if(action === "check for link"){ 90 | check_link(); 91 | } 92 | else if(action === "hide dictionary"){ 93 | hide_dictionary_or_directions(); 94 | } 95 | else if(action === "show directions"){ 96 | show_directions(); 97 | } 98 | else if(action = "add # word"){ 99 | add_hashtag_word(); 100 | } 101 | } 102 | else { 103 | if(action === "add word"){ 104 | add_first_word(); 105 | } 106 | else if(action === "add # word"){ 107 | add_first_hashtag_word(); 108 | } 109 | } 110 | }); 111 | 112 | } 113 | 114 | function show_directions () { 115 | document.activeElement.value = directions; 116 | } 117 | 118 | function show_dictionary () { 119 | let dict_display = ""; 120 | chrome.storage.local.get(["dictionary"], function(result) { 121 | let dictionary = result.dictionary; 122 | for(var key in dictionary) { 123 | if(dictionary[key] != "inactive"){ 124 | dict_display = dict_display + key + ", "; 125 | } 126 | } 127 | dict_display = dict_display.slice(0, dict_display.length-2); 128 | dict_display = "links: " + dict_display + "."; 129 | document.activeElement.value = dict_display; 130 | }); 131 | } 132 | 133 | function hide_dictionary_or_directions () { 134 | let str = document.activeElement.value; 135 | let dict_display = ""; 136 | chrome.storage.local.get(["dictionary"], function(result) { 137 | let dictionary = result.dictionary; 138 | for(var key in dictionary) { 139 | if(dictionary[key] != "inactive"){ 140 | dict_display = dict_display + key + ", "; 141 | } 142 | } 143 | dict_display = dict_display.slice(0, dict_display.length-2); 144 | dict_display = "links: " + dict_display + ".-"; 145 | if(str === dict_display || str === directions){ 146 | document.activeElement.value = ""; 147 | } 148 | }); 149 | } 150 | 151 | function check_link () { 152 | let str = document.activeElement.value; 153 | let display_str = document.activeElement.value; 154 | let check_str = ""; 155 | let min = 0; 156 | let max = str.length; 157 | 158 | let cursor_index = get_cursor_position(); 159 | if(cursor_index-longest_link > 0){ 160 | min = cursor_index-longest_link-1; 161 | } 162 | if(cursor_index+longest_link < str.length){ 163 | max = cursor_index+longest_link; 164 | } 165 | 166 | chrome.storage.local.get(["dictionary"], function(result) { 167 | let dictionary = result.dictionary; 168 | 169 | for(let i = min; i < cursor_index-1; i++){ 170 | check_str = str.slice(i, cursor_index - 1); 171 | if(dictionary[check_str] === "bracket" && (min === 0 || str[i-1] === " ")){ 172 | str = str.slice(0, i) + "[[" + check_str + "]] " + str.slice(cursor_index); 173 | document.activeElement.value = str; 174 | document.activeElement.selectionEnd = cursor_index + 4; 175 | break; 176 | } 177 | else if (dictionary[check_str] === "hashtag" && (min === 0 || str[i-1] === " ")){ 178 | str = str.slice(0, i) + "#" + check_str + " " + str.slice(cursor_index); 179 | document.activeElement.value = str; 180 | document.activeElement.selectionEnd = cursor_index + 1; 181 | break; 182 | } 183 | } 184 | for(let i = max; i > cursor_index; i--){ 185 | 186 | } 187 | 188 | }); 189 | } 190 | 191 | function get_link_at_start () { 192 | chrome.storage.local.get(["longest_link"], function(result) { 193 | if(typeof(result.longest_link) === "number"){ 194 | return result.longest_link; 195 | } 196 | else{ 197 | return 0; 198 | } 199 | }); 200 | } 201 | 202 | function add_first_word (){ 203 | let str = document.activeElement.value; 204 | let inner_str = str.slice(1); 205 | let dictionary = {}; 206 | longest_link = inner_str.length; 207 | dictionary[inner_str] = "bracket" 208 | chrome.storage.local.set({"dictionary": dictionary}, function() {}); 209 | chrome.storage.local.set({"longest_link": longest_link}, function() {}); 210 | } 211 | 212 | function add_word(){ 213 | let str = document.activeElement.value; 214 | let inner_str = str.slice(1); 215 | chrome.storage.local.get(["dictionary"], function(result) { 216 | let dictionary = result.dictionary; 217 | if(dictionary[inner_str] != "bracket"){ 218 | dictionary[inner_str] = "bracket"; 219 | } 220 | chrome.storage.local.set({"dictionary": dictionary}, function() {}); 221 | }); 222 | chrome.storage.local.get(["longest_link"], function(result) { 223 | if(inner_str.length > result.longest_link){ 224 | longest_link = inner_str.length; 225 | chrome.storage.local.set({"longest_link": longest_link}, function() {}); 226 | } 227 | }); 228 | 229 | document.activeElement.value = ""; 230 | } 231 | 232 | function add_first_hashtag_word () { 233 | let str = document.activeElement.value; 234 | let inner_str = str.slice(1); 235 | let dictionary = {}; 236 | longest_link = inner_str.length; 237 | dictionary[inner_str] = "hashtag" 238 | chrome.storage.local.set({"dictionary": dictionary}, function() {}); 239 | chrome.storage.local.set({"longest_link": longest_link}, function() {}); 240 | } 241 | 242 | function add_hashtag_word(){ 243 | let str = document.activeElement.value; 244 | let inner_str = str.slice(1); 245 | chrome.storage.local.get(["dictionary"], function(result) { 246 | let dictionary = result.dictionary; 247 | if(dictionary[inner_str] != "hashtag"){ 248 | dictionary[inner_str] = "hashtag"; 249 | } 250 | chrome.storage.local.set({"dictionary": dictionary}, function() {}); 251 | }); 252 | chrome.storage.local.get(["longest_link"], function(result) { 253 | if(inner_str.length > result.longest_link){ 254 | longest_link = inner_str.length; 255 | chrome.storage.local.set({"longest_link": longest_link}, function() {}); 256 | } 257 | }); 258 | 259 | document.activeElement.value = ""; 260 | } 261 | 262 | function remove_word(){ 263 | let str = document.activeElement.value; 264 | let inner_str = str.slice(1, str.length-1); 265 | chrome.storage.local.get(["dictionary"], function(result) { 266 | let dictionary = result.dictionary; 267 | if(dictionary[inner_str] != "inactive"){ 268 | dictionary[inner_str] = "inactive"; 269 | document.activeElement.value = ""; 270 | } 271 | chrome.storage.local.set({"dictionary": dictionary}, function() {}); 272 | }); 273 | } 274 | 275 | function get_cursor_position(){ 276 | let cursor_position = null; 277 | if(document.activeElement.selectionStart === document.activeElement.selectionEnd){ 278 | cursor_position = document.activeElement.selectionStart; 279 | } 280 | return cursor_position; 281 | }; 282 | 283 | //this is a function for dev use only that clears the chrome storage completely 284 | //i currently use it by swapping it in where show_dictionary gets called then typing ++ to trigger it, this is clunky but does not happen too often 285 | function dev_clear_chrome_storage () { 286 | chrome.storage.local.clear(function() { 287 | var error = chrome.runtime.lastError; 288 | if (error) { 289 | console.error(error); 290 | } 291 | }); 292 | } 293 | 294 | }) 295 | 296 | 297 | -------------------------------------------------------------------------------- /src/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "roam-autolink", 3 | "version": "1.3", 4 | "description": "track and automate words that have been made into links/pages on roam research", 5 | "manifest_version": 3, 6 | "permissions": ["storage"], 7 | "content_scripts": [ 8 | { 9 | "matches": ["https://roamresearch.com/*"], 10 | "js": ["content.js"], 11 | "run_at": "document_end" 12 | } 13 | ], 14 | "action": { 15 | "default_popup": "popup.html", 16 | "default_icon": { 17 | "16": "images/icon16.png", 18 | "48": "images/icon48.png", 19 | "128": "images/icon128.png" 20 | } 21 | }, 22 | "icons": { 23 | "16": "images/icon16.png", 24 | "48": "images/icon48.png", 25 | "128": "images/icon128.png" 26 | }, 27 | "host_permissions": [ 28 | "https://roamresearch.com/*" 29 | ] 30 | } --------------------------------------------------------------------------------