├── v2 ├── data │ ├── icons │ │ ├── 128.png │ │ ├── 16.png │ │ ├── 19.png │ │ ├── 256.png │ │ ├── 32.png │ │ ├── 38.png │ │ ├── 48.png │ │ ├── 512.png │ │ ├── 64.png │ │ ├── active │ │ │ ├── 16.png │ │ │ ├── 19.png │ │ │ ├── 32.png │ │ │ ├── 38.png │ │ │ ├── 48.png │ │ │ └── 64.png │ │ └── icon.svg │ └── toolbar │ │ ├── index.js │ │ ├── index.css │ │ ├── inject.js │ │ └── index.html ├── manifest.json └── background.js ├── v3 ├── data │ ├── icons │ │ ├── 128.png │ │ ├── 16.png │ │ ├── 256.png │ │ ├── 32.png │ │ ├── 48.png │ │ ├── 512.png │ │ ├── 64.png │ │ ├── active │ │ │ ├── 16.png │ │ │ ├── 19.png │ │ │ ├── 32.png │ │ │ ├── 38.png │ │ │ ├── 48.png │ │ │ └── 64.png │ │ └── icon.svg │ └── toolbar │ │ ├── inject │ │ ├── main.js │ │ └── isolated.js │ │ ├── index.css │ │ ├── index.js │ │ └── index.html ├── _locales │ ├── zh_CN │ │ └── messages.json │ ├── ja │ │ └── messages.json │ ├── el │ │ └── messages.json │ ├── ru │ │ └── messages.json │ ├── es │ │ └── messages.json │ ├── pl │ │ └── messages.json │ ├── pt_BR │ │ └── messages.json │ ├── pt_PT │ │ └── messages.json │ ├── fr │ │ └── messages.json │ ├── cs │ │ └── messages.json │ ├── en │ │ └── messages.json │ ├── it │ │ └── messages.json │ ├── nl │ │ └── messages.json │ └── de │ │ └── messages.json ├── manifest.json └── worker.js └── .github └── FUNDING.yml /v2/data/icons/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brian-girko/design-mode/HEAD/v2/data/icons/128.png -------------------------------------------------------------------------------- /v2/data/icons/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brian-girko/design-mode/HEAD/v2/data/icons/16.png -------------------------------------------------------------------------------- /v2/data/icons/19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brian-girko/design-mode/HEAD/v2/data/icons/19.png -------------------------------------------------------------------------------- /v2/data/icons/256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brian-girko/design-mode/HEAD/v2/data/icons/256.png -------------------------------------------------------------------------------- /v2/data/icons/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brian-girko/design-mode/HEAD/v2/data/icons/32.png -------------------------------------------------------------------------------- /v2/data/icons/38.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brian-girko/design-mode/HEAD/v2/data/icons/38.png -------------------------------------------------------------------------------- /v2/data/icons/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brian-girko/design-mode/HEAD/v2/data/icons/48.png -------------------------------------------------------------------------------- /v2/data/icons/512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brian-girko/design-mode/HEAD/v2/data/icons/512.png -------------------------------------------------------------------------------- /v2/data/icons/64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brian-girko/design-mode/HEAD/v2/data/icons/64.png -------------------------------------------------------------------------------- /v3/data/icons/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brian-girko/design-mode/HEAD/v3/data/icons/128.png -------------------------------------------------------------------------------- /v3/data/icons/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brian-girko/design-mode/HEAD/v3/data/icons/16.png -------------------------------------------------------------------------------- /v3/data/icons/256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brian-girko/design-mode/HEAD/v3/data/icons/256.png -------------------------------------------------------------------------------- /v3/data/icons/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brian-girko/design-mode/HEAD/v3/data/icons/32.png -------------------------------------------------------------------------------- /v3/data/icons/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brian-girko/design-mode/HEAD/v3/data/icons/48.png -------------------------------------------------------------------------------- /v3/data/icons/512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brian-girko/design-mode/HEAD/v3/data/icons/512.png -------------------------------------------------------------------------------- /v3/data/icons/64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brian-girko/design-mode/HEAD/v3/data/icons/64.png -------------------------------------------------------------------------------- /v2/data/icons/active/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brian-girko/design-mode/HEAD/v2/data/icons/active/16.png -------------------------------------------------------------------------------- /v2/data/icons/active/19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brian-girko/design-mode/HEAD/v2/data/icons/active/19.png -------------------------------------------------------------------------------- /v2/data/icons/active/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brian-girko/design-mode/HEAD/v2/data/icons/active/32.png -------------------------------------------------------------------------------- /v2/data/icons/active/38.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brian-girko/design-mode/HEAD/v2/data/icons/active/38.png -------------------------------------------------------------------------------- /v2/data/icons/active/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brian-girko/design-mode/HEAD/v2/data/icons/active/48.png -------------------------------------------------------------------------------- /v2/data/icons/active/64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brian-girko/design-mode/HEAD/v2/data/icons/active/64.png -------------------------------------------------------------------------------- /v3/data/icons/active/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brian-girko/design-mode/HEAD/v3/data/icons/active/16.png -------------------------------------------------------------------------------- /v3/data/icons/active/19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brian-girko/design-mode/HEAD/v3/data/icons/active/19.png -------------------------------------------------------------------------------- /v3/data/icons/active/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brian-girko/design-mode/HEAD/v3/data/icons/active/32.png -------------------------------------------------------------------------------- /v3/data/icons/active/38.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brian-girko/design-mode/HEAD/v3/data/icons/active/38.png -------------------------------------------------------------------------------- /v3/data/icons/active/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brian-girko/design-mode/HEAD/v3/data/icons/active/48.png -------------------------------------------------------------------------------- /v3/data/icons/active/64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brian-girko/design-mode/HEAD/v3/data/icons/active/64.png -------------------------------------------------------------------------------- /v3/_locales/zh_CN/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": { 3 | "message": "通过切换到设计模式来编辑任何页面的内容,在那里你可以修改内容,并像Word编辑器一样拖动图片。" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /v3/_locales/ja/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": { 3 | "message": "デザインモードに切り替えることで、ページのコンテンツを編集することができます。デザインモードでは、ワードエディタのようにコンテンツを修正したり、画像をドラッグしたりすることができます。" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /v3/_locales/el/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": { 3 | "message": "Επεξεργαστείτε το περιεχόμενο σελίδας στη λειτουργία σχεδίασης για να το τροποποιήσετε και να μετακινήσετε εικόνες." 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /v3/_locales/ru/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": { 3 | "message": "Редактируйте содержимое любой страницы в режиме дизайна, чтобы изменять текст и перемещать изображения, как в Word." 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /v3/_locales/es/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": { 3 | "message": "Edite el contenido de cualquier página en modo diseño para modificar texto y mover imágenes como en un editor de Word." 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /v3/_locales/pl/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": { 3 | "message": "Edytuj zawartość dowolnej strony w trybie projektowania, aby modyfikować tekst i przesuwać obrazy jak w edytorze Word." 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /v3/_locales/pt_BR/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": { 3 | "message": "Edite o conteúdo de qualquer página no modo de design para modificar texto e mover imagens como em um editor de Word." 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /v3/_locales/pt_PT/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": { 3 | "message": "Edite o conteúdo de qualquer página no modo de design para modificar texto e mover imagens como num editor de Word." 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /v3/_locales/fr/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": { 3 | "message": "Modifiez le contenu de n'importe quelle page en mode conception pour ajuster le texte et déplacer les images comme dans Word." 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /v3/_locales/cs/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": { 3 | "message": "Úprava obsahu libovolné stránky přepnutím do režimu návrhu, kde můžete upravovat obsah a přetahovat obrázky jako v editoru Word." 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /v3/_locales/en/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": { 3 | "message": "Edit any page's content by switching to design mode, where you can modify the content and drag images around like a Word Editor" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /v3/_locales/it/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": { 3 | "message": "Modifica il contenuto di qualsiasi pagina in modalità design per cambiare testo e spostare immagini come in un editor di Word." 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /v3/_locales/nl/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": { 3 | "message": "Bewerk de inhoud van elke pagina in ontwerpmodus om tekst te wijzigen en afbeeldingen te verplaatsen zoals in een Word-editor." 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /v3/_locales/de/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": { 3 | "message": "Bearbeiten Sie den Inhalt jeder Seite im Designmodus, wo Sie den Text ändern und Bilder wie in einem Word-Editor verschieben können." 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /v2/data/icons/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /v3/data/icons/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /v2/data/toolbar/index.js: -------------------------------------------------------------------------------- 1 | const post = (method, data) => top.postMessage({ 2 | method, 3 | data 4 | }, '*'); 5 | 6 | document.addEventListener('click', e => { 7 | const command = e.target.dataset.command; 8 | if (command) { 9 | post(command); 10 | } 11 | }); 12 | document.getElementById('heading').onchange = e => post('heading-' + e.target.value); 13 | 14 | // move 15 | document.getElementById('move').onmousedown = () => { 16 | document.onmousemove = e => { 17 | post('move', { 18 | dx: e.movementX, 19 | dy: e.movementY 20 | }); 21 | }; 22 | }; 23 | document.onmouseup = () => { 24 | document.onmousemove = ''; 25 | }; 26 | -------------------------------------------------------------------------------- /v3/data/toolbar/inject/main.js: -------------------------------------------------------------------------------- 1 | if (!customElements.get('dmp-iframe')) { 2 | class DMPIframe extends HTMLElement { 3 | constructor() { 4 | super(); 5 | this.attachShadow({mode: 'open'}); 6 | } 7 | connectedCallback() { 8 | const iframe = document.createElement('iframe'); 9 | iframe.src = this.getAttribute('src'); 10 | 11 | const style = document.createElement('style'); 12 | style.textContent = ` 13 | iframe { 14 | color-scheme: light; 15 | width: 100%; 16 | height: 100%; 17 | border: none; 18 | }`; 19 | 20 | this.shadowRoot.append(style, iframe); 21 | } 22 | } 23 | customElements.define('dmp-iframe', DMPIframe); 24 | } 25 | -------------------------------------------------------------------------------- /v2/data/toolbar/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | user-select: none; 4 | } 5 | #toolbar { 6 | background-color: #fafafa; 7 | border: solid 1px #c4c4c4; 8 | display: flex; 9 | padding: 3px 5px; 10 | align-items: center; 11 | } 12 | #toolbar > * { 13 | padding: 5px; 14 | cursor: pointer; 15 | } 16 | #toolbar > :not(div):hover { 17 | background-color: #e6e6e6; 18 | } 19 | svg { 20 | width: 20px; 21 | height: 20px; 22 | } 23 | svg * { 24 | pointer-events: none; 25 | } 26 | select { 27 | border: none; 28 | width: 120px; 29 | outline: none; 30 | } 31 | .separator { 32 | width: 1px; 33 | background-color: #c4c4c4; 34 | padding: 0 !important; 35 | margin: 0 5px; 36 | height: 22px; 37 | pointer-events: none; 38 | } 39 | #move { 40 | padding: 0; 41 | cursor: move; 42 | display: flex; 43 | } 44 | #move * { 45 | pointer-events: none; 46 | } 47 | -------------------------------------------------------------------------------- /v3/data/toolbar/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | user-select: none; 4 | } 5 | #toolbar { 6 | background-color: #fafafa; 7 | border: solid 1px #c4c4c4; 8 | display: flex; 9 | padding: 3px 5px; 10 | align-items: center; 11 | } 12 | #toolbar > * { 13 | padding: 5px; 14 | cursor: pointer; 15 | } 16 | #toolbar > :not(div):hover { 17 | background-color: #e6e6e6; 18 | } 19 | svg { 20 | width: 20px; 21 | height: 20px; 22 | } 23 | svg * { 24 | pointer-events: none; 25 | } 26 | select { 27 | border: none; 28 | width: 120px; 29 | outline: none; 30 | } 31 | .separator { 32 | width: 1px; 33 | background-color: #c4c4c4; 34 | padding: 0 !important; 35 | margin: 0 5px; 36 | height: 22px; 37 | pointer-events: none; 38 | } 39 | #move { 40 | padding: 0; 41 | cursor: move; 42 | display: flex; 43 | } 44 | #move * { 45 | pointer-events: none; 46 | } 47 | svg[data-command="spellcheck"].active { 48 | fill: blue; 49 | } 50 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: webextension 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 12 | polar: # Replace with a single Polar username 13 | buy_me_a_coffee: # Replace with a single Buy Me a Coffee username 14 | thanks_dev: # Replace with a single thanks.dev username 15 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 16 | -------------------------------------------------------------------------------- /v3/data/toolbar/index.js: -------------------------------------------------------------------------------- 1 | const args = new URLSearchParams(location.search); 2 | 3 | if (args.get('spellcheck') === 'true') { 4 | document.querySelector('svg[data-command="spellcheck"]').classList.add('active'); 5 | } 6 | 7 | const post = (method, data) => top.postMessage({ 8 | method, 9 | data 10 | }, '*'); 11 | 12 | document.addEventListener('click', e => { 13 | const command = e.target.dataset.command; 14 | if (command === 'spellcheck') { 15 | e.target.classList.toggle('active'); 16 | post(command + ':' + e.target.classList.contains('active')); 17 | } 18 | else if (command) { 19 | post(command); 20 | } 21 | }); 22 | document.getElementById('heading').onchange = e => post(e.target.value); 23 | 24 | // move 25 | document.getElementById('move').onmousedown = () => { 26 | document.onmousemove = e => { 27 | post('move', { 28 | dx: e.movementX, 29 | dy: e.movementY 30 | }); 31 | }; 32 | }; 33 | document.onmouseup = () => { 34 | document.onmousemove = ''; 35 | }; 36 | -------------------------------------------------------------------------------- /v2/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "version": "0.1.2", 4 | "name": "Design Mode - Page's Rich Text Editor", 5 | "description": "Change any webpage into its design mode (edit mode) where you can modify the content and drag images around like a Word Editor", 6 | "permissions": [ 7 | "storage", 8 | "notifications", 9 | "activeTab" 10 | ], 11 | "background": { 12 | "persistent": false, 13 | "scripts": [ 14 | "background.js" 15 | ] 16 | }, 17 | "homepage_url": "https://add0n.com/design-mode.html", 18 | "icons": { 19 | "16": "data/icons/16.png", 20 | "19": "data/icons/19.png", 21 | "32": "data/icons/32.png", 22 | "38": "data/icons/38.png", 23 | "48": "data/icons/48.png", 24 | "64": "data/icons/64.png", 25 | "128": "data/icons/128.png", 26 | "256": "data/icons/256.png", 27 | "512": "data/icons/512.png" 28 | }, 29 | "offline_enabled": true, 30 | "browser_action": {}, 31 | "web_accessible_resources": [ 32 | "data/toolbar/index.html" 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /v3/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 3, 3 | "version": "0.2.4", 4 | "name": "Design Mode - Page's Rich Text Editor", 5 | "description": "__MSG_description__", 6 | "default_locale": "en", 7 | "permissions": [ 8 | "storage", 9 | "notifications", 10 | "activeTab", 11 | "scripting" 12 | ], 13 | "background": { 14 | "service_worker": "worker.js", 15 | "scripts": ["worker.js"] 16 | }, 17 | "homepage_url": "https://add0n.com/design-mode.html", 18 | "icons": { 19 | "16": "/data/icons/16.png", 20 | "32": "/data/icons/32.png", 21 | "48": "/data/icons/48.png", 22 | "64": "/data/icons/64.png", 23 | "128": "/data/icons/128.png", 24 | "256": "/data/icons/256.png", 25 | "512": "/data/icons/512.png" 26 | }, 27 | "offline_enabled": true, 28 | "action": {}, 29 | "web_accessible_resources": [{ 30 | "resources": [ 31 | "/data/toolbar/index.html" 32 | ], 33 | "matches": ["*://*/*"] 34 | }], 35 | "commands": { 36 | "_execute_action": {} 37 | }, 38 | "browser_specific_settings": { 39 | "gecko": { 40 | "id": "{aaf4a359-9bc2-4292-900d-93ff2b71e5cf}", 41 | "strict_min_version": "128.0" 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /v2/background.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const notify = e => chrome.notifications.create({ 4 | type: 'basic', 5 | iconUrl: '/data/icons/48.png', 6 | title: chrome.runtime.getManifest().name, 7 | message: e.message || e 8 | }); 9 | 10 | const onCommand = tab => chrome.tabs.executeScript({ 11 | runAt: 'document_start', 12 | code: `document.designMode` 13 | }, arr => { 14 | const lastError = chrome.runtime.lastError; 15 | if (lastError) { 16 | return notify(lastError); 17 | } 18 | const mode = arr[0] === 'off' ? 'on' : 'off'; 19 | chrome.tabs.executeScript({ 20 | allFrames: true, 21 | runAt: 'document_start', 22 | code: `document.designMode = '${mode}';` 23 | }); 24 | chrome.browserAction.setIcon({ 25 | tabId: tab.id, 26 | path: { 27 | '16': 'data/icons/' + (mode === 'on' ? 'active/' : '') + '16.png', 28 | '19': 'data/icons/' + (mode === 'on' ? 'active/' : '') + '19.png', 29 | '32': 'data/icons/' + (mode === 'on' ? 'active/' : '') + '32.png', 30 | '38': 'data/icons/' + (mode === 'on' ? 'active/' : '') + '38.png', 31 | '48': 'data/icons/' + (mode === 'on' ? 'active/' : '') + '48.png', 32 | '64': 'data/icons/' + (mode === 'on' ? 'active/' : '') + '64.png' 33 | } 34 | }); 35 | if (mode === 'on') { 36 | chrome.tabs.executeScript({ 37 | file: 'data/toolbar/inject.js' 38 | }); 39 | } 40 | else { 41 | chrome.tabs.sendMessage(tab.id, { 42 | method: 'unload' 43 | }); 44 | } 45 | }); 46 | chrome.browserAction.onClicked.addListener(onCommand); 47 | chrome.runtime.onMessage.addListener((request, sender) => { 48 | if (request.method === 'close-me') { 49 | onCommand(sender.tab); 50 | } 51 | }); 52 | 53 | /* FAQs & Feedback */ 54 | { 55 | const {management, runtime: {onInstalled, setUninstallURL, getManifest}, storage, tabs} = chrome; 56 | if (navigator.webdriver !== true) { 57 | const page = getManifest().homepage_url; 58 | const {name, version} = getManifest(); 59 | onInstalled.addListener(({reason, previousVersion}) => { 60 | management.getSelf(({installType}) => installType === 'normal' && storage.local.get({ 61 | 'faqs': true, 62 | 'last-update': 0 63 | }, prefs => { 64 | if (reason === 'install' || (prefs.faqs && reason === 'update')) { 65 | const doUpdate = (Date.now() - prefs['last-update']) / 1000 / 60 / 60 / 24 > 45; 66 | if (doUpdate && previousVersion !== version) { 67 | tabs.create({ 68 | url: page + '?version=' + version + (previousVersion ? '&p=' + previousVersion : '') + '&type=' + reason, 69 | active: reason === 'install' 70 | }); 71 | storage.local.set({'last-update': Date.now()}); 72 | } 73 | } 74 | })); 75 | }); 76 | setUninstallURL(page + '?rd=feedback&name=' + encodeURIComponent(name) + '&version=' + version); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /v2/data/toolbar/inject.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | [...document.querySelectorAll('.edit-toolbar')].forEach(e => e.remove()); 4 | { 5 | const iframe = document.createElement('iframe'); 6 | 7 | const unload = (report = true) => { 8 | window.onmessage = ''; 9 | iframe.remove(); 10 | chrome.runtime.onMessage.removeListener(onmessage); 11 | if (report) { 12 | chrome.runtime.sendMessage({ 13 | method: 'close-me' 14 | }); 15 | } 16 | }; 17 | 18 | window.onmessage = e => { 19 | const command = e.data.method; 20 | const stop = () => { 21 | e.preventDefault(); 22 | e.stopPropagation(); 23 | }; 24 | 25 | if ( 26 | command === 'bold' || command === 'italic' || command === 'insertorderedlist' || command === 'removeformat' || 27 | command === 'insertunorderedlist' || command === 'indent' || command === 'outdent' 28 | ) { 29 | document.execCommand(command); 30 | stop(); 31 | } 32 | else if (command === 'link') { 33 | const href = prompt('Enter a URL (keep blank to remove link):', ''); 34 | if (href) { 35 | document.execCommand('createlink', false, href); 36 | } 37 | else { 38 | document.execCommand('unlink'); 39 | } 40 | stop(); 41 | } 42 | else if (command === 'insertimage') { 43 | const input = document.createElement('input'); 44 | input.type = 'file'; 45 | input.accept = 'image/*'; 46 | input.onchange = e => { 47 | const file = e.target.files[0]; 48 | const reader = new FileReader(); 49 | reader.onload = () => { 50 | document.execCommand('insertimage', false, reader.result); 51 | }; 52 | if (file) { 53 | reader.readAsDataURL(file); 54 | } 55 | }; 56 | input.click(); 57 | 58 | stop(); 59 | } 60 | else if (command === 'heading-0') { 61 | document.execCommand('formatBlock', false, 'p'); 62 | stop(); 63 | } 64 | else if (command === 'heading-1') { 65 | document.execCommand('formatBlock', false, 'h1'); 66 | stop(); 67 | } 68 | else if (command === 'heading-2') { 69 | document.execCommand('formatBlock', false, 'h2'); 70 | stop(); 71 | } 72 | else if (command === 'heading-3') { 73 | document.execCommand('formatBlock', false, 'h3'); 74 | stop(); 75 | } 76 | else if (command === 'blockquote') { 77 | document.execCommand('formatBlock', false, 'blockquote'); 78 | stop(); 79 | } 80 | else if (command === 'move') { 81 | iframe.style.left = (parseInt(iframe.style.left) + e.data.data.dx) + 'px'; 82 | iframe.style.top = (parseInt(iframe.style.top) + e.data.data.dy) + 'px'; 83 | stop(); 84 | } 85 | else if (command === 'close') { 86 | unload(); 87 | stop(); 88 | } 89 | }; 90 | 91 | iframe.src = chrome.runtime.getURL('/data/toolbar/index.html'); 92 | iframe.classList.add('edit-toolbar'); 93 | iframe.style = ` 94 | z-index: 1000000000000; 95 | position: fixed; 96 | top: 10px; 97 | left: 10px; 98 | width: 476px; 99 | height: 38px; 100 | border: none; 101 | `; 102 | document.documentElement.appendChild(iframe); 103 | 104 | const onmessage = request => { 105 | if (request.method === 'unload') { 106 | unload(false); 107 | } 108 | }; 109 | 110 | chrome.runtime.onMessage.addListener(onmessage); 111 | } 112 | -------------------------------------------------------------------------------- /v3/worker.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const notify = e => chrome.notifications.create({ 4 | type: 'basic', 5 | iconUrl: '/data/icons/48.png', 6 | title: chrome.runtime.getManifest().name, 7 | message: e.message || e 8 | }, id => setTimeout(chrome.notifications.clear, 5000, id)); 9 | 10 | const onCommand = async tab => { 11 | try { 12 | const r = await chrome.scripting.executeScript({ 13 | target: { 14 | tabId: tab.id 15 | }, 16 | func: () => document.designMode 17 | }); 18 | // Firefox's protected page 19 | if (r.length === 0 || r[0] === undefined) { 20 | throw Error('Cannot edit this page'); 21 | } 22 | 23 | const mode = r[0].result === 'off' ? 'on' : 'off'; 24 | 25 | chrome.scripting.executeScript({ 26 | target: { 27 | tabId: tab.id, 28 | allFrames: true 29 | }, 30 | func: mode => { 31 | document.designMode = mode; 32 | }, 33 | args: [mode] 34 | }); 35 | chrome.action.setIcon({ 36 | tabId: tab.id, 37 | path: { 38 | '16': '/data/icons/' + (mode === 'on' ? 'active/' : '') + '16.png', 39 | '32': '/data/icons/' + (mode === 'on' ? 'active/' : '') + '32.png', 40 | '48': '/data/icons/' + (mode === 'on' ? 'active/' : '') + '48.png' 41 | } 42 | }); 43 | if (mode === 'on') { 44 | await chrome.scripting.executeScript({ 45 | target: { 46 | tabId: tab.id 47 | }, 48 | files: ['/data/toolbar/inject/main.js'], 49 | world: 'MAIN' 50 | }); 51 | await chrome.scripting.executeScript({ 52 | target: { 53 | tabId: tab.id 54 | }, 55 | files: ['/data/toolbar/inject/isolated.js'] 56 | }); 57 | } 58 | else { 59 | chrome.tabs.sendMessage(tab.id, { 60 | method: 'unload' 61 | }).catch(() => {}); 62 | } 63 | } 64 | catch (e) { 65 | console.warn(e); 66 | notify(e); 67 | } 68 | }; 69 | chrome.action.onClicked.addListener(onCommand); 70 | chrome.runtime.onMessage.addListener((request, sender) => { 71 | if (request.method === 'close-me') { 72 | onCommand(sender.tab); 73 | } 74 | }); 75 | 76 | /* FAQs & Feedback */ 77 | { 78 | const {management, runtime: {onInstalled, setUninstallURL, getManifest}, storage, tabs} = chrome; 79 | if (navigator.webdriver !== true) { 80 | const {homepage_url: page, name, version} = getManifest(); 81 | onInstalled.addListener(({reason, previousVersion}) => { 82 | management.getSelf(({installType}) => installType === 'normal' && storage.local.get({ 83 | 'faqs': true, 84 | 'last-update': 0 85 | }, prefs => { 86 | if (reason === 'install' || (prefs.faqs && reason === 'update')) { 87 | const doUpdate = (Date.now() - prefs['last-update']) / 1000 / 60 / 60 / 24 > 45; 88 | if (doUpdate && previousVersion !== version) { 89 | tabs.query({active: true, lastFocusedWindow: true}, tbs => tabs.create({ 90 | url: page + '?version=' + version + (previousVersion ? '&p=' + previousVersion : '') + '&type=' + reason, 91 | active: reason === 'install', 92 | ...(tbs && tbs.length && {index: tbs[0].index + 1}) 93 | })); 94 | storage.local.set({'last-update': Date.now()}); 95 | } 96 | } 97 | })); 98 | }); 99 | setUninstallURL(page + '?rd=feedback&name=' + encodeURIComponent(name) + '&version=' + version); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /v3/data/toolbar/inject/isolated.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | [...document.querySelectorAll('.edit-toolbar')].forEach(e => e.remove()); 4 | { 5 | let p = document.createElement('dmp-iframe'); 6 | if (!p.shadowRoot) { 7 | p = document.createElement('iframe'); 8 | } 9 | p.style = ` 10 | z-index: 1000000000000; 11 | position: fixed; 12 | top: 10px; 13 | left: 10px; 14 | width: 476px; 15 | height: 38px; 16 | border: none; 17 | `; 18 | p.classList.add('edit-toolbar'); 19 | 20 | const click = e => e.stopPropagation(); 21 | const press = e => e.stopPropagation(); 22 | 23 | document.addEventListener('click', click, true); 24 | document.addEventListener('keypress', press, true); 25 | document.addEventListener('keydown', press, true); 26 | document.addEventListener('keyup', press, true); 27 | 28 | const unload = (report = true) => { 29 | document.removeEventListener('click', click, true); 30 | document.removeEventListener('keypress', press, true); 31 | document.removeEventListener('keydown', press, true); 32 | document.removeEventListener('keyup', press, true); 33 | window.onmessage = ''; 34 | p.remove(); 35 | chrome.runtime.onMessage.removeListener(onmessage); 36 | if (report) { 37 | chrome.runtime.sendMessage({ 38 | method: 'close-me' 39 | }); 40 | } 41 | }; 42 | 43 | 44 | window.onmessage = e => { 45 | const command = e.data.method; 46 | const stop = () => { 47 | e.preventDefault(); 48 | e.stopPropagation(); 49 | }; 50 | 51 | if ( 52 | command === 'bold' || command === 'italic' || command === 'insertorderedlist' || command === 'removeformat' || 53 | command === 'insertunorderedlist' || command === 'indent' || command === 'outdent' 54 | ) { 55 | document.execCommand(command); 56 | stop(); 57 | } 58 | else if (command === 'link') { 59 | const href = prompt('Enter a URL (keep blank to remove link):', ''); 60 | if (href) { 61 | document.execCommand('createlink', false, href); 62 | } 63 | else { 64 | document.execCommand('unlink'); 65 | } 66 | stop(); 67 | } 68 | else if (command === 'insertimage') { 69 | const input = document.createElement('input'); 70 | input.type = 'file'; 71 | input.accept = 'image/*'; 72 | input.onchange = e => { 73 | const file = e.target.files[0]; 74 | const reader = new FileReader(); 75 | reader.onload = () => { 76 | document.execCommand('insertimage', false, reader.result); 77 | }; 78 | if (file) { 79 | reader.readAsDataURL(file); 80 | } 81 | }; 82 | input.click(); 83 | 84 | stop(); 85 | } 86 | else if (['p', 'pre', 'div'].includes(command)) { 87 | document.execCommand('formatBlock', false, command); 88 | stop(); 89 | } 90 | else if (/h\d/.test(command)) { 91 | document.execCommand('formatBlock', false, command); 92 | stop(); 93 | } 94 | else if (command === 'blockquote') { 95 | document.execCommand('formatBlock', false, 'blockquote'); 96 | stop(); 97 | } 98 | else if (command === 'move') { 99 | const {left, top} = getComputedStyle(p); 100 | 101 | p.style.left = (parseInt(left) + e.data.data.dx) + 'px'; 102 | p.style.top = (parseInt(top) + e.data.data.dy) + 'px'; 103 | stop(); 104 | } 105 | else if (command === 'spellcheck:false') { 106 | document.documentElement.spellcheck = false; 107 | } 108 | else if (command === 'spellcheck:true') { 109 | document.documentElement.spellcheck = true; 110 | } 111 | else if (command === 'close') { 112 | unload(); 113 | stop(); 114 | } 115 | }; 116 | 117 | p.setAttribute( 118 | 'src', 119 | chrome.runtime.getURL('/data/toolbar/index.html?spellcheck=' + document.documentElement.spellcheck) 120 | ); 121 | document.documentElement.appendChild(p); 122 | 123 | const onmessage = request => { 124 | if (request.method === 'unload') { 125 | unload(false); 126 | } 127 | }; 128 | 129 | chrome.runtime.onMessage.addListener(onmessage); 130 | } 131 | -------------------------------------------------------------------------------- /v2/data/toolbar/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 |