├── .gitignore ├── LICENSE ├── README.md ├── icons ├── pinboard_icon.png ├── pinboard_icon_16.png ├── pinboard_icon_48.png ├── pinboard_icon_96.png ├── toolbar_icon.svg ├── toolbar_icon_16.png ├── toolbar_icon_16_light.png ├── toolbar_icon_32.png ├── toolbar_icon_32_light.png ├── toolbar_icon_48.png ├── toolbar_icon_48_light.png ├── toolbar_icon_96.png ├── toolbar_icon_96_light.png └── toolbar_icon_light.svg ├── link_saved.js ├── main.js ├── manifest.json ├── popup_menu.html ├── popup_menu.js ├── preferences.js ├── preferences_page.html └── preferences_page.js /.gitignore: -------------------------------------------------------------------------------- 1 | /*.code-workspace 2 | /*.sh 3 | web-ext-artifacts/ 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 George A. Pop 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pinboard WebExtension 2 | 3 | This is a Firefox extension for easily adding links to [Pinboard](https://pinboard.in). It is a remake of the official extension, developed on the WebExtension API. 4 | 5 | Install from AMO: https://addons.mozilla.org/en-US/firefox/addon/pinboard-webextension/. 6 | -------------------------------------------------------------------------------- /icons/pinboard_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gapop/pinboard-webextension/9de5872acae2ec433358099cebaf1b714d5bc84c/icons/pinboard_icon.png -------------------------------------------------------------------------------- /icons/pinboard_icon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gapop/pinboard-webextension/9de5872acae2ec433358099cebaf1b714d5bc84c/icons/pinboard_icon_16.png -------------------------------------------------------------------------------- /icons/pinboard_icon_48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gapop/pinboard-webextension/9de5872acae2ec433358099cebaf1b714d5bc84c/icons/pinboard_icon_48.png -------------------------------------------------------------------------------- /icons/pinboard_icon_96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gapop/pinboard-webextension/9de5872acae2ec433358099cebaf1b714d5bc84c/icons/pinboard_icon_96.png -------------------------------------------------------------------------------- /icons/toolbar_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Svg Vector Icons : http://www.onlinewebfonts.com/icon 6 | 7 | -------------------------------------------------------------------------------- /icons/toolbar_icon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gapop/pinboard-webextension/9de5872acae2ec433358099cebaf1b714d5bc84c/icons/toolbar_icon_16.png -------------------------------------------------------------------------------- /icons/toolbar_icon_16_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gapop/pinboard-webextension/9de5872acae2ec433358099cebaf1b714d5bc84c/icons/toolbar_icon_16_light.png -------------------------------------------------------------------------------- /icons/toolbar_icon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gapop/pinboard-webextension/9de5872acae2ec433358099cebaf1b714d5bc84c/icons/toolbar_icon_32.png -------------------------------------------------------------------------------- /icons/toolbar_icon_32_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gapop/pinboard-webextension/9de5872acae2ec433358099cebaf1b714d5bc84c/icons/toolbar_icon_32_light.png -------------------------------------------------------------------------------- /icons/toolbar_icon_48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gapop/pinboard-webextension/9de5872acae2ec433358099cebaf1b714d5bc84c/icons/toolbar_icon_48.png -------------------------------------------------------------------------------- /icons/toolbar_icon_48_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gapop/pinboard-webextension/9de5872acae2ec433358099cebaf1b714d5bc84c/icons/toolbar_icon_48_light.png -------------------------------------------------------------------------------- /icons/toolbar_icon_96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gapop/pinboard-webextension/9de5872acae2ec433358099cebaf1b714d5bc84c/icons/toolbar_icon_96.png -------------------------------------------------------------------------------- /icons/toolbar_icon_96_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gapop/pinboard-webextension/9de5872acae2ec433358099cebaf1b714d5bc84c/icons/toolbar_icon_96_light.png -------------------------------------------------------------------------------- /icons/toolbar_icon_light.svg: -------------------------------------------------------------------------------- 1 | 2 | 16 | Svg Vector Icons : http://www.onlinewebfonts.com/icon image/svg+xml 20 | 29 | -------------------------------------------------------------------------------- /link_saved.js: -------------------------------------------------------------------------------- 1 | browser.runtime.sendMessage({event: 'link_saved'}) 2 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | const Pinboard = { 2 | url: { 3 | add_link: 'https://pinboard.in/add?showtags={show_tags}&url={url}&title={title}&description={description}', 4 | read_later: 'https://pinboard.in/add?later=yes&noui=yes&jump=close&url={url}&title={title}', 5 | save_tabs: 'https://pinboard.in/tabs/save/', 6 | show_tabs: 'https://pinboard.in/tabs/show/', 7 | login: 'https://pinboard.in/popup_login/' 8 | }, 9 | 10 | async get_endpoint(url_handle, bookmark_info) { 11 | const url_template = this.url[url_handle] 12 | const show_tags = await Preferences.get('show_tags') ? 'yes' : 'no' 13 | let endpoint = url_template.replace('{show_tags}', show_tags) 14 | if (bookmark_info) { 15 | endpoint = endpoint.replace('{url}', encodeURIComponent(bookmark_info.url || '')) 16 | .replace('{title}', encodeURIComponent(bookmark_info.title || '')) 17 | .replace('{description}', encodeURIComponent(bookmark_info.description || '')) 18 | } 19 | return endpoint 20 | } 21 | } 22 | 23 | const App = { 24 | toolbar_button_state: Preferences.defaults.toolbar_button, 25 | 26 | // Returns the original URL for a page opened in Firefox's reader mode 27 | async strip_reader_mode_url(url) { 28 | if (url.indexOf('about:reader?url=') == 0) { 29 | url = decodeURIComponent(url.substr(17)) 30 | } 31 | return url 32 | }, 33 | 34 | async get_bookmark_info_from_current_tab() { 35 | const tabs = await browser.tabs.query({currentWindow: true, active: true}) 36 | const info = { 37 | url: await this.strip_reader_mode_url(tabs[0].url), 38 | title: tabs[0].title 39 | } 40 | try { 41 | info.description = await browser.tabs.executeScript({code: 'getSelection().toString()'}) 42 | } catch (error) { 43 | info.description = '' 44 | } 45 | return info 46 | }, 47 | 48 | async get_bookmark_info_from_context_menu_target(info, tab) { 49 | let url 50 | let title = '' 51 | 52 | if (info.linkUrl) { 53 | url = info.linkUrl 54 | if (info.linkText) { 55 | title = info.linkText.substr(0, 200) 56 | if (title.length < info.linkText.length) { 57 | title += '...' 58 | } 59 | } 60 | } else { 61 | url = info.pageUrl 62 | title = tab.title 63 | } 64 | 65 | return { 66 | url: await this.strip_reader_mode_url(url), 67 | title: title, 68 | description: info.selectionText || '' 69 | } 70 | }, 71 | 72 | // Opens a window for interacting with Pinboard 73 | async open_add_link_window(url) { 74 | const show_tags = await Preferences.get('show_tags') 75 | const bg_window = await browser.windows.getCurrent() 76 | const pin_window = await browser.windows.create({ 77 | url: url, 78 | type: 'popup', 79 | width: 750, 80 | height: show_tags ? 550 : 350, 81 | incognito: bg_window.incognito 82 | }) 83 | return pin_window 84 | }, 85 | 86 | // Open the Add Link form in a new tab 87 | async open_add_link_tab(url) { 88 | const active_tabs = await browser.tabs.query({currentWindow: true, active: true}) 89 | const opener_tab = active_tabs[0] 90 | const form_tab = await browser.tabs.create({ 91 | url: url, 92 | openerTabId: opener_tab.id 93 | }) 94 | return form_tab 95 | }, 96 | 97 | // Opens the Pinboard "Add Link" form 98 | async open_save_form(bookmark_info) { 99 | const endpoint = await Pinboard.get_endpoint('add_link', bookmark_info) 100 | const add_link_form_in_tab = await Preferences.get('add_link_form_in_tab') 101 | if (add_link_form_in_tab) { 102 | const tab = await this.open_add_link_tab(endpoint) 103 | this.close_save_form = async () => { 104 | await browser.tabs.remove(tab.id) 105 | } 106 | } else { 107 | const win = await this.open_add_link_window(endpoint) 108 | this.close_save_form = async () => { 109 | await browser.windows.remove(win.id) 110 | } 111 | } 112 | }, 113 | 114 | async close_save_form() { 115 | throw 'No close function defined.' 116 | }, 117 | 118 | // Saves the bookmark to read later 119 | async save_for_later(bookmark_info) { 120 | const endpoint = await Pinboard.get_endpoint('read_later', bookmark_info) 121 | const bg_window = await browser.windows.getCurrent() 122 | if (bg_window.incognito) { 123 | 124 | // In private mode we actually have to open a window, 125 | // because Firefox doesn't support split incognito mode 126 | // and gets confused about cookie jars. 127 | this.open_save_form(endpoint) 128 | 129 | } else { 130 | 131 | const http_response = await fetch(endpoint, {credentials: 'include'}) 132 | if (http_response.redirected && http_response.url.startsWith(await Pinboard.get_endpoint('login'))) { 133 | this.open_save_form(http_response.url) 134 | } else if (http_response.status !== 200 || http_response.ok !== true) { 135 | this.show_notification('FAILED TO ADD LINK. ARE YOU LOGGED-IN?', true) 136 | } else { 137 | this.show_notification('Saved to read later.') 138 | } 139 | 140 | } 141 | }, 142 | 143 | async save_tab_set() { 144 | const bg_window = browser.windows.getCurrent() 145 | if (bg_window.incognito) { 146 | this.show_notification("Due to a Firefox limitation, saving tab sets does not work in Private mode. Try normal mode!", true) 147 | return 148 | } 149 | 150 | const window_info = await browser.windows.getAll({populate: true, windowTypes: ['normal']}) 151 | let windows = [] 152 | for (let i = 0; i < window_info.length; i++) { 153 | const current_window_tabs = window_info[i].tabs 154 | let tabs = [] 155 | for (let j = 0; j < current_window_tabs.length; j++) { 156 | tabs.push({ 157 | title: current_window_tabs[j].title, 158 | url: await this.strip_reader_mode_url(current_window_tabs[j].url) 159 | }) 160 | } 161 | windows.push(tabs) 162 | } 163 | 164 | let payload = new FormData() 165 | payload.append('data', JSON.stringify({browser: 'ffox', windows: windows})) 166 | const http_response = await fetch(await Pinboard.get_endpoint('save_tabs'), {method: 'POST', body: payload, credentials: 'include'}) 167 | if (http_response.status !== 200 || http_response.ok !== true) { 168 | this.show_notification('FAILED TO SAVE TAB SET.', true) 169 | } else { 170 | browser.tabs.create({url: await Pinboard.get_endpoint('show_tabs')}) 171 | } 172 | }, 173 | 174 | async show_notification(message, force) { 175 | const show_notifications = await Preferences.get('show_notifications') 176 | if (force || show_notifications) { 177 | browser.notifications.create({ 178 | 'type': 'basic', 179 | 'title': 'Pinboard', 180 | 'message': message, 181 | 'iconUrl': 'icons/pinboard_icon_48.png' 182 | }) 183 | } 184 | }, 185 | 186 | async update_toolbar_button() { 187 | const pref = await Preferences.get('toolbar_button') 188 | if (pref != this.toolbar_button_state) { 189 | switch (pref) { 190 | 191 | case 'show_menu': 192 | browser.browserAction.setPopup({popup: 'popup_menu.html'}) 193 | browser.browserAction.setTitle({title: 'Add to Pinboard'}) 194 | break 195 | 196 | case 'save_dialog': 197 | browser.browserAction.setPopup({popup: ''}) 198 | browser.browserAction.setTitle({title: 'Add to Pinboard'}) 199 | break 200 | 201 | case 'read_later': 202 | browser.browserAction.setPopup({popup: ''}) 203 | browser.browserAction.setTitle({title: 'Add to Pinboard (read later)'}) 204 | break 205 | 206 | } 207 | this.toolbar_button_state = pref 208 | } 209 | }, 210 | 211 | async update_context_menu() { 212 | const add_context_menu_items = await Preferences.get('context_menu_items') 213 | if (add_context_menu_items) { 214 | browser.contextMenus.create({ 215 | id: 'save_dialog', 216 | title: 'Save...', 217 | contexts: ['link', 'page', 'selection'] 218 | }) 219 | browser.contextMenus.create({ 220 | id: 'read_later', 221 | title: 'Read later', 222 | contexts: ['link', 'page', 'selection'] 223 | }) 224 | browser.contextMenus.create({ 225 | id: 'save_tab_set', 226 | title: 'Save tab set...', 227 | contexts: ['page', 'selection'] 228 | }) 229 | } else { 230 | browser.contextMenus.removeAll() 231 | } 232 | }, 233 | 234 | async handle_message(message) { 235 | let bookmark_info 236 | switch (message) { 237 | case 'save_dialog': 238 | bookmark_info = await this.get_bookmark_info_from_current_tab() 239 | this.open_save_form(bookmark_info) 240 | break 241 | 242 | case 'read_later': 243 | bookmark_info = await this.get_bookmark_info_from_current_tab() 244 | this.save_for_later(bookmark_info) 245 | break 246 | 247 | case 'save_tab_set': 248 | this.save_tab_set() 249 | break 250 | 251 | case 'link_saved': 252 | this.close_save_form() 253 | this.show_notification('Link added to Pinboard') 254 | break 255 | } 256 | }, 257 | 258 | async handle_context_menu(info, tab) { 259 | let bookmark_info 260 | switch (info.menuItemId) { 261 | case 'save_dialog': 262 | bookmark_info = await this.get_bookmark_info_from_context_menu_target(info, tab) 263 | this.open_save_form(bookmark_info) 264 | break 265 | 266 | case 'read_later': 267 | bookmark_info = await this.get_bookmark_info_from_context_menu_target(info, tab) 268 | this.save_for_later(bookmark_info) 269 | break 270 | 271 | case 'save_tab_set': 272 | this.save_tab_set() 273 | break 274 | } 275 | }, 276 | 277 | async handle_preferences_changes(changes, area) { 278 | if (area !== Preferences.storage_area) { 279 | return 280 | } 281 | const key = Object.keys(changes).pop() 282 | switch (key) { 283 | case 'toolbar_button': 284 | this.update_toolbar_button() 285 | break 286 | case 'context_menu_items': 287 | this.update_context_menu() 288 | break 289 | } 290 | }, 291 | 292 | async handle_upgrade(details) { 293 | // Migrate user preferences from sync to local storage 294 | if (details.reason === 'update' && parseFloat(details.previousVersion) < 1.4) { 295 | await Preferences.migrate_to_local_storage() 296 | await this.update_toolbar_button() 297 | await this.update_context_menu() 298 | } 299 | } 300 | } 301 | 302 | // Attach message event handler 303 | browser.runtime.onMessage.addListener(message => { 304 | App.handle_message(message.event) 305 | }) 306 | 307 | // Toolbar button event handler 308 | browser.browserAction.onClicked.addListener(() => { 309 | App.handle_message(App.toolbar_button_state) 310 | }) 311 | 312 | // Keyboard shortcut event handler 313 | browser.commands.onCommand.addListener(command => {App.handle_message(command)}) 314 | 315 | // Context menu event handler 316 | browser.contextMenus.onClicked.addListener(async (info, tab) => { 317 | App.handle_context_menu(info, tab) 318 | }) 319 | 320 | // Preferences event handler 321 | browser.storage.onChanged.addListener((changes, area) => {App.handle_preferences_changes(changes, area)}) 322 | 323 | // Version update listener 324 | browser.runtime.onInstalled.addListener(details => {App.handle_upgrade(details)}) 325 | 326 | // Apply preferences when loading extension 327 | App.update_toolbar_button() 328 | App.update_context_menu() 329 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "name": "Pinboard WebExtension", 4 | "short_name": "Pinboard", 5 | "version": "1.64", 6 | "description": "Easily add links to Pinboard. A remake of the official Pinboard extension.", 7 | "author": "George Pop", 8 | "homepage_url": "https://github.com/gapop/pinboard-webextension", 9 | "applications": { 10 | "gecko": { 11 | "id": "pinboard-webextension@helloworld.ro", 12 | "strict_min_version": "60.0" 13 | } 14 | }, 15 | "icons": { 16 | "48": "icons/pinboard_icon_48.png", 17 | "96": "icons/pinboard_icon_96.png", 18 | "200": "icons/pinboard_icon.png" 19 | }, 20 | "permissions": [ 21 | "*://pinboard.in/*", 22 | "storage", 23 | "contextMenus", 24 | "activeTab", 25 | "notifications", 26 | "tabs" 27 | ], 28 | "browser_action": { 29 | "default_icon": { 30 | "16": "icons/toolbar_icon_16.png", 31 | "32": "icons/toolbar_icon_32.png", 32 | "48": "icons/toolbar_icon_48.png", 33 | "96": "icons/toolbar_icon_96.png" 34 | }, 35 | "theme_icons": [ 36 | { 37 | "dark": "icons/toolbar_icon_16.png", 38 | "light": "icons/toolbar_icon_16_light.png", 39 | "size": 16 40 | }, 41 | { 42 | "dark": "icons/toolbar_icon_32.png", 43 | "light": "icons/toolbar_icon_32_light.png", 44 | "size": 32 45 | }, 46 | { 47 | "dark": "icons/toolbar_icon_48.png", 48 | "light": "icons/toolbar_icon_48_light.png", 49 | "size": 48 50 | }, 51 | { 52 | "dark": "icons/toolbar_icon_96.png", 53 | "light": "icons/toolbar_icon_96_light.png", 54 | "size": 96 55 | } 56 | ], 57 | "default_title": "Add to Pinboard", 58 | "default_popup": "popup_menu.html", 59 | "browser_style": true 60 | }, 61 | "background": { 62 | "scripts": ["preferences.js", "main.js"] 63 | }, 64 | "content_scripts": [ 65 | { 66 | "matches": [ 67 | "https://pinboard.in/add", 68 | "https://pinboard.in/add*later=yes*" 69 | ], 70 | "js": ["link_saved.js"] 71 | } 72 | ], 73 | "commands": { 74 | "save_dialog": { 75 | "description": "Save to Pinboard (opens dialog)" 76 | }, 77 | "read_later": { 78 | "description": "Save to Read later" 79 | }, 80 | "save_tab_set": { 81 | "description": "Save tab set" 82 | } 83 | }, 84 | "options_ui": { 85 | "page": "preferences_page.html", 86 | "browser_style": true 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /popup_menu.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 14 | 15 | 16 |
17 |
18 |
19 | 20 |
21 |
22 |
Save to Pinboard...
23 |
Read later
24 |
Save tab set
25 |
26 | 27 | 28 |
29 | 30 |
31 |
32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /popup_menu.js: -------------------------------------------------------------------------------- 1 | document.addEventListener('DOMContentLoaded', async () => { 2 | // Match popup to theme colors 3 | const theme = await browser.theme.getCurrent() 4 | if (theme && theme.colors) { 5 | document.body.style.backgroundColor = theme.colors.popup 6 | document.body.style.color = theme.colors.popup_text 7 | const stylesheet = document.styleSheets[0] 8 | stylesheet.insertRule(`.panel-list-item:not(.disabled):hover { background-color: ${theme.colors.popup_highlight}; border-block: 1px solid ${theme.colors.popup_border} }`) 9 | stylesheet.insertRule(`.panel-list-item:not(.disabled):hover:active { background-color: ${theme.colors.popup_highlight} }`) 10 | stylesheet.insertRule(`.panel-section-separator { background-color: ${theme.colors.popup_border} }`) 11 | } 12 | 13 | const menu_elements = document.getElementsByClassName('message-on-click') 14 | for (let i = 0; i < menu_elements.length; i++) { 15 | let element = menu_elements[i] 16 | element.addEventListener('click', async () => { 17 | await browser.runtime.sendMessage({event: element.id}) 18 | window.close() 19 | }) 20 | } 21 | 22 | document.getElementById('menu-preferences').addEventListener('click', () => { 23 | browser.runtime.openOptionsPage() 24 | window.close() 25 | }) 26 | 27 | const links = document.getElementsByTagName('a') 28 | for (let i = 0; i < links.length; i++) { 29 | links[i].addEventListener('click', event => { 30 | event.preventDefault() 31 | event.target.blur() 32 | }) 33 | links[i].parentElement.addEventListener('click', () => { 34 | browser.tabs.create({url: links[i].href}) 35 | window.close() 36 | }) 37 | } 38 | }) 39 | -------------------------------------------------------------------------------- /preferences.js: -------------------------------------------------------------------------------- 1 | const Preferences = { 2 | 3 | storage_area: 'local', 4 | 5 | defaults: { 6 | toolbar_button: 'show_menu', 7 | show_notifications: true, 8 | context_menu_items: true, 9 | show_tags: true, 10 | add_link_form_in_tab: false 11 | }, 12 | 13 | async get(option) { 14 | let option_value = {} 15 | option_value[option] = this.defaults[option] 16 | const value = await browser.storage[this.storage_area].get(option_value) 17 | return value[option] 18 | }, 19 | 20 | async set(option, value) { 21 | let option_value = {} 22 | option_value[option] = value 23 | browser.storage[this.storage_area].set(option_value) 24 | }, 25 | 26 | async migrate_to_local_storage() { 27 | let value 28 | for (option in this.defaults) { 29 | this.storage_area = 'sync' 30 | value = await this.get(option) 31 | browser.storage.sync.remove(option) 32 | this.storage_area = 'local' 33 | this.set(option, value) 34 | } 35 | }, 36 | 37 | async get_keyboard_shortcuts() { 38 | const shortcuts = await browser.commands.getAll() 39 | return shortcuts.filter(shortcut => shortcut.shortcut) 40 | }, 41 | 42 | async remove_keyboard_shortcut(name) { 43 | browser.commands.reset(name) 44 | } 45 | 46 | }; -------------------------------------------------------------------------------- /preferences_page.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 12 |
13 | 14 | 19 |
20 | 21 |
22 | 23 | 24 |
25 | 26 |
27 | 28 | 29 |
30 | 31 |
32 | 33 | 34 |
35 | 36 |
37 | 38 | 39 |
40 | 41 |
42 |
43 | Keyboard shortcuts 44 |

To set up shortcuts, go to Manage Extension Shortcuts in the cogwheel menu at the top.

45 | 48 |
49 |
50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /preferences_page.js: -------------------------------------------------------------------------------- 1 | // Form getter & setter functions by element type 2 | const bind_functions = { 3 | SELECT: { 4 | get: (input) => input.value, 5 | set: (input, value) => {input.value = value} 6 | }, 7 | INPUT: { 8 | get: (input) => !!input.checked, 9 | set: (input, value) => {input.checked = !!value} 10 | } 11 | } 12 | 13 | async function bind_preference(option) { 14 | const input = document.getElementById(option) 15 | const bind_fn = bind_functions[input.tagName] 16 | 17 | // Set the form input value to current value for the preference 18 | bind_fn.set(input, await Preferences.get(option)) 19 | 20 | // Create an event listener for saving the preference value 21 | input.addEventListener('change', async event => { 22 | Preferences.set(option, bind_fn.get(event.target)) 23 | }) 24 | } 25 | 26 | async function init() { 27 | for (let option in Preferences.defaults) { 28 | bind_preference(option) 29 | } 30 | 31 | // Add remove links for keyboard shortcuts 32 | const shortcuts = await Preferences.get_keyboard_shortcuts() 33 | const container = document.getElementById('kb-shortcuts') 34 | const tpl = document.getElementById('kb-shortcut-tpl') 35 | shortcuts.forEach(shortcut => { 36 | const new_shortcut = tpl.cloneNode(true) 37 | new_shortcut.id = null 38 | new_shortcut.childNodes[0].textContent = shortcut.description + ': ' 39 | new_shortcut.childNodes[1].textContent = shortcut.shortcut 40 | const link = new_shortcut.getElementsByTagName('a')[0] 41 | link.addEventListener('click', async event => { 42 | event.preventDefault() 43 | Preferences.remove_keyboard_shortcut(shortcut.name) 44 | link.parentNode.remove() 45 | }) 46 | new_shortcut.style.display = 'block' 47 | container.appendChild(new_shortcut) 48 | }) 49 | 50 | // Match preferences form to theme colors 51 | const theme = await browser.theme.getCurrent() 52 | if (theme && theme.colors) { 53 | document.body.style.backgroundColor = theme.colors.ntp_background 54 | document.body.style.color = theme.colors.ntp_text 55 | const stylesheet = document.styleSheets[0] 56 | stylesheet.insertRule(`a { color: ${theme.colors.ntp_text} }`) 57 | } 58 | } 59 | 60 | // Bind all preferences 61 | document.addEventListener('DOMContentLoaded', init) 62 | --------------------------------------------------------------------------------