single-file-core 1.0.64 → 1.0.66

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -21,111 +21,11 @@
21
21
  * Source.
22
22
  */
23
23
 
24
- /* global globalThis, window, document, Node, getComputedStyle, XPathResult */
24
+ /* global globalThis, window, document */
25
25
 
26
- (globalThis => {
26
+ import { appendInfobar, refreshInfobarInfo, extractInfobarData } from "./infobar.js";
27
27
 
28
- const INFOBAR_TAGNAME = "singlefile-infobar";
29
- const LINK_ICON = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABAAgMAAADXB5lNAAABhmlDQ1BJQ0MgcHJvZmlsZQAAKJF9kj1Iw0AYht+mSkUrDnYQcchQnSyIijqWKhbBQmkrtOpgcukfNGlIUlwcBdeCgz+LVQcXZ10dXAVB8AfEydFJ0UVK/C4ptIjx4LiH9+59+e67A4RGhalm1wSgapaRisfEbG5VDLyiDwEAvZiVmKkn0osZeI6ve/j4ehfhWd7n/hz9St5kgE8kjjLdsIg3iGc2LZ3zPnGIlSSF+Jx43KACiR+5Lrv8xrnosMAzQ0YmNU8cIhaLHSx3MCsZKvE0cVhRNcoXsi4rnLc4q5Uaa9XJbxjMaytprtMcQRxLSCAJETJqKKMCCxFaNVJMpGg/5uEfdvxJcsnkKoORYwFVqJAcP/gb/O6tWZiadJOCMaD7xbY/RoHALtCs2/b3sW03TwD/M3Cltf3VBjD3SXq9rYWPgIFt4OK6rcl7wOUOMPSkS4bkSH6aQqEAvJ/RM+WAwVv6EGtu31r7OH0AMtSr5Rvg4BAYK1L2use9ezr79u+ZVv9+AFlNcp0UUpiqAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAB3RJTUUH5AsHAB8H+DhhoQAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAAJUExURQAAAICHi4qKioTuJAkAAAABdFJOUwBA5thmAAAAAWJLR0QCZgt8ZAAAAJJJREFUOI3t070NRCEMA2CnYAOyDyPwpHj/Va7hJ3FzV7zy3ET5JIwoAF6Jk4wzAJAkzxAYG9YRTgB+24wBgKmfrGAKTcEfAY4KRlRoIeBTgKOCERVaCPgU4Khge2GqKOBTgKOCERVaAEC/4PNcnyoSWHpjqkhwKxbcig0Q6AorXYF/+A6eIYD1lVbwG/jdA6/kA2THRAURVubcAAAAAElFTkSuQmCC";
30
- const CLOSE_ICON = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABAAgMAAADXB5lNAAABhmlDQ1BJQ0MgcHJvZmlsZQAAKJF9kj1Iw0AYht+mSkUrDnYQcchQnSyIijqWKhbBQmkrtOpgcukfNGlIUlwcBdeCgz+LVQcXZ10dXAVB8AfEydFJ0UVK/C4ptIjx4LiH9+59+e67A4RGhalm1wSgapaRisfEbG5VDLyiDwEAvZiVmKkn0osZeI6ve/j4ehfhWd7n/hz9St5kgE8kjjLdsIg3iGc2LZ3zPnGIlSSF+Jx43KACiR+5Lrv8xrnosMAzQ0YmNU8cIhaLHSx3MCsZKvE0cVhRNcoXsi4rnLc4q5Uaa9XJbxjMaytprtMcQRxLSCAJETJqKKMCCxFaNVJMpGg/5uEfdvxJcsnkKoORYwFVqJAcP/gb/O6tWZiadJOCMaD7xbY/RoHALtCs2/b3sW03TwD/M3Cltf3VBjD3SXq9rYWPgIFt4OK6rcl7wOUOMPSkS4bkSH6aQqEAvJ/RM+WAwVv6EGtu31r7OH0AMtSr5Rvg4BAYK1L2use9ezr79u+ZVv9+AFlNcp0UUpiqAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAB3RJTUUH5AsHAB8VC4EQ6QAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAAJUExURQAAAICHi4qKioTuJAkAAAABdFJOUwBA5thmAAAAAWJLR0QCZgt8ZAAAAJtJREFUOI3NkrsBgCAMRLFwBPdxBArcfxXFkO8rbKWAAJfHJ9faf9vuYX/749T5NmShm3bEwbe2SxeuM4+2oxDL1cDoKtVUjRy+tH78Cv2CS+wIiQNC1AEhk4AQeUTMWUJMfUJMSEJMSEY8kIx4IONroaYAimNxsXp1PA7PxwfVL8QnowwoVC0lig07wDDVUjAdbAnjwtow/z/bDW7eI4M2KruJAAAAAElFTkSuQmCC";
31
- const IMAGE_ICON = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABABAMAAABYR2ztAAABhmlDQ1BJQ0MgcHJvZmlsZQAAKJF9kj1Iw0AYht+mSkUrDnYQcchQnSyIijqWKhbBQmkrtOpgcukfNGlIUlwcBdeCgz+LVQcXZ10dXAVB8AfEydFJ0UVK/C4ptIjx4LiH9+59+e67A4RGhalm1wSgapaRisfEbG5VDLyiDwEAvZiVmKkn0osZeI6ve/j4ehfhWd7n/hz9St5kgE8kjjLdsIg3iGc2LZ3zPnGIlSSF+Jx43KACiR+5Lrv8xrnosMAzQ0YmNU8cIhaLHSx3MCsZKvE0cVhRNcoXsi4rnLc4q5Uaa9XJbxjMaytprtMcQRxLSCAJETJqKKMCCxFaNVJMpGg/5uEfdvxJcsnkKoORYwFVqJAcP/gb/O6tWZiadJOCMaD7xbY/RoHALtCs2/b3sW03TwD/M3Cltf3VBjD3SXq9rYWPgIFt4OK6rcl7wOUOMPSkS4bkSH6aQqEAvJ/RM+WAwVv6EGtu31r7OH0AMtSr5Rvg4BAYK1L2use9ezr79u+ZVv9+AFlNcp0UUpiqAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAB3RJTUUH5AsHADIRLMaOHwAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAAPUExURQAAAIqKioyNjY2OjvDw8L2y1DEAAAABdFJOUwBA5thmAAAAAWJLR0QB/wIt3gAAAGNJREFUSMdjYCAJsLi4OBCQx6/CBQwIGIDPCBcXAkYQUsACU+AwlBVQHg6Eg5pgZBGOboIJZugDFwRwoJECJCUOhJI1wZwzqmBUwagCuipgIqTABG9h7YIKaKGAURAFEF/6AQAO4HqSoDP8bgAAAABJRU5ErkJggg==";
32
- const SINGLEFILE_COMMENT = "SingleFile";
33
- const SINGLE_FILE_UI_ELEMENT_CLASS = "single-file-ui-element";
34
- const INFOBAR_STYLES = `
35
- .infobar {
36
- background-color: #737373;
37
- color: white;
38
- display: flex;
39
- position: fixed;
40
- top: 16px;
41
- right: 16px;
42
- height: auto;
43
- width: auto;
44
- min-height: 24px;
45
- min-width: 24px;
46
- background-position: center;
47
- background-repeat: no-repeat;
48
- z-index: 2147483647;
49
- margin: 0 0 0 16px;
50
- background-image: url(${IMAGE_ICON});
51
- border-radius: 16px;
52
- user-select: none;
53
- -moz-user-select: none;
54
- opacity: .7;
55
- cursor: pointer;
56
- padding-left: 0;
57
- padding-right: 0;
58
- padding-top: 0;
59
- padding-bottom: 0;
60
- border: 2px solid #eee;
61
- background-size: 70% 70%;
62
- transition: all 250ms;
63
- font-size: 13px;
64
- }
65
- .infobar:hover {
66
- opacity: 1;
67
- }
68
- .infobar-open {
69
- opacity: 1;
70
- background-color: #f9f9f9;
71
- cursor: auto;
72
- color: #2d2d2d;
73
- padding-top: 2px;
74
- padding-bottom: 2px;
75
- border: 2px solid #878787;
76
- background-image: none;
77
- border-radius: 8px;
78
- user-select: initial;
79
- -moz-user-select: initial;
80
- }
81
- .infobar-close-button {
82
- display: none;
83
- opacity: .7;
84
- padding-top: 4px;
85
- padding-left: 8px;
86
- padding-right: 8px;
87
- cursor: pointer;
88
- transition: opacity 250ms;
89
- height: 16px;
90
- }
91
- .infobar-close-button:hover {
92
- opacity: 1;
93
- }
94
- .infobar-content {
95
- display: none;
96
- font-family: Arial;
97
- font-size: 14px;
98
- line-height: 22px;
99
- word-break: break-word;
100
- white-space: pre-wrap;
101
- position: relative;
102
- top: 1px;
103
- text-align: left;
104
- }
105
- .infobar-link {
106
- display: none;
107
- padding-left: 8px;
108
- padding-right: 8px;
109
- line-height: 11px;
110
- cursor: pointer;
111
- user-select: none;
112
- outline: 0;
113
- }
114
- .infobar-link-icon {
115
- padding-top: 4px;
116
- padding-left: 2px;
117
- cursor: pointer;
118
- opacity: .7;
119
- transition: opacity 250ms;
120
- height: 16px;
121
- }
122
- .infobar-link-icon:hover {
123
- opacity: 1;
124
- }
125
- .infobar-open .infobar-close-button, .infobar-open .infobar-content, .infobar-open .infobar-link {
126
- display: inline-block;
127
- }`;
128
- let SHADOW_DOM_SUPPORTED = true;
28
+ (globalThis => {
129
29
 
130
30
  const browser = globalThis.browser;
131
31
 
@@ -137,169 +37,26 @@
137
37
  }
138
38
  }
139
39
  if (globalThis.singlefile) {
140
- globalThis.singlefile.infobar = {
40
+ globalThis.singlefile.infobar = {
141
41
  displayIcon
142
42
  };
143
43
  }
144
44
 
145
45
  async function displayIcon() {
146
- const result = document.evaluate("//comment()", document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
147
- let singleFileComment = result && result.singleNodeValue;
148
- if (singleFileComment && isSingleFileComment(singleFileComment)) {
149
- const info = singleFileComment.textContent.split("\n");
150
- const [, , urlData, ...optionalData] = info;
151
- const urlMatch = urlData.match(/^ url: (.*) ?$/);
152
- const url = urlMatch && urlMatch[1];
153
- if (url) {
154
- let options;
155
- if (browser && browser.runtime && browser.runtime.sendMessage) {
156
- try {
157
- options = await browser.runtime.sendMessage({ method: "tabs.getOptions", url });
158
- } catch (error) {
159
- options = { displayInfobar: true };
160
- }
161
- } else {
162
- options = { displayInfobar: true };
163
- }
164
- if (options.displayInfobar) {
165
- await initInfobar(url, optionalData);
46
+ let options = { displayInfobar: true };
47
+ const infoData = extractInfobarData(document);
48
+ if (infoData && infoData.saveUrl) {
49
+ if (browser && browser.runtime && browser.runtime.sendMessage) {
50
+ try {
51
+ options = await browser.runtime.sendMessage({ method: "tabs.getOptions", url: infoData.saveUrl });
52
+ } catch (error) {
53
+ // ignored
166
54
  }
167
55
  }
168
- }
169
- }
170
-
171
- function isSingleFileComment(node) {
172
- return node.nodeType == Node.COMMENT_NODE && node.textContent.includes(SINGLEFILE_COMMENT);
173
- }
174
-
175
- async function initInfobar(url, optionalData /* saveDate, infoData */) {
176
- let infobarElement = document.querySelector(INFOBAR_TAGNAME);
177
- if (!infobarElement) {
178
- let infoData = "";
179
- if (optionalData.length) {
180
- const saveDate = optionalData[0].split("saved date: ")[1];
181
- if (saveDate) {
182
- optionalData.shift();
183
- }
184
- if (optionalData.length > 1) {
185
- let content = optionalData[0].split("info: ")[1].trim();
186
- for (let indexLine = 1; indexLine < optionalData.length - 1; indexLine++) {
187
- content += "\n" + optionalData[indexLine].trim();
188
- }
189
- infoData = content.trim();
190
- } else {
191
- infoData = saveDate;
192
- }
56
+ if (options.displayInfobar) {
57
+ appendInfobar(document, options, true);
58
+ refreshInfobarInfo(document, infoData);
193
59
  }
194
- infoData = infoData || "No info";
195
- infobarElement = createElement(INFOBAR_TAGNAME, document.body);
196
- infobarElement.className = SINGLE_FILE_UI_ELEMENT_CLASS;
197
- const shadowRoot = await getShadowRoot(infobarElement);
198
- const styleElement = document.createElement("style");
199
- styleElement.textContent = INFOBAR_STYLES;
200
- shadowRoot.appendChild(styleElement);
201
- const infobarContent = document.createElement("div");
202
- infobarContent.classList.add("infobar");
203
- shadowRoot.appendChild(infobarContent);
204
- const closeElement = document.createElement("img");
205
- closeElement.classList.add("infobar-close-button");
206
- infobarContent.appendChild(closeElement);
207
- closeElement.src = CLOSE_ICON;
208
- closeElement.onclick = event => {
209
- if (event.button === 0) {
210
- infobarElement.remove();
211
- }
212
- };
213
- const infoElement = document.createElement("span");
214
- infobarContent.appendChild(infoElement);
215
- infoElement.classList.add("infobar-content");
216
- infoElement.textContent = infoData;
217
- const linkElement = document.createElement("a");
218
- linkElement.classList.add("infobar-link");
219
- infobarContent.appendChild(linkElement);
220
- linkElement.target = "_blank";
221
- linkElement.rel = "noopener noreferrer";
222
- linkElement.title = "Open source URL: " + url;
223
- linkElement.href = url;
224
- const imgElement = document.createElement("img");
225
- imgElement.classList.add("infobar-link-icon");
226
- linkElement.appendChild(imgElement);
227
- imgElement.src = LINK_ICON;
228
- hideInfobar(infobarContent);
229
-
230
- document.addEventListener("click", event => {
231
- if (event.button === 0) {
232
- let element = event.target;
233
- while (element && element != infobarElement) {
234
- element = element.parentElement;
235
- }
236
- if (element != infobarElement) {
237
- hideInfobar(infobarContent);
238
- }
239
- }
240
- });
241
- }
242
- }
243
-
244
- function displayInfobar(infobarContent) {
245
- if (!SHADOW_DOM_SUPPORTED) {
246
- const infobarElement = document.querySelector(INFOBAR_TAGNAME);
247
- const frameElement = infobarElement.childNodes[0];
248
- frameElement.contentWindow.getSelection().removeAllRanges();
249
- }
250
- infobarContent.classList.add("infobar-open");
251
- infobarContent.onclick = null;
252
- infobarContent.onmouseout = null;
253
- if (!SHADOW_DOM_SUPPORTED) {
254
- const infobarElement = document.querySelector(INFOBAR_TAGNAME);
255
- const frameElement = infobarElement.childNodes[0];
256
- frameElement.style.setProperty("width", "100vw", "important");
257
- frameElement.style.setProperty("height", "100vh", "important");
258
- frameElement.style.setProperty("width", (infobarContent.getBoundingClientRect().width + 33) + "px", "important");
259
- frameElement.style.setProperty("height", (infobarContent.getBoundingClientRect().height + 21) + "px", "important");
260
- }
261
- }
262
-
263
- function hideInfobar(infobarContent) {
264
- infobarContent.classList.remove("infobar-open");
265
- infobarContent.onclick = event => {
266
- if (event.button === 0) {
267
- displayInfobar(infobarContent);
268
- return false;
269
- }
270
- };
271
- if (!SHADOW_DOM_SUPPORTED) {
272
- const infobarElement = document.querySelector(INFOBAR_TAGNAME);
273
- const frameElement = infobarElement.childNodes[0];
274
- frameElement.style.setProperty("width", "44px", "important");
275
- frameElement.style.setProperty("height", "48px", "important");
276
- }
277
- }
278
-
279
- function createElement(tagName, parentElement) {
280
- const element = document.createElement(tagName);
281
- parentElement.appendChild(element);
282
- Array.from(getComputedStyle(element)).forEach(property => element.style.setProperty(property, "initial", "important"));
283
- return element;
284
- }
285
-
286
- async function getShadowRoot(element) {
287
- if (element.attachShadow) {
288
- return element.attachShadow({ mode: "open" });
289
- } else {
290
- SHADOW_DOM_SUPPORTED = false;
291
- const iframe = createElement("iframe", element);
292
- iframe.style.setProperty("background-color", "transparent", "important");
293
- iframe.style.setProperty("position", "fixed", "important");
294
- iframe.style.setProperty("top", 0, "important");
295
- iframe.style.setProperty("right", 0, "important");
296
- iframe.style.setProperty("width", "44px", "important");
297
- iframe.style.setProperty("height", "48px", "important");
298
- iframe.style.setProperty("z-index", 2147483647, "important");
299
- return new Promise(resolve => {
300
- iframe.contentDocument.body.style.setProperty("margin", 0);
301
- iframe.onload = () => resolve(iframe.contentDocument.body);
302
- });
303
60
  }
304
61
  }
305
62
 
@@ -0,0 +1,277 @@
1
+ /*
2
+ * Copyright 2010-2022 Gildas Lormeau
3
+ * contact : gildas.lormeau <at> gmail.com
4
+ *
5
+ * This file is part of SingleFile.
6
+ *
7
+ * The code in this file is free software: you can redistribute it and/or
8
+ * modify it under the terms of the GNU Affero General Public License
9
+ * (GNU AGPL) as published by the Free Software Foundation, either version 3
10
+ * of the License, or (at your option) any later version.
11
+ *
12
+ * The code in this file is distributed in the hope that it will be useful,
13
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
15
+ * General Public License for more details.
16
+ *
17
+ * As additional permission under GNU AGPL version 3 section 7, you may
18
+ * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
19
+ * AGPL normally required by section 4, provided you include this license
20
+ * notice and a URL through which recipients can access the Corresponding
21
+ * Source.
22
+ */
23
+
24
+ /* global getComputedStyle, XPathResult, Node */
25
+
26
+ const INFOBAR_TAGNAME = "single-file-infobar";
27
+ const INFOBAR_STYLES = `
28
+ .infobar,
29
+ .infobar .infobar-icon,
30
+ .infobar .infobar-link-icon {
31
+ min-inline-size: 28px;
32
+ min-block-size: 28px;
33
+ box-sizing: border-box;
34
+ }
35
+
36
+ .infobar,
37
+ .infobar .infobar-close-icon,
38
+ .infobar .infobar-link-icon {
39
+ opacity: 0.7;
40
+ transition: opacity 250ms;
41
+ }
42
+
43
+ .infobar:hover,
44
+ .infobar .infobar-close-icon:hover,
45
+ .infobar .infobar-link-icon:hover {
46
+ opacity: 1;
47
+ }
48
+
49
+ .infobar,
50
+ .infobar-content {
51
+ display: flex;
52
+ }
53
+
54
+ .infobar {
55
+ position: fixed;
56
+ top: 16px;
57
+ right: 16px;
58
+ margin-inline-start: 16px;
59
+ margin-block-end: 16px;
60
+ color: #2d2d2d;
61
+ background-color: #737373;
62
+ border: 2px solid;
63
+ border-color: #eee;
64
+ border-radius: 16px;
65
+ z-index: 2147483647;
66
+ }
67
+
68
+ .infobar:valid, .infobar:not(:focus-within) .infobar-content {
69
+ display: none;
70
+ }
71
+
72
+ .infobar:focus-within {
73
+ background-color: #f9f9f9;
74
+ border-color: #878787;
75
+ border-radius: 8px;
76
+ opacity: 1;
77
+ transition-property: opacity, background-color, border-color, border-radius,
78
+ color;
79
+ }
80
+
81
+ .infobar-content {
82
+ align-items: center;
83
+ }
84
+
85
+ .infobar-content span {
86
+ font-family: Arial, Helvetica, sans-serif;
87
+ font-size: 14px;
88
+ line-height: 18px;
89
+ word-break: break-word;
90
+ white-space: pre-wrap;
91
+ margin-inline: 4px;
92
+ margin-block: 4px;
93
+ }
94
+
95
+ .infobar .infobar-icon,
96
+ .infobar .infobar-close-icon,
97
+ .infobar .infobar-link-icon {
98
+ cursor: pointer;
99
+ background-position: center;
100
+ background-repeat: no-repeat;
101
+ }
102
+
103
+ .infobar .infobar-close-icon,
104
+ .infobar .infobar-link-icon {
105
+ align-self: flex-start;
106
+ }
107
+
108
+ .infobar .infobar-icon {
109
+ position: absolute;
110
+ min-inline-size: 24px;
111
+ min-block-size: 24px;
112
+ }
113
+
114
+ .infobar:focus-within .infobar-icon {
115
+ z-index: -1;
116
+ background-image: none;
117
+ }
118
+
119
+ .infobar .infobar-close-icon {
120
+ min-inline-size: 22px;
121
+ min-block-size: 22px;
122
+ }
123
+
124
+ .infobar .infobar-icon {
125
+ background-color: transparent;
126
+ background-size: 70%;
127
+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABABAMAAABYR2ztAAABhmlDQ1BJQ0MgcHJvZmlsZQAAKJF9kj1Iw0AYht+mSkUrDnYQcchQnSyIijqWKhbBQmkrtOpgcukfNGlIUlwcBdeCgz+LVQcXZ10dXAVB8AfEydFJ0UVK/C4ptIjx4LiH9+59+e67A4RGhalm1wSgapaRisfEbG5VDLyiDwEAvZiVmKkn0osZeI6ve/j4ehfhWd7n/hz9St5kgE8kjjLdsIg3iGc2LZ3zPnGIlSSF+Jx43KACiR+5Lrv8xrnosMAzQ0YmNU8cIhaLHSx3MCsZKvE0cVhRNcoXsi4rnLc4q5Uaa9XJbxjMaytprtMcQRxLSCAJETJqKKMCCxFaNVJMpGg/5uEfdvxJcsnkKoORYwFVqJAcP/gb/O6tWZiadJOCMaD7xbY/RoHALtCs2/b3sW03TwD/M3Cltf3VBjD3SXq9rYWPgIFt4OK6rcl7wOUOMPSkS4bkSH6aQqEAvJ/RM+WAwVv6EGtu31r7OH0AMtSr5Rvg4BAYK1L2use9ezr79u+ZVv9+AFlNcp0UUpiqAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAB3RJTUUH5AsHADIRLMaOHwAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAAPUExURQAAAIqKioyNjY2OjvDw8L2y1DEAAAABdFJOUwBA5thmAAAAAWJLR0QB/wIt3gAAAGNJREFUSMdjYCAJsLi4OBCQx6/CBQwIGIDPCBcXAkYQUsACU+AwlBVQHg6Eg5pgZBGOboIJZugDFwRwoJECJCUOhJI1wZwzqmBUwagCuipgIqTABG9h7YIKaKGAURAFEF/6AQAO4HqSoDP8bgAAAABJRU5ErkJggg==);
128
+ }
129
+
130
+ .infobar .infobar-link-icon {
131
+ background-size: 60%;
132
+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABAAgMAAADXB5lNAAABhmlDQ1BJQ0MgcHJvZmlsZQAAKJF9kj1Iw0AYht+mSkUrDnYQcchQnSyIijqWKhbBQmkrtOpgcukfNGlIUlwcBdeCgz+LVQcXZ10dXAVB8AfEydFJ0UVK/C4ptIjx4LiH9+59+e67A4RGhalm1wSgapaRisfEbG5VDLyiDwEAvZiVmKkn0osZeI6ve/j4ehfhWd7n/hz9St5kgE8kjjLdsIg3iGc2LZ3zPnGIlSSF+Jx43KACiR+5Lrv8xrnosMAzQ0YmNU8cIhaLHSx3MCsZKvE0cVhRNcoXsi4rnLc4q5Uaa9XJbxjMaytprtMcQRxLSCAJETJqKKMCCxFaNVJMpGg/5uEfdvxJcsnkKoORYwFVqJAcP/gb/O6tWZiadJOCMaD7xbY/RoHALtCs2/b3sW03TwD/M3Cltf3VBjD3SXq9rYWPgIFt4OK6rcl7wOUOMPSkS4bkSH6aQqEAvJ/RM+WAwVv6EGtu31r7OH0AMtSr5Rvg4BAYK1L2use9ezr79u+ZVv9+AFlNcp0UUpiqAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAB3RJTUUH5AsHAB8H+DhhoQAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAAJUExURQAAAICHi4qKioTuJAkAAAABdFJOUwBA5thmAAAAAWJLR0QCZgt8ZAAAAJJJREFUOI3t070NRCEMA2CnYAOyDyPwpHj/Va7hJ3FzV7zy3ET5JIwoAF6Jk4wzAJAkzxAYG9YRTgB+24wBgKmfrGAKTcEfAY4KRlRoIeBTgKOCERVaCPgU4Khge2GqKOBTgKOCERVaAEC/4PNcnyoSWHpjqkhwKxbcig0Q6AorXYF/+A6eIYD1lVbwG/jdA6/kA2THRAURVubcAAAAAElFTkSuQmCC);
133
+ }
134
+
135
+ .infobar .infobar-close-icon {
136
+ appearance: none;
137
+ background-size: 80%;
138
+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABAAgMAAADXB5lNAAABhmlDQ1BJQ0MgcHJvZmlsZQAAKJF9kj1Iw0AYht+mSkUrDnYQcchQnSyIijqWKhbBQmkrtOpgcukfNGlIUlwcBdeCgz+LVQcXZ10dXAVB8AfEydFJ0UVK/C4ptIjx4LiH9+59+e67A4RGhalm1wSgapaRisfEbG5VDLyiDwEAvZiVmKkn0osZeI6ve/j4ehfhWd7n/hz9St5kgE8kjjLdsIg3iGc2LZ3zPnGIlSSF+Jx43KACiR+5Lrv8xrnosMAzQ0YmNU8cIhaLHSx3MCsZKvE0cVhRNcoXsi4rnLc4q5Uaa9XJbxjMaytprtMcQRxLSCAJETJqKKMCCxFaNVJMpGg/5uEfdvxJcsnkKoORYwFVqJAcP/gb/O6tWZiadJOCMaD7xbY/RoHALtCs2/b3sW03TwD/M3Cltf3VBjD3SXq9rYWPgIFt4OK6rcl7wOUOMPSkS4bkSH6aQqEAvJ/RM+WAwVv6EGtu31r7OH0AMtSr5Rvg4BAYK1L2use9ezr79u+ZVv9+AFlNcp0UUpiqAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAB3RJTUUH5AsHAB8VC4EQ6QAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAAJUExURQAAAICHi4qKioTuJAkAAAABdFJOUwBA5thmAAAAAWJLR0QCZgt8ZAAAAJtJREFUOI3NkrsBgCAMRLFwBPdxBArcfxXFkO8rbKWAAJfHJ9faf9vuYX/749T5NmShm3bEwbe2SxeuM4+2oxDL1cDoKtVUjRy+tH78Cv2CS+wIiQNC1AEhk4AQeUTMWUJMfUJMSEJMSEY8kIx4IONroaYAimNxsXp1PA7PxwfVL8QnowwoVC0lig07wDDVUjAdbAnjwtow/z/bDW7eI4M2KruJAAAAAElFTkSuQmCC);
139
+ }
140
+ `;
141
+
142
+ export { displayIcon, appendInfobar, refreshInfobarInfo, extractInfobarData, INFOBAR_TAGNAME };
143
+
144
+ function appendInfobar(doc, options, useShadowRoot) {
145
+ if (!doc.querySelector(INFOBAR_TAGNAME)) {
146
+ let infoData = options.infobarContent ? options.infobarContent : options.saveDate;
147
+ infoData = infoData || "No info";
148
+ const infobarElement = createElement(doc, INFOBAR_TAGNAME, doc.body);
149
+ let infobarContainer;
150
+ if (useShadowRoot) {
151
+ infobarContainer = infobarElement.attachShadow({ mode: "open" });
152
+ } else {
153
+ const shadowRootTemplate = doc.createElement("template");
154
+ shadowRootTemplate.setAttribute("shadowroot", "open");
155
+ infobarElement.appendChild(shadowRootTemplate);
156
+ infobarContainer = shadowRootTemplate;
157
+ }
158
+ const shadowRootContent = doc.createElement("div");
159
+ const styleElement = doc.createElement("style");
160
+ styleElement.textContent = INFOBAR_STYLES
161
+ .replace(/ {2}/g, "")
162
+ .replace(/\n/g, "")
163
+ .replace(/: /g, ":")
164
+ .replace(/, /g, ",");
165
+ shadowRootContent.appendChild(styleElement);
166
+ const infobarContent = doc.createElement("form");
167
+ infobarContent.classList.add("infobar");
168
+ shadowRootContent.appendChild(infobarContent);
169
+ const iconElement = doc.createElement("span");
170
+ iconElement.tabIndex = -1;
171
+ iconElement.classList.add("infobar-icon");
172
+ infobarContent.appendChild(iconElement);
173
+ const contentElement = doc.createElement("span");
174
+ contentElement.tabIndex = -1;
175
+ contentElement.classList.add("infobar-content");
176
+ const closeButtonElement = doc.createElement("input");
177
+ closeButtonElement.type = "checkbox";
178
+ closeButtonElement.required = true;
179
+ closeButtonElement.classList.add("infobar-close-icon");
180
+ contentElement.appendChild(closeButtonElement);
181
+ const textElement = doc.createElement("span");
182
+ textElement.textContent = infoData;
183
+ contentElement.appendChild(textElement);
184
+ const linkElement = doc.createElement("a");
185
+ linkElement.classList.add("infobar-link-icon");
186
+ linkElement.target = "_blank";
187
+ linkElement.rel = "noopener noreferrer";
188
+ linkElement.title = "Open source URL: " + options.saveUrl;
189
+ linkElement.href = options.saveUrl;
190
+ contentElement.appendChild(linkElement);
191
+ infobarContent.appendChild(contentElement);
192
+ if (useShadowRoot) {
193
+ infobarContainer.appendChild(shadowRootContent);
194
+ } else {
195
+ const scriptElement = doc.createElement("script");
196
+ let scriptContent = (refreshInfobarInfo.toString());
197
+ scriptContent += (extractInfobarData.toString());
198
+ scriptContent += "refreshInfobarInfo(document, extractInfobarData(document))";
199
+ scriptContent = scriptContent
200
+ .replace(/ == /g, "==")
201
+ .replace(/ > /g, ">")
202
+ .replace(/ < /g, "<")
203
+ .replace(/ \+ /g, "+")
204
+ .replace(/ - /g, "-")
205
+ .replace(/ = /g, "=")
206
+ .replace(/ \+= /g, "+=")
207
+ .replace(/ && /g, "&&")
208
+ .replace(/ \|\| /g, "||")
209
+ .replace(/{ /g, "{")
210
+ .replace(/ }/g, "}")
211
+ .replace(/, /g, ",")
212
+ .replace(/; /g, ";")
213
+ .replace(/ \(/g, "(")
214
+ .replace(/ \)/g, ")")
215
+ .replace(/\t/g, "")
216
+ .replace(/\n/g, "");
217
+ scriptElement.textContent = scriptContent;
218
+ shadowRootContent.appendChild(scriptElement);
219
+ infobarContainer.innerHTML = shadowRootContent.outerHTML;
220
+ }
221
+ doc.body.appendChild(infobarElement);
222
+ }
223
+ }
224
+
225
+ function extractInfobarData(doc) {
226
+ const result = doc.evaluate("//comment()", doc, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
227
+ let singleFileComment = result && result.singleNodeValue;
228
+ if (singleFileComment && singleFileComment.nodeType == Node.COMMENT_NODE && singleFileComment.textContent.includes("SingleFile")) {
229
+ const info = singleFileComment.textContent.split("\n");
230
+ const [, , urlData, ...optionalData] = info;
231
+ const urlMatch = urlData.match(/^ url: (.*) ?$/);
232
+ const saveUrl = urlMatch && urlMatch[1];
233
+ if (saveUrl) {
234
+ let infobarContent, saveDate;
235
+ if (optionalData.length) {
236
+ saveDate = optionalData[0].split("saved date: ")[1];
237
+ if (saveDate) {
238
+ optionalData.shift();
239
+ }
240
+ if (optionalData.length > 1) {
241
+ let content = optionalData[0].split("info: ")[1].trim();
242
+ for (let indexLine = 1; indexLine < optionalData.length - 1; indexLine++) {
243
+ content += "\n" + optionalData[indexLine].trim();
244
+ }
245
+ infobarContent = content.trim();
246
+ }
247
+ }
248
+ return { saveUrl, infobarContent, saveDate };
249
+ }
250
+ }
251
+ }
252
+
253
+ function refreshInfobarInfo(doc, { saveUrl, infobarContent, saveDate }) {
254
+ if (saveUrl) {
255
+ const infobarElement = doc.querySelector("single-file-infobar");
256
+ const shadowRootFragment = infobarElement.shadowRoot;
257
+ const infobarContentElement = shadowRootFragment.querySelector(".infobar-content span");
258
+ infobarContentElement.textContent = infobarContent || saveDate;
259
+ const linkElement = shadowRootFragment.querySelector(".infobar-content .infobar-link-icon");
260
+ linkElement.href = saveUrl;
261
+ }
262
+ }
263
+
264
+ function displayIcon(doc, useShadowRoot) {
265
+ const infoData = extractInfobarData(doc);
266
+ if (infoData.saveUrl) {
267
+ appendInfobar(doc, infoData, useShadowRoot);
268
+ refreshInfobarInfo(doc, infoData);
269
+ }
270
+ }
271
+
272
+ function createElement(doc, tagName, parentElement) {
273
+ const element = doc.createElement(tagName);
274
+ parentElement.appendChild(element);
275
+ Array.from(getComputedStyle(element)).forEach(property => element.style.setProperty(property, "initial", "important"));
276
+ return element;
277
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "single-file-core",
3
- "version": "1.0.64",
3
+ "version": "1.0.66",
4
4
  "description": "SingleFile Core",
5
5
  "author": "Gildas Lormeau",
6
6
  "license": "AGPL-3.0-or-later",
@@ -494,6 +494,15 @@ class Processor {
494
494
  );
495
495
  this.doc.documentElement.insertBefore(commentNode, this.doc.documentElement.firstChild);
496
496
  }
497
+ if (this.doc.querySelector("template[" + SHADOWROOT_ATTRIBUTE_NAME + "]") || (this.options.shadowRoots && this.options.shadowRoots.length)) {
498
+ if (this.options.blockScripts) {
499
+ this.doc.querySelectorAll("script[" + SCRIPT_TEMPLATE_SHADOW_ROOT + "]").forEach(element => element.remove());
500
+ }
501
+ const scriptElement = this.doc.createElement("script");
502
+ scriptElement.setAttribute(SCRIPT_TEMPLATE_SHADOW_ROOT, "");
503
+ scriptElement.textContent = `(()=>{document.currentScript.remove();processNode(document);function processNode(node){node.querySelectorAll("template[${SHADOWROOT_ATTRIBUTE_NAME}]").forEach(element=>{let shadowRoot = element.parentElement.shadowRoot;if (!shadowRoot) {try {shadowRoot=element.parentElement.attachShadow({mode:element.getAttribute("${SHADOWROOT_ATTRIBUTE_NAME}")});shadowRoot.innerHTML=element.innerHTML;element.remove()} catch (error) {} if (shadowRoot) {processNode(shadowRoot)}}})}})()`;
504
+ this.doc.body.appendChild(scriptElement);
505
+ }
497
506
  if (this.options.insertCanonicalLink && this.options.saveUrl.match(HTTP_URI_PREFIX)) {
498
507
  let canonicalLink = this.doc.querySelector("link[rel=canonical]");
499
508
  if (!canonicalLink) {
@@ -724,15 +733,16 @@ class Processor {
724
733
 
725
734
  removeEmbedScripts() {
726
735
  const JAVASCRIPT_URI_PREFIX = "javascript:";
736
+ const DISABLED_SCRIPT = "javascript:void(0)";
727
737
  this.onEventAttributeNames.forEach(attributeName => this.doc.querySelectorAll("[" + attributeName + "]").forEach(element => element.removeAttribute(attributeName)));
728
738
  this.doc.querySelectorAll("[href]").forEach(element => {
729
739
  if (element.href && element.href.match && element.href.trim().startsWith(JAVASCRIPT_URI_PREFIX)) {
730
- element.removeAttribute("href");
740
+ element.setAttribute("href", DISABLED_SCRIPT);
731
741
  }
732
742
  });
733
743
  this.doc.querySelectorAll("[src]").forEach(element => {
734
744
  if (element.src && element.src.trim().startsWith(JAVASCRIPT_URI_PREFIX)) {
735
- element.removeAttribute("src");
745
+ element.setAttribute("src", DISABLED_SCRIPT);
736
746
  }
737
747
  });
738
748
  const scriptElements = this.doc.querySelectorAll("script:not([type=\"application/ld+json\"]):not([" + SCRIPT_TEMPLATE_SHADOW_ROOT + "])");
@@ -854,9 +864,9 @@ class Processor {
854
864
  }
855
865
 
856
866
  saveFavicon() {
857
- let faviconElement = this.doc.querySelector("link[href][rel=\"icon\"]");
867
+ let faviconElement = this.doc.querySelector("link[href][rel=\"shortcut icon\"]");
858
868
  if (!faviconElement) {
859
- faviconElement = this.doc.querySelector("link[href][rel=\"shortcut icon\"]");
869
+ faviconElement = this.doc.querySelector("link[href][rel=\"icon\"]");
860
870
  }
861
871
  if (!faviconElement) {
862
872
  faviconElement = this.doc.createElement("link");
@@ -874,7 +884,11 @@ class Processor {
874
884
  if (attributeValue) {
875
885
  const canvasData = this.options.canvases[Number(attributeValue)];
876
886
  if (canvasData) {
877
- ProcessorHelper.setBackgroundImage(canvasElement, "url(" + canvasData.dataURI + ")");
887
+ const backgroundStyle = {};
888
+ if (canvasData.backgroundColor) {
889
+ backgroundStyle["background-color"] = canvasData.backgroundColor;
890
+ }
891
+ ProcessorHelper.setBackgroundImage(canvasElement, "url(" + canvasData.dataURI + ")", backgroundStyle);
878
892
  this.stats.add("processed", "canvas", 1);
879
893
  }
880
894
  }
@@ -1139,13 +1153,6 @@ class Processor {
1139
1153
  const options = this.options;
1140
1154
  if (options.shadowRoots && options.shadowRoots.length) {
1141
1155
  processElement(this.doc);
1142
- if (options.blockScripts) {
1143
- this.doc.querySelectorAll("script[" + SCRIPT_TEMPLATE_SHADOW_ROOT + "]").forEach(element => element.remove());
1144
- }
1145
- const scriptElement = doc.createElement("script");
1146
- scriptElement.setAttribute(SCRIPT_TEMPLATE_SHADOW_ROOT, "");
1147
- scriptElement.textContent = `(()=>{document.currentScript.remove();processNode(document);function processNode(node){node.querySelectorAll("template[${SHADOWROOT_ATTRIBUTE_NAME}]").forEach(element=>{let shadowRoot = element.parentElement.shadowRoot;if (!shadowRoot) {try {shadowRoot=element.parentElement.attachShadow({mode:element.getAttribute("${SHADOWROOT_ATTRIBUTE_NAME}")});shadowRoot.innerHTML=element.innerHTML;element.remove()} catch (error) {} if (shadowRoot) {processNode(shadowRoot)}}})}})()`;
1148
- doc.body.appendChild(scriptElement);
1149
1156
  }
1150
1157
 
1151
1158
  function processElement(element) {
@@ -1506,11 +1513,14 @@ class ProcessorHelper {
1506
1513
  }
1507
1514
 
1508
1515
  static processShortcutIcons(doc) {
1509
- let shortcutIcon = findShortcutIcon(Array.from(doc.querySelectorAll("link[href][rel=\"icon\"], link[href][rel=\"shortcut icon\"]")));
1516
+ let shortcutIcon = findShortcutIcon(Array.from(doc.querySelectorAll("link[href][rel=\"shortcut icon\"]")));
1517
+ if (!shortcutIcon) {
1518
+ shortcutIcon = findShortcutIcon(Array.from(doc.querySelectorAll("link[href][rel=\"icon\"]")));
1519
+ }
1510
1520
  if (!shortcutIcon) {
1511
1521
  shortcutIcon = findShortcutIcon(Array.from(doc.querySelectorAll("link[href][rel*=\"icon\"]")));
1512
1522
  if (shortcutIcon) {
1513
- shortcutIcon.rel = "icon";
1523
+ shortcutIcon.rel = "shortcut icon";
1514
1524
  }
1515
1525
  }
1516
1526
  if (shortcutIcon) {
@@ -25,6 +25,7 @@
25
25
 
26
26
  import * as cssUnescape from "./vendor/css-unescape.js";
27
27
  import * as hooksFrames from "./processors/hooks/content/content-hooks-frames.js";
28
+ import * as infobar from "./common/infobar.js";
28
29
 
29
30
  const ON_BEFORE_CAPTURE_EVENT_NAME = "single-file-on-before-capture";
30
31
  const ON_AFTER_CAPTURE_EVENT_NAME = "single-file-on-after-capture";
@@ -79,6 +80,7 @@ export {
79
80
  getFontWeight,
80
81
  normalizeFontFamily,
81
82
  getShadowRoot,
83
+ appendInfobar,
82
84
  ON_BEFORE_CAPTURE_EVENT_NAME,
83
85
  ON_AFTER_CAPTURE_EVENT_NAME,
84
86
  WIN_ID_ATTRIBUTE_NAME,
@@ -265,7 +267,10 @@ function getResourcesInfo(win, doc, element, options, data, elementHidden, compu
265
267
  const tagName = element.tagName && element.tagName.toUpperCase();
266
268
  if (tagName == "CANVAS") {
267
269
  try {
268
- data.canvases.push({ dataURI: element.toDataURL("image/png", "") });
270
+ data.canvases.push({
271
+ dataURI: element.toDataURL("image/png", ""),
272
+ backgroundColor: computedStyle.getPropertyValue("background-color")
273
+ });
269
274
  element.setAttribute(CANVAS_ATTRIBUTE_NAME, data.canvases.length - 1);
270
275
  data.markedElements.push(element);
271
276
  } catch (error) {
@@ -395,6 +400,10 @@ function getShadowRoot(element) {
395
400
  }
396
401
  }
397
402
 
403
+ function appendInfobar(doc, options, useShadowRoot) {
404
+ return infobar.appendInfobar(doc, options, useShadowRoot);
405
+ }
406
+
398
407
  function normalizeFontFamily(fontFamilyName = "") {
399
408
  return removeQuotes(cssUnescape.process(fontFamilyName.trim())).toLowerCase();
400
409
  }
@@ -0,0 +1,50 @@
1
+ /*
2
+ * Copyright 2010-2020 Gildas Lormeau
3
+ * contact : gildas.lormeau <at> gmail.com
4
+ *
5
+ * This file is part of SingleFile.
6
+ *
7
+ * The code in this file is free software: you can redistribute it and/or
8
+ * modify it under the terms of the GNU Affero General Public License
9
+ * (GNU AGPL) as published by the Free Software Foundation, either version 3
10
+ * of the License, or (at your option) any later version.
11
+ *
12
+ * The code in this file is distributed in the hope that it will be useful,
13
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
15
+ * General Public License for more details.
16
+ *
17
+ * As additional permission under GNU AGPL version 3 section 7, you may
18
+ * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
19
+ * AGPL normally required by section 4, provided you include this license
20
+ * notice and a URL through which recipients can access the Corresponding
21
+ * Source.
22
+ */
23
+
24
+ import * as serializer from "./modules/html-serializer.js";
25
+ import * as infobar from "./common/infobar.js";
26
+ import { getInstance } from "./single-file-util.js";
27
+
28
+ const util = getInstance();
29
+ const helper = {
30
+ serialize(doc, compressHTML) {
31
+ return serializer.process(doc, compressHTML);
32
+ },
33
+ getDoctypeString(doc) {
34
+ return util.getDoctypeString(doc);
35
+ },
36
+ appendInfobar(doc, options, useShadowRoot) {
37
+ return infobar.appendInfobar(doc, options, useShadowRoot);
38
+ },
39
+ extractInfobarData(doc) {
40
+ return infobar.extractInfobarData(doc);
41
+ },
42
+ displayIcon(doc, useShadowRoot) {
43
+ return infobar.displayIcon(doc, useShadowRoot);
44
+ },
45
+ INFOBAR_TAGNAME: infobar.INFOBAR_TAGNAME
46
+ };
47
+
48
+ export {
49
+ helper
50
+ };
@@ -160,6 +160,9 @@ function getInstance(utilOptions) {
160
160
  removeQuotes(string) {
161
161
  return helper.removeQuotes(string);
162
162
  },
163
+ appendInfobar(doc, options) {
164
+ return helper.appendInfobar(doc, options);
165
+ },
163
166
  ON_BEFORE_CAPTURE_EVENT_NAME: helper.ON_BEFORE_CAPTURE_EVENT_NAME,
164
167
  ON_AFTER_CAPTURE_EVENT_NAME: helper.ON_AFTER_CAPTURE_EVENT_NAME,
165
168
  WIN_ID_ATTRIBUTE_NAME: helper.WIN_ID_ATTRIBUTE_NAME,