PageSourceSearch

https://soci-269-w9.netlify.app/index_files/libs/revealjs/plugin/simplemenu/simplemenu.js

js soci-269-w9.netlify.app collected 2026-10-03 11:26:24 UTC 22,622 bytes, 644 lines download raw bytes

1
2/*****************************************************************
3 * @author: Martijn De Jongh (Martino), [email protected]
4 * https://github.com/Martinomagnifico
5 *
6 * Simplemenu.js for Reveal.js 
7 * Version 2.0.1
8 * 
9 * @license 
10 * MIT licensed
11 *
12 * Thanks to:
13 *  - Hakim El Hattab, Reveal.js 
14 ******************************************************************/
15
16
17
18(function (global, factory) {
19	typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
20	typeof define === 'function' && define.amd ? define(factory) :
21	(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Simplemenu = factory());
22})(this, (function () { 'use strict';
23
24	const Plugin = () => {
25	  let options = {};
26	  const vars = {};
27	  const sections = {};
28	  const mainArray = [];
29	  let autoListItems = [];
30	  let manualListItems = [];
31
32	  const debugLog = text => {
33	    if (options.debug) console.log(text);
34	  };
35
36	  const isObject = item => {
37	    return item && typeof item === 'object' && !Array.isArray(item);
38	  };
39
40	  const mergeDeep = function (target) {
41	    for (var _len = arguments.length, sources = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
42	      sources[_key - 1] = arguments[_key];
43	    }
44
45	    if (!sources.length) return target;
46	    const source = sources.shift();
47
48	    if (isObject(target) && isObject(source)) {
49	      for (const key in source) {
50	        if (isObject(source[key])) {
51	          if (!target[key]) Object.assign(target, {
52	            [key]: {}
53	          });
54	          mergeDeep(target[key], source[key]);
55	        } else {
56	          Object.assign(target, {
57	            [key]: source[key]
58	          });
59	        }
60	      }
61	    }
62
63	    return mergeDeep(target, ...sources);
64	  };
65
66	  const selectionArray = (container, selectors) => {
67	    let selections = container.querySelectorAll(selectors);
68	    let selectionarray = Array.prototype.slice.call(selections);
69	    return selectionarray;
70	  };
71
72	  const pluginPath = filename => {
73	    let path;
74	    let pluginScript = document.querySelector(`script[src$="${filename}"]`);
75
76	    if (pluginScript) {
77	      path = pluginScript.getAttribute("src").slice(0, -1 * filename.length);
78	    } else {
79	      path = (typeof document === 'undefined' && typeof location === 'undefined' ? new (require('u' + 'rl').URL)('file:' + __filename).href : typeof document === 'undefined' ? location.href : (document.currentScript && document.currentScript.src || new URL('simplemenu.js', document.baseURI).href)).slice(0, (typeof document === 'undefined' && typeof location === 'undefined' ? new (require('u' + 'rl').URL)('file:' + __filename).href : typeof document === 'undefined' ? location.href : (document.currentScript && document.currentScript.src || new URL('simplemenu.js', document.baseURI).href)).lastIndexOf('/') + 1);
80	    }
81
82	    return path;
83	  };
84
85	  const isBefore = (a, b) => {
86	    var all = document.getElementsByTagName('*');
87
88	    for (var i = 0; i < all.length; ++i) {
89	      if (all[i] === a) return true;else if (all[i] === b) return false;
90	    }
91	  };
92
93	  const isStack = section => {
94	    let isStack = false;
95
96	    for (let i = 0; i < section.childNodes.length; i++) {
97	      if (section.childNodes[i].tagName == "SECTION") {
98	        isStack = true;
99	        break;
100	      }
101	    }
102
103	    return isStack;
104	  };
105
106	  const createNode = thehtml => {
107	    const fragment = document.createRange().createContextualFragment(thehtml);
108	    return fragment.firstElementChild;
109	  };
110
111	  const loadStyle = (url, type, callback) => {
112	    let head = document.querySelector('head');
113	    let style = document.createElement('link');
114	    style.rel = 'stylesheet';
115	    style.href = url;
116
117	    let finish = () => {
118	      if (typeof callback === 'function') {
119	        callback.call();
120	        callback = null;
121	      }
122	    };
123
124	    style.onload = finish;
125
126	    style.onreadystatechange = function () {
127	      if (this.readyState === 'loaded') {
128	        finish();
129	      }
130	    };
131
132	    head.appendChild(style);
133	  };
134
135	  const checkOccurrence = (array, element) => {
136	    let counter = 0;
137
138	    for (let i = 0; i <= array.length; i++) {
139	      if (array[i] == element) {
140	        counter++;
141	      }
142	    }
143
144	    return counter;
145	  };
146
147	  const menuArray = () => {
148	    const matchString = vars.matchString;
149	    let menulist = selectionArray(vars.viewport, `.${options.menuclass}`) ? selectionArray(vars.viewport, `.${options.menuclass}`) : [];
150	    let automenus = [];
151	    let manualmenus = [];
152
153	    if (menulist.length) {
154	      menulist.forEach(menu => {
155	        if (menu.getElementsByTagName('li').length < 1) {
156	          menu.setAttribute('data-simplemenu-auto', '');
157	          automenus.push(menu);
158	        } else {
159	          if (options.selectby == "data-name" || options.selectby == "name") {
160	            let existingListItems = selectionArray(menu, `.${options.menuclass} ${options.activeelement}`);
161	            existingListItems.forEach(listItem => {
162	              if (!listItem.dataset[matchString]) {
163	                let content = listItem.textContent || listItem.querySelector('a').textContent;
164	                listItem.setAttribute(`data-${matchString}`, content);
165	              }
166	            });
167	          }
168
169	          manualmenus.push(menu);
170	        }
171	      });
172	      return {
173	        automenus: automenus,
174	        manualmenus: manualmenus
175	      };
176	    } else {
177	      return false;
178	    }
179	  };
180
181	  const setScale = revealScale => {
182	    let totalScale = revealScale * vars.userScale;
183	    vars.viewport.style.setProperty('--simplemenu-scale', totalScale.toFixed(3));
184	  };
185
186	  const moveRevealUI = (curUiEl, newUiEl) => {
187	    let newUiElClassList = newUiEl.classList;
188	    newUiEl.parentNode.replaceChild(curUiEl, newUiEl);
189	    curUiEl.classList = newUiElClassList;
190	  };
191
192	  const getRevealUI = () => {
193	    let revealUIs = ['controls', 'slide-number'];
194	    revealUIs.forEach(uielement => {
195	      let curUiEl = vars.deck.getRevealElement().querySelector(`.reveal > .${uielement}`);
196	      let newUiEl = vars.deck.getRevealElement().querySelector(`.reveal > * .${uielement}`);
197
198	      if (curUiEl && newUiEl) {
199	        moveRevealUI(curUiEl, newUiEl);
200	      }
201	    });
202	  };
203
204	  function copyDataAttributes(source, target) {
205	    [...source.attributes].filter(attr => attr.nodeName.indexOf('data') > -1).forEach(attr => {
206	      target.setAttribute(attr.nodeName, attr.nodeValue);
207	    });
208	  }
209
210	  const prepareSlides = () => {
211	    debugLog("Preparing slides");
212	    sections.all = selectionArray(vars.viewport, "section");
213	    sections.all.forEach(section => {
214	      // In Markdown environments, setting a data-name of a stack is not directly possible. 
215	      // Satting a data-stack-name on the first child solves this.
216	      if (!section.parentNode.dataset.name && section.dataset && section.dataset.stackName) {
217	        section.parentNode.dataset.name = section.dataset.stackName;
218	      } // If a section has a data-sm='none', it will also remove the data-name.
219
220
221	      if (section.dataset && section.dataset[vars.matchString] && section.dataset[vars.matchString] == "false" && section.dataset.name) {
222	        delete section.dataset.name;
223	      }
224	    }); // Get all of the kinds of sections
225
226	    sections.top = sections.all.filter(section => section.parentNode.classList.contains('slides') && !(section.dataset[vars.matchString] && section.dataset[vars.matchString] == "false"));
227	    sections.named = sections.top.filter(section => section.dataset.name || section.getAttribute('name'));
228	    sections.namedvisible = sections.named.filter(section => section.dataset.visibility != "hidden"); // Go through all the named sections
229
230	    let namedsectionMatches = [];
231	    sections.named.forEach(namedsection => {
232	      // The 'name' attribute is also allowed. 
233	      let matchName = namedsection.dataset.name || namedsection.getAttribute('name'); // Named sections can have the same name, but should then be differentiated.
234
235	      namedsectionMatches.push(matchName);
236	      let dupsBefore = matchName && checkOccurrence(namedsectionMatches, matchName) > 1 ? `-${checkOccurrence(namedsectionMatches, matchName)}` : null;
237	      let match = dupsBefore ? matchName + dupsBefore : matchName; // We set the name of the match as a data-attribute
238
239	      namedsection.setAttribute(`data-${vars.matchString}`, match); // If the (named) section is not a stack and does not have an ID, we need to give it one.
240
241	      if (!isStack(namedsection) && !namedsection.id) {
242	        // Note: Quarto will already have assigned an ID, but it may also have been done manually.
243	        namedsection.id = match.toLowerCase().replace(/\W/g, '');
244	      } else if (isStack(namedsection)) {
245	        // Find the first (visible) section inside a stack.
246	        let allsects = selectionArray(namedsection, `section`);
247	        let allVisibleSects = allsects.filter(section => section.dataset.visibility != "hidden");
248	        let firstChildSection = allVisibleSects[0];
249
250	        if (firstChildSection && !firstChildSection.id) {
251	          firstChildSection.id = match.toLowerCase().replace(/\W/g, '');
252
253	          if (namedsection.id == firstChildSection.id) {
254	            namedsection.removeAttribute('id');
255	          }
256	        }
257	      }
258	    });
259	    let currentMatch = null;
260	    let currentid = null; // Get all the sections that are actually slides
261
262	    sections.regular = sections.all.filter(section => !isStack(section) && section.dataset.visibility != "hidden"); // Go through all the sections
263
264	    sections.regular.forEach((section, i) => {
265	      // Filling an array with the needed comparison information
266	      let isChildSection = isStack(section.parentNode) && section.parentNode.tagName == "SECTION";
267	      let theSection = isChildSection ? section.parentNode : section;
268	      let dataname = theSection.dataset.name;
269	      let name = theSection.getAttribute(`name`);
270	      let dataintl = theSection.getAttribute(vars.langattribute) ? theSection.getAttribute(vars.langattribute) : null;
271	      let parentid = section.parentNode.id ? section.parentNode.id : null;
272	      let id = section.id ? section.id : isChildSection ? parentid : null;
273	      let match = theSection.dataset[vars.matchString];
274
275	      if (match) {
276	        currentMatch = match;
277	      }
278
279	      if (id || match == "false") {
280	        currentid = i;
281	      }
282
283	      if (options.flat) {
284	        if (match != "false") {
285	          match = currentMatch;
286	        }
287	      }
288
289	      if (match == "false") {
290	        match = null;
291	      }
292
293	      if (dataname == "false") {
294	        dataname = null;
295	      }
296
297	      let sectionObject = {
298	        index: i,
299	        ...(section && {
300	          section
301	        }),
302	        ...(dataname && {
303	          dataname
304	        }),
305	        ...(name && {
306	          name
307	        }),
308	        ...(id && {
309	          id
310	        }),
311	        ...(match && {
312	          match
313	        }),
314	        ...(currentid && {
315	          currentid
316	        }),
317	        ...(dataintl && {
318	          dataintl
319	        })
320	      };
321	      mainArray.push(sectionObject);
322	    });
323	  };
324
325	  const prepareMenubars = () => {
326	    debugLog("Preparing menubars");
327	    let menubars = selectionArray(vars.viewport, `.${options.menubarclass}`) ? selectionArray(vars.viewport, `.${options.menubarclass}`) : [];
328
329	    if (options.barhtml.header) {
330	      // Generate header menubar
331	      let bar = createNode(options.barhtml.header);
332	      menubars.push(bar);
333	      vars.slides.before(bar);
334	    }
335
336	    if (options.barhtml.footer) {
337	      // Generate footer menubar
338	      let bar = createNode(options.barhtml.footer);
339	      menubars.push(bar);
340	      vars.slides.after(bar);
341	    }
342
343	    if (menubars.length) {
344	      // If menubar (pre-existing or just added):
345	      setScale(vars.deck.getScale());
346	      menubars.forEach((menubar, i) => {
347	        let barLocation = isBefore(menubar, vars.slides) ? "top" : "bottom";
348	        menubar.classList.add(barLocation);
349
350	        if (!menubar.id) {
351	          menubar.id = `${options.menubarclass}${barLocation}`;
352	        }
353
354	        menubar.classList.add("ready");
355	      });
356	      vars.menubars = menubars;
357	    } else {
358	      console.log("There are no menubars. You can still use Simplemenu to populate empty menus like in an Agenda or Table Of Contents.");
359	    }
360	  };
361
362	  const prepareMenus = () => {
363	    debugLog("Preparing menus");
364	    let menus = menuArray();
365
366	    if (!menus || menus && !menus.automenus) {
367	      console.log("There are no menus. Please add one or more menus manually or through the 'barhtml' option.");
368	      return;
369	    }
370
371	    if (menus.automenus.length >= 1 && sections.namedvisible.length >= 1) {
372	      // There are empty menus. Autofill them.
373	      let idArray = [];
374	      const autoMenuLinks = sections.namedvisible.map(section => {
375	        let match = section.dataset[vars.matchString];
376	        let name = section.dataset.name || section.getAttribute(`name`) || section.id;
377	        let id = section.id || name.toLowerCase().replace(/\W/g, '');
378	        idArray.push(id);
379
380	        if (vars.quarto) {
381	          id = mainArray.find(item => item.match === match).id;
382	        }
383
384	        let duplicatesBefore = checkOccurrence(idArray, id) > 1 ? `-${checkOccurrence(idArray, id)}` : '';
385	        let href = vars.quarto ? id : id + duplicatesBefore;
386	        let smmatchString = ` data-${vars.matchString}="${match}"`;
387	        let nameString = section.getAttribute(`name`) ? ` name="${section.getAttribute(`name`)}"` : '';
388	        let intlString = section.getAttribute(vars.langattribute) ? ` ${vars.langattribute}="${section.getAttribute(vars.langattribute)}"` : '';
389	        return `<li><a href="#/${href}"${intlString}${nameString}${smmatchString}>${name}</a></li>`;
390	      }).reduce((combinedHTML, itemHTML) => {
391	        let orderedHTML = vars.rtl ? itemHTML + combinedHTML : combinedHTML + itemHTML;
392	        return orderedHTML;
393	      });
394	      menus.automenus.forEach(automenu => {
395	        automenu.innerHTML = autoMenuLinks;
396	      });
397	      autoListItems = menus.automenus.map(menu => Array.from(menu.querySelectorAll(options.activeelement))).flat();
398	    }
399
400	    if (menus.manualmenus.length >= 1) {
401	      // There are pre-existing menus. Fix link to ID if needed.
402	      // Only get the listitems
403	      manualListItems = menus.manualmenus.map(menu => Array.from(menu.querySelectorAll(options.activeelement))).flat();
404	      manualListItems.forEach(listItem => {
405	        // Get the anchorlinks
406	        let linker = listItem.tagName == "a" ? listItem : listItem.querySelector('a');
407	        let linkhref = linker.getAttribute('href');
408
409	        if (linkhref === "#") {
410	          let newLink = listItem.dataset[vars.matchString].toLowerCase().replace(/\W/g, '');
411	          linker.href = `#/${newLink}`;
412	        }
413	      });
414	    }
415	  };
416
417	  const preparePrint = () => {
418	    const urlParams = new URLSearchParams(window.location.search);
419	    const hasPrintParam = urlParams.has('print-pdf');
420
421	    if (hasPrintParam) {
422	      mainArray.forEach(item => {
423	        let printSection = item.section;
424	        let datainfo = document.createElement("div");
425	        datainfo.classList.add("datainfo");
426	        copyDataAttributes(printSection, datainfo);
427	        let moreData = ['match', 'name', 'dataname', 'currentid', 'id', 'dataintl'];
428	        moreData.forEach(moreDataItem => {
429	          if (item[moreDataItem]) {
430	            datainfo.dataset[moreDataItem] = item[moreDataItem];
431	          }
432	        });
433	        printSection.appendChild(datainfo);
434	      });
435	    }
436	  };
437
438	  const prepare = resolve => {
439	    prepareSlides();
440	    prepareMenubars();
441	    prepareMenus();
442	    preparePrint();
443	    return setTimeout(resolve, 0);
444	  };
445
446	  const compare = (listItem, section) => {
447	    let menukind = listItem.parentNode.hasAttribute('data-simplemenu-auto') ? "auto" : "manual";
448	    let sectionmatch = section.match ? section.match : null;
449
450	    if (menukind == "manual") {
451	      if (options.selectby == "id") {
452	        sectionmatch = section.id ? section.id : section.currentid ? mainArray[section.currentid].id : null;
453	      } else if (options.selectby == "name") {
454	        sectionmatch = section.name;
455	      } else {
456	        sectionmatch = section.dataname;
457	      }
458	    }
459
460	    if (sectionmatch) {
461	      let menumatch = listItem.dataset[vars.matchString] || listItem.querySelector('a').dataset[vars.matchString];
462
463	      if (options.selectby == "id" && menukind == "manual") {
464	        let href = listItem.href || listItem.querySelector('a').href;
465	        let lastHref = href ? href.substring(href.lastIndexOf("/") + 1) : '';
466	        menumatch = lastHref;
467	      }
468
469	      if (options.selectby == "data-name" && menukind == "manual") {
470	        sectionmatch = section.dataname ? section.dataname : null;
471	      }
472
473	      if (menumatch && menumatch == sectionmatch) {
474	        listItem.classList.add(options.activeclass);
475	      } else {
476	        listItem.classList.remove(options.activeclass);
477	      }
478	    } else {
479	      listItem.classList.remove(options.activeclass);
480	    }
481	  };
482
483	  const checkSlidesNormal = event => {
484	    const index = sections.regular.indexOf(event.currentSlide);
485	    let section = mainArray[index];
486	    autoListItems.filter(listItem => {
487	      compare(listItem, section);
488	    });
489	    manualListItems.filter(listItem => {
490	      compare(listItem, section);
491	    });
492	  };
493
494	  const checkSlidesPDF = event => {
495	    let pdfPages = selectionArray(vars.viewport, '.slides .pdf-page'); // Check if any menubar has a slide number
496
497	    let anyMenubarHasSlidenumber = false;
498
499	    if (vars.menubars) {
500	      vars.menubars.forEach(menubar => {
501	        anyMenubarHasSlidenumber = !!menubar.getElementsByClassName("slide-number");
502	      });
503	    }
504
505	    pdfPages.forEach(pdfPage => {
506	      // The original section has gone, so we rebuild it with the saved data-attributes
507	      let datainfo = pdfPage.getElementsByClassName("datainfo")[0];
508	      let section = {};
509	      section.name = datainfo.dataset.name;
510	      section.dataname = datainfo.dataset.dataname;
511	      section.currentid = datainfo.dataset.currentid;
512	      section.match = datainfo.dataset.match;
513	      section.id = datainfo.dataset.id;
514
515	      if (datainfo.dataset.state) {
516	        let newClasses = datainfo.dataset.state.split(" ");
517	        newClasses.forEach(newClass => {
518	          pdfPage.classList.add(newClass);
519	          let vp = vars.deck.getRevealElement().closest(".reveal-viewport");
520	          vp.classList.remove(newClass);
521	        });
522	      } // If any menubar has a slide number, turn the original one on this slide off
523
524
525	      if (anyMenubarHasSlidenumber && pdfPage.getElementsByClassName("slide-number").length > 0) {
526	        pdfPage.getElementsByClassName("slide-number")[0].style.display = "none";
527	      }
528
529	      if (vars.menubars) {
530	        vars.menubars.forEach(menubar => {
531	          let bar = menubar.cloneNode(true);
532	          pdfPage.appendChild(bar);
533	          let listItems = selectionArray(bar, `.${options.menuclass} ${options.activeelement}`);
534	          listItems.forEach(listItem => {
535	            compare(listItem, section);
536	          }); // If there is a slidenumber in the menu,
537
538	          let newSN = pdfPage.querySelector(`.${options.menubarclass} .slide-number`);
539	          let oldSN = pdfPage.querySelector(`:scope > .slide-number`);
540
541	          if (newSN && oldSN) {
542	            // ...then fill it with the current (total) slidenumber.
543	            newSN.textContent = oldSN.textContent;
544	          }
545	        });
546	      }
547	    });
548
549	    if (vars.menubars) {
550	      vars.menubars.forEach(menubar => {
551	        menubar.parentNode.removeChild(menubar);
552	      });
553	    }
554	  };
555
556	  const chapterize = event => {
557	    if (event && event.type == "ready") {
558	      debugLog(mainArray);
559	      getRevealUI();
560	    }
561
562	    if (event && (event.type == "ready" || event.type == "slidechanged")) {
563	      checkSlidesNormal(event);
564	    }
565
566	    if (event && event.type == "pdf-ready") {
567	      checkSlidesPDF();
568	    }
569	  };
570
571	  const simpleMenu = (deck, options, es5Filename) => {
572	    deck.configure({
573	      hash: true
574	    });
575	    vars.deck = deck;
576	    vars.viewport = deck.getRevealElement().tagName == "BODY" ? document : deck.getRevealElement();
577	    vars.slides = deck.getSlidesElement();
578	    vars.langattribute = deck.getConfig().internation ? deck.getConfig().internation.langattribute ? deck.getConfig().internation.langattribute : "data-i18n" : false;
579	    vars.rtl = deck.getConfig().rtl;
580	    vars.quarto = document.querySelector('[name=generator]') && document.querySelector('[name=generator]').content.includes("quarto") ? true : false;
581	    vars.matchString = "sm";
582	    vars.userScale = options.scale;
583	    deck.addEventListener('ready', chapterize, false);
584	    deck.addEventListener('slidechanged', chapterize, false);
585	    deck.addEventListener('pdf-ready', chapterize, false);
586	    deck.addEventListener('resize', _ref => {
587	      let {
588	        scale
589	      } = _ref;
590	      return setScale(scale);
591	    }, false);
592	    const SimplemenuStylePath = options.csspath ? options.csspath : `${pluginPath(es5Filename)}simplemenu.css` || 'plugin/simplemenu/simplemenu.css';
593	    return new Promise(resolve => {
594	      if (options.csspath === false) {
595	        return prepare(resolve);
596	      } else {
597	        loadStyle(SimplemenuStylePath, 'stylesheet', async () => prepare(resolve));
598	      }
599	    });
600	  };
601
602	  const init = deck => {
603	    let defaultOptions = {
604	      menubarclass: 'menubar',
605	      menuclass: 'menu',
606	      activeclass: 'active',
607	      activeelement: 'li',
608	      selectby: 'id',
609	      barhtml: {
610	        header: '',
611	        footer: ''
612	      },
613	      flat: false,
614	      scale: 0.67,
615	      csspath: ''
616	    };
617	    options = deck.getConfig().simplemenu || {};
618	    options = mergeDeep(defaultOptions, options);
619	    let wronginputs = false;
620	    let warning = '';
621
622	    if (options.selectby !== "id" && options.selectby !== "data-name" && options.selectby !== "name") {
623	      wronginputs = true;
624	      warning = 'The selectby option can be only "id",  "data-name" or "name".';
625	    }
626
627	    if (wronginputs) {
628	      console.log('Simplemenu did not load:');
629	      console.log(warning);
630	      return false;
631	    }
632
633	    return simpleMenu(deck, options, "simplemenu.js");
634	  };
635
636	  return {
637	    id: 'simplemenu',
638	    init: init
639	  };
640	};
641
642	return Plugin;
643
644}));

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.