|
|
(٢١ مراجعات متوسطة بواسطة نفس المستخدم غير معروضة) |
سطر ١: |
سطر ١: |
| /* الجافاسكريبت الموضوع هنا سيتم تحميله لكل المستخدمين مع كل تحميل للصفحة. */ | | /* الجافاسكريبت الموضوع هنا سيتم تحميله لكل المستخدمين مع كل تحميل للصفحة. */ |
| | | |
− | // =============================================================================
| + | 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
| + | |
− | //
| + | |
− | // =============================================================================
| + | |
| | | |
− | // -----------------------------------------------------------------------------
| + | $(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 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 = 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 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
| + | |
− | ); | + | |
− | // lines = arrayTrim(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">' + // 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="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
| + | |
| }); | | }); |
− | // ----------------------------------------------------------------- | + | 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 )*/
| + | |
− | | + | |
− | )
| + | |
− | );
| + | |
− | 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="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
| + | |
− | }
| + | |
− | console.log(bWidth);
| + | |
− | 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" style="white-space:pre-wrap">' +
| |
− | 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"); |