«ميدياويكي:Common.js»: الفرق بين المراجعتين

من Sudan Memory
اذهب إلى: تصفح، ابحث
 
(١٩ مراجعات متوسطة بواسطة نفس المستخدم غير معروضة)
سطر ١: سطر ١:
 
/* الجافاسكريبت الموضوع هنا سيتم تحميله لكل المستخدمين مع كل تحميل للصفحة. */
 
/* الجافاسكريبت الموضوع هنا سيتم تحميله لكل المستخدمين مع كل تحميل للصفحة. */
  
// =============================================================================
+
console.log("Entering Common.js");
// Formate a poem
+
// 2024-02-27: Created by Abdalla G. M. Ahmed
+
// 2024-03-07: Updated to include annotation
+
// 2024-03-08: Added automatic footnote numbering
+
//            Added resetting the parameters to admit multiple sections
+
// 2024-03-09: Added line emphasizing and passing attributes.
+
//            Added formatting replacement for smart quotation
+
//            Fixed error with symbols-only lines
+
//           
+
// =============================================================================
+
  
// -----------------------------------------------------------------------------
+
$(function () {
// Measure displayed width of an HTML text
+
     if (document.querySelectorAll('.abyat').length > 0) {
// -----------------------------------------------------------------------------
+
         var script = document.createElement('script');
 
+
         script.src = "https://abdallagafar.github.io/poemFormat/poemFormat.js";
function measureTextWidth(text, font) {
+
        script.addEventListener('load', function() {                           // Now the external script is loaded and its functions are ready to be used
    var tmpSpan = document.createElement("span");
+
             document.querySelectorAll('.abyat').forEach(function(poem) {       // Search for poems
    tmpSpan.style.font = font;                                                  // Apply the font style
+
                 poem.innerHTML = poemFormat(poem);
    tmpSpan.style.position = "absolute";
+
    tmpSpan.style.visibility = "hidden";
+
    tmpSpan.style.whiteSpace = "nowrap";
+
    tmpSpan.innerHTML = text;
+
 
+
    document.body.appendChild(tmpSpan);
+
    var textWidth = tmpSpan.offsetWidth;
+
    document.body.removeChild(tmpSpan);
+
 
+
    return textWidth;
+
}
+
 
+
// -----------------------------------------------------------------------------
+
// Retrieve font specs of an element
+
// -----------------------------------------------------------------------------
+
 
+
function getFontStyle(node) {
+
    var computedStyle = window.getComputedStyle(node);
+
    var fontSize = computedStyle.fontSize;
+
    var fontFamily = computedStyle.fontFamily;
+
    var fontWeight = computedStyle.fontWeight;
+
    var fontStyle = computedStyle.fontStyle;
+
    var fontVariant = computedStyle.fontVariant;
+
    return (
+
        fontStyle + " " + fontVariant + " " +
+
        fontWeight + " " + fontSize + " " + fontFamily
+
    );
+
}
+
 
+
// -----------------------------------------------------------------------------
+
// Trim empty leading and trailing entries of an array
+
// -----------------------------------------------------------------------------
+
 
+
function arrayTrim(arr) {
+
    var result = arr.slice();                                                  // Clone the array to avoid modifying the original array
+
    while (result.length > 0 && !result[0]) { result.shift(); }                // Remove empty elements from the beginning
+
    while (result.length > 0 && !result[result.length - 1]) { result.pop(); }  // Remove empty elements from the end
+
    return result;
+
}
+
 
+
// -----------------------------------------------------------------------------
+
// Escape special characters in strings used to build RegExp
+
// -----------------------------------------------------------------------------
+
 
+
function escRegExp(string) {
+
    return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');                      // $& means the whole matched string
+
}
+
 
+
// -----------------------------------------------------------------------------
+
// Replace digits to use Hindi Arabic (٠-٩)
+
// -----------------------------------------------------------------------------
+
 
+
function arabic2hindi(input) {
+
     return input.replace(/\d/g, function(match) {
+
        return String.fromCharCode(parseInt(match, 10) + 0x0660);
+
    });
+
}
+
 
+
// =============================================================================
+
// Format contents of a container (e.g., div) as a poem
+
// =============================================================================
+
 
+
function poemFormat(poem) {
+
    const titlePlcHldr = '<+>';                                                // A placeholder for in-place footnotes, distinct from '+' characters that may exist in the text
+
    const ftntPlcHldr = '<*>';                                                  // A placeholder for gutter footnotes
+
    var fontStyle = getFontStyle(poem);                                        // For determining the rendered widths of parts
+
    var bWidth0 = parseFloat(poem.getAttribute('data-bWidth')) || 0;            // Initial overall width of beit
+
    var cols = parseInt(poem.getAttribute('data-cols')) || 0;                  // 0 means auto, determined by contents
+
    var emphasizeAll = parseInt(poem.getAttribute('data-emph')) || 0;
+
    var attribs = poem.getAttribute('data-attribs');                            // Attributes passed to the div.
+
    var lines = (
+
        poem
+
        .innerHTML                                                              // We get the HTML to retain inline tags <a,i,b,s,u>
+
        .trim()                                                                // Remove leading and trailing empty lines so that the enclosing <div> tag may be placed in separate lines
+
    );
+
    // -------------------------------------------------------------------------
+
    // Extract referenced footnotes
+
    // -------------------------------------------------------------------------
+
    var ftntTitles = [];                                                        // Array to hold the contents of in-place notes.
+
    var ftntReferenced = [];                                                    // Referenced footnotes
+
    var ftntStandalone = [];                                                    // Standalone footnotes
+
    var ftntLabels = [];                                                        // Array to save footnote labels during processing
+
    for (var found = true; found; ) {
+
        found = false;
+
        lines = lines.replace(
+
          /\((\d+|[٠١٢٣٤٥٦٧٨٩]+|[*+])\)([\s\S]*?)\n? *\(\1([^)][\s\S]*?)\1\)/, // Find a parenthesized label, e.g., "(1)", and the first matching "(1 delimited text 1)"
+
            function(match, label, textBetween, content) {
+
                found = true;
+
                if (label === '+') {                                            // A '+' label is reserved for in-place notes displayed as titles
+
                    ftntTitles.push(content.trim());
+
                    return titlePlcHldr + textBetween;
+
                }
+
                else {                                                          // A regular referenced footnote
+
                    if (label === '0' || label === '٠') {                       // A label of '0' or '٠' is reserved for auto numbering
+
                        label = (ftntLabels.length + 1).toString();            // Explicit conversion to string is needed by arabic2hindi
+
                    }
+
                    label = arabic2hindi(label);                                // Use Eastern-Arabic numbers
+
                    ftntReferenced.push(
+
                        '<p>(' + label + ') ' + content.trim() + '</p>'
+
                    );
+
                    ftntLabels.push(label);
+
                    return ftntPlcHldr + textBetween;
+
                }
+
            }
+
         );
+
    }
+
    // -------------------------------------------------------------------------
+
    // Extract standalone footnotes.
+
    // Even though they are displayed above referenced notes, we collect
+
    // them later to give the user the chance to include referenced footnotes
+
    // content within the standalone footnotes
+
    // -------------------------------------------------------------------------
+
    lines = lines.replace(
+
        /\n? *\(!([\s\S]*?)!\) */g,                                          // We remove all preceding white space, including a single line break, if any
+
        function(match, content) {
+
            if (content) {                                                      // To support empty comments placeholders by users
+
                content = content.trim();                                      // Remove surrounding white space after removing the delimiters
+
                if (!/<p/.test(content)) {                                      // Loose text?
+
                    content = '<p>' + content + '</p>';                        // Enclose in a paragraph container
+
                }
+
                ftntStandalone.push(content);                                  // Append to footnotes
+
            }
+
            return '';
+
        }
+
    );
+
    var footnotes = ftntStandalone.join('') + ftntReferenced.join('');
+
 
+
    // =========================================================================
+
    // Format a block of preprocessed poem lines
+
    // =========================================================================
+
    function formatLines(lines) {
+
         const bGap = 50;                                                        // This is twice the sum of widths of bPrefix and bSuffix in CSS
+
        const bStaggerOffset = 75;                                              // Should match CSS
+
        const bWidthMax = 450;                                                  // This should match the CSS declaration of the width of poemBody div less padding and bPrefix/bSuffix width
+
        const elementSeparator = '<->';                                        // A placeholder for separating identified parts during processing
+
        const partSeparator = '<=>';                                            // A placeholder for separating prefix/body/suffix of a part
+
        const titlePlcHldr = '<+>';                                            // A placeholder for in-place footnotes, distinct from '+' characters that may exist in the text
+
        const ftntPlcHldr = '<*>';                                              // A placeholder for gutter footnotes
+
        var bWidth = bWidth0;                                                  // Initial bWidth
+
        var pWidth = [0, 0, 0, 0, 0];                                          // Widths of part for different number of parts, index 0 is used for centered verses
+
        var nGaps  = [2, 0, 1, 2, 3];                                          // Number of gaps for different number of parts, initially assuming no leading gaps
+
        var staggered = false;                                                  // Whether the poem is fed as one half per line, indenting second halves
+
        lines = (
+
            lines
+
            .replace(/<\/p><p>/gi, '<br>\n')                                    // Undo wiki removal of single empty lines
+
            .replace(/\s*<(p|div|pre)[^>]*>/gi, '')                            // Remove opening container tags and preceding whitespace
+
            .replace(/<\/(p|div|pre)>/gi, '')                                  // Remove closing tags
+
            .replace(/\s*<hr[^>]*>\s*/gi, '\n----\n')                          // Replace <hr> in wiki with our symbol for an <hr>
+
            .replace(/<br>/gi, '\n')                                            // Replace line break tags with line-break characters
+
            .replace(/^ *(\..*\.) *$/gm, '$1')                                  // Trim spaces around centered lines to avoid confusion with indentation
+
            .replace(/^ *(!.*)$/gm, '$1')                                      // Trim spaces in the beginning of comment lines to avoid confusion with indentation
+
            .replace(
+
                /\n {2,}(\S)/g,                                                // Remaining indented lines?
+
                function(match,char) { staggered = true; return '  ' + char; }  // Merge with preceding half, and declare staggered as true
+
            )
+
            .split(/\r?\n/)                                                    // Break into lines, assuming one beit per line
+
        );
+
        var formattedLines = [];                                                // Array to hold formatted lines
+
        for (var i = 0; i < lines.length; i++) {
+
            var line = lines[i].trim();                                        // Remove white space around
+
            var bClass = 'beit';                                                // Default class
+
            if (emphasizeAll) bClass += ' bEmph';
+
            var nParts =  0;                                                    // Number of parts
+
            if (line === "") {                                                  // Empty line?
+
                formattedLines.push('<br>');                                    // Replace by a line break
+
                continue;                                                      // Next!
+
            }
+
            if (line === "----") {                                              // Separator line
+
                formattedLines.push('<hr style="width:%hWidth%px">');          // Insert a horizontal break formatted to the width of verses
+
                continue;                                                      // Next!
+
            }
+
            if (line.match(/\.!$/)) {                                          // A line ending with ".!" is emphasized
+
                if (!emphasizeAll) { bClass += ' bEmph' };
+
                line = line.slice(0, -2).trim();                                // Remove the symbols and preceding spaces
+
            }
+
            if (line.match(/^!/)) {                                            // A line starting with an exclamation mark is a comment
+
                formattedLines.push(
+
                    '<p class="bComment">' +                                    // Enclose in a comment paragraph
+
                    line
+
                    .substring(1)                                              // Remove the exclamation mark
+
                    .replace(
+
                        new RegExp(escRegExp(titlePlcHldr), "g"),
+
                        '<sup>+</sup>'                                          // Restore the unified title label, if any
+
                    )
+
                    .replace(
+
                        new RegExp(escRegExp(ftntPlcHldr), "g"),
+
                        function(match) {
+
                            return '<sup>' + ftntLabels.shift() + '</sup>';    // Restore the footnote labels, if any, and dispense with
+
                        }
+
                    )
+
                    + '</p>'
+
                );
+
                continue;                                                      // Next!
+
            }
+
            if (line.startsWith('..') && line.endsWith('..')) {                // Lines delimited by two dots are treated as closing halves.
+
                line = (
+
                    line
+
                    .substring(2, line.length - 2)                              // Remove delimiters
+
                    .trim()                                                    // Remove white space that may offset delimiters
+
                    .replace(/\s{2,}/g, ' ')                                    // Remove multiple spaces to ensure processing as a single part
+
                );
+
                nParts++;                                                      // To process as a half or two rather than a one
+
            }
+
            if (line.startsWith('.') && line.endsWith('.')) {                  // Excluding the two-dots, lines delimited by dots are centered.
+
                line = line.substring(1, line.length - 1).trim();              // Remove delimiters and any white space
+
                pWidth[0] = Math.max(pWidth[0], measureTextWidth(line));
+
                formattedLines.push(
+
                    '<p class="' + bClass + '">'
+
                    + '<span class="bJustified">'
+
                    + line
+
                    + '</span>'
+
                    + '</p>'
+
                );
+
                continue;                                                      // Next
+
            }
+
            // -----------------------------------------------------------------
+
            // Adjust separators, can be expanded later
+
             // -----------------------------------------------------------------
+
            if (cols == 1) {
+
                line = line.replace(/\s{2,}/g, ' ')                            // Remove multiple spaces to ensure processing as a single part
+
            }
+
            // -----------------------------------------------------------------
+
            // Handle replacements
+
            // -----------------------------------------------------------------
+
            if (line.startsWith('{') && line.endsWith('}')) {                  // Delimiting by braces prevents replacements
+
                line = line.substring(1, line.length - 1).trim();              // Remove delimiters and leave the rest as is
+
            }
+
            else {                                                             // Here we apply all text replacements that may evolve over time
+
                 line = (
+
                    line
+
                    .replace(/\('/g, '”').replace(/'\)/g, '“')                  // Replace ("text") by smart quotations
+
                );
+
            }
+
            // -----------------------------------------------------------------
+
            // Now we are left with actual lines of the poem
+
            // -----------------------------------------------------------------
+
            // Collect inline tags and replace with all-symbols
+
            // placeholders to enable correct splitting of prefixes and suffixes
+
            // -----------------------------------------------------------------
+
            var tags = [];
+
            line = line.replace(
+
                /<\/([abius])>/gi,                                              // Find closing <a/i/b/u/s> tags, this gives the right order for nested tags
+
                function(match, tag) {
+
                    return '</' + (tags.push(tag) - 1) + '>';
+
                }
+
            );
+
            var tagsOpen = tags.map(function(tag, index) {                      // Iterate to find matching opening tags
+
                var tagOpen = '';
+
                var placeholder = '<' + index + '>';
+
                line = line.replace(
+
                    new RegExp('<' + tag + '(?:\\s[^>]*)?>', 'i'),              // First occurrence of a matching opening tag
+
                    function(match) {
+
                        tagOpen = match;                                        // Capture the matching tag
+
                        return placeholder;                                    // This return is to replace the tag placeholder in the line text
+
                    }
+
                );
+
                return tagOpen;                                                // This return is for inserting the matching opening tag in the list
+
 
             });
 
             });
             // -----------------------------------------------------------------
+
             console.log('Processed ' + poemFormatGrossCounter + ' beit.');
            // Split parts
+
            // -----------------------------------------------------------------
+
            line = (
+
                line
+
                .split(/\s{2,}/)                                                // Split parts at two or more spaces
+
                // -------------------------------------------------------------
+
                // For each part:
+
                // -------------------------------------------------------------
+
                .map(function(part) {
+
                    var elements = (
+
                        part
+
                        .replace(/</g, ' <').replace(/>/g, '> ')                // Offset tags from words to isolate words
+
                        .match(                                                // Split into three elements:
+
 
+
                            /^([^\u0620-\u064Aa-zA-Z]*)(.*?[\u0620-\u064Aa-zA-Z])([^\u0620-\u064Aa-zA-Z]*)$/
+
                            /*(  prefix of symbols  )(    body of alphabets  )(  suffix of symbols  )*/
+
 
+
                        )
+
                    );
+
                    if (!elements) {                                            // Typically happens when the line contains only symbols
+
                        elements = ['', part, '', ''];                          // Put all contents in prefix
+
                    }
+
                    elements.shift();                                          // Pop the first entry, which matches the whole regex
+
                    // ---------------------------------------------------------
+
                    // Enclose words in spans to enable justification
+
                    // ---------------------------------------------------------
+
                    elements[1] = (                                            // The body
+
                        elements[1]
+
                        .split(/\s+/)                                          // Split at white spaces
+
                        .map(function(item) {
+
                            if (/^<.*>$/.test(item)) { return item; }          // Return tag placeholders as is
+
                            return '<span>' + item + '</span>';                // Enclose each word in a span to enable justification
+
                        })
+
                        .join(' ')                                              // Join all items back
+
                        .replace(
+
                            /(<\/span>)\s*(<[+*]>)/g, '$2$1'                    // Move superscripts inside word spans
+
                        )
+
                    );
+
                    return elements.join(' ' + elementSeparator + ' ');        // A separator mark between elements, with spaces to offset from words
+
                })
+
                .join(' ' + partSeparator + ' ')                                // A separator mark between parts
+
            );
+
            // -----------------------------------------------------------------
+
            // Distribute tags over words so that they can be split properly
+
            // between parts/elements
+
            // The order we collected the tags makes the innermost nested tags
+
            // processed first, hence nest them properly
+
            // -----------------------------------------------------------------
+
            tags.map(function(tag, i) {                                        // For the ith tag ..
+
                line = line.replace(
+
                    new RegExp('<' + i + '>(.*)</' + i + '>'),                  // Find the enclosed text
+
                    function(match, content) {
+
                        return (
+
                            content                                            // Content inside tag
+
                            .trim()                                            // Remove leading/trailing spaces
+
                            .split(/\s+/)                                      // Split at white space and include white spaces in the list
+
                            .map(function(item) {
+
                                if (/<[-=]>/.test(item)) {
+
                                    return ' ' + item + ' ';                    // Return part/element separators as is, but keep spaces around
+
                                }
+
                                return '<' + i + '>' + item + '</' + i + '>';  // Enclose contiguous non-white space in tag
+
                            })
+
                            .join(' ')                                          // Join all items back together
+
                        );
+
                    }
+
                );
+
            });
+
            // -----------------------------------------------------------------
+
            // Restore tags
+
            // -----------------------------------------------------------------
+
            tags.map(function(tag, i) {                                        // For the ith tag ..
+
                line = (
+
                    line
+
                    .replace(new RegExp('<'+i+'>', 'g'), tagsOpen[i])
+
                    .replace(new RegExp('<\/'+i+'>', 'g'), '</' + tag + '>')
+
                );
+
            });
+
            // -----------------------------------------------------------------
+
            // Now split again to format parts
+
            // -----------------------------------------------------------------
+
            var partWidth = 0;                                                  // Maximum part length
+
            line = (
+
                line
+
                .split(' ' + partSeparator + ' ')
+
                .map(function(part) {
+
                    nParts++;                                                  // Increment number of parts to count this one
+
                    part = (
+
                        part
+
                        .replace(
+
                            new RegExp(escRegExp(titlePlcHldr), "g"),
+
                            '<sup>+</sup>'                                      // Restore the unified title label
+
                        )
+
                        .replace(
+
                            new RegExp(escRegExp(ftntPlcHldr), "g"),
+
                            function(match) {
+
                                return '<sup>' + ftntLabels.shift() + '</sup>'; // Restore the label and dispense with it
+
                            }
+
                        )
+
                    );
+
                    var elements = part.split(' ' + elementSeparator + ' ');
+
                    elements[1] = elements[1].replace(
+
                        /(<\/span>)\s*(<sup>.*?<\/sup>)/g, '$2$1'              // Move superscripts inside word spans
+
                    );
+
                    // ---------------------------------------------------------
+
                    partWidth = Math.max(
+
                        partWidth, measureTextWidth(elements[1], fontStyle)    // Update longest part length
+
                    );
+
                    return (
+
                        '<span class="bPrefix">' + elements[0] + '</span>' +
+
                        '<span class="bJustified" style="width:%pWidth%px">'
+
                        + elements[1] +
+
                        '</span>' +
+
                        '<span class="bSuffix">' + elements[2] + '</span>'
+
                    );
+
                })
+
                .join('<=>')
+
                .replace(/%pWidth%/g, '%pWidth' + nParts + '%')                // The number of parts is known now
+
            );
+
            pWidth[nParts] = Math.max(pWidth[nParts], partWidth);
+
            formattedLines.push('<p class="'+bClass+'">' + line + '</p>');
+
        }
+
        for (var i = 0; i < pWidth.length; i++) {
+
            var bWidth_i = pWidth[i] ? pWidth[i] * i + nGaps[i] * bGap : 0;    // Width to accommodate this number/width of parts
+
            bWidth = Math.max(bWidth, bWidth_i);                                // Accommodate the widest
+
        }
+
        pWidth = pWidth.map(function(w, i) {
+
            return (bWidth - nGaps[i] * bGap) / (i ? i : 1);
+
 
         });
 
         });
         if (bWidth > bWidthMax) { staggered = true; }                          // Split too long parts over rows
+
         document.head.appendChild(script);                                      // Append the script element to the document's head to start loading the script
        var actualPartSeparator = (
+
            staggered?
+
            '<span class="bStaggerOffset"></span><br>' +                        // Add an offsetting space after first half
+
            '<span class="bStaggerOffset">    </span>'                          // and before second half, and include staggering spaces
+
            : '<span class="bGap">    <\/span>'                                // Otherwise, make a fixed-width gap with white spaces
+
        );
+
        var hWidth = staggered ? pWidth[2] + bStaggerOffset : bWidth;          // Horizontal line width, default beit width, but only single part width when staggered
+
        return (
+
            formattedLines
+
            .join('')                                                          // Concatenate
+
            .replace(/<=>/g, actualPartSeparator)
+
            .replace(/%pWidth(\d+)%/g, function(match, i) {                    // Replace computed part widths
+
                return pWidth[i].toFixed(1);
+
            })
+
            .replace(/\s*<sup>/g, '<sup>')                                     // Stick notes labels to preceding text
+
            .replace(/\s*<sup>\+<\/sup>/g, function() {                        // Restore saved titles
+
                return '<sup title="' + ftntTitles.shift() + '">+</sup>';
+
            })
+
            .replace(/%bWidth%/g, bWidth)                                      // Replace computed overall beit width
+
            .replace(/%hWidth%/g, hWidth)                                      // Replace computed overall beit width
+
        );
+
 
     }
 
     }
    // =========================================================================
 
    // End of block formatting function
 
    // =========================================================================
 
 
    var formattedPoem = (
 
        '<div class="poemBody" ' + attribs + '>' +
 
        lines
 
        .split(/\n *\+{3,} *\n/)
 
        .map(formatLines)
 
        .join('')
 
        + '</div>'
 
    );
 
    if (footnotes) {
 
        formattedPoem += '<div class="poemGutter">' + footnotes + '</div>'
 
    }
 
    return formattedPoem;
 
}
 
 
$(function () {
 
    document.querySelectorAll('.ash3ar').forEach(function(poems) {              // Divisions declared as lists of أشعار
 
        poems.innerHTML = (                                                    // Replace their content with ..
 
            poems.innerHTML                                                    // Take innerHTML to preserve formatting
 
            .split(/\+{3,}/)                                                    // Use three or more "+" characters to split
 
            .map(function(txt) { return '<div class="abyat">'+txt+'</div>'; })  // Enclose in a class declared as abyat for subsequent identification
 
            .join('\n<hr>\n')                                                         
 
        );
 
    });
 
 
    document.querySelectorAll('.abyat').forEach(function(poem) {                // Iterate through containers declared as أبيات
 
        poem.innerHTML = poemFormat(poem);                                      // Process to format
 
    });
 
  
 
     var toggler = document.getElementsByClassName("treeBranch");
 
     var toggler = document.getElementsByClassName("treeBranch");

المراجعة الحالية بتاريخ ١٧:٣٥، ٢٦ مارس ٢٠٢٤

/* الجافاسكريبت الموضوع هنا سيتم تحميله لكل المستخدمين مع كل تحميل للصفحة. */
 
console.log("Entering Common.js");
 
$(function () {
    if (document.querySelectorAll('.abyat').length > 0) {
        var script = document.createElement('script');
        script.src = "https://abdallagafar.github.io/poemFormat/poemFormat.js";
        script.addEventListener('load', function() {                            // Now the external script is loaded and its functions are ready to be used
            document.querySelectorAll('.abyat').forEach(function(poem) {        // Search for poems
                poem.innerHTML = poemFormat(poem);
            });
            console.log('Processed ' + poemFormatGrossCounter + ' beit.');
        });
        document.head.appendChild(script);                                      // Append the script element to the document's head to start loading the script
    }
 
    var toggler = document.getElementsByClassName("treeBranch");
    var i;
 
    for (i = 0; i < toggler.length; i++) {
        toggler[i].addEventListener("click", function() {
            this.parentElement.querySelector(".treeBranch-nested").classList.toggle("treeBranch-active");
            this.classList.toggle("treeBranch-down");
        });
    }
}());