├── vim ├── common.js ├── data │ ├── guide │ ├── inject │ ├── options │ ├── devtools │ └── icons │ │ ├── 128.png │ │ ├── 16.png │ │ ├── 256.png │ │ ├── 32.png │ │ ├── 48.png │ │ ├── 512.png │ │ └── 64.png ├── devtools │ ├── index.js │ └── index.html ├── config.js └── manifest.json ├── notepad++ ├── common.js ├── data │ ├── guide │ ├── inject │ ├── options │ ├── devtools │ └── icons │ │ ├── 128.png │ │ ├── 16.png │ │ ├── 256.png │ │ ├── 32.png │ │ ├── 48.png │ │ └── 64.png ├── devtools │ ├── index.js │ └── index.html ├── config.js └── manifest.json ├── sublime ├── common.js ├── data │ ├── guide │ ├── devtools │ ├── inject │ ├── options │ └── icons │ │ ├── 16.png │ │ ├── 32.png │ │ ├── 48.png │ │ ├── 64.png │ │ ├── 128.png │ │ ├── 256.png │ │ └── 512.png ├── devtools │ ├── index.js │ └── index.html ├── config.js └── manifest.json └── common ├── data ├── options │ ├── matched.js │ ├── matched.json │ ├── index.html │ └── index.js ├── guide │ ├── info.svg │ ├── success.svg │ ├── error.svg │ ├── index.js │ └── index.html ├── inject │ ├── inspect.css │ └── inspect.js └── devtools │ ├── index.html │ └── index.js └── common.js /vim/common.js: -------------------------------------------------------------------------------- 1 | ../common/common.js -------------------------------------------------------------------------------- /notepad++/common.js: -------------------------------------------------------------------------------- 1 | ../common/common.js -------------------------------------------------------------------------------- /sublime/common.js: -------------------------------------------------------------------------------- 1 | ../common/common.js -------------------------------------------------------------------------------- /vim/data/guide: -------------------------------------------------------------------------------- 1 | ../../common/data/guide/ -------------------------------------------------------------------------------- /sublime/data/guide: -------------------------------------------------------------------------------- 1 | ../../common/data/guide/ -------------------------------------------------------------------------------- /vim/data/inject: -------------------------------------------------------------------------------- 1 | ../../common/data/inject/ -------------------------------------------------------------------------------- /vim/data/options: -------------------------------------------------------------------------------- 1 | ../../common/data/options/ -------------------------------------------------------------------------------- /notepad++/data/guide: -------------------------------------------------------------------------------- 1 | ../../common/data/guide/ -------------------------------------------------------------------------------- /notepad++/data/inject: -------------------------------------------------------------------------------- 1 | ../../common/data/inject/ -------------------------------------------------------------------------------- /notepad++/data/options: -------------------------------------------------------------------------------- 1 | ../../common/data/options/ -------------------------------------------------------------------------------- /sublime/data/devtools: -------------------------------------------------------------------------------- 1 | ../../common/data/devtools/ -------------------------------------------------------------------------------- /sublime/data/inject: -------------------------------------------------------------------------------- 1 | ../../common/data/inject/ -------------------------------------------------------------------------------- /sublime/data/options: -------------------------------------------------------------------------------- 1 | ../../common/data/options/ -------------------------------------------------------------------------------- /vim/data/devtools: -------------------------------------------------------------------------------- 1 | ../../common/data/devtools/ -------------------------------------------------------------------------------- /notepad++/data/devtools: -------------------------------------------------------------------------------- 1 | ../../common/data/devtools/ -------------------------------------------------------------------------------- /common/data/options/matched.js: -------------------------------------------------------------------------------- 1 | ../../../../_/matched/matched.js -------------------------------------------------------------------------------- /common/data/options/matched.json: -------------------------------------------------------------------------------- 1 | ../../../../_/matched/matched.json -------------------------------------------------------------------------------- /vim/data/icons/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/belaviyo/edit-as-html/HEAD/vim/data/icons/128.png -------------------------------------------------------------------------------- /vim/data/icons/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/belaviyo/edit-as-html/HEAD/vim/data/icons/16.png -------------------------------------------------------------------------------- /vim/data/icons/256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/belaviyo/edit-as-html/HEAD/vim/data/icons/256.png -------------------------------------------------------------------------------- /vim/data/icons/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/belaviyo/edit-as-html/HEAD/vim/data/icons/32.png -------------------------------------------------------------------------------- /vim/data/icons/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/belaviyo/edit-as-html/HEAD/vim/data/icons/48.png -------------------------------------------------------------------------------- /vim/data/icons/512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/belaviyo/edit-as-html/HEAD/vim/data/icons/512.png -------------------------------------------------------------------------------- /vim/data/icons/64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/belaviyo/edit-as-html/HEAD/vim/data/icons/64.png -------------------------------------------------------------------------------- /sublime/data/icons/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/belaviyo/edit-as-html/HEAD/sublime/data/icons/16.png -------------------------------------------------------------------------------- /sublime/data/icons/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/belaviyo/edit-as-html/HEAD/sublime/data/icons/32.png -------------------------------------------------------------------------------- /sublime/data/icons/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/belaviyo/edit-as-html/HEAD/sublime/data/icons/48.png -------------------------------------------------------------------------------- /sublime/data/icons/64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/belaviyo/edit-as-html/HEAD/sublime/data/icons/64.png -------------------------------------------------------------------------------- /notepad++/data/icons/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/belaviyo/edit-as-html/HEAD/notepad++/data/icons/128.png -------------------------------------------------------------------------------- /notepad++/data/icons/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/belaviyo/edit-as-html/HEAD/notepad++/data/icons/16.png -------------------------------------------------------------------------------- /notepad++/data/icons/256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/belaviyo/edit-as-html/HEAD/notepad++/data/icons/256.png -------------------------------------------------------------------------------- /notepad++/data/icons/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/belaviyo/edit-as-html/HEAD/notepad++/data/icons/32.png -------------------------------------------------------------------------------- /notepad++/data/icons/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/belaviyo/edit-as-html/HEAD/notepad++/data/icons/48.png -------------------------------------------------------------------------------- /notepad++/data/icons/64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/belaviyo/edit-as-html/HEAD/notepad++/data/icons/64.png -------------------------------------------------------------------------------- /sublime/data/icons/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/belaviyo/edit-as-html/HEAD/sublime/data/icons/128.png -------------------------------------------------------------------------------- /sublime/data/icons/256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/belaviyo/edit-as-html/HEAD/sublime/data/icons/256.png -------------------------------------------------------------------------------- /sublime/data/icons/512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/belaviyo/edit-as-html/HEAD/sublime/data/icons/512.png -------------------------------------------------------------------------------- /sublime/devtools/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | if (chrome.devtools.panels.elements.createSidebarPane) { 4 | chrome.devtools.panels.elements.createSidebarPane('Sublime Text', sidebar => { 5 | sidebar.setPage('/data/devtools/index.html'); 6 | }); 7 | } 8 | -------------------------------------------------------------------------------- /vim/devtools/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | if (chrome.devtools.panels.elements.createSidebarPane) { 4 | chrome.devtools.panels.elements.createSidebarPane(chrome.runtime.getManifest().name, sidebar => { 5 | sidebar.setPage('/data/devtools/index.html'); 6 | }); 7 | } 8 | -------------------------------------------------------------------------------- /notepad++/devtools/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | if (chrome.devtools.panels.elements.createSidebarPane) { 4 | const name = chrome.runtime.getManifest().name; 5 | chrome.devtools.panels.elements.createSidebarPane(name, sidebar => { 6 | sidebar.setPage('/data/devtools/index.html'); 7 | }); 8 | } 9 | -------------------------------------------------------------------------------- /vim/devtools/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /sublime/devtools/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /notepad++/devtools/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /common/data/guide/info.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /notepad++/config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const config = {}; 4 | 5 | config.id = 'com.add0n.native_client'; 6 | 7 | config.command = () => new Promise(resolve => { 8 | chrome.storage.local.get({ 9 | Mac: 'open -a "Notepad++" %path;', 10 | Lin: '/opt/notepadpp/notepadpp %path;', 11 | Win: '"%ProgramFiles(x86)%\\Notepad++\\notepad++.exe" %path;' 12 | }, prefs => { 13 | resolve(prefs[navigator.platform.substr(0, 3)]); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /sublime/config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const config = {}; 4 | 5 | config.id = 'com.add0n.native_client'; 6 | 7 | config.command = () => new Promise(resolve => { 8 | chrome.storage.local.get({ 9 | Mac: 'open -a "Sublime Text" %path;', 10 | Lin: '/opt/sublime_text/sublime_text %path;', 11 | Win: '"%ProgramFiles%\\Sublime Text 3\\subl.exe" %path;' 12 | }, prefs => { 13 | resolve(prefs[navigator.platform.substr(0, 3)]); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /common/data/inject/inspect.css: -------------------------------------------------------------------------------- 1 | .inspectEditor { 2 | border: dashed 1px #222831; 3 | position: absolute; 4 | pointer-events: none; 5 | box-sizing: border-box; 6 | z-index: 2147483647; 7 | } 8 | .inspectEditor:before { 9 | box-sizing: border-box; 10 | content: attr(data-value); 11 | color: #EEEEEE; 12 | background-color: #222831; 13 | font-size: 11px; 14 | font-weight: bold; 15 | white-space: nowrap; 16 | margin-top: -20px; 17 | float: right; 18 | padding: 3px 5px; 19 | } 20 | -------------------------------------------------------------------------------- /vim/config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const config = {}; 4 | 5 | config.id = 'com.add0n.native_client'; 6 | 7 | config.command = () => new Promise(resolve => { 8 | chrome.storage.local.get('command', prefs => { 9 | if (prefs.command) { 10 | return resolve(prefs.command); 11 | } 12 | resolve({ 13 | Mac: 'open -a "MacVim" %path;', 14 | Lin: '/usr/local/bin/gvim %path;', 15 | Win: '"%ProgramFiles(x86)%\\Vim\\vim81\\gvim.exe" %path;' 16 | }[navigator.platform.substr(0, 3)]); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /common/data/options/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Options :: Edit as HTML 6 | 7 | 8 | 9 |
10 | 11 |

A system-level command to open the external text editor

12 |
13 | 14 |
15 | 16 |

17 | 18 | 19 | 20 | 21 |

22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /common/data/guide/success.svg: -------------------------------------------------------------------------------- 1 | 5 | -------------------------------------------------------------------------------- /common/data/guide/error.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /common/data/devtools/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 |
13 |
14 | Inspecting: | 15 |
16 |
17 |
18 | Logs 19 |
20 |
21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /common/data/options/index.js: -------------------------------------------------------------------------------- 1 | /* globals config */ 2 | 'use strict'; 3 | 4 | const toast = document.getElementById('toast'); 5 | 6 | function save() { 7 | const command = document.getElementById('command').value; 8 | chrome.storage.local.set({command}, () => { 9 | toast.textContent = 'Options saved.'; 10 | setTimeout(() => toast.textContent = '', 750); 11 | }); 12 | } 13 | 14 | document.addEventListener('DOMContentLoaded', () => config.command().then(command => { 15 | document.getElementById('command').value = command; 16 | })); 17 | document.getElementById('save').addEventListener('click', save); 18 | document.getElementById('support').addEventListener('click', () => chrome.tabs.create({ 19 | url: chrome.runtime.getManifest().homepage_url + '&rd=donate' 20 | })); 21 | document.getElementById('permission').addEventListener('click', () => chrome.permissions.request({ 22 | origins: [''] 23 | }, granted => { 24 | toast.textContent = 'Remote frames access ' + (granted ? 'is' : 'is not') + ' granted'; 25 | setTimeout(() => toast.textContent = '', 2000); 26 | })); 27 | -------------------------------------------------------------------------------- /notepad++/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Edit with Notepad++", 3 | "description": "Edit an HTML element and all its child nodes in Notepad++ Text editor with real-time updates", 4 | "version": "0.1.4", 5 | "manifest_version": 2, 6 | "permissions": [ 7 | "storage", 8 | "activeTab", 9 | "nativeMessaging", 10 | "notifications", 11 | "contextMenus" 12 | ], 13 | "optional_permissions": [ 14 | "", 15 | "downloads" 16 | ], 17 | "background": { 18 | "persistent": false, 19 | "scripts": [ 20 | "config.js", 21 | "common.js" 22 | ] 23 | }, 24 | "offline_enabled": true, 25 | "browser_action": {}, 26 | "devtools_page": "devtools/index.html", 27 | "options_ui": { 28 | "page": "data/options/index.html", 29 | "chrome_style": true 30 | }, 31 | "icons": { 32 | "16": "data/icons/16.png", 33 | "32": "data/icons/32.png", 34 | "48": "data/icons/48.png", 35 | "64": "data/icons/64.png", 36 | "128": "data/icons/128.png", 37 | "256": "data/icons/256.png" 38 | }, 39 | "homepage_url": "https://add0n.com/edit-as-html.html?from=notepad" 40 | } 41 | -------------------------------------------------------------------------------- /vim/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Edit with VIM text editor", 3 | "description": "Edit an HTML element and all its child nodes in VIM text editor with real-time updates", 4 | "version": "0.1.2", 5 | "manifest_version": 2, 6 | "permissions": [ 7 | "storage", 8 | "activeTab", 9 | "nativeMessaging", 10 | "notifications", 11 | "contextMenus" 12 | ], 13 | "optional_permissions": [ 14 | "", 15 | "downloads" 16 | ], 17 | "background": { 18 | "persistent": false, 19 | "scripts": [ 20 | "config.js", 21 | "common.js" 22 | ] 23 | }, 24 | "offline_enabled": true, 25 | "browser_action": {}, 26 | "devtools_page": "devtools/index.html", 27 | "options_ui": { 28 | "page": "data/options/index.html", 29 | "chrome_style": true 30 | }, 31 | "icons": { 32 | "16": "data/icons/16.png", 33 | "32": "data/icons/32.png", 34 | "48": "data/icons/48.png", 35 | "64": "data/icons/64.png", 36 | "128": "data/icons/128.png", 37 | "256": "data/icons/256.png", 38 | "512": "data/icons/512.png" 39 | }, 40 | "homepage_url": "https://add0n.com/edit-as-html.html?from=vim" 41 | } 42 | -------------------------------------------------------------------------------- /sublime/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Edit with Sublime Text", 3 | "description": "Edit an HTML element and all its child nodes in Sublime Text editor with real-time updates", 4 | "version": "0.1.5", 5 | "manifest_version": 2, 6 | "permissions": [ 7 | "storage", 8 | "activeTab", 9 | "nativeMessaging", 10 | "notifications", 11 | "contextMenus" 12 | ], 13 | "optional_permissions": [ 14 | "downloads", 15 | "" 16 | ], 17 | "background": { 18 | "persistent": false, 19 | "scripts": [ 20 | "config.js", 21 | "common.js" 22 | ] 23 | }, 24 | "offline_enabled": true, 25 | "browser_action": {}, 26 | "devtools_page": "devtools/index.html", 27 | "options_ui": { 28 | "page": "data/options/index.html", 29 | "chrome_style": true 30 | }, 31 | "icons": { 32 | "16": "data/icons/16.png", 33 | "32": "data/icons/32.png", 34 | "48": "data/icons/48.png", 35 | "64": "data/icons/64.png", 36 | "128": "data/icons/128.png", 37 | "256": "data/icons/256.png", 38 | "512": "data/icons/512.png" 39 | }, 40 | "homepage_url": "https://add0n.com/edit-as-html.html?from=sublime" 41 | } 42 | -------------------------------------------------------------------------------- /common/data/inject/inspect.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function release() { 4 | document.removeEventListener('mouseover', window.mouseover); 5 | document.removeEventListener('click', window.click); 6 | [...document.querySelectorAll('.inspectEditor')] 7 | .forEach(n => document.body.removeChild(n)); 8 | } 9 | release(); 10 | window.div = document.createElement('div'); 11 | window.div.classList.add('inspectEditor'); 12 | document.body.appendChild(window.div); 13 | 14 | window.mouseover = e => { 15 | let node = e.target; 16 | if (node.nodeType !== 1) { 17 | node = document.body; 18 | } 19 | const rect = node.getBoundingClientRect(); 20 | Object.assign(window.div.style, { 21 | width: Math.min(rect.width, document.body.clientWidth) + 'px', 22 | height: rect.height + 'px', 23 | left: Math.max(window.scrollX + rect.left, 0) + 'px', 24 | top: (window.scrollY + rect.top) + 'px', 25 | display: node.localName === 'iframe' ? 'none' : 'block' 26 | }); 27 | const cl = [...node.classList].join('.'); 28 | const list = [node.parentNode.parentNode, node.parentNode, node] 29 | .filter((n, i, l) => l.indexOf(n) === i) 30 | .map(n => n.localName) 31 | .filter(n => n); 32 | window.div.dataset.value = list.join('>') + (cl ? '.' + cl : '') + ' | ' + 33 | rect.width.toFixed(2) + 'x' + rect.height.toFixed(2); 34 | }; 35 | 36 | window.click = e => { 37 | if (document.querySelector('.inspectEditor')) { 38 | e.preventDefault(); 39 | chrome.runtime.sendMessage({ 40 | method: 'bounce-release' 41 | }); 42 | 43 | const target = e.target; 44 | const background = chrome.runtime.connect({ 45 | name: 'content-script' 46 | }); 47 | background.postMessage({ 48 | method: 'edit-with', 49 | content: target.innerHTML, 50 | ext: 'html' 51 | }); 52 | background.onMessage.addListener(request => { 53 | if (request.method === 'file-changed') { 54 | target.innerHTML = request.content; 55 | } 56 | }); 57 | } 58 | }; 59 | 60 | document.addEventListener('mouseover', window.mouseover); 61 | 62 | chrome.runtime.onMessage.addListener(request => { 63 | if (request.method === 'release') { 64 | release(); 65 | } 66 | }); 67 | 68 | document.addEventListener('keydown', e => { 69 | if (e.key === 'Esc' || e.key === 'Escape') { 70 | chrome.runtime.sendMessage({ 71 | method: 'bounce-release' 72 | }); 73 | } 74 | }); 75 | 76 | document.addEventListener('click', window.click); 77 | -------------------------------------------------------------------------------- /common/data/guide/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | let os = 'windows'; 4 | if (navigator.userAgent.indexOf('Mac') !== -1) { 5 | os = 'mac'; 6 | } 7 | else if (navigator.userAgent.indexOf('Linux') !== -1) { 8 | os = 'linux'; 9 | } 10 | document.body.dataset.os = (os === 'mac' || os === 'linux') ? 'linux' : 'windows'; 11 | 12 | const notify = (function() { 13 | const parent = document.getElementById('notify'); 14 | const elems = []; 15 | return { 16 | show: function(type, msg, delay) { 17 | const elem = document.createElement('div'); 18 | elem.textContent = msg; 19 | elem.dataset.type = type; 20 | parent.appendChild(elem); 21 | window.setTimeout(() => { 22 | try { 23 | parent.removeChild(elem); 24 | } 25 | catch (e) {} 26 | }, delay || 3000); 27 | elems.push(elem); 28 | }, 29 | destroy: function() { 30 | elems.forEach(elem => { 31 | try { 32 | parent.removeChild(elem); 33 | } 34 | catch (e) {} 35 | }); 36 | } 37 | }; 38 | })(); 39 | 40 | document.addEventListener('click', e => { 41 | const target = e.target; 42 | if (target.dataset.cmd === 'download') { 43 | chrome.permissions.request({ 44 | permissions: ['downloads'] 45 | }, granted => { 46 | if (granted) { 47 | notify.show('info', 'Looking for the latest version of the native-client', 60000); 48 | const req = new window.XMLHttpRequest(); 49 | req.open('GET', 'https://api.github.com/repos/belaviyo/native-client/releases/latest'); 50 | req.responseType = 'json'; 51 | req.onload = () => { 52 | try { 53 | chrome.downloads.download({ 54 | url: req.response.assets.filter(a => a.name === os + '.zip')[0].browser_download_url, 55 | filename: os + '.zip' 56 | }, () => { 57 | notify.destroy(); 58 | notify.show('success', 'Download is started. Extract and install when it is done'); 59 | document.body.dataset.step = 1; 60 | }); 61 | } 62 | catch (e) { 63 | notify.show('error', e.message || e); 64 | } 65 | }; 66 | req.onerror = () => { 67 | notify('error', 'Something went wrong! Please download the package manually'); 68 | window.setTimeout(() => { 69 | window.open('https://github.com/belaviyo/native-client/releases'); 70 | }, 5000); 71 | }; 72 | req.send(); 73 | } 74 | else { 75 | notify.show('error', 'Cannot initiate file downloading. Please download the file manually', 60000); 76 | } 77 | }); 78 | } 79 | else if (target.dataset.cmd === 'check') { 80 | chrome.runtime.sendNativeMessage('com.add0n.native_client', { 81 | method: 'spec' 82 | }, response => { 83 | if (response) { 84 | notify.show('success', 'Native client version is ' + response.version); 85 | } 86 | else { 87 | notify.show('error', 'Cannot find the native client. Follow the 3 steps to install the native client'); 88 | } 89 | }); 90 | } 91 | else if (target.dataset.cmd === 'options') { 92 | chrome.runtime.openOptionsPage(); 93 | } 94 | }); 95 | -------------------------------------------------------------------------------- /common/data/devtools/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const background = chrome.runtime.connect({ 4 | name: 'devtools-panel' 5 | }); 6 | 7 | background.onMessage.addListener(request => { 8 | if (request.method === 'log') { 9 | const p = document.createElement('p'); 10 | p.textContent = (new Date()).toLocaleTimeString() + ': ' + request.msg; 11 | document.querySelector('details').appendChild(p); 12 | p.scrollIntoView(); 13 | } 14 | }); 15 | background.postMessage({ 16 | method: 'tabId', 17 | tabId: chrome.devtools.inspectedWindow.tabId 18 | }); 19 | 20 | document.getElementById('toolbar').addEventListener('click', e => { 21 | const type = e.target.id; 22 | if (type !== 'outerHTML' && type !== 'innerHTML') { 23 | return; 24 | } 25 | 26 | const cmd = ` 27 | $0.${type} 28 | `; 29 | chrome.devtools.inspectedWindow.eval(cmd, (content, exception) => { 30 | if (exception) { 31 | alert(exception.value); 32 | } 33 | else { 34 | chrome.devtools.inspectedWindow.eval('location.href', (result, isException) => { 35 | if (isException) { 36 | return alert('Cannot access page URL'); 37 | } 38 | chrome.permissions.request({ 39 | origins: [result] 40 | }, granted => { 41 | if (granted) { 42 | const tabId = chrome.devtools.inspectedWindow.tabId; 43 | chrome.tabs.executeScript(tabId, { 44 | runAt: 'document_start', 45 | matchAboutBlank: true, 46 | allFrames: true, 47 | code: `var test = target => { 48 | const background = chrome.runtime.connect({ 49 | name: 'devtools-inject' 50 | }); 51 | let ext = 'html'; 52 | if (target.tagName === 'STYLE' && '${type}' === 'innerHTML') { 53 | ext = 'css'; 54 | } 55 | if (target.tagName === 'SCRIPT' && '${type}' === 'innerHTML') { 56 | ext = 'js'; 57 | } 58 | background.postMessage({ 59 | method: 'edit-with', 60 | content: target.${type}, 61 | ext 62 | }); 63 | background.onMessage.addListener(request => { 64 | if (request.method === 'file-changed') { 65 | if ('${type}' === 'outerHTML') { 66 | const template = document.createElement('template'); 67 | template.innerHTML = request.content; 68 | const root = template.content.firstChild; 69 | target.replaceWith(template.content); 70 | target = root; 71 | } 72 | else { 73 | target.${type} = request.content; 74 | } 75 | } 76 | }); 77 | };` 78 | }, () => chrome.devtools.inspectedWindow.eval(`test($0)`, { 79 | useContentScriptContext: true 80 | })); 81 | } 82 | else { 83 | alert('Cannot access to the DOM object. Permission denied'); 84 | } 85 | }); 86 | }); 87 | } 88 | }); 89 | }); 90 | document.getElementById('local').addEventListener('click', () => { 91 | chrome.devtools.inspectedWindow.eval(`{ 92 | const target = $0; 93 | fetch(target.href || target.src).then(r => r.text()).then(content => { 94 | const e = document.createElement(target.tagName === 'LINK' ? 'style' : target.localName); 95 | e.textContent = content; 96 | target.replaceWith(e); 97 | }).catch(e => alert(e.message)); 98 | };`); 99 | }); 100 | 101 | function inspect() { 102 | const cmd = `{ 103 | const node = $0; 104 | const rect = node.getBoundingClientRect(); 105 | const cl = [...node.classList].join('.'); 106 | const list = [node.parentNode.parentNode, node.parentNode, node] 107 | .filter((n, i, l) => l.indexOf(n) === i) 108 | .map(n => n.localName) 109 | .filter(n => n); 110 | 111 | [list, cl, { 112 | width: rect.width, 113 | height: rect.height 114 | }, Boolean(node.textContent), (node.tagName === 'LINK' && node.href && node.href.indexOf('.css') !== -1) || (node.tagName === 'SCRIPT' && node.src)] 115 | }`; 116 | chrome.devtools.inspectedWindow.eval(cmd, (result, exception) => { 117 | if (!exception) { 118 | const [list, cl, rect, text, remote] = result; 119 | 120 | document.getElementById('inspect').textContent = list.join('>'); 121 | document.getElementById('class').textContent = cl ? '.' + cl : ''; 122 | document.getElementById('dimension').textContent = rect.width.toFixed(2) + 'x' + rect.height.toFixed(2); 123 | 124 | document.getElementById('local').disabled = !remote; 125 | document.getElementById('innerHTML').disabled = text === false; 126 | } 127 | }); 128 | } 129 | 130 | chrome.devtools.panels.elements.onSelectionChanged.addListener(inspect); 131 | 132 | document.addEventListener('DOMContentLoaded', inspect); 133 | -------------------------------------------------------------------------------- /common/data/guide/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | One more step :: Edit as HTML 6 | 105 | 106 | 107 |

One last step

108 |

For the extension to execute external commands (start the external text editor and exchange content) one extra step is required. Please download, unzip and install this minimal native client.

109 |
    110 |
  • Click here to download the package for you OS or click here to browse all available download options.
  • 111 |
  • Extract the downloaded package into a local directory
  • 112 |
  • Now open a terminal in the root directory and run ./install.sh
  • 113 |
  • Now double-click the install.bat file
  • 114 |
115 |
116 | 117 |
118 |

The installer script copies a few files to two different locations for your browser to be able to detect this native client (paths of these directories are being printed based on your OS and configuration). If you already have NodeJS in your system, the installed version of NodeJS will be used. So please make sure you have an up-to-date version of NodeJS installed. If you don't have this application, the portable version will be used. Read more here.

119 |

To completely remove this native client, run ./uninstall.shuninstall.bat.

120 |

To make sure the native client is accessible, click on the "Check Connection" button. If you still get this page even when the "Check Button" reports successful connection, it means the application cannot be accessed (application's path is not correct in the options page). If the path to the executable is correct and the native client is connected, you can debug the connection by enabling console logs.

121 |

There is no need for administration permission for the native client to operate.

122 | 123 |
124 | Sample Installation Guide: 125 | 129 |
130 |
131 | 132 | 133 | 134 | -------------------------------------------------------------------------------- /common/common.js: -------------------------------------------------------------------------------- 1 | /* globals config */ 2 | 'use strict'; 3 | 4 | function notify(message) { 5 | chrome.notifications.create({ 6 | title: chrome.runtime.getManifest().name, 7 | type: 'basic', 8 | iconUrl: '/data/icons/48.png', 9 | message 10 | }); 11 | } 12 | 13 | chrome.browserAction.onClicked.addListener(tab => { 14 | chrome.tabs.insertCSS(tab.id, { 15 | allFrames: true, 16 | matchAboutBlank: true, 17 | runAt: 'document_start', 18 | file: '/data/inject/inspect.css' 19 | }, () => { 20 | if (chrome.runtime.lastError) { 21 | notify(chrome.runtime.lastError.message); 22 | } 23 | else { 24 | chrome.tabs.executeScript(tab.id, { 25 | allFrames: true, 26 | matchAboutBlank: true, 27 | runAt: 'document_start', 28 | file: '/data/inject/inspect.js' 29 | }); 30 | } 31 | }); 32 | }); 33 | 34 | function editor(request, observe) { 35 | const native = chrome.runtime.connectNative(config.id); 36 | native.onDisconnect.addListener(() => observe()); 37 | native.onMessage.addListener(observe); 38 | native.postMessage({ 39 | permissions: ['crypto', 'fs', 'path', 'os'], 40 | args: [request.content, request.ext], 41 | script: ` 42 | const crypto = require('crypto'); 43 | const fs = require('fs'); 44 | 45 | const [content, ext] = args; 46 | 47 | const filename = require('path').join( 48 | require('os').tmpdir(), 49 | 'editor-' + crypto.randomBytes(4).readUInt32LE(0) + '.' + ext 50 | ); 51 | fs.writeFile(filename, content, e => { 52 | if (e) { 53 | push({ 54 | method: 'error', 55 | error: e.message 56 | }); 57 | close(); 58 | } 59 | else { 60 | push({ 61 | method: 'file-created', 62 | filename 63 | }); 64 | fs.watchFile(filename, event => { 65 | fs.readFile(filename, 'utf8', (e, content) => { 66 | if (e) { 67 | push({ 68 | type: 'error', 69 | error: e.message 70 | }); 71 | } 72 | else { 73 | push({ 74 | method: 'file-changed', 75 | content, 76 | event 77 | }); 78 | } 79 | }); 80 | }); 81 | } 82 | }); 83 | ` 84 | }); 85 | return native; 86 | } 87 | 88 | chrome.runtime.onMessage.addListener((request, sender) => { 89 | if (request.method === 'bounce-release') { 90 | chrome.tabs.sendMessage(sender.tab.id, { 91 | method: 'release' 92 | }); 93 | } 94 | }); 95 | 96 | const panels = {}; 97 | chrome.tabs.onRemoved.addListener(tabId => delete panels[tabId]); 98 | 99 | chrome.runtime.onConnect.addListener(devToolsConnection => { 100 | if (devToolsConnection.name === 'devtools-panel') { 101 | return devToolsConnection.onMessage.addListener(request => { 102 | if (request.method === 'tabId') { 103 | panels[request.tabId] = devToolsConnection; 104 | devToolsConnection.onDisconnect.addListener(() => { 105 | delete panels[request.tabId]; 106 | }); 107 | } 108 | }); 109 | } 110 | const connectListener = request => { 111 | const id = devToolsConnection.sender.tab.id; 112 | const log = msg => panels[id] ? panels[id].postMessage({ 113 | method: 'log', 114 | msg 115 | }) : ''; 116 | if (request.method === 'edit-with') { 117 | const native = editor(request, res => { 118 | if (!res) { 119 | const lastError = chrome.runtime.lastError; 120 | let msg = 'The native client is not installed or the native application exited with an error.'; 121 | if (lastError) { 122 | msg += ' -- ' + lastError.message; 123 | } 124 | notify(msg); 125 | chrome.tabs.create({ 126 | url: '/data/guide/index.html' 127 | }); 128 | } 129 | else if (res.method === 'error') { 130 | notify(res.error); 131 | } 132 | else if (res.method === 'file-created') { 133 | log('Temporary file is created at ' + res.filename); 134 | config.command().then(command => { 135 | chrome.runtime.sendNativeMessage(config.id, { 136 | permissions: ['child_process'], 137 | args: [command.replace('%path;', res.filename)], 138 | script: String.raw` 139 | const {exec} = require('child_process'); 140 | const command = args[0].replace(/%([^%]+)%/g, (_, n) => env[n]); 141 | exec(command, (error, stdout, stderr) => { 142 | push({error, stdout, stderr}); 143 | close(); 144 | }); 145 | ` 146 | }, res => { 147 | if (res.stderr) { 148 | notify(res.stderr); 149 | } 150 | }); 151 | }); 152 | } 153 | else if (res.method === 'file-changed') { 154 | log('File content is changed'); 155 | devToolsConnection.postMessage({ 156 | method: 'file-changed', 157 | content: res.content 158 | }); 159 | } 160 | }); 161 | connectListener.natives.push(native); 162 | } 163 | }; 164 | connectListener.natives = []; 165 | // add the listener 166 | devToolsConnection.onMessage.addListener(connectListener); 167 | // disconnect 168 | devToolsConnection.onDisconnect.addListener(() => { 169 | connectListener.natives.forEach(n => n.disconnect()); 170 | devToolsConnection.onMessage.removeListener(connectListener); 171 | }); 172 | }); 173 | 174 | // context menu 175 | { 176 | const callback = () => { 177 | chrome.contextMenus.create({ 178 | id: 'edit-with', 179 | title: chrome.runtime.getManifest().name, 180 | contexts: ['editable'] 181 | }); 182 | }; 183 | chrome.runtime.onInstalled.addListener(callback); 184 | chrome.runtime.onStartup.addListener(callback); 185 | } 186 | chrome.contextMenus.onClicked.addListener((info, tab) => { 187 | chrome.tabs.executeScript(tab.id, { 188 | frameId: info.frameId, 189 | runAt: 'document_start', 190 | matchAboutBlank: true, 191 | code: `{ 192 | const target = document.activeElement; 193 | const background = chrome.runtime.connect({ 194 | name: 'context-menu' 195 | }); 196 | background.postMessage({ 197 | method: 'edit-with', 198 | content: target.value, 199 | ext: 'txt' 200 | }); 201 | background.onMessage.addListener(request => { 202 | if (request.method === 'file-changed') { 203 | target.value = request.content; 204 | } 205 | }); 206 | }` 207 | }); 208 | }); 209 | /* FAQs & Feedback */ 210 | { 211 | const {onInstalled, setUninstallURL, getManifest} = chrome.runtime; 212 | const {name, version} = getManifest(); 213 | const page = getManifest().homepage_url; 214 | if (navigator.webdriver !== true) { 215 | onInstalled.addListener(({reason, previousVersion}) => { 216 | chrome.storage.local.get({ 217 | 'faqs': true, 218 | 'last-update': 0 219 | }, prefs => { 220 | if (reason === 'install' || (prefs.faqs && reason === 'update')) { 221 | const doUpdate = (Date.now() - prefs['last-update']) / 1000 / 60 / 60 / 24 > 45; 222 | if (doUpdate && previousVersion !== version) { 223 | chrome.tabs.create({ 224 | url: page + '&version=' + version + 225 | (previousVersion ? '&p=' + previousVersion : '') + 226 | '&type=' + reason, 227 | active: reason === 'install' 228 | }); 229 | chrome.storage.local.set({'last-update': Date.now()}); 230 | } 231 | } 232 | }); 233 | }); 234 | setUninstallURL(page + '&rd=feedback&name=' + encodeURIComponent(name) + '&version=' + version); 235 | } 236 | } 237 | --------------------------------------------------------------------------------