Commit e787274e31b573fea422c58d226a4510109917c8
moved plugin files to own directory
dust committed on 3/30/2016, 6:35:24 AMParent: 09f12de5622b73e85b6a4ea7cf82783ac81dd05b
Files changed
capsule.html | deleted |
chromium/capsule.html | added |
chromium/capsule.js | added |
chromium/icon.png | added |
chromium/manifest.json | added |
chromium/serializeSelectedHTML.js | added |
capsule.js | deleted |
icon.png | deleted |
manifest.json | deleted |
serializeSelectedHTML.js | deleted |
capsule.html | ||
---|---|---|
@@ -1,112 +1,0 @@ | ||
1 | - | |
2 | -<html> | |
3 | - <head> | |
4 | - <title>Capsule</title> | |
5 | - <style> | |
6 | - body { | |
7 | - font-family: "Segoe UI", "Lucida Grande", Tahoma, sans-serif; | |
8 | - font-size: 100%; | |
9 | - width: 400px; | |
10 | - height: 600px; | |
11 | - } | |
12 | - #status { | |
13 | - /* avoid an excessively wide status text */ | |
14 | - white-space: pre; | |
15 | - text-overflow: ellipsis; | |
16 | - overflow: hidden; | |
17 | - max-width: 400px; | |
18 | - } | |
19 | - #capsule-menu { | |
20 | - z-index: 999; | |
21 | - position: fixed; | |
22 | - top: 0; | |
23 | - right: 0; | |
24 | - width: 400px; | |
25 | - height: 600px; | |
26 | - background: #fff; | |
27 | - /* border-width: 2px; */ | |
28 | - border-color: cornsilk !important; | |
29 | - border-radius: 5px; | |
30 | - } | |
31 | - | |
32 | - #capsule-selected-content { | |
33 | - position: relative; | |
34 | - width: 100%; | |
35 | - /* overflow-wrap: break-word; */ | |
36 | - overflow: scroll; | |
37 | - background-color: inherit; | |
38 | - max-height: 400px; | |
39 | - height: 40%; | |
40 | - } | |
41 | - | |
42 | - #capsule-selected-content {} | |
43 | - | |
44 | - #capsule-hrule { | |
45 | - background-color: #EEE; | |
46 | - color: #EEE; | |
47 | - height:1px; | |
48 | - border:none; | |
49 | - } | |
50 | - | |
51 | - #capsule-title { | |
52 | - color: #EEE; | |
53 | - text-align: center; | |
54 | - background: gray; | |
55 | - border-width: 2px; | |
56 | - border-color: black; | |
57 | - border-radius: 2px; | |
58 | - padding: 0.25rem; | |
59 | - border-left: 0.25rem; | |
60 | - border-right: 0.25rem; | |
61 | - } | |
62 | - | |
63 | - #capsule-comment-field { | |
64 | - background-color: steelblue; | |
65 | - border-width: thick; | |
66 | - border-color: white; | |
67 | - border-radius: 2px; | |
68 | - min-height: 4em; | |
69 | - overflow-y: visible; | |
70 | - } | |
71 | - | |
72 | - #capsule-controls { | |
73 | - display: block; | |
74 | - text-align: center; | |
75 | - padding: 0.5rem; | |
76 | - } | |
77 | - | |
78 | - #capsule-button { | |
79 | - background-color: darkslategrey; | |
80 | - color: #EEE; | |
81 | - border: 0; | |
82 | - border-radius: 5px; | |
83 | - padding-top: 0.25rem; | |
84 | - padding-bottom: 0.25rem; | |
85 | - } | |
86 | - </style> | |
87 | - | |
88 | - <!-- | |
89 | - - JavaScript and HTML must be in separate files: see our Content Security | |
90 | - - Policy documentation[1] for details and explanation. | |
91 | - - | |
92 | - - [1]: https://developer.chrome.com/extensions/contentSecurityPolicy | |
93 | - --> | |
94 | - <script src="capsule.js"></script> | |
95 | - </head> | |
96 | - <body> | |
97 | - <div id="capsule-menu"> | |
98 | - <div id="capsule-title">Capsule</div> | |
99 | - <div id="capsule-selected-content"> | |
100 | - no selected HTML could be parsed :( | |
101 | - </div> | |
102 | - <hr id="capsule-hrule" /> | |
103 | - <div id="capsule-comment-field"> | |
104 | - Comments go here | |
105 | - </div> | |
106 | - <div id="capsule-controls"> | |
107 | - <button id="capsule-send">Send</button> | |
108 | - </div> | |
109 | - </div> | |
110 | - </body> | |
111 | -</html> | |
112 | - |
chromium/capsule.html | ||
---|---|---|
@@ -1,0 +1,112 @@ | ||
1 | + | |
2 | +<html> | |
3 | + <head> | |
4 | + <title>Capsule</title> | |
5 | + <style> | |
6 | + body { | |
7 | + font-family: "Segoe UI", "Lucida Grande", Tahoma, sans-serif; | |
8 | + font-size: 100%; | |
9 | + width: 400px; | |
10 | + height: 600px; | |
11 | + } | |
12 | + #status { | |
13 | + /* avoid an excessively wide status text */ | |
14 | + white-space: pre; | |
15 | + text-overflow: ellipsis; | |
16 | + overflow: hidden; | |
17 | + max-width: 400px; | |
18 | + } | |
19 | + #capsule-menu { | |
20 | + z-index: 999; | |
21 | + position: fixed; | |
22 | + top: 0; | |
23 | + right: 0; | |
24 | + width: 400px; | |
25 | + height: 600px; | |
26 | + background: #fff; | |
27 | + /* border-width: 2px; */ | |
28 | + border-color: cornsilk !important; | |
29 | + border-radius: 5px; | |
30 | + } | |
31 | + | |
32 | + #capsule-selected-content { | |
33 | + position: relative; | |
34 | + width: 100%; | |
35 | + /* overflow-wrap: break-word; */ | |
36 | + overflow: scroll; | |
37 | + background-color: inherit; | |
38 | + max-height: 400px; | |
39 | + height: 40%; | |
40 | + } | |
41 | + | |
42 | + #capsule-selected-content {} | |
43 | + | |
44 | + #capsule-hrule { | |
45 | + background-color: #EEE; | |
46 | + color: #EEE; | |
47 | + height:1px; | |
48 | + border:none; | |
49 | + } | |
50 | + | |
51 | + #capsule-title { | |
52 | + color: #EEE; | |
53 | + text-align: center; | |
54 | + background: gray; | |
55 | + border-width: 2px; | |
56 | + border-color: black; | |
57 | + border-radius: 2px; | |
58 | + padding: 0.25rem; | |
59 | + border-left: 0.25rem; | |
60 | + border-right: 0.25rem; | |
61 | + } | |
62 | + | |
63 | + #capsule-comment-field { | |
64 | + background-color: steelblue; | |
65 | + border-width: thick; | |
66 | + border-color: white; | |
67 | + border-radius: 2px; | |
68 | + min-height: 4em; | |
69 | + overflow-y: visible; | |
70 | + } | |
71 | + | |
72 | + #capsule-controls { | |
73 | + display: block; | |
74 | + text-align: center; | |
75 | + padding: 0.5rem; | |
76 | + } | |
77 | + | |
78 | + #capsule-button { | |
79 | + background-color: darkslategrey; | |
80 | + color: #EEE; | |
81 | + border: 0; | |
82 | + border-radius: 5px; | |
83 | + padding-top: 0.25rem; | |
84 | + padding-bottom: 0.25rem; | |
85 | + } | |
86 | + </style> | |
87 | + | |
88 | + <!-- | |
89 | + - JavaScript and HTML must be in separate files: see our Content Security | |
90 | + - Policy documentation[1] for details and explanation. | |
91 | + - | |
92 | + - [1]: https://developer.chrome.com/extensions/contentSecurityPolicy | |
93 | + --> | |
94 | + <script src="capsule.js"></script> | |
95 | + </head> | |
96 | + <body> | |
97 | + <div id="capsule-menu"> | |
98 | + <div id="capsule-title">Capsule</div> | |
99 | + <div id="capsule-selected-content"> | |
100 | + no selected HTML could be parsed :( | |
101 | + </div> | |
102 | + <hr id="capsule-hrule" /> | |
103 | + <div id="capsule-comment-field"> | |
104 | + Comments go here | |
105 | + </div> | |
106 | + <div id="capsule-controls"> | |
107 | + <button id="capsule-send">Send</button> | |
108 | + </div> | |
109 | + </div> | |
110 | + </body> | |
111 | +</html> | |
112 | + |
chromium/capsule.js | ||
---|---|---|
@@ -1,0 +1,56 @@ | ||
1 | +var getSelectedHTML = function() { | |
2 | + chrome.tabs.query({active: true}, function(tabs) { | |
3 | + var tab = tabs[0] | |
4 | + | |
5 | + chrome.tabs.executeScript( | |
6 | + { | |
7 | + file: './serializeSelectedHTML.js', | |
8 | + runAt: 'document_end' | |
9 | + }, | |
10 | + renderSelectedHTML | |
11 | + ) | |
12 | + }) | |
13 | +} | |
14 | + | |
15 | +var renderSelectedHTML = function(selection) { | |
16 | + const parsedSelection = JSON.parse(selection) | |
17 | + | |
18 | + var modify = [] | |
19 | + | |
20 | + const selectedBox = document.getElementById('capsule-selected-content') | |
21 | + | |
22 | + if (parsedSelection.html) { | |
23 | + selectedBox.innerHTML = parsedSelection.html | |
24 | + } | |
25 | +} | |
26 | + | |
27 | +var sendHTML = function(htmlString) { | |
28 | + chrome.tabs.getSelected(function(selectedTab) { | |
29 | + let serialisedURI = encodeURI('ssb-capsule://transmit?body=' | |
30 | + .concat(htmlString) | |
31 | + .concat('&src=').concat(selectedTab.url)) | |
32 | + | |
33 | + const serialiserTabProps = { | |
34 | + url: serialisedURI, | |
35 | + active: true | |
36 | + } | |
37 | + | |
38 | + console.log('launching a capsule...') | |
39 | + chrome.tabs.create(serialiserTabProps, function(URITab) { | |
40 | + // TODO this doesn't close the new tab | |
41 | + chrome.tabs.remove(URITab.id) | |
42 | + }) | |
43 | + }) | |
44 | +} | |
45 | + | |
46 | +var hookToElements = function() { | |
47 | + const sendButton = document.getElementById('capsule-send') | |
48 | + sendButton.addEventListener('click', function() { | |
49 | + const selectedBox = document.getElementById('capsule-selected-content') | |
50 | + | |
51 | + sendHTML(selectedBox.innerHTML) | |
52 | + }) | |
53 | +} | |
54 | + | |
55 | +document.addEventListener('DOMContentLoaded', getSelectedHTML) | |
56 | +document.addEventListener('DOMContentLoaded', hookToElements) |
chromium/icon.png |
---|
chromium/manifest.json | ||
---|---|---|
@@ -1,0 +1,15 @@ | ||
1 | +{ | |
2 | + "manifest_version": 2, | |
3 | + | |
4 | + "name": "Capsule (SSB)", | |
5 | + "description": "This extension allows publishing of HTML snippets to the SSB galaxy.", | |
6 | + "version": "1.0", | |
7 | + | |
8 | + "browser_action": { | |
9 | + "default_icon": "icon.png", | |
10 | + "default_popup": "capsule.html" | |
11 | + }, | |
12 | + "permissions": [ | |
13 | + "activeTab" | |
14 | + ] | |
15 | +} |
chromium/serializeSelectedHTML.js | ||
---|---|---|
@@ -1,0 +1,288 @@ | ||
1 | +/** | |
2 | + * Snapshooter is responsible for returning HTML and computed CSS of all nodes from selected DOM subtree. | |
3 | + * | |
4 | + * @param HTMLElement root Root node for the subtree that will be processed | |
5 | + * @returns {*} object with HTML as a string and CSS as an array of arrays of css properties | |
6 | + */ | |
7 | +function Snapshooter(root) { | |
8 | + "use strict"; | |
9 | + | |
10 | + // list of shorthand properties based on CSSShorthands.in from the Chromium | |
11 | + // code (https://code.google.com/p/chromium/codesearch) | |
12 | + // TODO this list should not be hardcoded here | |
13 | + var shorthandProperties = { | |
14 | + 'animation': 'animation', | |
15 | + 'background': 'background', | |
16 | + 'border': 'border', | |
17 | + 'border-top': 'borderTop', | |
18 | + 'border-right': 'borderRight', | |
19 | + 'border-bottom': 'borderBottom', | |
20 | + 'border-left': 'borderLeft', | |
21 | + 'border-width': 'borderWidth', | |
22 | + 'border-color': 'borderColor', | |
23 | + 'border-style': 'borderStyle', | |
24 | + 'border-radius': 'borderRadius', | |
25 | + 'border-image': 'borderImage', | |
26 | + 'border-spacing': 'borderSpacing', | |
27 | + 'flex': 'flex', | |
28 | + 'flex-flow': 'flexFlow', | |
29 | + 'font': 'font', | |
30 | + 'grid-area': 'gridArea', | |
31 | + 'grid-column': 'gridColumn', | |
32 | + 'grid-row': 'gridRow', | |
33 | + 'list-style': 'listStyle', | |
34 | + 'margin': 'margin', | |
35 | + 'marker': 'marker', | |
36 | + 'outline': 'outline', | |
37 | + 'overflow': 'overflow', | |
38 | + 'padding': 'padding', | |
39 | + 'text-decoration': 'textDecoration', | |
40 | + 'transition': 'transition', | |
41 | + '-webkit-border-after': 'webkitBorderAfter', | |
42 | + '-webkit-border-before': 'webkitBorderBefore', | |
43 | + '-webkit-border-end': 'webkitBorderEnd', | |
44 | + '-webkit-border-start': 'webkitBorderStart', | |
45 | + '-webkit-columns': 'webkitBorderColumns', | |
46 | + '-webkit-column-rule': 'webkitBorderColumnRule', | |
47 | + '-webkit-margin-collapse': 'webkitMarginCollapse', | |
48 | + '-webkit-mask': 'webkitMask', | |
49 | + '-webkit-mask-position': 'webkitMaskPosition', | |
50 | + '-webkit-mask-repeat': 'webkitMaskRepeat', | |
51 | + '-webkit-text-emphasis': 'webkitTextEmphasis', | |
52 | + '-webkit-transition': 'webkitTransition', | |
53 | + '-webkit-transform-origin': 'webkitTransformOrigin' | |
54 | + }, | |
55 | + idCounter = 1; | |
56 | + | |
57 | + /** | |
58 | + * Changes CSSStyleDeclaration to simple Object removing unwanted properties | |
59 | + * ('1','2','parentRule','cssText' etc.) in the process. | |
60 | + * | |
61 | + * @param CSSStyleDeclaration style | |
62 | + * @returns {} | |
63 | + */ | |
64 | + function styleDeclarationToSimpleObject(style) { | |
65 | + var i, l, cssName, camelCaseName, | |
66 | + output = {}; | |
67 | + | |
68 | + for (i = 0, l = style.length; i < l; i++) { | |
69 | + output[style[i]] = style[style[i]]; | |
70 | + } | |
71 | + | |
72 | + // Work around http://crbug.com/313670 (the "content" property is not | |
73 | + // present as a computed style indexed property value). | |
74 | + output.content = fixContentProperty(style.content); | |
75 | + | |
76 | + // Since shorthand properties are not available in the indexed array, copy | |
77 | + // them from named properties | |
78 | + for (cssName in shorthandProperties) { | |
79 | + if (shorthandProperties.hasOwnProperty(cssName)) { | |
80 | + camelCaseName = shorthandProperties[cssName]; | |
81 | + output[cssName] = style[camelCaseName]; | |
82 | + } | |
83 | + } | |
84 | + | |
85 | + return output; | |
86 | + } | |
87 | + | |
88 | + // Partial workaround for http://crbug.com/315028 (single words in the | |
89 | + // "content" property are not wrapped with quotes) | |
90 | + function fixContentProperty(content) { | |
91 | + var values, output, value, i, l; | |
92 | + | |
93 | + output = []; | |
94 | + | |
95 | + if (content) { | |
96 | + //content property can take multiple values - we need to split them up | |
97 | + //FIXME this won't work for '\'' | |
98 | + values = content.match(/(?:[^\s']+|'[^']*')+/g); | |
99 | + | |
100 | + for (i = 0, l = values.length; i < l; i++) { | |
101 | + value = values[i]; | |
102 | + | |
103 | + if (value.match(/^(url\()|(attr\()|normal|none|open-quote|close-quote|no-open-quote|no-close-quote|chapter_counter|'/g)) { | |
104 | + output.push(value); | |
105 | + } else { | |
106 | + output.push("'" + value + "'"); | |
107 | + } | |
108 | + } | |
109 | + } | |
110 | + | |
111 | + return output.join(' '); | |
112 | + } | |
113 | + | |
114 | + function createID(node) { | |
115 | + //":snappysnippet_prefix:" is a prefix placeholder | |
116 | + return ':snappysnippet_prefix:' + node.tagName + '_' + idCounter++; | |
117 | + } | |
118 | + | |
119 | + function dumpCSS(node, pseudoElement) { | |
120 | + var styles; | |
121 | + | |
122 | + styles = node.ownerDocument.defaultView.getComputedStyle(node, pseudoElement); | |
123 | + | |
124 | + if (pseudoElement) { | |
125 | + //if we are dealing with pseudoelement, check if 'content' property isn't empty | |
126 | + //if it is, then we can ignore the whole element | |
127 | + if (!styles.getPropertyValue('content')) { | |
128 | + return null; | |
129 | + } | |
130 | + } | |
131 | + | |
132 | + return styleDeclarationToSimpleObject(styles); | |
133 | + } | |
134 | + | |
135 | + function cssObjectForElement(element, omitPseudoElements) { | |
136 | + return { | |
137 | + id: createID(element), | |
138 | + tagName: element.tagName, | |
139 | + node: dumpCSS(element, null), | |
140 | + before: omitPseudoElements ? null : dumpCSS(element, ':before'), | |
141 | + after: omitPseudoElements ? null : dumpCSS(element, ':after') | |
142 | + }; | |
143 | + } | |
144 | + | |
145 | + function ancestorTagHTML(element, closingTag) { | |
146 | + var i, attr, value, idSeen, | |
147 | + result, attributes; | |
148 | + | |
149 | + if (closingTag) { | |
150 | + return '</' + element.tagName + '>'; | |
151 | + } | |
152 | + | |
153 | + result = '<' + element.tagName; | |
154 | + attributes = element.attributes; | |
155 | + | |
156 | + for (i = 0; i < attributes.length; ++i) { | |
157 | + attr = attributes[i]; | |
158 | + | |
159 | + if (attr.name.toLowerCase() === 'id') { | |
160 | + value = createID(element); | |
161 | + idSeen = true; | |
162 | + } else { | |
163 | + value = attr.value; | |
164 | + } | |
165 | + | |
166 | + result += ' ' + attributes[i].name + '="' + value + '"'; | |
167 | + } | |
168 | + | |
169 | + if (!idSeen) { | |
170 | + result += ' id="' + createID(element) + '"'; | |
171 | + } | |
172 | + | |
173 | + result += '>'; | |
174 | + | |
175 | + return result; | |
176 | + } | |
177 | + | |
178 | + /** | |
179 | + * Replaces all relative URLs (in images, links etc.) with absolute URLs | |
180 | + * @param element | |
181 | + */ | |
182 | + function relativeURLsToAbsoluteURLs(element) { | |
183 | + switch (element.nodeName) { | |
184 | + case 'A': | |
185 | + case 'AREA': | |
186 | + case 'LINK': | |
187 | + case 'BASE': | |
188 | + if (element.hasAttribute('href')) { | |
189 | + element.setAttribute('href', element.href); | |
190 | + } | |
191 | + break; | |
192 | + case 'IMG': | |
193 | + case 'IFRAME': | |
194 | + case 'INPUT': | |
195 | + case 'FRAME': | |
196 | + case 'SCRIPT': | |
197 | + if (element.hasAttribute('src')) { | |
198 | + element.setAttribute('src', element.src); | |
199 | + } | |
200 | + break; | |
201 | + case 'FORM': | |
202 | + if (element.hasAttribute('action')) { | |
203 | + element.setAttribute('action', element.action); | |
204 | + } | |
205 | + break; | |
206 | + } | |
207 | + } | |
208 | + | |
209 | + function init() { | |
210 | + var css = [], | |
211 | + ancestorCss = [], | |
212 | + descendants, | |
213 | + descendant, | |
214 | + htmlSegments, | |
215 | + leadingAncestorHtml, | |
216 | + trailingAncestorHtml, | |
217 | + reverseAncestors = [], | |
218 | + i, l, | |
219 | + parent, | |
220 | + clone; | |
221 | + | |
222 | + descendants = root.getElementsByTagName('*'); | |
223 | + | |
224 | + parent = root.parentElement; | |
225 | + while (parent && parent !== document.body) { | |
226 | + reverseAncestors.push(parent); | |
227 | + parent = parent.parentElement; | |
228 | + } | |
229 | + | |
230 | + // First we go through all nodes and dump all CSS | |
231 | + css.push(cssObjectForElement(root)); | |
232 | + | |
233 | + for (i = 0, l = descendants.length; i < l; i++) { | |
234 | + css.push(cssObjectForElement(descendants[i])); | |
235 | + } | |
236 | + | |
237 | + for (i = reverseAncestors.length - 1; i >= 0; i--) { | |
238 | + ancestorCss.push(cssObjectForElement(reverseAncestors[i], true)); | |
239 | + } | |
240 | + | |
241 | + // Next we dump all HTML and update IDs | |
242 | + // Since we don't want to touch original DOM and we want to change IDs, we clone the original DOM subtree | |
243 | + clone = root.cloneNode(true); | |
244 | + descendants = clone.getElementsByTagName('*'); | |
245 | + idCounter = 1; | |
246 | + | |
247 | + clone.setAttribute('id', createID(clone)); | |
248 | + | |
249 | + for (i = 0, l = descendants.length; i < l; i++) { | |
250 | + descendant = descendants[i]; | |
251 | + descendant.setAttribute('id', createID(descendant)); | |
252 | + relativeURLsToAbsoluteURLs(descendant); | |
253 | + } | |
254 | + | |
255 | + // Build leading and trailing HTML for ancestors | |
256 | + htmlSegments = []; | |
257 | + for (i = reverseAncestors.length - 1; i >= 0; i--) { | |
258 | + htmlSegments.push(ancestorTagHTML(reverseAncestors[i])); | |
259 | + } | |
260 | + leadingAncestorHtml = htmlSegments.join(''); | |
261 | + | |
262 | + htmlSegments = []; | |
263 | + for (i = 0, l = reverseAncestors.length; i < l; i++) { | |
264 | + htmlSegments.push(ancestorTagHTML(reverseAncestors[i], true)); | |
265 | + } | |
266 | + trailingAncestorHtml = htmlSegments.join(''); | |
267 | + | |
268 | + return JSON.stringify({ | |
269 | + html: clone.outerHTML, | |
270 | + leadingAncestorHtml: leadingAncestorHtml, | |
271 | + trailingAncestorHtml: trailingAncestorHtml, | |
272 | + css: css, | |
273 | + ancestorCss: ancestorCss | |
274 | + }); | |
275 | + } | |
276 | + | |
277 | + return init(); | |
278 | +} | |
279 | + | |
280 | +var selectionDiv = document.createElement('div') | |
281 | +var selection = window.getSelection().getRangeAt(0) | |
282 | +var startNode = selection.startContainer.parentNode.cloneNode(true) | |
283 | +var endNode = selection.endContainer.parentNode.cloneNode(true) | |
284 | + | |
285 | +selectionDiv.appendChild(startNode) | |
286 | +selectionDiv.appendChild(endNode) | |
287 | + | |
288 | +Snapshooter(selectionDiv) |
capsule.js | ||
---|---|---|
@@ -1,57 +1,0 @@ | ||
1 | -var getSelectedHTML = function() { | |
2 | - chrome.tabs.query({active: true}, function(tabs) { | |
3 | - var tab = tabs[0] | |
4 | - | |
5 | - chrome.tabs.executeScript( | |
6 | - { | |
7 | - file: './serializeSelectedHTML.js', | |
8 | - runAt: 'document_end' | |
9 | - }, | |
10 | - renderSelectedHTML | |
11 | - ) | |
12 | - }) | |
13 | -} | |
14 | - | |
15 | -var renderSelectedHTML = function(selection) { | |
16 | - const parsedSelection = JSON.parse(selection) | |
17 | - | |
18 | - var modify = [] | |
19 | - | |
20 | - const selectedBox = document.getElementById('capsule-selected-content') | |
21 | - | |
22 | - if (parsedSelection.html) { | |
23 | - selectedBox.innerHTML = parsedSelection.html | |
24 | - } | |
25 | -} | |
26 | - | |
27 | -var sendHTML = function(htmlString) { | |
28 | - chrome.tabs.getSelected(function(selectedTab) { | |
29 | - let serialisedURI = 'ssb-capsule://?body=' | |
30 | - .concat(htmlString) | |
31 | - .concat('&src=') | |
32 | - .concat(selectedTab.url) | |
33 | - | |
34 | - const serialiserTabProps = { | |
35 | - url: serialisedURI, | |
36 | - active: true | |
37 | - } | |
38 | - | |
39 | - console.log('launching a capsule...') | |
40 | - chrome.tabs.create(serialiserTabProps, function(URITab) { | |
41 | - // TODO this doesn't close the new tab | |
42 | - chrome.tabs.remove(URITab.id) | |
43 | - }) | |
44 | - }) | |
45 | -} | |
46 | - | |
47 | -var hookToElements = function() { | |
48 | - const sendButton = document.getElementById('capsule-send') | |
49 | - sendButton.addEventListener('click', function() { | |
50 | - const selectedBox = document.getElementById('capsule-selected-content') | |
51 | - | |
52 | - sendHTML(selectedBox.innerHTML) | |
53 | - }) | |
54 | -} | |
55 | - | |
56 | -document.addEventListener('DOMContentLoaded', getSelectedHTML) | |
57 | -document.addEventListener('DOMContentLoaded', hookToElements) |
icon.png |
---|
manifest.json | ||
---|---|---|
@@ -1,15 +1,0 @@ | ||
1 | -{ | |
2 | - "manifest_version": 2, | |
3 | - | |
4 | - "name": "Capsule (SSB)", | |
5 | - "description": "This extension allows publishing of HTML snippets to the SSB galaxy.", | |
6 | - "version": "1.0", | |
7 | - | |
8 | - "browser_action": { | |
9 | - "default_icon": "icon.png", | |
10 | - "default_popup": "capsule.html" | |
11 | - }, | |
12 | - "permissions": [ | |
13 | - "activeTab" | |
14 | - ] | |
15 | -} |
serializeSelectedHTML.js | ||
---|---|---|
@@ -1,288 +1,0 @@ | ||
1 | -/** | |
2 | - * Snapshooter is responsible for returning HTML and computed CSS of all nodes from selected DOM subtree. | |
3 | - * | |
4 | - * @param HTMLElement root Root node for the subtree that will be processed | |
5 | - * @returns {*} object with HTML as a string and CSS as an array of arrays of css properties | |
6 | - */ | |
7 | -function Snapshooter(root) { | |
8 | - "use strict"; | |
9 | - | |
10 | - // list of shorthand properties based on CSSShorthands.in from the Chromium | |
11 | - // code (https://code.google.com/p/chromium/codesearch) | |
12 | - // TODO this list should not be hardcoded here | |
13 | - var shorthandProperties = { | |
14 | - 'animation': 'animation', | |
15 | - 'background': 'background', | |
16 | - 'border': 'border', | |
17 | - 'border-top': 'borderTop', | |
18 | - 'border-right': 'borderRight', | |
19 | - 'border-bottom': 'borderBottom', | |
20 | - 'border-left': 'borderLeft', | |
21 | - 'border-width': 'borderWidth', | |
22 | - 'border-color': 'borderColor', | |
23 | - 'border-style': 'borderStyle', | |
24 | - 'border-radius': 'borderRadius', | |
25 | - 'border-image': 'borderImage', | |
26 | - 'border-spacing': 'borderSpacing', | |
27 | - 'flex': 'flex', | |
28 | - 'flex-flow': 'flexFlow', | |
29 | - 'font': 'font', | |
30 | - 'grid-area': 'gridArea', | |
31 | - 'grid-column': 'gridColumn', | |
32 | - 'grid-row': 'gridRow', | |
33 | - 'list-style': 'listStyle', | |
34 | - 'margin': 'margin', | |
35 | - 'marker': 'marker', | |
36 | - 'outline': 'outline', | |
37 | - 'overflow': 'overflow', | |
38 | - 'padding': 'padding', | |
39 | - 'text-decoration': 'textDecoration', | |
40 | - 'transition': 'transition', | |
41 | - '-webkit-border-after': 'webkitBorderAfter', | |
42 | - '-webkit-border-before': 'webkitBorderBefore', | |
43 | - '-webkit-border-end': 'webkitBorderEnd', | |
44 | - '-webkit-border-start': 'webkitBorderStart', | |
45 | - '-webkit-columns': 'webkitBorderColumns', | |
46 | - '-webkit-column-rule': 'webkitBorderColumnRule', | |
47 | - '-webkit-margin-collapse': 'webkitMarginCollapse', | |
48 | - '-webkit-mask': 'webkitMask', | |
49 | - '-webkit-mask-position': 'webkitMaskPosition', | |
50 | - '-webkit-mask-repeat': 'webkitMaskRepeat', | |
51 | - '-webkit-text-emphasis': 'webkitTextEmphasis', | |
52 | - '-webkit-transition': 'webkitTransition', | |
53 | - '-webkit-transform-origin': 'webkitTransformOrigin' | |
54 | - }, | |
55 | - idCounter = 1; | |
56 | - | |
57 | - /** | |
58 | - * Changes CSSStyleDeclaration to simple Object removing unwanted properties | |
59 | - * ('1','2','parentRule','cssText' etc.) in the process. | |
60 | - * | |
61 | - * @param CSSStyleDeclaration style | |
62 | - * @returns {} | |
63 | - */ | |
64 | - function styleDeclarationToSimpleObject(style) { | |
65 | - var i, l, cssName, camelCaseName, | |
66 | - output = {}; | |
67 | - | |
68 | - for (i = 0, l = style.length; i < l; i++) { | |
69 | - output[style[i]] = style[style[i]]; | |
70 | - } | |
71 | - | |
72 | - // Work around http://crbug.com/313670 (the "content" property is not | |
73 | - // present as a computed style indexed property value). | |
74 | - output.content = fixContentProperty(style.content); | |
75 | - | |
76 | - // Since shorthand properties are not available in the indexed array, copy | |
77 | - // them from named properties | |
78 | - for (cssName in shorthandProperties) { | |
79 | - if (shorthandProperties.hasOwnProperty(cssName)) { | |
80 | - camelCaseName = shorthandProperties[cssName]; | |
81 | - output[cssName] = style[camelCaseName]; | |
82 | - } | |
83 | - } | |
84 | - | |
85 | - return output; | |
86 | - } | |
87 | - | |
88 | - // Partial workaround for http://crbug.com/315028 (single words in the | |
89 | - // "content" property are not wrapped with quotes) | |
90 | - function fixContentProperty(content) { | |
91 | - var values, output, value, i, l; | |
92 | - | |
93 | - output = []; | |
94 | - | |
95 | - if (content) { | |
96 | - //content property can take multiple values - we need to split them up | |
97 | - //FIXME this won't work for '\'' | |
98 | - values = content.match(/(?:[^\s']+|'[^']*')+/g); | |
99 | - | |
100 | - for (i = 0, l = values.length; i < l; i++) { | |
101 | - value = values[i]; | |
102 | - | |
103 | - if (value.match(/^(url\()|(attr\()|normal|none|open-quote|close-quote|no-open-quote|no-close-quote|chapter_counter|'/g)) { | |
104 | - output.push(value); | |
105 | - } else { | |
106 | - output.push("'" + value + "'"); | |
107 | - } | |
108 | - } | |
109 | - } | |
110 | - | |
111 | - return output.join(' '); | |
112 | - } | |
113 | - | |
114 | - function createID(node) { | |
115 | - //":snappysnippet_prefix:" is a prefix placeholder | |
116 | - return ':snappysnippet_prefix:' + node.tagName + '_' + idCounter++; | |
117 | - } | |
118 | - | |
119 | - function dumpCSS(node, pseudoElement) { | |
120 | - var styles; | |
121 | - | |
122 | - styles = node.ownerDocument.defaultView.getComputedStyle(node, pseudoElement); | |
123 | - | |
124 | - if (pseudoElement) { | |
125 | - //if we are dealing with pseudoelement, check if 'content' property isn't empty | |
126 | - //if it is, then we can ignore the whole element | |
127 | - if (!styles.getPropertyValue('content')) { | |
128 | - return null; | |
129 | - } | |
130 | - } | |
131 | - | |
132 | - return styleDeclarationToSimpleObject(styles); | |
133 | - } | |
134 | - | |
135 | - function cssObjectForElement(element, omitPseudoElements) { | |
136 | - return { | |
137 | - id: createID(element), | |
138 | - tagName: element.tagName, | |
139 | - node: dumpCSS(element, null), | |
140 | - before: omitPseudoElements ? null : dumpCSS(element, ':before'), | |
141 | - after: omitPseudoElements ? null : dumpCSS(element, ':after') | |
142 | - }; | |
143 | - } | |
144 | - | |
145 | - function ancestorTagHTML(element, closingTag) { | |
146 | - var i, attr, value, idSeen, | |
147 | - result, attributes; | |
148 | - | |
149 | - if (closingTag) { | |
150 | - return '</' + element.tagName + '>'; | |
151 | - } | |
152 | - | |
153 | - result = '<' + element.tagName; | |
154 | - attributes = element.attributes; | |
155 | - | |
156 | - for (i = 0; i < attributes.length; ++i) { | |
157 | - attr = attributes[i]; | |
158 | - | |
159 | - if (attr.name.toLowerCase() === 'id') { | |
160 | - value = createID(element); | |
161 | - idSeen = true; | |
162 | - } else { | |
163 | - value = attr.value; | |
164 | - } | |
165 | - | |
166 | - result += ' ' + attributes[i].name + '="' + value + '"'; | |
167 | - } | |
168 | - | |
169 | - if (!idSeen) { | |
170 | - result += ' id="' + createID(element) + '"'; | |
171 | - } | |
172 | - | |
173 | - result += '>'; | |
174 | - | |
175 | - return result; | |
176 | - } | |
177 | - | |
178 | - /** | |
179 | - * Replaces all relative URLs (in images, links etc.) with absolute URLs | |
180 | - * @param element | |
181 | - */ | |
182 | - function relativeURLsToAbsoluteURLs(element) { | |
183 | - switch (element.nodeName) { | |
184 | - case 'A': | |
185 | - case 'AREA': | |
186 | - case 'LINK': | |
187 | - case 'BASE': | |
188 | - if (element.hasAttribute('href')) { | |
189 | - element.setAttribute('href', element.href); | |
190 | - } | |
191 | - break; | |
192 | - case 'IMG': | |
193 | - case 'IFRAME': | |
194 | - case 'INPUT': | |
195 | - case 'FRAME': | |
196 | - case 'SCRIPT': | |
197 | - if (element.hasAttribute('src')) { | |
198 | - element.setAttribute('src', element.src); | |
199 | - } | |
200 | - break; | |
201 | - case 'FORM': | |
202 | - if (element.hasAttribute('action')) { | |
203 | - element.setAttribute('action', element.action); | |
204 | - } | |
205 | - break; | |
206 | - } | |
207 | - } | |
208 | - | |
209 | - function init() { | |
210 | - var css = [], | |
211 | - ancestorCss = [], | |
212 | - descendants, | |
213 | - descendant, | |
214 | - htmlSegments, | |
215 | - leadingAncestorHtml, | |
216 | - trailingAncestorHtml, | |
217 | - reverseAncestors = [], | |
218 | - i, l, | |
219 | - parent, | |
220 | - clone; | |
221 | - | |
222 | - descendants = root.getElementsByTagName('*'); | |
223 | - | |
224 | - parent = root.parentElement; | |
225 | - while (parent && parent !== document.body) { | |
226 | - reverseAncestors.push(parent); | |
227 | - parent = parent.parentElement; | |
228 | - } | |
229 | - | |
230 | - // First we go through all nodes and dump all CSS | |
231 | - css.push(cssObjectForElement(root)); | |
232 | - | |
233 | - for (i = 0, l = descendants.length; i < l; i++) { | |
234 | - css.push(cssObjectForElement(descendants[i])); | |
235 | - } | |
236 | - | |
237 | - for (i = reverseAncestors.length - 1; i >= 0; i--) { | |
238 | - ancestorCss.push(cssObjectForElement(reverseAncestors[i], true)); | |
239 | - } | |
240 | - | |
241 | - // Next we dump all HTML and update IDs | |
242 | - // Since we don't want to touch original DOM and we want to change IDs, we clone the original DOM subtree | |
243 | - clone = root.cloneNode(true); | |
244 | - descendants = clone.getElementsByTagName('*'); | |
245 | - idCounter = 1; | |
246 | - | |
247 | - clone.setAttribute('id', createID(clone)); | |
248 | - | |
249 | - for (i = 0, l = descendants.length; i < l; i++) { | |
250 | - descendant = descendants[i]; | |
251 | - descendant.setAttribute('id', createID(descendant)); | |
252 | - relativeURLsToAbsoluteURLs(descendant); | |
253 | - } | |
254 | - | |
255 | - // Build leading and trailing HTML for ancestors | |
256 | - htmlSegments = []; | |
257 | - for (i = reverseAncestors.length - 1; i >= 0; i--) { | |
258 | - htmlSegments.push(ancestorTagHTML(reverseAncestors[i])); | |
259 | - } | |
260 | - leadingAncestorHtml = htmlSegments.join(''); | |
261 | - | |
262 | - htmlSegments = []; | |
263 | - for (i = 0, l = reverseAncestors.length; i < l; i++) { | |
264 | - htmlSegments.push(ancestorTagHTML(reverseAncestors[i], true)); | |
265 | - } | |
266 | - trailingAncestorHtml = htmlSegments.join(''); | |
267 | - | |
268 | - return JSON.stringify({ | |
269 | - html: clone.outerHTML, | |
270 | - leadingAncestorHtml: leadingAncestorHtml, | |
271 | - trailingAncestorHtml: trailingAncestorHtml, | |
272 | - css: css, | |
273 | - ancestorCss: ancestorCss | |
274 | - }); | |
275 | - } | |
276 | - | |
277 | - return init(); | |
278 | -} | |
279 | - | |
280 | -var selectionDiv = document.createElement('div') | |
281 | -var selection = window.getSelection().getRangeAt(0) | |
282 | -var startNode = selection.startContainer.parentNode.cloneNode(true) | |
283 | -var endNode = selection.endContainer.parentNode.cloneNode(true) | |
284 | - | |
285 | -selectionDiv.appendChild(startNode) | |
286 | -selectionDiv.appendChild(endNode) | |
287 | - | |
288 | -Snapshooter(selectionDiv) |
Built with git-ssb-web