main.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463
  1. document.addEventListener('DOMContentLoaded', () => {
  2. // Cookies
  3. const cookieBookModalName = 'bulma_closed_book_modal';
  4. const cookieBookModal = Cookies.getJSON(cookieBookModalName) || false;
  5. // Sidebar links
  6. const $categories = getAll('#categories .bd-category');
  7. if ($categories.length > 0) {
  8. $categories.forEach(el => {
  9. const toggle_el = el.querySelector('.bd-category-toggle');
  10. toggle_el.addEventListener('click', event => {
  11. closeCategories(el);
  12. el.classList.toggle('is-active');
  13. });
  14. });
  15. }
  16. function closeCategories(current_el) {
  17. $categories.forEach(el => {
  18. if (current_el == el) {
  19. return;
  20. }
  21. el.classList.remove('is-active');
  22. });
  23. }
  24. const anchors_ref_el = document.getElementById('anchorsReference');
  25. const anchors_el = document.getElementById('anchors');
  26. const anchor_links_el = getAll('.bd-anchor-link');
  27. let anchors_by_id = {};
  28. let anchors_order = [];
  29. let anchor_nav_els = [];
  30. if (anchors_el && anchor_links_el.length > 0) {
  31. anchors_el.classList.add('is-active');
  32. const anchors_el_list = anchors_el.querySelector('.bd-anchors-list');
  33. anchor_links_el.forEach((el, index) => {
  34. const link_target = el.getAttribute('href');
  35. const link_text = el.previousElementSibling.innerText;
  36. if (link_text != '') {
  37. const item_el = createAnchorLink(link_text, link_target);
  38. anchors_el_list.appendChild(item_el);
  39. const anchor_key = link_target.substring(1); // #target -> target
  40. anchors_by_id[anchor_key] = {
  41. id: anchor_key,
  42. index,
  43. target: link_target,
  44. text: link_text,
  45. nav_el: item_el,
  46. };
  47. anchors_order.push(anchor_key);
  48. anchor_nav_els.push(item_el);
  49. }
  50. });
  51. const back_to_top_el = createAnchorLink('Back to top', '');
  52. back_to_top_el.onclick = scrollToTop;
  53. anchors_el_list.appendChild(back_to_top_el);
  54. }
  55. function scrollToTop() {
  56. window.scrollTo(0, 0);
  57. }
  58. function createAnchorLink(text, target) {
  59. const item_el = document.createElement('li');
  60. const link_el = document.createElement('a');
  61. const text_node = document.createTextNode(text);
  62. if (target) {
  63. link_el.setAttribute('href', target);
  64. }
  65. link_el.appendChild(text_node);
  66. item_el.appendChild(link_el);
  67. return item_el;
  68. }
  69. function closeCategories(current_el) {
  70. $categories.forEach(el => {
  71. if (current_el == el) {
  72. return;
  73. }
  74. el.classList.remove('is-active');
  75. });
  76. }
  77. // Meta links
  78. const $metalinks = getAll('#meta a');
  79. if ($metalinks.length > 0) {
  80. $metalinks.forEach($el => {
  81. $el.addEventListener('click', event => {
  82. event.preventDefault();
  83. const target = $el.getAttribute('href');
  84. const $target = document.getElementById(target.substring(1));
  85. $target.scrollIntoView(true);
  86. return false;
  87. });
  88. });
  89. }
  90. // Dropdowns
  91. const $dropdowns = getAll('.dropdown:not(.is-hoverable)');
  92. if ($dropdowns.length > 0) {
  93. $dropdowns.forEach($el => {
  94. $el.addEventListener('click', event => {
  95. event.stopPropagation();
  96. $el.classList.toggle('is-active');
  97. });
  98. });
  99. document.addEventListener('click', event => {
  100. closeDropdowns();
  101. });
  102. }
  103. function closeDropdowns() {
  104. $dropdowns.forEach($el => {
  105. $el.classList.remove('is-active');
  106. });
  107. }
  108. // Toggles
  109. const $burgers = getAll('.burger');
  110. if ($burgers.length > 0) {
  111. $burgers.forEach($el => {
  112. $el.addEventListener('click', () => {
  113. const target = $el.dataset.target;
  114. const $target = document.getElementById(target);
  115. $el.classList.toggle('is-active');
  116. $target.classList.toggle('is-active');
  117. });
  118. });
  119. }
  120. // Modals
  121. const rootEl = document.documentElement;
  122. const $modals = getAll('.modal');
  123. const $modalButtons = getAll('.modal-button');
  124. const $modalCloses = getAll('.modal-background, .modal-close, .modal-card-head .delete, .modal-card-foot .button');
  125. if ($modalButtons.length > 0) {
  126. $modalButtons.forEach($el => {
  127. $el.addEventListener('click', () => {
  128. const target = $el.dataset.target;
  129. openModal(target);
  130. });
  131. });
  132. }
  133. if ($modalCloses.length > 0) {
  134. $modalCloses.forEach($el => {
  135. $el.addEventListener('click', () => {
  136. closeModals();
  137. });
  138. });
  139. }
  140. function openModal(target) {
  141. const $target = document.getElementById(target);
  142. rootEl.classList.add('is-clipped');
  143. $target.classList.add('is-active');
  144. }
  145. function closeModals() {
  146. rootEl.classList.remove('is-clipped');
  147. $modals.forEach($el => {
  148. $el.classList.remove('is-active');
  149. });
  150. }
  151. document.addEventListener('keydown', event => {
  152. const e = event || window.event;
  153. if (e.keyCode === 27) {
  154. closeModals();
  155. closeDropdowns();
  156. }
  157. });
  158. // Clipboard
  159. const $highlights = getAll('.highlight');
  160. let itemsProcessed = 0;
  161. if ($highlights.length > 0) {
  162. $highlights.forEach($el => {
  163. const copyEl = '<button class="button is-small bd-copy">Copy</button>';
  164. const expandEl = '<button class="button is-small bd-expand">Expand</button>';
  165. $el.insertAdjacentHTML('beforeend', copyEl);
  166. const $parent = $el.parentNode;
  167. if ($parent && $parent.classList.contains('bd-is-more')) {
  168. const showEl = '<button class="bd-show"><div><span class="icon"><i class="fas fa-code"></i></span> <strong>Show code</strong></div></button>';
  169. $el.insertAdjacentHTML('beforeend', showEl);
  170. } else if ($el.firstElementChild.scrollHeight > 480 && $el.firstElementChild.clientHeight <= 480) {
  171. $el.insertAdjacentHTML('beforeend', expandEl);
  172. }
  173. itemsProcessed++;
  174. if (itemsProcessed === $highlights.length) {
  175. addHighlightControls();
  176. }
  177. });
  178. }
  179. function addHighlightControls() {
  180. const $highlightButtons = getAll('.highlight .bd-copy, .highlight .bd-expand');
  181. $highlightButtons.forEach($el => {
  182. $el.addEventListener('mouseenter', () => {
  183. $el.parentNode.classList.add('bd-is-hovering');
  184. });
  185. $el.addEventListener('mouseleave', () => {
  186. $el.parentNode.classList.remove('bd-is-hovering');
  187. });
  188. });
  189. const $highlightExpands = getAll('.highlight .bd-expand');
  190. $highlightExpands.forEach($el => {
  191. $el.addEventListener('click', () => {
  192. $el.parentNode.firstElementChild.style.maxHeight = 'none';
  193. });
  194. });
  195. const $highlightShows = getAll('.highlight .bd-show');
  196. $highlightShows.forEach($el => {
  197. $el.addEventListener('click', () => {
  198. $el.parentNode.parentNode.classList.remove('bd-is-more-clipped');
  199. });
  200. });
  201. }
  202. setTimeout(() => {
  203. new Clipboard('.bd-copy', {
  204. target: trigger => {
  205. return trigger.previousElementSibling.firstElementChild;
  206. }
  207. });
  208. }, 100);
  209. // Functions
  210. function getAll(selector) {
  211. return Array.prototype.slice.call(document.querySelectorAll(selector), 0);
  212. }
  213. // Scrolling
  214. const html_el = document.documentElement;
  215. const navbarEl = document.getElementById('navbar');
  216. const navbarBurger = document.getElementById('navbarBurger');
  217. const specialShadow = document.getElementById('specialShadow');
  218. const NAVBAR_HEIGHT = 52;
  219. const THRESHOLD = 160;
  220. let navbarOpen = false;
  221. let horizon = NAVBAR_HEIGHT;
  222. let whereYouStoppedScrolling = 0;
  223. let scrollFactor = 0;
  224. let currentTranslate = 0;
  225. navbarBurger.addEventListener('click', el => {
  226. navbarOpen = !navbarOpen;
  227. if (navbarOpen) {
  228. rootEl.classList.add('bd-is-clipped-touch');
  229. } else {
  230. rootEl.classList.remove('bd-is-clipped-touch');
  231. }
  232. });
  233. // Anchors highlight
  234. let past_anchors = [];
  235. anchor_links_el.reverse();
  236. const trigger_offset = 24 ; // In pixels
  237. const typo_el = document.getElementById('typo');
  238. function whenScrolling() {
  239. if (anchors_ref_el) {
  240. const bounds = anchors_ref_el.getBoundingClientRect();
  241. const anchors_height = anchors_el.clientHeight;
  242. const typo_bounds = typo_el.getBoundingClientRect();
  243. const typo_height = typo_el.clientHeight;
  244. if (bounds.top < 1 && typo_bounds.top - anchors_height + typo_height > 0) {
  245. anchors_el.classList.add('is-pinned');
  246. } else {
  247. anchors_el.classList.remove('is-pinned');
  248. }
  249. anchor_links_el.some(el => {
  250. const bounds = el.getBoundingClientRect();
  251. const href = el.getAttribute('href');
  252. const key = href.substring(1); // #target -> target
  253. if (bounds.top < 1 + trigger_offset && past_anchors.indexOf(key) == -1) {
  254. past_anchors.push(key);
  255. highlightAnchor();
  256. return;
  257. } else if (bounds.top > 0 + trigger_offset && past_anchors.indexOf(key) != -1) {
  258. removeFromArray(past_anchors, key);
  259. highlightAnchor();
  260. return;
  261. }
  262. });
  263. }
  264. }
  265. function highlightAnchor() {
  266. const future_anchors = anchors_order.diff(past_anchors);
  267. let highest_index = -1;
  268. let highest_anchor_key = '';
  269. if (past_anchors.length > 0) {
  270. past_anchors.forEach((key, index) => {
  271. const anchor = anchors_by_id[key];
  272. anchor.nav_el.className = 'is-past';
  273. // Keep track of the bottom most item
  274. if (anchor.index > highest_index) {
  275. highest_index = anchor.index;
  276. highest_anchor_key = key;
  277. }
  278. });
  279. if (highest_anchor_key in anchors_by_id) {
  280. anchors_by_id[highest_anchor_key].nav_el.className = 'is-current';
  281. }
  282. }
  283. if (future_anchors.length > 0) {
  284. future_anchors.forEach((key, index) => {
  285. const anchor = anchors_by_id[key];
  286. anchor.nav_el.className = '';
  287. });
  288. }
  289. }
  290. // Scroll
  291. function upOrDown(lastY, currentY) {
  292. if (currentY >= lastY) {
  293. return goingDown(currentY);
  294. }
  295. return goingUp(currentY);
  296. }
  297. function goingDown(currentY) {
  298. const trigger = NAVBAR_HEIGHT;
  299. whereYouStoppedScrolling = currentY;
  300. if (currentY > horizon) {
  301. horizon = currentY;
  302. }
  303. translateHeader(currentY, false);
  304. }
  305. function goingUp(currentY) {
  306. const trigger = 0;
  307. if (currentY < (whereYouStoppedScrolling - NAVBAR_HEIGHT)) {
  308. horizon = currentY + NAVBAR_HEIGHT;
  309. }
  310. translateHeader(currentY, true);
  311. }
  312. function constrainDelta(delta) {
  313. return Math.max(0, Math.min(delta, NAVBAR_HEIGHT));
  314. }
  315. function translateHeader(currentY, upwards) {
  316. // let topTranslateValue;
  317. let translateValue;
  318. if (upwards && currentTranslate == 0) {
  319. translateValue = 0;
  320. } else if (currentY <= NAVBAR_HEIGHT) {
  321. translateValue = currentY * -1;
  322. } else {
  323. const delta = constrainDelta(Math.abs(currentY - horizon));
  324. translateValue = delta - NAVBAR_HEIGHT;
  325. }
  326. if (translateValue != currentTranslate) {
  327. const navbarStyle = `
  328. transform: translateY(${translateValue}px);
  329. `;
  330. currentTranslate = translateValue;
  331. navbarEl.setAttribute('style', navbarStyle);
  332. }
  333. if (currentY > THRESHOLD * 2) {
  334. scrollFactor = 1;
  335. } else if (currentY > THRESHOLD) {
  336. scrollFactor = (currentY - THRESHOLD) / THRESHOLD;
  337. } else {
  338. scrollFactor = 0;
  339. }
  340. const translateFactor = 1 + translateValue / NAVBAR_HEIGHT;
  341. if (specialShadow) {
  342. specialShadow.style.opacity = scrollFactor;
  343. specialShadow.style.transform = 'scaleY(' + translateFactor + ')';
  344. }
  345. }
  346. let ticking = false;
  347. let lastY = 0;
  348. window.addEventListener('scroll', function() {
  349. const currentY = window.scrollY;
  350. if (!ticking) {
  351. window.requestAnimationFrame(function() {
  352. // upOrDown(lastY, currentY);
  353. whenScrolling();
  354. ticking = false;
  355. lastY = currentY;
  356. });
  357. }
  358. ticking = true;
  359. });
  360. // Utils
  361. function removeFromArray(array, value) {
  362. if (array.includes(value)) {
  363. const value_index = array.indexOf(value);
  364. array.splice(value_index, 1);
  365. }
  366. return array;
  367. }
  368. Array.prototype.diff = function(a) {
  369. return this.filter(function(i) {return a.indexOf(i) < 0;});
  370. };
  371. });