content-infobar-web.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  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. /* global document, Node, window, top, getComputedStyle, setTimeout, XPathResult */
  24. (() => {
  25. const INFOBAR_TAGNAME = "singlefile-infobar";
  26. const LINK_ICON = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAABmJLR0QABQDuAACS38mlAAAACXBIWXMAACfuAAAn7gExzuVDAAAAB3RJTUUH4ggCDDcMnYqGGAAAATtJREFUOMvNk19LwlAYxp+zhOoqpxJ1la3patFVINk/oRDBLuyreiPFMmcj/QQRSOOwpEINDCpwRr7d1HBMc4sufO7Oe877e5/zcA4wbWLDi8urGr2+vXsOFfJZdnPboDtuueoRcQEH6RQDgNBP8bxcpfvmA0QxPHF6u/MMInLVHFDP7kMUwyjks2xU8+ZGkgGAbtSp1e5gRhBc+0KQHHSjTg2TY0tVEItF/wYqV6+pYXKoiox0atvjOuQXYnILqiJj/ztceXUlGEirGGRyC0pCciDDmfm6mlYxiFtNKAkJmb0dV2OxpFGxpNFE0NmFTtxqQpbiHsgojQX1bBuyFMfR4S7zk+PYjE5PcizI0xD+6685jubnZvH41MJwgL+p233B8tKiF7SeXMPnYIB+/8OXg2hERO44wzC1+gJYGGpVbtoqiAAAAABJRU5ErkJggg==";
  27. const IMAGE_ICON = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAABIUlEQVQ4y+2TsarCMBSGvxTBRdqiUZAWOrhJB9EXcPKFfCvfQYfulUKHDqXg4CYUJSioYO4mSDX3ttzt3n87fMlHTpIjlsulxpDZbEYYhgghSNOUOI5Ny2mZYBAELBYLer0eAJ7ncTweKYri4x7LJJRS0u12n7XrukgpjSc0CpVSXK/XZ32/31FKNW85z3PW6zXT6RSAJEnIsqy5UGvNZrNhu90CcDqd+C6tT6J+v//2Th+PB2VZ1hN2Oh3G4zGTyQTbtl/YbrdjtVpxu91+Ljyfz0RRhG3bzOfzF+Y4TvNXvlwuaK2pE4tfzr/wzwsty0IIURlL0998KxRCMBqN8H2/wlzXJQxD2u12vVkeDoeUZUkURRU+GAw4HA7s9/sK+wK6CWHasQ/S/wAAAABJRU5ErkJggg==";
  28. const SINGLEFILE_COMMENT = "SingleFile";
  29. const browser = this.browser;
  30. if (window == top) {
  31. if (document.readyState == "loading") {
  32. document.addEventListener("DOMContentLoaded", displayIcon, false);
  33. } else {
  34. displayIcon();
  35. }
  36. }
  37. async function displayIcon() {
  38. const result = document.evaluate("//comment()", document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
  39. let singleFileComment = result && result.singleNodeValue;
  40. if (singleFileComment && isSingleFileComment(singleFileComment)) {
  41. const info = singleFileComment.textContent.split("\n");
  42. const [, , url, saveDate, ...infoData] = info;
  43. if (url && saveDate) {
  44. let options;
  45. if (browser && browser.runtime && browser.runtime.sendMessage) {
  46. options = await browser.runtime.sendMessage({ method: "tabs.getOptions", url });
  47. } else {
  48. options = { displayInfobar: true };
  49. }
  50. if (options.displayInfobar) {
  51. initInfobar(url, saveDate, infoData);
  52. }
  53. }
  54. }
  55. }
  56. function isSingleFileComment(node) {
  57. return node.nodeType == Node.COMMENT_NODE && node.textContent.includes(SINGLEFILE_COMMENT);
  58. }
  59. function initInfobar(url, saveDate, infoData) {
  60. let infobarElement = document.querySelector(INFOBAR_TAGNAME);
  61. if (!infobarElement) {
  62. url = url.split("url: ")[1];
  63. saveDate = saveDate.split("saved date: ")[1];
  64. if (infoData && infoData.length > 1) {
  65. let content = infoData[0].split("info: ")[1].trim();
  66. for (let indexLine = 1; indexLine < infoData.length - 1; indexLine++) {
  67. content += "\n" + infoData[indexLine].trim();
  68. }
  69. infoData = content.trim();
  70. } else {
  71. infoData = saveDate;
  72. }
  73. infobarElement = createElement(INFOBAR_TAGNAME, document.body);
  74. setProperty(infobarElement, "background-color", "#f9f9f9");
  75. setProperty(infobarElement, "display", "flex");
  76. setProperty(infobarElement, "position", "fixed");
  77. setProperty(infobarElement, "top", "16px");
  78. setProperty(infobarElement, "right", "16px");
  79. setProperty(infobarElement, "height", "auto");
  80. setProperty(infobarElement, "min-height", "24px");
  81. setProperty(infobarElement, "min-width", "24px");
  82. setProperty(infobarElement, "background-position", "center");
  83. setProperty(infobarElement, "background-repeat", "no-repeat");
  84. setProperty(infobarElement, "z-index", 2147483647);
  85. setProperty(infobarElement, "text-align", "center");
  86. setProperty(infobarElement, "will-change", "opacity, padding-left, padding-right, width, background-color, color");
  87. setProperty(infobarElement, "margin", "0 0 0 16px");
  88. const closeElement = createElement("span", infobarElement);
  89. closeElement.textContent = "✕";
  90. setProperty(closeElement, "display", "none");
  91. setProperty(closeElement, "opacity", .7);
  92. setProperty(closeElement, "padding-right", "8px");
  93. setProperty(closeElement, "cursor", "pointer");
  94. setProperty(closeElement, "color", "#9aa0a6");
  95. setProperty(closeElement, "line-height", "24px");
  96. closeElement.onmouseover = () => setProperty(closeElement, "opacity", 1);
  97. closeElement.onmouseout = () => setProperty(closeElement, "opacity", .7);
  98. closeElement.onclick = event => {
  99. if (event.button === 0) {
  100. infobarElement.remove();
  101. }
  102. };
  103. const infoElement = createElement("span", infobarElement);
  104. setProperty(infoElement, "font-family", "Arial");
  105. setProperty(infoElement, "color", "#9aa0a6");
  106. setProperty(infoElement, "font-size", "14px");
  107. setProperty(infoElement, "line-height", "22px");
  108. setProperty(infoElement, "word-break", "break-word");
  109. setProperty(infoElement, "white-space", "pre-wrap");
  110. infoElement.textContent = infoData;
  111. const linkElement = createElement("a", infobarElement);
  112. setProperty(linkElement, "display", "inline-block");
  113. setProperty(linkElement, "padding-left", "8px");
  114. setProperty(linkElement, "line-height", "11px");
  115. setProperty(linkElement, "cursor", "pointer");
  116. setProperty(linkElement, "user-select", "none");
  117. linkElement.target = "_blank";
  118. linkElement.rel = "noopener noreferrer";
  119. linkElement.title = "Open source URL: " + url;
  120. linkElement.href = url;
  121. const imgElement = createElement("img", linkElement);
  122. setProperty(imgElement, "padding-top", "3px");
  123. setProperty(imgElement, "-webkit-padding-after", "2px");
  124. setProperty(imgElement, "padding-left", "2px");
  125. setProperty(imgElement, "-webkit-padding-start", "2px");
  126. setProperty(imgElement, "cursor", "pointer");
  127. setProperty(infobarElement, "text-align", "right");
  128. imgElement.src = LINK_ICON;
  129. hideInfobar(infobarElement, linkElement, infoElement, closeElement);
  130. infobarElement.onmouseover = () => setProperty(infobarElement, "opacity", 1);
  131. document.addEventListener("click", event => {
  132. if (event.button === 0) {
  133. let element = event.target;
  134. while (element && element != infobarElement) {
  135. element = element.parentElement;
  136. }
  137. if (element != infobarElement) {
  138. hideInfobar(infobarElement, linkElement, infoElement, closeElement);
  139. }
  140. }
  141. });
  142. setTimeout(() => {
  143. setProperty(infobarElement, "transition-property", "opacity");
  144. setProperty(infobarElement, "transition-duration", "250ms");
  145. });
  146. }
  147. }
  148. function displayInfobar(infobarElement, linkElement, infoElement, closeElement) {
  149. setProperty(infobarElement, "font-size", "13px");
  150. setProperty(infobarElement, "opacity", 1);
  151. setProperty(infobarElement, "width", "auto");
  152. setProperty(infobarElement, "background-color", "#f9f9f9");
  153. setProperty(infobarElement, "cursor", "auto");
  154. setProperty(infobarElement, "color", "#9aa0a6");
  155. setProperty(infobarElement, "padding-left", "8px");
  156. setProperty(infobarElement, "padding-right", "4px");
  157. setProperty(infobarElement, "padding-top", "2px");
  158. setProperty(infobarElement, "padding-bottom", "2px");
  159. setProperty(infobarElement, "-webkit-padding-start", "8px");
  160. setProperty(infobarElement, "-webkit-padding-end", "4px");
  161. setProperty(infobarElement, "border", "2px solid #555");
  162. setProperty(infobarElement, "-webkit-border-start", "2px solid #555");
  163. setProperty(infobarElement, "-webkit-border-before", "2px solid #555");
  164. setProperty(infobarElement, "-webkit-border-end", "2px solid #555");
  165. setProperty(infobarElement, "-webkit-border-after", "2px solid #555");
  166. setProperty(infobarElement, "background-image", "none");
  167. setProperty(infobarElement, "border-radius", "8px");
  168. setProperty(infoElement, "display", "inline-block");
  169. setProperty(linkElement, "display", "inline-block");
  170. setProperty(closeElement, "display", "inline-block");
  171. setProperty(infobarElement, "user-select", "initial");
  172. setProperty(infobarElement, "-moz-user-select", "initial");
  173. infobarElement.onclick = null;
  174. infobarElement.onmouseout = null;
  175. }
  176. function hideInfobar(infobarElement, linkElement, infoElement, closeElement) {
  177. setProperty(infobarElement, "user-select", "none");
  178. setProperty(infobarElement, "-moz-user-select", "none");
  179. setProperty(infobarElement, "opacity", .7);
  180. infobarElement.onmouseout = () => setProperty(infobarElement, "opacity", .7);
  181. setProperty(infobarElement, "width", "24px");
  182. setProperty(infobarElement, "background-color", "#737373");
  183. setProperty(infobarElement, "cursor", "pointer");
  184. setProperty(infobarElement, "color", "white");
  185. setProperty(infobarElement, "padding-left", 0);
  186. setProperty(infobarElement, "padding-right", 0);
  187. setProperty(infobarElement, "padding-top", 0);
  188. setProperty(infobarElement, "padding-bottom", 0);
  189. setProperty(infobarElement, "-webkit-padding-start", 0);
  190. setProperty(infobarElement, "-webkit-padding-end", 0);
  191. setProperty(infobarElement, "border", "2px solid #eee");
  192. setProperty(infobarElement, "-webkit-border-start", "2px solid #eee");
  193. setProperty(infobarElement, "-webkit-border-before", "2px solid #eee");
  194. setProperty(infobarElement, "-webkit-border-end", "2px solid #eee");
  195. setProperty(infobarElement, "-webkit-border-after", "2px solid #eee");
  196. setProperty(infobarElement, "background-image", "url(" + IMAGE_ICON + ")");
  197. setProperty(infobarElement, "background-size", "70% 70%");
  198. setProperty(infobarElement, "border-radius", "16px");
  199. setProperty(closeElement, "display", "none");
  200. setProperty(linkElement, "display", "none");
  201. setProperty(infoElement, "display", "none");
  202. infobarElement.onclick = event => {
  203. if (event.button === 0) {
  204. displayInfobar(infobarElement, linkElement, infoElement, closeElement);
  205. return false;
  206. }
  207. };
  208. }
  209. function createElement(tagName, parentElement) {
  210. const element = document.createElement(tagName);
  211. parentElement.appendChild(element);
  212. Array.from(getComputedStyle(element)).forEach(property => setProperty(element, property, "initial"));
  213. return element;
  214. }
  215. function setProperty(element, name, value) {
  216. element.style.setProperty(name, value, "important");
  217. }
  218. })();