├── .gitignore ├── assets ├── settings │ ├── id.png │ ├── label.png │ ├── compact.png │ ├── numberOfCards.png │ └── hideAddNewList.png ├── logos │ ├── logo_128.png │ ├── logo_16.png │ ├── logo_19.png │ ├── logo_256.png │ ├── logo_32.png │ ├── logo_38.png │ ├── logo_64.png │ └── logo.svg └── page-action │ ├── download-16.svg │ ├── page-action-16.svg │ ├── page-action-19.svg │ ├── page-action-32.svg │ └── page-action-38.svg ├── Privacy_Policy.md ├── translate.js ├── page-action ├── main.js └── index.html ├── package.json ├── LICENSE ├── settings ├── options.js └── options.html ├── manifest.json ├── README.md ├── inject ├── enhancedStyles.css └── inject.js ├── _locales ├── es │ └── messages.json ├── fr │ └── messages.json ├── en │ └── messages.json └── de │ └── messages.json ├── CONTRIBUTING.md ├── background.js └── papaparse.min.js /.gitignore: -------------------------------------------------------------------------------- 1 | web-ext-artifacts 2 | node_modules 3 | -------------------------------------------------------------------------------- /assets/settings/id.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christiankaindl/trello-super-powers/HEAD/assets/settings/id.png -------------------------------------------------------------------------------- /assets/logos/logo_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christiankaindl/trello-super-powers/HEAD/assets/logos/logo_128.png -------------------------------------------------------------------------------- /assets/logos/logo_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christiankaindl/trello-super-powers/HEAD/assets/logos/logo_16.png -------------------------------------------------------------------------------- /assets/logos/logo_19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christiankaindl/trello-super-powers/HEAD/assets/logos/logo_19.png -------------------------------------------------------------------------------- /assets/logos/logo_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christiankaindl/trello-super-powers/HEAD/assets/logos/logo_256.png -------------------------------------------------------------------------------- /assets/logos/logo_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christiankaindl/trello-super-powers/HEAD/assets/logos/logo_32.png -------------------------------------------------------------------------------- /assets/logos/logo_38.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christiankaindl/trello-super-powers/HEAD/assets/logos/logo_38.png -------------------------------------------------------------------------------- /assets/logos/logo_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christiankaindl/trello-super-powers/HEAD/assets/logos/logo_64.png -------------------------------------------------------------------------------- /assets/settings/label.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christiankaindl/trello-super-powers/HEAD/assets/settings/label.png -------------------------------------------------------------------------------- /assets/settings/compact.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christiankaindl/trello-super-powers/HEAD/assets/settings/compact.png -------------------------------------------------------------------------------- /assets/settings/numberOfCards.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christiankaindl/trello-super-powers/HEAD/assets/settings/numberOfCards.png -------------------------------------------------------------------------------- /assets/settings/hideAddNewList.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christiankaindl/trello-super-powers/HEAD/assets/settings/hideAddNewList.png -------------------------------------------------------------------------------- /assets/page-action/download-16.svg: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /Privacy_Policy.md: -------------------------------------------------------------------------------- 1 | # Trello Super Powers Privacy Policy 2 | 3 | Trello Super Powers does not collect any user information/data. Trello Super Powers only saves users preferences, nothing is ever synchronized to Trello Super Powers servers. Preferences may be synchronized with Firefox/Mozilla servers when the user uses Firefox Sync. 4 | 5 | Trello Super Powers also does not collect any information from your Trello boards. 6 | -------------------------------------------------------------------------------- /translate.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Translate an HTML page with the i18n API 3 | * 4 | * @param {string} property Name of the HTML attribute used for localization 5 | */ 6 | function translate(property = 'data-translate') { 7 | let translateables = document.querySelectorAll(`[${property}]`); 8 | 9 | for (let i = 0; i < translateables.length; i++) { 10 | let string = translateables[i].getAttribute(property); 11 | translateables[i].textContent = browser.i18n.getMessage(string); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /page-action/main.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Handle form submit. Gather input values and send them to the Background Script. 3 | */ 4 | async function handleSubmit() { 5 | console.log("I AM HERE"); 6 | let data; 7 | 8 | data = { 9 | filename: `${document.getElementById("file-name").value}.csv`, 10 | delimiter: document.querySelector('input[name="delimiter"]:checked').value, 11 | includeArchived: document.getElementById("include-archived").checked 12 | }; 13 | 14 | exportButton.setAttribute("disabled", "disabled"); 15 | await browser.runtime.sendMessage({ type: "exportCSV", data: data }); 16 | exportButton.removeAttribute("disabled"); 17 | } 18 | 19 | let form = document.getElementById('csv-export'), 20 | exportButton = document.getElementById("export-button"); 21 | 22 | form.addEventListener('submit', handleSubmit); 23 | 24 | // exportButton.addEventListener("click", handleSubmit); 25 | translate(); 26 | -------------------------------------------------------------------------------- /assets/page-action/page-action-16.svg: -------------------------------------------------------------------------------- 1 | 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "trello-super-powers", 3 | "description": "Firefox add-on to enhance your Trello (trello.com) experience. Get it here -> [https://addons.mozilla.org/en-US/firefox/addon/trello-super-powers/](https://addons.mozilla.org/en-US/firefox/addon/trello-super-powers/)", 4 | "version": "1.0.0", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1", 7 | "start:firefox": "web-ext run --source-dir ./", 8 | "build": "web-ext build", 9 | "lint": "web-ext lint" 10 | }, 11 | "keywords": [ 12 | "add-on", 13 | "web-extension", 14 | "trello" 15 | ], 16 | "author": "Christian Kaindl", 17 | "license": "MIT", 18 | "bugs": { 19 | "url": "https://github.com/christiankaindl/trello-super-powers/issues" 20 | }, 21 | "homepage": "https://github.com/christiankaindl/trello-super-powers#readme", 22 | "devDependencies": { 23 | "web-ext": "^6.5.0" 24 | }, 25 | "repository": { 26 | "type": "git", 27 | "url": "git+https://github.com/christiankaindl/trello-super-powers.git" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Christian Kaindl 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 | -------------------------------------------------------------------------------- /settings/options.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** 4 | * Update storage with changed settings 5 | * 6 | * @param {object} e 'onchange' event object 7 | */ 8 | async function updateStorage (e) { 9 | var setting = e.target.getAttribute('name'), 10 | value = e.target.checked 11 | await browser.storage.local.set({ [setting]: value }) 12 | 13 | browser.notifications.create({ 14 | type: 'basic', 15 | title: browser.i18n.getMessage('settingsSavedNotificationTitle'), 16 | message: browser.i18n.getMessage('settingsSavedNotificationBody'), 17 | iconUrl: '../assets/logo.svg' 18 | }) 19 | } 20 | 21 | /** 22 | * Initialize settings page. Apply saved settings from storage and add evnt listener to the input elements. 23 | */ 24 | async function initialize () { 25 | var settings = await browser.storage.local.get(), 26 | inputs = document.getElementsByTagName('input'), 27 | translateables = document.querySelectorAll('[data-translate]') 28 | 29 | // Apply settings 30 | for (let i = 0; i < inputs.length; i++) { 31 | inputs[i].checked = settings[inputs[i].getAttribute('name')] 32 | inputs[i].addEventListener('change', updateStorage) 33 | } 34 | 35 | // Apply localized/translated strings 36 | translate() 37 | } 38 | 39 | initialize() 40 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "name": "__MSG_extensionName__", 4 | "version": "0.13.3", 5 | 6 | "description": "__MSG_extensionDescription__", 7 | "homepage_url": "https://github.com/christiankaindl/trello-super-powers", 8 | "icons": { 9 | "16": "assets/logos/logo_16.png", 10 | "32": "assets/logos/logo_32.png", 11 | "64": "assets/logos/logo_64.png", 12 | "128": "assets/logos/logo_128.png", 13 | "256": "assets/logos/logo_256.png" 14 | }, 15 | "permissions": [ 16 | "tabs", "storage", "https://trello.com/b/*/", "notifications", "downloads" 17 | ], 18 | "background": { 19 | "scripts": ["background.js"] 20 | }, 21 | "page_action": { 22 | "browser_style": true, 23 | "default_icon": { 24 | "16": "assets/logos/logo_16.png", 25 | "19": "assets/logos/logo_19.png", 26 | "32": "assets/logos/logo_32.png", 27 | "38": "assets/logos/logo_38.png", 28 | "64": "assets/logos/logo_64.png", 29 | "128": "assets/logos/logo_128.png", 30 | "256": "assets/logos/logo_256.png" 31 | }, 32 | "default_title": "Export Trello board as CSV", 33 | "default_popup": "/page-action/index.html" 34 | }, 35 | 36 | "options_ui": { 37 | "page": "settings/options.html", 38 | "browser_style": true 39 | }, 40 | 41 | "applications": { 42 | "gecko": { 43 | "id": "{e8a71c3b-3deb-4ab3-834a-5c0aee943847}", 44 | "strict_min_version": "52.0" 45 | } 46 | }, 47 | "default_locale": "en" 48 | } 49 | -------------------------------------------------------------------------------- /assets/page-action/page-action-19.svg: -------------------------------------------------------------------------------- 1 | 5 | -------------------------------------------------------------------------------- /assets/page-action/page-action-32.svg: -------------------------------------------------------------------------------- 1 | 5 | -------------------------------------------------------------------------------- /assets/page-action/page-action-38.svg: -------------------------------------------------------------------------------- 1 | 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Trello Super Powers 2 | 3 | Firefox add-on to enhance your Trello (trello.com) experience. Get it here -> [https://addons.mozilla.org/en-US/firefox/addon/trello-super-powers/](https://addons.mozilla.org/en-US/firefox/addon/trello-super-powers/) 4 | 5 | --- 6 | 7 | ## Features 8 | 9 | Trello Super Powers implements some small yet useful enhancements into your everyday Trello usage. 10 | 11 | - Visible label text 12 | - Clickable IDs on hover (+ they get copied to your clipboard when clicked) 13 | - Compact Mode: Removes all clutter if you want to focus 14 | - Resizable lists: Click and drag to adjust the size of your Trello lists 15 | - See at a glance how many cards are in each list 16 | - Option to hide "Add new List"-Element 17 | 18 | Also you can find explanation of these features and a way to turn them off in the settings of the Add-on ('about:addons' in your address bar or 'Add-Ons' in the hamburger menu). 19 | 20 | 21 | There are plenty of features waiting to be implemented, most notably Account integration, which would allow for blazing fast access to your boards and notification integration which would give you the newest from the newest directly from the Firefox toolbar. 22 | 23 | # Contribute 24 | 25 | If you want to contribute to this project, that is awesome! I tried to comment the code as good as I can and optimize it for readability. Hints and tricks to get you started can be found in the [CONTRIBUTING.md](https://github.com/christiankaindl/trello-super-powers/blob/master/CONTRIBUTING.md) file. 26 | 27 | If you have something on your mind or need help, hop to the [issues](https://github.com/christiankaindl/trello-super-powers/issues) tab and ask your question. There are lots of friendly people who will gladly help you out. 28 | 29 | Quick jump to... 30 | 31 | - **[Translation help](https://github.com/christiankaindl/trello-super-powers/blob/master/CONTRIBUTING.md#translations)** 32 | - **[Coding help](https://github.com/christiankaindl/trello-super-powers/blob/master/CONTRIBUTING.md#coding)** 33 | 34 | # Development 35 | - Clone repository: `git clone https://github.com/christiankaindl/trello-super-powers.git` 36 | - Install npm dependencies: 37 | - Start local dev environment: `npm run start` (starts a fresh Firefox instance with Trello Super Powers pre-loaded) 38 | 39 | # License 40 | 41 | This project, Trello Super Powers, is licensed under MIT. For details, please refer to the [LICENSE file](https://github.com/christiankaindl/trello-super-powers/blob/master/LICENSE) 42 | -------------------------------------------------------------------------------- /inject/enhancedStyles.css: -------------------------------------------------------------------------------- 1 | /*==== LABEL TEXT ====*/ 2 | /* Label text is already there by default in Trello, it is just hidden */ 3 | .TSP-label-enabled .list-card-labels .card-label { 4 | border-radius: 4px; 5 | font-size: 0.8em; 6 | height: auto; 7 | line-height: 10px; 8 | padding: 4px 6px; 9 | text-shadow: none; 10 | width: auto; 11 | min-width: 16px; 12 | max-width: 100%; 13 | } 14 | .TSP-label-enabled .list-card-labels .card-label > span { 15 | display: inline; 16 | } 17 | 18 | /*==== CARD ID ====*/ 19 | .TSP-id-enabled .card-short-id { 20 | display: inline; 21 | margin-right: 5px; 22 | color: #333; 23 | font-weight: bold; 24 | } 25 | 26 | .TSP-id-enabled .card-short-id:hover { 27 | color: grey; 28 | } 29 | 30 | .TSP-id-enabled.onlyShowOnHover .card-short-id { 31 | display: none; 32 | } 33 | .TSP-id-enabled.onlyShowOnHover .active-card .card-short-id { 34 | display: inline; 35 | } 36 | 37 | /*==== CARDS PER LIST ====*/ 38 | /* This is already there by default in Trello, it is just hidden */ 39 | .TSP-numberOfCards-enabled .list-header-num-cards.hide { 40 | display: block; 41 | } 42 | 43 | /*==== COMPACT MODE ====*/ 44 | .compact-mode-button { 45 | padding-left: 8px; 46 | margin-left: 4px; 47 | } 48 | .TSP-compact-enabled.compact-mode div.board-header-btns.mod-left a.board-header-btn.compact-mode-button { 49 | background-color: rgba(0, 0, 0, 0.14); 50 | } 51 | .TSP-compact-enabled.compact-mode .list-card-cover, 52 | .TSP-compact-enabled.compact-mode .list-card-details .badges, 53 | .TSP-compact-enabled.compact-mode .list-card-members, 54 | .TSP-compact-enabled.compact-mode .list-card-stickers-area, 55 | .TSP-compact-enabled.compact-mode .list-card-labels { 56 | display: none; 57 | } 58 | .TSP-compact-enabled.compact-mode .list-card.is-stickered .list-card-details { 59 | margin-top: 0; 60 | } 61 | 62 | /*==== RESIZABLE LISTS ====*/ 63 | .resize-element { 64 | height: 100%; 65 | width: 8px; 66 | cursor: col-resize; 67 | margin: 0px -4px; 68 | border-radius: 99px; 69 | 70 | /* borrowed from Trellos .list-wrapper*/ 71 | box-sizing: border-box; 72 | display: inline-block; 73 | vertical-align: top; 74 | white-space: nowrap; 75 | } 76 | .resize-element:hover { 77 | background-color: rgba(170, 170, 170, 0.25); 78 | } 79 | .resize-element:active { 80 | background-color: rgba(170, 170, 170, 0.5); 81 | } 82 | .list-card { 83 | max-width: none; 84 | min-width: none; 85 | } 86 | 87 | /*==== HIDE ELEMENT ====*/ 88 | .hide-element { 89 | display: none; 90 | } -------------------------------------------------------------------------------- /page-action/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 |Found an error? Help getting it fixed by reporting an issue.
96 | 97 | 98 | 99 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /background.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | console.clear() 3 | 4 | const trelloBoardURL = /\S+:\/\/\S*\.?trello\.com\/b\/\S+/ 5 | var injectStatus = undefined 6 | 7 | /** 8 | * Checks whether or not a given url is a Trello board url. 9 | * 10 | * @param {number} id id of the tab the url is From 11 | * @param {string} updateReason Reason why the function was called 12 | * @param {object} state Properties of the tab 13 | */ 14 | async function urlCheck (id, updateReason, state) { 15 | if (!state.url.match(trelloBoardURL)) return 16 | if (injectStatus === 'pending') return 17 | 18 | injectStatus = 'pending' 19 | 20 | try { 21 | await browser.tabs.insertCSS({ file: 'inject/enhancedStyles.css' }) 22 | await browser.tabs.executeScript({ file: 'inject/inject.js' }) 23 | 24 | browser.pageAction.show(id) 25 | 26 | injectStatus = 'fullfilled' 27 | console.info(`[Trello Super Powers] Successfully injected 'inject.js' into ${state.url}`) 28 | } catch (e) { 29 | injectStatus = 'rejected' 30 | console.error( 31 | `[Trello Super Powers] Error: Could not inject 'inject.js' into ${state.url}: `, 32 | e 33 | ) 34 | } 35 | } 36 | 37 | /** 38 | * Checks current settings and defaults them as necessary 39 | */ 40 | async function checkSettings () { 41 | var settings = await browser.storage.local.get() 42 | 43 | if (settings['compactMode'] === undefined) { 44 | browser.storage.local.set({ compactMode: true }) 45 | } 46 | if (settings['compactModeByDefault'] === undefined) { 47 | browser.storage.local.set({ compactModeByDefault: false }) 48 | } 49 | if (settings['numberOfCards'] === undefined) { 50 | browser.storage.local.set({ numberOfCards: true }) 51 | } 52 | if (settings['labelText'] === undefined) { 53 | browser.storage.local.set({ labelText: true }) 54 | } 55 | if (settings['copyId'] === undefined) { 56 | browser.storage.local.set({ copyId: true }) 57 | } 58 | if (settings['copyId_onlyShowOnHover'] === undefined) { 59 | browser.storage.local.set({ copyId_onlyShowOnHover: true }) 60 | } 61 | if (settings['list'] === undefined) { 62 | browser.storage.local.set({ list: { width: 270 } }) 63 | } 64 | if (settings['hideAddNewList'] === undefined) { 65 | browser.storage.local.set({ hideAddNewList: false }) 66 | } 67 | } 68 | 69 | browser.runtime.onMessage.addListener(handleMessage) 70 | 71 | /** 72 | * Message handler for incomming messages 73 | * 74 | * @param {object} message Message from a Script, containing a 'type' property. 75 | */ 76 | async function handleMessage (message) { 77 | if (message.type === 'notification') { 78 | browser.notifications.create({ 79 | type: 'basic', 80 | title: 'Trello Super Powers', 81 | message: message.message, 82 | iconUrl: '/assets/logo.svg' 83 | }) 84 | } 85 | 86 | if (message.type === 'exportCSV') { 87 | if (!navigator.onLine) { 88 | browser.notifications.create({ 89 | type: 'basic', 90 | title: 'You are offline', 91 | message: 92 | 'Trello Super Powers could not connect to the internet. Please check your connection and try again.' 93 | }) 94 | 95 | return 96 | } 97 | 98 | let tabId, tabUrl, csvBlob, downloadUrl 99 | let { 100 | filename = 'CSV-export.csv', 101 | delimiter = ',', 102 | includeArchived = false 103 | } = message.data; 104 | 105 | [{ id: tabId, url: tabUrl }] = await browser.tabs.query({ 106 | active: true, 107 | currentWindow: true 108 | }) 109 | 110 | await browser.tabs.executeScript(tabId, { file: 'papaparse.min.js' }) 111 | 112 | // TODO: Don't use a content script for this operation. Do everything in the 113 | // background script 114 | csvBlob = await browser.tabs.sendMessage(tabId, { 115 | type: 'fetch', 116 | tabUrl, 117 | delimiter, 118 | includeArchived 119 | }) 120 | 121 | // Create a file URL so we can download it 122 | downloadUrl = URL.createObjectURL(csvBlob) 123 | 124 | try { // Try downloading the CSV file 125 | browser.downloads.download({ 126 | url: downloadUrl, 127 | filename: filename 128 | }) 129 | } catch (e) { 130 | console.error('[Trello Super Powers] Error: ', e) 131 | 132 | browser.notifications.create({ 133 | type: 'basic', 134 | title: 'Export failed', 135 | message: `Trello Super Powers could export your board. We're sorry. Error message: ${e}` 136 | }) 137 | } 138 | 139 | browser.notifications.create({ 140 | type: 'basic', 141 | title: 'Board exported', 142 | message: 'Successfully downloaded your board as CSV.' 143 | }) 144 | } 145 | } 146 | 147 | /* Trello uses AJAX loading and because of this when a board is loaded from 148 | within the UI (not from URL) 'inject.js' won't be fired. To overcome this we 149 | listen for URL changes initTrelloBoardand fire inizializations manually */ 150 | browser.tabs.onUpdated.addListener(urlCheck) 151 | 152 | browser.runtime.onStartup.addListener(checkSettings) 153 | browser.runtime.onInstalled.addListener(checkSettings) 154 | 155 | console.info('Trello Super Powers WebExtension started successfully.') 156 | -------------------------------------------------------------------------------- /assets/logos/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 174 | -------------------------------------------------------------------------------- /inject/inject.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /* 4 | Inizialize Trello Super Powers features 5 | - copy card id on click 6 | - compact mode 7 | - resizable lists 8 | - label text 9 | - number of cards per list 10 | */ 11 | 12 | function gracefullyInject (feature, args) { 13 | /** Takes a funtion and injects it 14 | * TODO: add error handling logic 15 | */ 16 | feature(args) 17 | } 18 | 19 | /** 20 | * Wrapper for `browser.storage.local.get()`. 21 | * 22 | * @returns {object} An object with current settings. 23 | */ 24 | async function getSettings () { 25 | return browser.storage.local.get() 26 | } 27 | 28 | // NOTE: Ideally in the future, each feature has its own module 29 | var features = { 30 | /** 31 | * Initializes ID feature. 32 | * 33 | * @returns {promise} 34 | */ 35 | id (onlyShowOnHover) { 36 | return new Promise(function (resolve, reject) { 37 | const input = document.createElement('input') 38 | 39 | // Add dummy element so we can use the clipboard functionality 40 | input.setAttribute('style', 'height: 0px;padding: 0px; margin: 0px; border: none;') 41 | document.getElementsByTagName('body')[0].appendChild(input) 42 | 43 | document.addEventListener('click', copyCardURL, true) 44 | function copyCardURL (e) { 45 | // Don't move on if the element clicked is not an ID 46 | if (!e.target.classList.contains('card-short-id')) return 47 | 48 | let elem 49 | 50 | e.stopImmediatePropagation() 51 | e.preventDefault() 52 | 53 | elem = e.target 54 | // Traverse up the DOM to find the anchor element with the URL we want to copy. 55 | while (true) { 56 | elem = elem.parentNode 57 | 58 | if (elem.tagName !== 'A') continue 59 | 60 | input.value = elem.href 61 | input.select() 62 | document.execCommand('copy') 63 | break 64 | } 65 | 66 | // NOTE: Content Scripts do not have access to the notifications API. If a notification should be sent, we have to communicate with the background script using the messaging API 67 | browser.runtime.sendMessage({ 68 | type: 'notification', 69 | message: browser.i18n.getMessage('injectIdNotificationBody') 70 | }) 71 | } 72 | 73 | console.info('[Trello Super Powers] Injected feature: "Show card IDs"') 74 | board.classList.add('TSP-id-enabled') 75 | if (onlyShowOnHover) { 76 | board.classList.add('onlyShowOnHover') 77 | } 78 | resolve() 79 | }) 80 | }, 81 | /** 82 | * Initializes Compact Mode feature 83 | * 84 | * @returns {promise} 85 | */ 86 | compact (byDefault) { 87 | return new Promise(async function (resolve, reject) { 88 | function createCompactModeButton () { 89 | const parent = document.createElement('a') 90 | parent.setAttribute('class', 'board-header-btn compact-mode-button') 91 | // TODO: Internationalize this string! 92 | parent.setAttribute('title', 'Toggle Compact Mode') 93 | 94 | const child = document.createElement('span') 95 | child.setAttribute('class', 'board-header-btn-text') // The `board-header-btn-text` class is provided by Trello itself 96 | child.textContent = browser.i18n.getMessage('settingsCompactTitle') 97 | 98 | parent.appendChild(child) 99 | 100 | return parent 101 | } 102 | 103 | function toggleCompactMode () { 104 | // Apply Compact Mode CSS 105 | board.classList.toggle('compact-mode') 106 | } 107 | 108 | const compactModeButton = createCompactModeButton() 109 | 110 | compactModeButton.addEventListener('click', toggleCompactMode) 111 | 112 | // Inject into page 113 | document 114 | .getElementById('permission-level') 115 | .parentElement.appendChild(compactModeButton) 116 | 117 | console.info('[Trello Super Powers] Injected feature "Compact Modew"') 118 | board.classList.add('TSP-compact-enabled') 119 | 120 | byDefault && compactModeButton.click() 121 | resolve() 122 | }) 123 | }, 124 | /** 125 | * Initializes "Hide 'Add new List'-Button" feature 126 | * 127 | * @returns {promise} 128 | */ 129 | hideAddNewList () { 130 | return new Promise(function (resolve, reject) { 131 | const addNewListElement = document.getElementsByClassName('js-add-list')[0] 132 | addNewListElement.classList.toggle('hide-element') 133 | 134 | console.info('[Trello Super Powers] Injected feature: "Hide \'Add new List\'"') 135 | 136 | resolve() 137 | }) 138 | }, 139 | /** 140 | * Initializes Resizable Lists feature 141 | * 142 | * @returns {promise} 143 | */ 144 | resize () { 145 | return new Promise(async function (resolve, reject) { 146 | function createResizeElem () { 147 | function attachListeners () { 148 | document.addEventListener('mousemove', adjustSize) 149 | document.addEventListener('mouseup', setSize) 150 | } 151 | 152 | function adjustSize (e) { 153 | const styleId = document.getElementById('inserted-tsp-styles') 154 | const currentWidth = 155 | listWidth + e.movementX / 3 < 500 && 156 | listWidth + e.movementX / 3 > 150 157 | ? (listWidth = listWidth + e.movementX / 3) 158 | : listWidth 159 | 160 | styleId.textContent = `.list-wrapper {width: ${currentWidth}px}` 161 | } 162 | 163 | function setSize (e) { 164 | document.removeEventListener('mousemove', adjustSize) 165 | document.removeEventListener('mouseup', setSize) 166 | 167 | browser.storage.local.set({ list: { width: listWidth } }).catch(e => { 168 | console.error( 169 | "[Trello Super Powers] Could not save 'listWidth' to browser sync storage.", 170 | e 171 | ) 172 | }) 173 | } 174 | 175 | const resizeElem = document.createElement('div') 176 | 177 | resizeElem.setAttribute('class', 'resize-element') 178 | // Listen for mouse down 179 | resizeElem.addEventListener('mousedown', attachListeners) 180 | 181 | return resizeElem 182 | } 183 | 184 | var listWidth 185 | var lists = [] 186 | 187 | try { 188 | listWidth = (await browser.storage.local.get()).list.width 189 | } catch (e) { 190 | console.error('[Trello Super Powers] Error: Could not get local storage', e) 191 | listWidth = 270 192 | } 193 | 194 | // Insert a