help.js 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
  2. /*
  3. * This file is part of the LibreOffice project.
  4. *
  5. * This Source Code Form is subject to the terms of the Mozilla Public
  6. * License, v. 2.0. If a copy of the MPL was not distributed with this
  7. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
  8. */
  9. // Pagination and bookmark search
  10. var url = window.location.pathname;
  11. var moduleRegex = new RegExp('text\\/(\\w+)\\/');
  12. var regexArray = moduleRegex.exec(url);
  13. var userModule = currentModule();
  14. var modules = ['CALC', 'WRITER', 'IMPRESS', 'DRAW', 'BASE', 'MATH', 'CHART', 'BASIC', 'SHARED'];
  15. var indexEl = document.getElementsByClassName("index")[0];
  16. var fullLinks = fullLinkify(indexEl, bookmarks, modules, userModule);
  17. var search = document.getElementById('search-bar');
  18. search.addEventListener('keyup', debounce(filter, 100, indexEl));
  19. var flexIndex = new FlexSearch.Document({ document: {
  20. // Only the text content gets indexed, the others get stored as-is
  21. index: [{
  22. field: 'text',
  23. tokenize: 'full'
  24. }],
  25. store: ['url','app','text']
  26. }
  27. });
  28. // Populate FlexSearch index
  29. loadSearch();
  30. // Render the unfiltered index list on page load
  31. fillIndex(indexEl, fullLinks, modules);
  32. // Preserve search input value during the session
  33. search.value = sessionStorage.getItem('searchsave');
  34. if (search.value !== undefined) {
  35. filter(indexEl);
  36. }
  37. window.addEventListener('unload', function(event) {
  38. sessionStorage.setItem('searchsave', search.value);
  39. });
  40. function getQuery(q) {
  41. var pattern = new RegExp('[?&]' + q + '=([^&]+)');
  42. var param = window.location.search.match(pattern);
  43. if (param) {
  44. return param[1];
  45. }
  46. return null;
  47. }
  48. function currentModule() {
  49. // We need to know the module that the user is using when they call for help
  50. let module = getQuery('DbPAR');
  51. let moduleFromURL = regexArray[1].toUpperCase();
  52. if (module == null) {
  53. // first deal with snowflake Base
  54. if(url.indexOf('/sdatabase/') !== -1) {
  55. module = 'BASE';
  56. } else {
  57. if (null === regexArray || moduleFromURL === 'SHARED') {
  58. // comes from search or elsewhere, no defined module in URL
  59. module = 'SHARED'
  60. } else {
  61. // drop the 's' from the start
  62. module = moduleFromURL.substring(1);
  63. }
  64. }
  65. }
  66. return module;
  67. };
  68. function fullLinkify(indexEl, bookmarks, modules, currentModule) {
  69. var fullLinkified = '';
  70. // if user is not on a shared category page, limit the index to the current module + shared
  71. if(currentModule !== 'SHARED') {
  72. bookmarks = bookmarks.filter(function(obj) {
  73. return obj['app'] === currentModule || obj['app'] === 'SHARED';
  74. });
  75. }
  76. bookmarks.forEach(function(obj) {
  77. fullLinkified += '<a href="' + obj['url'] + '" class="' + obj['app'] + '" dir="auto">' + obj['text'] + '</a>';
  78. });
  79. return fullLinkified;
  80. }
  81. function loadSearch() {
  82. bookmarks.forEach((el, i) => {
  83. flexIndex.add(i, el);
  84. });
  85. }
  86. function fillIndex(indexEl, content, modules) {
  87. indexEl.innerHTML = content;
  88. var indexKids = indexEl.children;
  89. for (var i = 0, len = indexKids.length; i < len; i++) {
  90. indexKids[i].removeAttribute("id");
  91. }
  92. modules.forEach(function(module) {
  93. var moduleHeader = indexEl.getElementsByClassName(module)[0];
  94. if (typeof moduleHeader !== 'undefined') {
  95. // let's wrap the header in a span, so the ::before element will not become a link
  96. moduleHeader.outerHTML = '<span id="' + module + '" class="' + module + '">' + moduleHeader.outerHTML + '</span>';
  97. }
  98. });
  99. Paginator(indexEl);
  100. }
  101. // filter the index list based on search field input
  102. function filter(indexList) {
  103. let group = [];
  104. let target = search.value.trim();
  105. let filtered = '';
  106. if (target.length < 1) {
  107. fillIndex(indexEl, fullLinks, modules);
  108. return;
  109. }
  110. // Regex for highlighting the match
  111. let regex = new RegExp(target.split(/\s+/).filter((i) => i?.length).join("|"), 'gi');
  112. let results = flexIndex.search(target, { pluck: "text", enrich: true, limit: 1000 });
  113. // Similarly to fullLinkify(), limit search results to the user's current module + shared
  114. // unless they're somehow not coming from a module.
  115. if(userModule !== 'SHARED') {
  116. resultModules = [userModule, 'SHARED'];
  117. } else {
  118. resultModules = modules;
  119. }
  120. // tdf#123506 - Group the filtered list into module groups, keeping the ordering
  121. modules.forEach(function(module) {
  122. group[module] = '';
  123. });
  124. results.forEach(function(result) {
  125. group[result.doc.app] += '<a href="' + result.doc.url + '" class="' + result.doc.app + '">' + result.doc.text.replace(regex, (match) => `<strong>${match}</strong>`) + '</a>';
  126. });
  127. resultModules.forEach(function(module) {
  128. if (group[module].length > 0) {
  129. filtered += group[module];
  130. }
  131. });
  132. fillIndex(indexList, filtered, modules);
  133. };
  134. // delay the rendering of the filtered results while user is typing
  135. function debounce(fn, wait, indexList) {
  136. var timeout;
  137. return function() {
  138. clearTimeout(timeout);
  139. timeout = setTimeout(function() {
  140. fn.call(this, indexList);
  141. }, (wait || 150));
  142. };
  143. }
  144. // copy pycode, sqlcode and bascode to clipboard on mouse click
  145. // Show border when copy is done
  146. divcopyable(document.getElementsByClassName("bascode"));
  147. divcopyable(document.getElementsByClassName("pycode"));
  148. divcopyable(document.getElementsByClassName("sqlcode"));
  149. function divcopyable(itemcopyable){
  150. for (var i = 0, len = itemcopyable.length; i < len; i++) {
  151. (function() {
  152. var item = itemcopyable[i];
  153. function changeBorder(item, color) {
  154. var saveBorder = item.style.border;
  155. item.style.borderColor = color;
  156. setTimeout(function() {
  157. item.style.border = saveBorder;
  158. }, 150);
  159. }
  160. item.onclick = function() {
  161. document.execCommand("copy");
  162. changeBorder(item, "#18A303");
  163. };
  164. item.addEventListener("copy", function(event) {
  165. event.preventDefault();
  166. if (event.clipboardData) {
  167. event.clipboardData.setData("text/plain", item.textContent);
  168. }
  169. });
  170. }());
  171. }
  172. }
  173. // copy useful content to clipboard on mouse click
  174. var copyable = document.getElementsByClassName("input");
  175. for (var i = 0, len = copyable.length; i < len; i++) {
  176. (function() {
  177. var item = copyable[i];
  178. function changeColor(item, color, colorToChangeBackTo) {
  179. item.style.backgroundColor = color;
  180. setTimeout(function() {
  181. item.style.backgroundColor = colorToChangeBackTo;
  182. }, 150);
  183. }
  184. item.onclick = function() {
  185. document.execCommand("copy");
  186. changeColor(item, "#18A303", "transparent");
  187. };
  188. item.addEventListener("copy", function(event) {
  189. event.preventDefault();
  190. if (event.clipboardData) {
  191. event.clipboardData.setData("text/plain", item.textContent);
  192. }
  193. });
  194. }());
  195. }
  196. // auto-expand contents per subitem
  197. var pathname = window.location.pathname;
  198. var pathRegex = /text\/.*\.html$/;
  199. var linkIndex = 0;
  200. var contentMatch = pathname.match(pathRegex);
  201. function linksMatch(content) {
  202. var linkMatch = new RegExp(content);
  203. var links = document.getElementById("Contents").getElementsByTagName("a");
  204. for (var i = 0, len = links.length; i < len; i++) {
  205. if (links[i].href.match(linkMatch)) {
  206. return i;
  207. }
  208. }
  209. }
  210. linkIndex = linksMatch(contentMatch);
  211. if (typeof linkIndex !== "undefined") {
  212. var current = document.getElementById("Contents").getElementsByTagName("a")[linkIndex];
  213. var cItem = current.parentElement;
  214. var parents = [];
  215. while (cItem.parentElement && !cItem.parentElement.matches("#Contents") && parents.indexOf(cItem.parentElement) == -1) {
  216. parents.push(cItem = cItem.parentElement);
  217. }
  218. var liParents = [].filter.call(parents, function(item) {
  219. return item.matches("li");
  220. });
  221. for (var i = 0, len = liParents.length; i < len; i++) {
  222. var input = liParents[i].querySelectorAll(':scope > input');
  223. document.getElementById(input[0].id).checked = true;
  224. }
  225. current.classList.add('contents-current');
  226. }
  227. // close navigation menus when clicking anywhere on the page
  228. // (ignoring menu button clicks and mobile browsing)
  229. document.addEventListener('click', function(event) {
  230. let a11yButton = event.target.getAttribute("data-a11y-toggle");
  231. let vw = Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0);
  232. if (!a11yButton && vw >= 960) {
  233. document.querySelectorAll("[data-a11y-toggle] + nav").forEach((el) => {
  234. el.setAttribute("aria-hidden", true);
  235. });
  236. }
  237. });
  238. // YouTube consent click. This only works for a single video.
  239. let youtubePlaceholder = document.querySelector(".youtube_placeholder");
  240. if (youtubePlaceholder) {
  241. youtubePlaceholder.prepend(...document.querySelectorAll(".youtube_consent"));
  242. }
  243. function youtubeLoader(ytId, width, height) {
  244. let iframeMarkup = `<iframe width="${width}" height="${height}" src="https://www.youtube-nocookie.com/embed/${ytId}?version=3" allowfullscreen="true" frameborder="0"></iframe>`;
  245. let placeholder = document.getElementById(ytId);
  246. placeholder.innerHTML = iframeMarkup;
  247. placeholder.removeAttribute("style");
  248. }
  249. /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */