«ميدياويكي:Common.js»: الفرق بين المراجعتين
من Sudan Memory
سطر ٤: | سطر ٤: | ||
// Formate a poem | // Formate a poem | ||
// 2024-02-27: Created by Abdalla G. M. Ahmed | // 2024-02-27: Created by Abdalla G. M. Ahmed | ||
+ | // 2024-03-07: Updated to include annotation | ||
// ============================================================================= | // ============================================================================= | ||
سطر ٥١: | سطر ٥٢: | ||
while (result.length > 0 && !result[result.length - 1]) { result.pop(); } // Remove empty elements from the end | while (result.length > 0 && !result[result.length - 1]) { result.pop(); } // Remove empty elements from the end | ||
return result; | 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); | ||
+ | }); | ||
} | } | ||
سطر ٥٨: | سطر ٧٧: | ||
function poemFormat(poem) { | function poemFormat(poem) { | ||
− | + | const bGap = 50; // This is twice the sum of widths of bPrefix and bSuffix in CSS | |
− | var | + | 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 lines = poem.innerHTML; // We get the HTML to retain inline tags <a,i,b,s,u> | ||
+ | var bWidth = parseFloat(poem.getAttribute('data-bWidth') || 0); // Overall width of beit | ||
var pWidth = [0, 0, 0, 0, 0]; // Widths of part for different number of parts, index 0 is used for centered verses | 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 nGaps = [2, 0, 1, 2, 3]; // Number of gaps for different number of parts, initially assuming no leading gaps | ||
− | var fontStyle = getFontStyle(poem); | + | var fontStyle = getFontStyle(poem); // For determining the rendered widths of parts |
− | var | + | var staggered = false; // Whether the poem is fed as one half per line, indenting second halves |
− | + | // ------------------------------------------------------------------------- | |
− | . | + | // 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 | ||
+ | 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(''); | ||
+ | // ------------------------------------------------------------------------- | ||
+ | // Now we proceed to process the lines | ||
+ | // ------------------------------------------------------------------------- | ||
+ | lines = ( | ||
+ | lines | ||
.replace(/<\/p><p>/gi, '<br>\n') // Undo wiki removal of single empty lines | .replace(/<\/p><p>/gi, '<br>\n') // Undo wiki removal of single empty lines | ||
− | .replace(/\s*( | + | .replace(/\s*<(p|div|pre)[^>]*>/gi, '') // Remove opening container tags and preceding whitespace |
− | .replace(/<\/p | + | .replace(/<\/(p|div|pre)>/gi, '') // Remove closing tags |
− | .replace(/\s*<hr[^>]*>\s*/gi, '\n----\n') | + | .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 ~ ~ | ||
+ | .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 | ||
); | ); | ||
lines = arrayTrim(lines); // Remove leading and trailing empty lines so that the enclosing <div> tag may be placed in separate lines | lines = arrayTrim(lines); // Remove leading and trailing empty lines so that the enclosing <div> tag may be placed in separate lines | ||
− | var formattedLines = []; | + | var formattedLines = []; // Array to hold formatted lines |
− | + | ||
− | + | ||
− | + | ||
for (var i = 0; i < lines.length; i++) { | for (var i = 0; i < lines.length; i++) { | ||
− | var line = lines[i] | + | var line = lines[i].trim(); // Remove white space around |
− | + | var nParts = 0; // Number of parts | |
− | + | if (line === "") { // Empty line? | |
− | + | formattedLines.push('<br>'); // Replace by a line break | |
− | if (line === "" | + | continue; // Next! |
− | formattedLines.push('<br>'); // | + | |
− | continue; // | + | |
} | } | ||
if (line === "----") { // Separator line | if (line === "----") { // Separator line | ||
− | formattedLines.push('<hr style="width:% | + | formattedLines.push('<hr style="width:%hWidth%px">'); // Insert a horizontal break formatted to the width of verses |
− | continue; // | + | continue; // Next! |
} | } | ||
if (line.match(/^!/)) { // A line starting with an exclamation mark is a comment | if (line.match(/^!/)) { // A line starting with an exclamation mark is a comment | ||
formattedLines.push( | formattedLines.push( | ||
− | '<p class="bComment">' | + | '<p class="bComment">' + line.substring(1) + '</p>' // Remove the exclamation mark and enclose in a comment paragraph |
− | + | ||
− | + | ||
); | ); | ||
− | continue; // | + | continue; // Next! |
} | } | ||
− | |||
if (line.startsWith('..') && line.endsWith('..')) { // Lines delimited by two dots are treated as closing halves. | if (line.startsWith('..') && line.endsWith('..')) { // Lines delimited by two dots are treated as closing halves. | ||
− | line = line.substring(2, line.length - 2).trim() | + | 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('.')) { // | + | 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 | line = line.substring(1, line.length - 1).trim(); // Remove delimiters and any white space | ||
pWidth[0] = Math.max(pWidth[0], measureTextWidth(line)); | pWidth[0] = Math.max(pWidth[0], measureTextWidth(line)); | ||
سطر ١١٥: | سطر ١٩٤: | ||
+ '</p>' | + '</p>' | ||
); | ); | ||
− | continue; // | + | continue; // Next |
} | } | ||
// --------------------------------------------------------------------- | // --------------------------------------------------------------------- | ||
− | // | + | // 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( | line = line.replace( | ||
− | / | + | /<\/([abius])>/gi, // Find closing <a/i/b/u/s> tags, this gives the right order for nested tags |
− | function(match, | + | function(match, tag) { |
− | return | + | 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 | |
− | ); | + | }); |
// --------------------------------------------------------------------- | // --------------------------------------------------------------------- | ||
// Split parts | // Split parts | ||
// --------------------------------------------------------------------- | // --------------------------------------------------------------------- | ||
− | + | line = ( | |
− | + | ||
− | + | ||
line | line | ||
− | .split(/ | + | .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 )*/ | ||
+ | ) | ||
+ | ); | ||
+ | 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) { | .map(function(item) { | ||
− | if ( | + | if (/<[-=]>/.test(item)) { |
− | + | return ' ' + item + ' '; // Return part/element separators as is, but keep spaces around | |
− | + | ||
− | + | ||
− | return ' | + | |
} | } | ||
− | return item; | + | return '<' + i + '>' + item + '</' + i + '>'; // Enclose contiguous non-white space in tag |
}) | }) | ||
− | .join('') | + | .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 | ||
+ | // .trim() // Remove leading/trailing spaces | ||
+ | .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); | pWidth[nParts] = Math.max(pWidth[nParts], partWidth); | ||
− | + | formattedLines.push('<p class="beit">' + line + '</p>'); | |
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | formattedLines.push( | + | |
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
} | } | ||
for (var i = 0; i < pWidth.length; i++) { | for (var i = 0; i < pWidth.length; i++) { | ||
سطر ٢٢٥: | سطر ٣٤٦: | ||
return (bWidth - nGaps[i] * bGap) / (i ? i : 1); | return (bWidth - nGaps[i] * bGap) / (i ? i : 1); | ||
}); | }); | ||
− | var | + | if (bWidth > bWidthMax) { staggered = true; } // Split too long parts over rows |
− | staggered ? | + | var actualPartSeparator = ( |
− | '<span class=" | + | 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 | + | var hWidth = staggered ? pWidth[2] + bStaggerOffset : bWidth; // Horizontal line width, default beit width, but only single part width when staggered |
− | + | var formattedPoem = ( | |
− | formattedLines | + | '<div class="poemBody">' + |
− | + | 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 | ||
+ | + '</div>' | ||
); | ); | ||
+ | if (footnotes) { | ||
+ | formattedPoem += '<div class="poemGutter">' + footnotes + '</div>' | ||
+ | } | ||
+ | return formattedPoem; | ||
} | } | ||
مراجعة ٠٣:٢٩، ٨ مارس ٢٠٢٤
/* الجافاسكريبت الموضوع هنا سيتم تحميله لكل المستخدمين مع كل تحميل للصفحة. */ // ============================================================================= // Formate a poem // 2024-02-27: Created by Abdalla G. M. Ahmed // 2024-03-07: Updated to include annotation // ============================================================================= // ----------------------------------------------------------------------------- // Measure displayed width of an HTML text // ----------------------------------------------------------------------------- function measureTextWidth(text, font) { var tmpSpan = document.createElement("span"); tmpSpan.style.font = font; // Apply the font style 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 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 lines = poem.innerHTML; // We get the HTML to retain inline tags <a,i,b,s,u> var bWidth = parseFloat(poem.getAttribute('data-bWidth') || 0); // Overall width of beit 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 fontStyle = getFontStyle(poem); // For determining the rendered widths of parts var staggered = false; // Whether the poem is fed as one half per line, indenting second halves // ------------------------------------------------------------------------- // 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 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(''); // ------------------------------------------------------------------------- // Now we proceed to process the lines // ------------------------------------------------------------------------- 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 ~ ~ .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 ); lines = arrayTrim(lines); // Remove leading and trailing empty lines so that the enclosing <div> tag may be placed in separate lines 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 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 starting with an exclamation mark is a comment formattedLines.push( '<p class="bComment">' + line.substring(1) + '</p>' // Remove the exclamation mark and enclose in a comment paragraph ); 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="beit">' + '<span class="bJustified">' + line + '</span>' + '</p>' ); continue; // Next } // --------------------------------------------------------------------- // 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 }); // --------------------------------------------------------------------- // 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 )*/ ) ); 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 // .trim() // Remove leading/trailing spaces .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="beit">' + 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 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 var formattedPoem = ( '<div class="poemBody">' + 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 + '</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 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"); }); } }());