single-file-core 1.0.63 → 1.0.65

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.63",
3
+ "version": "1.0.65",
4
4
  "description": "SingleFile Core",
5
5
  "author": "Gildas Lormeau",
6
6
  "license": "AGPL-3.0-or-later",
@@ -314,10 +314,12 @@
314
314
 
315
315
  addEventListener(GET_ADOPTED_STYLESHEETS_REQUEST_EVENT, event => {
316
316
  const shadowRoot = event.target.shadowRoot;
317
- const adoptedStyleSheets = Array.from(shadowRoot.adoptedStyleSheets).map(stylesheet => Array.from(stylesheet.cssRules).map(cssRule => cssRule.cssText).join("\n"));
318
- if (adoptedStyleSheets.length) {
319
- event.target.dispatchEvent(new CustomEvent(GET_ADOPTED_STYLESHEETS_RESPONSE_EVENT, { detail: { adoptedStyleSheets } }));
320
- }
317
+ if (shadowRoot) {
318
+ const adoptedStyleSheets = Array.from(shadowRoot.adoptedStyleSheets).map(stylesheet => Array.from(stylesheet.cssRules).map(cssRule => cssRule.cssText).join("\n"));
319
+ if (adoptedStyleSheets.length) {
320
+ event.target.dispatchEvent(new CustomEvent(GET_ADOPTED_STYLESHEETS_RESPONSE_EVENT, { detail: { adoptedStyleSheets } }));
321
+ }
322
+ }
321
323
  }, { capture: true });
322
324
 
323
325
  if (globalThis.FontFace) {
@@ -494,6 +494,18 @@ class Processor {
494
494
  );
495
495
  this.doc.documentElement.insertBefore(commentNode, this.doc.documentElement.firstChild);
496
496
  }
497
+ if (this.options.includeInfobar) {
498
+ util.appendInfobar(this.doc, this.options);
499
+ }
500
+ if (this.doc.querySelector("template[" + SHADOWROOT_ATTRIBUTE_NAME + "]") || (this.options.shadowRoots && this.options.shadowRoots.length)) {
501
+ if (this.options.blockScripts) {
502
+ this.doc.querySelectorAll("script[" + SCRIPT_TEMPLATE_SHADOW_ROOT + "]").forEach(element => element.remove());
503
+ }
504
+ const scriptElement = this.doc.createElement("script");
505
+ scriptElement.setAttribute(SCRIPT_TEMPLATE_SHADOW_ROOT, "");
506
+ 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)}}})}})()`;
507
+ this.doc.body.appendChild(scriptElement);
508
+ }
497
509
  if (this.options.insertCanonicalLink && this.options.saveUrl.match(HTTP_URI_PREFIX)) {
498
510
  let canonicalLink = this.doc.querySelector("link[rel=canonical]");
499
511
  if (!canonicalLink) {
@@ -724,15 +736,16 @@ class Processor {
724
736
 
725
737
  removeEmbedScripts() {
726
738
  const JAVASCRIPT_URI_PREFIX = "javascript:";
739
+ const DISABLED_SCRIPT = "javascript:void(0)";
727
740
  this.onEventAttributeNames.forEach(attributeName => this.doc.querySelectorAll("[" + attributeName + "]").forEach(element => element.removeAttribute(attributeName)));
728
741
  this.doc.querySelectorAll("[href]").forEach(element => {
729
742
  if (element.href && element.href.match && element.href.trim().startsWith(JAVASCRIPT_URI_PREFIX)) {
730
- element.removeAttribute("href");
743
+ element.setAttribute("href", DISABLED_SCRIPT);
731
744
  }
732
745
  });
733
746
  this.doc.querySelectorAll("[src]").forEach(element => {
734
747
  if (element.src && element.src.trim().startsWith(JAVASCRIPT_URI_PREFIX)) {
735
- element.removeAttribute("src");
748
+ element.setAttribute("src", DISABLED_SCRIPT);
736
749
  }
737
750
  });
738
751
  const scriptElements = this.doc.querySelectorAll("script:not([type=\"application/ld+json\"]):not([" + SCRIPT_TEMPLATE_SHADOW_ROOT + "])");
@@ -874,7 +887,11 @@ class Processor {
874
887
  if (attributeValue) {
875
888
  const canvasData = this.options.canvases[Number(attributeValue)];
876
889
  if (canvasData) {
877
- ProcessorHelper.setBackgroundImage(canvasElement, "url(" + canvasData.dataURI + ")");
890
+ const backgroundStyle = {};
891
+ if (canvasData.backgroundColor) {
892
+ backgroundStyle["background-color"] = canvasData.backgroundColor;
893
+ }
894
+ ProcessorHelper.setBackgroundImage(canvasElement, "url(" + canvasData.dataURI + ")", backgroundStyle);
878
895
  this.stats.add("processed", "canvas", 1);
879
896
  }
880
897
  }
@@ -1139,13 +1156,6 @@ class Processor {
1139
1156
  const options = this.options;
1140
1157
  if (options.shadowRoots && options.shadowRoots.length) {
1141
1158
  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
1159
  }
1150
1160
 
1151
1161
  function processElement(element) {
@@ -1157,13 +1167,6 @@ class Processor {
1157
1167
  if (shadowRootData) {
1158
1168
  const templateElement = doc.createElement("template");
1159
1169
  templateElement.setAttribute(SHADOWROOT_ATTRIBUTE_NAME, shadowRootData.mode);
1160
- if (shadowRootData.adoptedStyleSheets) {
1161
- shadowRootData.adoptedStyleSheets.forEach(stylesheetContent => {
1162
- const styleElement = doc.createElement("style");
1163
- styleElement.textContent = stylesheetContent;
1164
- templateElement.appendChild(styleElement);
1165
- });
1166
- }
1167
1170
  const shadowDoc = util.parseDocContent(shadowRootData.content);
1168
1171
  if (shadowDoc.head) {
1169
1172
  const metaCharset = shadowDoc.head.querySelector("meta[charset]");
@@ -1181,6 +1184,13 @@ class Processor {
1181
1184
  } else {
1182
1185
  element.appendChild(templateElement);
1183
1186
  }
1187
+ if (shadowRootData.adoptedStyleSheets) {
1188
+ shadowRootData.adoptedStyleSheets.forEach(stylesheetContent => {
1189
+ const styleElement = doc.createElement("style");
1190
+ styleElement.textContent = stylesheetContent;
1191
+ templateElement.appendChild(styleElement);
1192
+ });
1193
+ }
1184
1194
  }
1185
1195
  }
1186
1196
  });
@@ -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,45 @@
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
+
27
+ const helper = {
28
+ serialize(doc, compressHTML) {
29
+ return serializer.process(doc, compressHTML);
30
+ },
31
+ appendInfobar(doc, options, useShadowRoot) {
32
+ return infobar.appendInfobar(doc, options, useShadowRoot);
33
+ },
34
+ extractInfobarData(doc) {
35
+ return infobar.extractInfobarData(doc);
36
+ },
37
+ displayIcon(doc, useShadowRoot) {
38
+ return infobar.displayIcon(doc, useShadowRoot);
39
+ },
40
+ INFOBAR_TAGNAME: infobar.INFOBAR_TAGNAME
41
+ };
42
+
43
+ export {
44
+ helper
45
+ };
@@ -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,