├── 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 |
--------------------------------------------------------------------------------