content-infobar-web.js 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  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,iVBORw0KGgoAAAANSUhEUgAAABIAAAASBAMAAACk4JNkAAABhWlDQ1BJQ0MgcHJvZmlsZQAAKJF9kT1Iw0AcxV9TtaIVByuIOGSoTi2IijpKFYtgobQVWnUwufQLmjQkKS6OgmvBwY/FqoOLs64OroIg+AHi5Oik6CIl/i8ptIj14Lgf7+497t4BQq3EVLNjHFA1y0hEI2I6syr6XtGLLgwihBmJmXosuZhC2/F1Dw9f78I8q/25P0efkjUZ4BGJ55huWMQbxNObls55nzjACpJCfE4cMuiCxI9cl11+45x3WOCZASOVmCcOEIv5FpZbmBUMlXiKOKioGuULaZcVzluc1VKFNe7JX+jPaitJrtMcQRRLiCEOETIqKKIEC2FaNVJMJGg/0sY/7Pjj5JLJVQQjxwLKUCE5fvA/+N2tmZuccJP8EaDzxbY/RgHfLlCv2vb3sW3XTwDvM3ClNf3lGjD7SXq1qQWPgP5t4OK6qcl7wOUOMPSkS4bkSF6aQi4HvJ/RN2WAgVugZ83trbGP0wcgRV0t3wAHh8BYnrLX27y7u7W3f880+vsB4D1y0ysS64EAAAAJcEhZcwAALiMAAC4jAXilP3YAAAAHdElNRQfkCwYVMBTMyx7sAAAAGXRFWHRDb21tZW50AENyZWF0ZWQgd2l0aCBHSU1QV4EOFwAAAA9QTFRFAAAAfIeNfoeMgIeLioqKCstQngAAAAF0Uk5TAEDm2GYAAAABYktHRASPaNlRAAAAO0lEQVQIHQXBAQkAMAwDsEANzMMNFOrf2xMAACCbrSh4KCQoNCgiKCooAUqBgqAgOBAcCA4E2bYVAAB80bAE6Tea12kAAAAASUVORK5CYII=";
  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. -moz-user-select: none;
  99. opacity: .7;
  100. cursor: pointer;
  101. padding-left: 0;
  102. padding-right: 0;
  103. padding-top: 0;
  104. padding-bottom: 0;
  105. border: 2px solid #eee;
  106. background-size: 70% 70%;
  107. transition: all 250ms;
  108. font-size: 13px;
  109. }
  110. .infobar:hover {
  111. opacity: 1;
  112. }
  113. .infobar-open {
  114. opacity: 1;
  115. background-color: #f9f9f9;
  116. cursor: auto;
  117. color: #2d2d2d;
  118. padding-top: 2px;
  119. padding-bottom: 2px;
  120. border: 2px solid #878787;
  121. background-image: none;
  122. border-radius: 8px;
  123. user-select: initial;
  124. -moz-user-select: initial;
  125. }
  126. .infobar-close-button {
  127. display: none;
  128. opacity: .7;
  129. padding-left: 8px;
  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. padding-right: 8px;
  154. line-height: 11px;
  155. cursor: pointer;
  156. user-select: none;
  157. }
  158. .infobar-link-icon {
  159. padding-top: 3px;
  160. padding-left: 2px;
  161. cursor: pointer;
  162. opacity: .7;
  163. transition: opacity 250ms;
  164. }
  165. .infobar-link-icon:hover {
  166. opacity: 1;
  167. }
  168. .infobar-open .infobar-close-button, .infobar-open .infobar-content, .infobar-open .infobar-link {
  169. display: inline-block;
  170. }
  171. `;
  172. shadowRoot.appendChild(styleElement);
  173. const infobarContent = document.createElement("div");
  174. infobarContent.classList.add("infobar");
  175. shadowRoot.appendChild(infobarContent);
  176. const closeElement = document.createElement("span");
  177. closeElement.classList.add("infobar-close-button");
  178. infobarContent.appendChild(closeElement);
  179. closeElement.textContent = "✕";
  180. closeElement.onclick = event => {
  181. if (event.button === 0) {
  182. infobarElement.remove();
  183. }
  184. };
  185. const infoElement = document.createElement("span");
  186. infobarContent.appendChild(infoElement);
  187. infoElement.classList.add("infobar-content");
  188. infoElement.textContent = infoData;
  189. const linkElement = document.createElement("a");
  190. linkElement.classList.add("infobar-link");
  191. infobarContent.appendChild(linkElement);
  192. linkElement.target = "_blank";
  193. linkElement.rel = "noopener noreferrer";
  194. linkElement.title = "Open source URL: " + url;
  195. linkElement.href = url;
  196. const imgElement = document.createElement("img");
  197. imgElement.classList.add("infobar-link-icon");
  198. linkElement.appendChild(imgElement);
  199. imgElement.src = LINK_ICON;
  200. hideInfobar(infobarContent);
  201. document.addEventListener("click", event => {
  202. if (event.button === 0) {
  203. let element = event.target;
  204. while (element && element != infobarElement) {
  205. element = element.parentElement;
  206. }
  207. if (element != infobarElement) {
  208. hideInfobar(infobarContent);
  209. }
  210. }
  211. });
  212. }
  213. }
  214. function displayInfobar(infobarContent) {
  215. infobarContent.classList.add("infobar-open");
  216. infobarContent.onclick = null;
  217. infobarContent.onmouseout = null;
  218. }
  219. function hideInfobar(infobarContent) {
  220. infobarContent.classList.remove("infobar-open");
  221. infobarContent.onclick = event => {
  222. if (event.button === 0) {
  223. displayInfobar(infobarContent);
  224. return false;
  225. }
  226. };
  227. }
  228. function createElement(tagName, parentElement) {
  229. const element = document.createElement(tagName);
  230. parentElement.appendChild(element);
  231. Array.from(getComputedStyle(element)).forEach(property => element.style.setProperty(property, "initial", "important"));
  232. return element;
  233. }
  234. })();