1
0

content-infobar-web.js 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  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, XPathResult */
  24. (() => {
  25. const INFOBAR_TAGNAME = "singlefile-infobar";
  26. const LINK_ICON = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAABGdBTUEAALGPC/xhBQAAAYVpQ0NQSUNDIHByb2ZpbGUAACiRfZE9SMNAHMVfW0tFqw7tIOIQoTpZEBVxlCoWwUJpK7TqYHLpFzRpSFJcHAXXgoMfi1UHF2ddHVwFQfADxMnRSdFFSvxfUmgR48FxP97de9y9A7yNClOMrglAUU09FY8J2dyqEHhFL/zoxwhCIjO0RHoxA9fxdQ8PX++iPMv93J+jT84bDPAIxHNM003iDeKZTVPjvE8cZiVRJj4nHtfpgsSPXJccfuNctNnLM8N6JjVPHCYWih0sdTAr6QrxNHFEVlTK92YdljlvcVYqNda6J39hMK+upLlOcxhxLCGBJARIqKGMCkxEaVVJMZCi/ZiLf8j2J8klkasMRo4FVKFAtP3gf/C7W6MwNekkBWOA/8WyPkaBwC7QrFvW97FlNU8A3zNwpbb91QYw+0l6va1FjoCBbeDiuq1Je8DlDjD4pIm6aEs+mt5CAXg/o2/KAaFboGfN6a21j9MHIENdLd8AB4fAWJGy113e3d3Z279nWv39AFcqcpwP1hSSAAAABmJLR0QAigCKAIrj2uckAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAB3RJTUUH5AsFDjc5xJM3IQAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAABqSURBVDjLY2AYbIARmVPT3v0fXUFLZSkjITkGQobgA3XtPf+R9TCR44269p7/TZUlKK5hooYhJBuEyxCSDMJnCNGBXdfeQ3xE4DKIJEPIiX50PUzUStlMFKQEBorSEc0NYqQkwLFm2kEDAJWJMxMPlm59AAAAAElFTkSuQmCC";
  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 SINGLE_FILE_UI_ELEMENT_CLASS = "single-file-ui-element";
  30. const browser = this.browser;
  31. if (window == top) {
  32. if (document.readyState == "loading") {
  33. document.addEventListener("DOMContentLoaded", displayIcon, false);
  34. } else {
  35. displayIcon();
  36. }
  37. }
  38. async function displayIcon() {
  39. const result = document.evaluate("//comment()", document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
  40. let singleFileComment = result && result.singleNodeValue;
  41. if (singleFileComment && isSingleFileComment(singleFileComment)) {
  42. const info = singleFileComment.textContent.split("\n");
  43. const [, , url, saveDate, ...infoData] = info;
  44. if (url && saveDate) {
  45. let options;
  46. if (browser && browser.runtime && browser.runtime.sendMessage) {
  47. options = await browser.runtime.sendMessage({ method: "tabs.getOptions", url });
  48. } else {
  49. options = { displayInfobar: true };
  50. }
  51. if (options.displayInfobar) {
  52. initInfobar(url, saveDate, infoData);
  53. }
  54. }
  55. }
  56. }
  57. function isSingleFileComment(node) {
  58. return node.nodeType == Node.COMMENT_NODE && node.textContent.includes(SINGLEFILE_COMMENT);
  59. }
  60. function initInfobar(url, saveDate, infoData) {
  61. let infobarElement = document.querySelector(INFOBAR_TAGNAME);
  62. if (!infobarElement) {
  63. url = url.split("url: ")[1];
  64. saveDate = saveDate.split("saved date: ")[1];
  65. if (infoData && infoData.length > 1) {
  66. let content = infoData[0].split("info: ")[1].trim();
  67. for (let indexLine = 1; indexLine < infoData.length - 1; indexLine++) {
  68. content += "\n" + infoData[indexLine].trim();
  69. }
  70. infoData = content.trim();
  71. } else {
  72. infoData = saveDate;
  73. }
  74. infobarElement = createElement(INFOBAR_TAGNAME, document.body);
  75. infobarElement.className = SINGLE_FILE_UI_ELEMENT_CLASS;
  76. const shadowRoot = infobarElement.attachShadow({ mode: "open" });
  77. const styleElement = document.createElement("style");
  78. styleElement.textContent = `
  79. .infobar {
  80. background-color: #737373;
  81. color: white;
  82. display: flex;
  83. position: fixed;
  84. top: 16px;
  85. right: 16px;
  86. height: auto;
  87. width: auto;
  88. min-height: 24px;
  89. min-width: 24px;
  90. background-position: center;
  91. background-repeat: no-repeat;
  92. z-index: 2147483647;
  93. text-align: center;
  94. margin: 0 0 0 16px;
  95. background-image: url(${IMAGE_ICON});
  96. border-radius: 16px;
  97. user-select: none;
  98. opacity: .7;
  99. cursor: pointer;
  100. padding-left: 0;
  101. padding-right: 0;
  102. padding-top: 0;
  103. padding-bottom: 0;
  104. border: 2px solid #eee;
  105. background-size: 70% 70%;
  106. transition: all 250ms;
  107. font-size: 13px;
  108. }
  109. .infobar:hover {
  110. opacity: 1;
  111. }
  112. .infobar-open {
  113. opacity: 1;
  114. background-color: #f9f9f9;
  115. cursor: auto;
  116. color: #2d2d2d;
  117. padding-top: 2px;
  118. padding-bottom: 2px;
  119. -webkit-padding-start: 8px;
  120. -webkit-padding-end: 4px;
  121. border: 2px solid #878787;
  122. background-image: none;
  123. border-radius: 8px;
  124. user-select: initial;
  125. -moz-user-select: initial;
  126. }
  127. .infobar-close-button {
  128. display: none;
  129. opacity: .7;
  130. padding-right: 8px;
  131. cursor: pointer;
  132. color: rgb(126 135 140);
  133. line-height: 24px;
  134. font-size: 16px;
  135. transition: opacity 250ms;
  136. }
  137. .infobar-close-button:hover {
  138. opacity: 1;
  139. }
  140. .infobar-content {
  141. display: none;
  142. font-family: Arial;
  143. font-size: 14px;
  144. line-height: 22px;
  145. word-break: break-word;
  146. white-space: pre-wrap;
  147. position: relative;
  148. top: 1px;
  149. }
  150. .infobar-link {
  151. display: none;
  152. padding-left: 8px;
  153. line-height: 11px;
  154. cursor: pointer;
  155. user-select: none;
  156. }
  157. .infobar-link-icon {
  158. padding-top: 3px;
  159. padding-left: 2px;
  160. cursor: pointer;
  161. opacity: .7;
  162. transition: opacity 250ms;
  163. }
  164. .infobar-link-icon:hover {
  165. opacity: 1;
  166. }
  167. .infobar-open .infobar-close-button, .infobar-open .infobar-content, .infobar-open .infobar-link {
  168. display: inline-block;
  169. }
  170. `;
  171. shadowRoot.appendChild(styleElement);
  172. const infobarContent = document.createElement("div");
  173. infobarContent.classList.add("infobar");
  174. shadowRoot.appendChild(infobarContent);
  175. const closeElement = document.createElement("span");
  176. closeElement.classList.add("infobar-close-button");
  177. infobarContent.appendChild(closeElement);
  178. closeElement.textContent = "✕";
  179. closeElement.onclick = event => {
  180. if (event.button === 0) {
  181. infobarElement.remove();
  182. }
  183. };
  184. const infoElement = document.createElement("span");
  185. infobarContent.appendChild(infoElement);
  186. infoElement.classList.add("infobar-content");
  187. infoElement.textContent = infoData;
  188. const linkElement = document.createElement("a");
  189. linkElement.classList.add("infobar-link");
  190. infobarContent.appendChild(linkElement);
  191. linkElement.target = "_blank";
  192. linkElement.rel = "noopener noreferrer";
  193. linkElement.title = "Open source URL: " + url;
  194. linkElement.href = url;
  195. const imgElement = document.createElement("img");
  196. imgElement.classList.add("infobar-link-icon");
  197. linkElement.appendChild(imgElement);
  198. imgElement.src = LINK_ICON;
  199. hideInfobar(infobarContent);
  200. document.addEventListener("click", event => {
  201. if (event.button === 0) {
  202. let element = event.target;
  203. while (element && element != infobarElement) {
  204. element = element.parentElement;
  205. }
  206. if (element != infobarElement) {
  207. hideInfobar(infobarContent);
  208. }
  209. }
  210. });
  211. }
  212. }
  213. function displayInfobar(infobarContent) {
  214. infobarContent.classList.add("infobar-open");
  215. infobarContent.onclick = null;
  216. infobarContent.onmouseout = null;
  217. }
  218. function hideInfobar(infobarContent) {
  219. infobarContent.classList.remove("infobar-open");
  220. infobarContent.onclick = event => {
  221. if (event.button === 0) {
  222. displayInfobar(infobarContent);
  223. return false;
  224. }
  225. };
  226. }
  227. function createElement(tagName, parentElement) {
  228. const element = document.createElement(tagName);
  229. parentElement.appendChild(element);
  230. Array.from(getComputedStyle(element)).forEach(property => element.style.setProperty(property, "initial", "important"));
  231. return element;
  232. }
  233. })();