├── doc └── main.md ├── data ├── images │ ├── icon-16.png │ ├── icon-32.png │ └── icon-64.png ├── panel.html ├── panel.css └── panel.js ├── package.json ├── test └── test-main.js ├── .github └── FUNDING.yml ├── lib ├── copy-as-org.js ├── org.js ├── main.js └── table.js └── README.org /doc/main.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/images/icon-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kuanyui/copy-as-org-mode-legacy-firefox/HEAD/data/images/icon-16.png -------------------------------------------------------------------------------- /data/images/icon-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kuanyui/copy-as-org-mode-legacy-firefox/HEAD/data/images/icon-32.png -------------------------------------------------------------------------------- /data/images/icon-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kuanyui/copy-as-org-mode-legacy-firefox/HEAD/data/images/icon-64.png -------------------------------------------------------------------------------- /data/panel.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "copy-as-org-mode", 3 | "title": "Copy as Org-mode", 4 | "id": "jid1-Yvk3FWXQLSdKUg", 5 | "description": " Copy Link, Image and Table in Page as Org-mode Format!", 6 | "author": "Hiroko", 7 | "license": "MIT", 8 | "version": "0.1" 9 | } 10 | -------------------------------------------------------------------------------- /test/test-main.js: -------------------------------------------------------------------------------- 1 | var main = require("./main"); 2 | 3 | exports["test main"] = function(assert) { 4 | assert.pass("Unit test running!"); 5 | }; 6 | 7 | exports["test main async"] = function(assert, done) { 8 | assert.pass("async Unit test running!"); 9 | done(); 10 | }; 11 | 12 | require("sdk/test").run(exports); 13 | -------------------------------------------------------------------------------- /data/panel.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: sans-serif; 3 | margin: 0; 4 | } 5 | 6 | .command { 7 | /* restyle button */ 8 | outline: none; 9 | background-color: transparent; 10 | border: none; 11 | width: 100%; 12 | font-size: 14px; 13 | text-align: left; 14 | 15 | padding: .3em .5em; 16 | cursor: pointer; 17 | white-space: nowrap; 18 | } 19 | 20 | .command:hover { 21 | background-color: #268BD2; 22 | color: white; 23 | } 24 | 25 | .command.highlight-success { 26 | background-color: #259286; 27 | color: #fdf5dd; 28 | } 29 | -------------------------------------------------------------------------------- /data/panel.js: -------------------------------------------------------------------------------- 1 | var commandDispatcher = function(event) { 2 | var button = event.target; 3 | self.port.emit(button.dataset.command, button.dataset.scope); 4 | 5 | button.classList.add('highlight-success'); 6 | 7 | // close panel after command 8 | setTimeout(function() { 9 | self.port.emit("close"); 10 | button.classList.remove('highlight-success') 11 | }, 300); 12 | }; 13 | 14 | var buttons = document.querySelectorAll("button[data-command]"); 15 | 16 | for (var i = 0; i < buttons.length; i++) { 17 | buttons[i].addEventListener('click', commandDispatcher, false); 18 | } 19 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [kuanyui] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: onoono # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 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: onoono # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /lib/copy-as-org.js: -------------------------------------------------------------------------------- 1 | var SDK = { 2 | Clipboard: require("sdk/clipboard") 3 | }; 4 | 5 | var Org = require("org"); 6 | 7 | var copyToClipboard = function(string) { 8 | SDK.Clipboard.set(string, "text"); 9 | }; 10 | 11 | exports.link = function(url, title) { 12 | title = title || url; 13 | 14 | var string = Org.formatLink(url, title); 15 | 16 | copyToClipboard(string); 17 | }; 18 | 19 | exports.image = function(url, title) { 20 | title = title || url; 21 | 22 | var string = Org.formatImage(url, title); 23 | 24 | copyToClipboard(string); 25 | } 26 | 27 | exports.tab = function(tab) { 28 | this.link(tab.url, tab.title); 29 | }; 30 | 31 | exports.tabs = function(tabs) { 32 | var formattedTabs = new Array(tabs.length); 33 | 34 | for (var i = 0; i < tabs.length; i++) { 35 | var tab = tabs[i]; 36 | formattedTabs[i] = Org.formatLink(tab.url, tab.title); 37 | } 38 | 39 | var string = Org.formatList(formattedTabs); 40 | 41 | copyToClipboard(string); 42 | }; 43 | 44 | exports.singleLineTable = function(table) { 45 | var string = Org.formatSingleLineTable(table); 46 | 47 | copyToClipboard(string); 48 | }; 49 | -------------------------------------------------------------------------------- /lib/org.js: -------------------------------------------------------------------------------- 1 | var Table = require("table"); 2 | 3 | function chomp(string) { 4 | // string chomp! 5 | return string.replace(/^\s+/, '').replace(/\s+$/, ''); 6 | } 7 | 8 | function removeNewlines(string) { 9 | // remove any new-line chars 10 | return string.replace("\n", ''); 11 | } 12 | 13 | function determineTitle(title) { 14 | title = removeNewlines(chomp(title)); 15 | 16 | if (title === '') { 17 | title = "(No Title)"; 18 | } 19 | 20 | return title.replace(/\[/, '').replace(/\[/, ' - '); 21 | } 22 | 23 | exports.formatLink = function(url, title) { 24 | return "[[" + url + "][" + determineTitle(title) + "]]"; 25 | }; 26 | 27 | exports.formatImage = function(url, title) { 28 | return "[[" + url + "]]"; 29 | }; 30 | 31 | exports.formatList = function(texts) { 32 | // new line chars are appended at the end of each line 33 | // to make sure that we'll have a new line char at the very end. 34 | return texts.map(function(text) { 35 | return "- " + text + "\n"; 36 | }).join(""); 37 | }; 38 | 39 | exports.formatSingleLineTable = function(tableList) { 40 | return Table.singleLineTable(Table.preprocessTableList(tableList)); 41 | } 42 | -------------------------------------------------------------------------------- /README.org: -------------------------------------------------------------------------------- 1 | #+begin_quote 2 | This repository has been deprecated and archived because this project is for legacy Firefox (< Firefox 68). 3 | 4 | For latest Firefox (WebExtension), please see [[https://github.com/kuanyui/copy-as-org-mode][copy-as-org-mode]]. 5 | #+end_quote 6 | * Copy as Org-Mode 7 | 8 | Copy the contents in page as Org-mode format. 9 | 10 | Forked from and based on chitsaou's [[https://github.com/chitsaou/copy-as-markdown][copy-as-markdown]]. 11 | 12 | ** Requirements 13 | 14 | - Firefox 30+ 15 | 16 | ** Installation 17 | 18 | Install /Copy as Org-mode/ on [[https://addons.mozilla.org/firefox/addon/copy-as-org-mode/][Firefox Addons]]. 19 | 20 | ** Features 21 | 22 | - Copy == as an Org table (single line each cell *currently*). 23 | - Copy all tabs of current window as a Org list. 24 | - Right click on anywhere of a page and copy the page title with URL as Org. 25 | - Right click on a link and copy it as Org. 26 | - Right click on an image and copy it as Org. 27 | 28 | ** Todo 29 | 30 | - [ ] Multiple line table cell. 31 | - [ ] When copying an Image, if it is wrapped by a link, should also include that link. 32 | 33 | ** Development 34 | 35 | 1. Install Firefox 30+. 36 | 2. [[https://developer.mozilla.org/en-US/Add-ons/SDK/Tutorials/Installation][Install Firefox Add-ON SDK]] if you haven't installed it. 37 | 3. Run =cfx run= from Terminal 38 | 39 | ** License 40 | 41 | The MIT License 42 | 43 | Copyright (c) 2015 Hiroko (kuanyui) 44 | 45 | Permission is hereby granted, free of charge, to any person obtaining a copy 46 | of this software and associated documentation files (the "Software"), to deal 47 | in the Software without restriction, including without limitation the rights 48 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 49 | copies of the Software, and to permit persons to whom the Software is 50 | furnished to do so, subject to the following conditions: 51 | 52 | The above copyright notice and this permission notice shall be included in 53 | all copies or substantial portions of the Software. 54 | 55 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 56 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 57 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 58 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 59 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 60 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 61 | THE SOFTWARE. 62 | -------------------------------------------------------------------------------- /lib/main.js: -------------------------------------------------------------------------------- 1 | var SDK = { 2 | UI: { 3 | Button: { 4 | Toggle: require('sdk/ui/button/toggle') 5 | } 6 | }, 7 | Tabs: require("sdk/tabs"), 8 | Windows: require("sdk/windows"), 9 | Panels: require("sdk/panel"), 10 | Self: require("sdk/self"), 11 | ContextMenu: require("sdk/context-menu") 12 | }; 13 | 14 | var CopyAsOrg = require("copy-as-org"); 15 | 16 | var togglePanel = function(state) { 17 | if (state.checked) { 18 | panel.show({ 19 | position: button 20 | }); 21 | } 22 | }; 23 | 24 | // bootstrap 25 | var button = SDK.UI.Button.Toggle.ToggleButton({ 26 | id: "copy-as-org", 27 | label: "Copy as Org", 28 | icon: { 29 | "16": "./images/icon-16.png", 30 | "32": "./images/icon-32.png", 31 | "64": "./images/icon-64.png" 32 | }, 33 | onChange: togglePanel 34 | }); 35 | 36 | var handleHide = function() { 37 | button.state('window', { checked: false }); 38 | }; 39 | 40 | var panel = SDK.Panels.Panel({ 41 | contentURL: SDK.Self.data.url("panel.html"), 42 | contentStyleFile: SDK.Self.data.url("panel.css"), 43 | contentScriptFile: SDK.Self.data.url("panel.js"), 44 | onHide: handleHide, 45 | width: 100, 46 | height: 56 47 | }); 48 | 49 | panel.port.on("copy", function(scope) { 50 | var currentWindow = SDK.Windows.browserWindows.activeWindow; 51 | 52 | switch (scope) { 53 | case "current-tab": 54 | CopyAsOrg.tab(currentWindow.tabs.activeTab); 55 | break; 56 | 57 | case "all-tabs": 58 | CopyAsOrg.tabs(currentWindow.tabs); 59 | break; 60 | } 61 | }); 62 | 63 | panel.port.on("close", function() { 64 | panel.hide(); 65 | }); 66 | 67 | var anyContext = SDK.ContextMenu.PredicateContext(function() { 68 | return true; 69 | }); 70 | 71 | var contextMenu = SDK.ContextMenu.Menu({ 72 | label: "Copy as Org", 73 | context: anyContext, 74 | image: SDK.Self.data.url("images/icon-16.png") 75 | }); 76 | 77 | // context menu for a link 78 | var copyLinkAsOrgMenuItem = SDK.ContextMenu.Item({ 79 | label: "[[URL][Link Title]]", 80 | data: "copyLinkAsOrg", 81 | parentMenu: contextMenu, 82 | context: SDK.ContextMenu.SelectorContext("a"), 83 | // TODO: use contentScriptFile 84 | contentScript: 'self.on("click", function(node, data) {' + 85 | ' self.postMessage({ url: node.href, title: node.textContent });' + 86 | '});', 87 | onMessage: function(message) { 88 | CopyAsOrg.link(message.url, message.title); 89 | } 90 | }); 91 | 92 | var copyImageAsOrg = SDK.ContextMenu.Item({ 93 | label: "[[Image URL]]", 94 | data: "copyImageAsOrg", 95 | parentMenu: contextMenu, 96 | context: SDK.ContextMenu.SelectorContext("img"), 97 | // TODO: use contentScriptFile 98 | contentScript: 'self.on("click", function(node, data) {' + 99 | ' self.postMessage({ url: node.src, title: node.alt });' + 100 | '});', 101 | onMessage: function(message) { 102 | CopyAsOrg.image(message.url, message.title); 103 | } 104 | }); 105 | 106 | var copySingleLineTableAsOrgMenuItem = SDK.ContextMenu.Item({ 107 | label: "| Single Line | Table |", 108 | data: "copySingleLineTableAsOrg", 109 | parentMenu: contextMenu, 110 | context: SDK.ContextMenu.SelectorContext("table"), 111 | contentScript: 'self.on("click", function(node, data) {' + 112 | ' var table = [];' + 113 | ' for (var i in node.rows) {' + 114 | ' var tr = node.rows[i];' + 115 | ' var row = [];' + 116 | ' for (var j in tr.cells) {' + 117 | ' var td = tr.cells[j];' + 118 | ' var attr = td.attributes;' + 119 | ' if (attr) {' + 120 | ' var rowspan = attr.getNamedItem("rowspan");' + 121 | ' rowspan = rowspan ? rowspan.value : undefined;' + 122 | ' var colspan = attr.getNamedItem("colspan");' + 123 | ' colspan = colspan ? colspan.value : undefined;' + 124 | ' row.push([td.textContent, rowspan, colspan]);' + 125 | ' }' + 126 | ' }' + 127 | ' table.push(row);' + 128 | ' }' + 129 | ' self.postMessage({ table: table });' + 130 | '});' , 131 | onMessage: function(message) { 132 | CopyAsOrg.singleLineTable(message.table); 133 | } 134 | }); 135 | 136 | // context menu actions for page itself 137 | var copyCurrentPageAsOrgMenuItem = SDK.ContextMenu.Item({ 138 | label: "[[URL][Page Title]]", 139 | data: "copyCurrentPageAsOrg", 140 | parentMenu: contextMenu, 141 | context: anyContext, 142 | // TODO: use contentScriptFile 143 | contentScript: 'self.on("click", function(node, data) {' + 144 | ' self.postMessage({ url: window.location.href, title: document.title });' + 145 | '});', 146 | onMessage: function(message) { 147 | CopyAsOrg.link(message.url, message.title); 148 | } 149 | }); 150 | -------------------------------------------------------------------------------- /lib/table.js: -------------------------------------------------------------------------------- 1 | exports.preprocessTableList = function (tableList){ 2 | /* 3 | a grid is ["TextContent", "rowspan", "colspan"] 4 | input: [[["T 1",null,null],["T 2",null,null],["T 3",null,null],["T 4",null,null]], 5 | [["2,2","2","2"],["3,2","3","2"]], 6 | [], 7 | [["colspan=2",null,"2"]], 8 | [["A\n(L2)\n(L3)",null,"2"],["K\n(L2)",null,null],["B","2",null]], 9 | [["X",null,null],["Y",null,null],["Z",null,null]],[],[],[]]; 10 | final: [["T 1", "T 2", "T 3", "T 4"], 11 | ["2,2", "", "3,2", ""], 12 | ["", "", "", ""], 13 | ["colspan=2", "", "", ""], 14 | ["A\n(L2)\n(L3)", "", "K\n(L2)", "B"], 15 | ["X", "Y", "Z", ""]] 16 | lastFinal: [["T 1", "T 2", "T 3", "T 4"], 17 | ["2,2", "", "3,2", ""], 18 | ["", "", "", ""], 19 | ["colspan=2", "", "", ""], 20 | ["A", "", "K", "B"], 21 | ["(L2)", "", "(L2)", ""], 22 | ["(L3)", "", "", ""], 23 | ["X", "Y", "Z", ""]] 24 | */ 25 | 26 | // Remove /^\n+/ in inputed tableList cells. 27 | var table = tableList.map(function(row){ 28 | return row.map(function(cell) { 29 | return [cell[0].replace(/^\n+/, ""), cell[1], cell[2]]; 30 | }); 31 | }); 32 | 33 | // Remove empty arrays at tail 34 | while(table[table.length - 1].length == 0) { 35 | table.pop(); 36 | } 37 | // Deal with rowspan and colspan 38 | var rowspan, colspan; 39 | 40 | // Get the width of table (the totalamount of fields) 41 | var tableWidth = table[0].map(function(x){ 42 | rowspan = parseInt(x[2]); 43 | return rowspan === rowspan ? rowspan : 1; 44 | }).reduce(function(total, x) { return total + x;}, 0); 45 | 46 | // var "record" save if a position in "final" has been used/processed. 47 | // initiallize record & final 48 | var row, col, r, c; 49 | var record = {}; // init: {0:{0:false, 1:false, 2:false}, 1:{...}} 50 | var final = []; // init: [["", "", ""], ["", "", ""]] 51 | var i, j; 52 | for (i = 0; i < table.length; i++) { 53 | record[i] = {}; 54 | final[i] = []; 55 | for (j = 0; j < tableWidth; j++) { 56 | record[i][j] = false; // haven't be used. 57 | final[i][j] = "--"; // useless (for test purpose) 58 | } 59 | } 60 | 61 | // make "final" 62 | for (row = 0; row < table.length; row++){ 63 | for (col = 0; col < table[row].length; col++){ 64 | var item = table[row][col]; 65 | rowspan = parseInt(item[1]); 66 | rowspan = rowspan === rowspan ? rowspan : 1; 67 | colspan = parseInt(item[2]); 68 | colspan = colspan === colspan ? colspan : 1; 69 | c = 0; 70 | // c, r is to adjust column begin position 71 | while (record[row][col + c]) { c++; }; 72 | 73 | // rowspan & colspan 74 | if (final[row][col + c] != undefined ) { // if NOT out of array 75 | for (var R = 0; R < rowspan; R++) { 76 | for (var C = 0; C < colspan; C++) { 77 | if (R == 0 && C == 0) { 78 | final[row][col + c] = item[0]; 79 | } else { 80 | final[row + R][col + c + C] = ""; 81 | } 82 | record[row + R][col + c + C] = true; 83 | } 84 | } 85 | } 86 | } 87 | } 88 | 89 | // [0, 0, 0, 0, 2, 0] 90 | var maxNewlinesAmountAtRow = final.map( 91 | function(row){ 92 | return Math.max.apply( 93 | undefined, 94 | row.map( 95 | function(cellString) { 96 | // May be null (when nothing matched) 97 | var matchedNewlines = cellString.match(/\n/g); 98 | return matchedNewlines ? matchedNewlines.length: 0; 99 | }) 100 | ); 101 | } 102 | ); 103 | 104 | 105 | // Deal with newlines...... 106 | var lastFinal = []; 107 | for (r = 0; r < final.length; r++) { 108 | if (maxNewlinesAmountAtRow[r] > 0) { 109 | 110 | // final[r] = ["(L1)\n(L2)", "", "(L1)"] 111 | // splitedCell = ["(L1)"] ---> ["(L1)", ""] 112 | // cellsList = [["(L1)", "(L2)"], ["", ""], ["(L1)", ""]] 113 | var cellsList = final[r].map( 114 | function(cell){ 115 | var splitedCell = cell.split("\n"); 116 | for (var p = splitedCell.length; p < maxNewlinesAmountAtRow[r]; p++){ 117 | splitedCell.push(""); // fill splitedCell with empty string "" 118 | } 119 | return splitedCell; 120 | }); 121 | 122 | // Rotate: [["(L1)", "(L2)"], ["", ""], ["(L1)", ""]] 123 | // => [["(L1)", "", "(L1)"], ["(L2)", "", ""]] 124 | var rotatedCellList = []; 125 | for (i = 0; i < cellsList[0].length; i++) { 126 | var line=[]; 127 | for (j = 0; j < cellsList.length; j++) { 128 | line.push(cellsList[j][i]); 129 | } 130 | rotatedCellList.push(line); 131 | } 132 | lastFinal = lastFinal.concat(rotatedCellList); // Why Array.concat() is not destructive 133 | } else { 134 | lastFinal.push(final[r]); 135 | } 136 | } 137 | // remove /^ +/ in each cell. 138 | return lastFinal.map(function(row){ 139 | return row.map(function(cell) { 140 | return cell.replace(/^ +/, ""); 141 | }); 142 | }); 143 | }; 144 | 145 | function getStringWidth(str){ 146 | /* Get string's "real" width. 147 | http://www.rikai.com/library/kanjitables/kanji_codes.unicode.shtml 148 | CJK characters will be replaced with "xx", and be counted as 2-width character. 149 | */ 150 | var string = str; 151 | return string.replace(/[\u4e00-\u9faf\u3000-\u30ff\uff00-\uff60\uffe0-\uffe6]/g, "xx").length; 152 | }; 153 | 154 | function fillWithSpaces(str, toWidth){ 155 | // Fill string with spaces to a specific width. 156 | var stringWidth = getStringWidth(str); 157 | return str + Array((toWidth - stringWidth) + 1).join(" "); 158 | }; 159 | 160 | exports.singleLineTable = function (preprocessedTable){ 161 | var table = preprocessedTable; 162 | var rowAmount = table.length; 163 | var colAmount = table[0].length; 164 | var fieldWidths = []; 165 | var row, col; 166 | // Get all columns' (fields') widths as a list "fieldWidths". 167 | for (col = 0; col < colAmount; col++) { 168 | var _lens = table.map(function(r){return getStringWidth(r[col]);}); 169 | fieldWidths.push(Math.max.apply(null, _lens)); 170 | } 171 | // Use fillWithSpaces() 172 | for (row = 0; row < rowAmount; row++) { 173 | for (col = 0; col < colAmount; col++) { 174 | table[row][col] = fillWithSpaces(table[row][col], fieldWidths[col]); 175 | } 176 | } 177 | // Format 178 | var splitLine = fieldWidths.map(function(w){return Array(w + 1).join('-');}).join("-+-"); 179 | splitLine = "|-" + splitLine + "-|\n"; 180 | 181 | var finale = ""; 182 | finale += "| " + table[0].join(" | ") + " |\n"; 183 | finale += splitLine; 184 | for (row = 1; row < rowAmount; row++) { 185 | finale += "| " + table[row].join(" | ") + " |\n"; 186 | } 187 | return finale; 188 | }; 189 | --------------------------------------------------------------------------------