1/* DokuWiki MoaiEditor Match_paragraphs.js file 2 Version : 0.5 (May 5, 2026) 3 Author : MoaiTools <info@moaitools.org> 4 License : GPL 3 (http://www.gnu.org/licenses/gpl.html) */ 5 6MoaiEditor.MatchParagraphs = class { 7 8 constructor(outer) { 9 this.outer = outer; 10 this.tools = outer.tools; 11 } 12 // ──────────────────────────────────── 13 match () { 14 15 // Gather <p> nodes in this section 16 var nodes = []; 17 for (let node of this.outer.section.nodes) 18 if (node.type == 'P') 19 nodes.push(node); 20 21 // Return if no <p> nodes were found 22 if (nodes.length == 0) 23 return; 24 25 // Filter out all blocks of text that aren't very likely paragraphs 26 var blocks = []; 27 for (let block of this.outer.blocks) 28 if (this.isIsolatedParagraph(block)) 29 blocks.push(block); 30 31 32 // Return if no isolated blocks of text were found 33 if (blocks.length == 0) 34 return; 35 36 // Iterate the <p> nodes 37 for (let node of nodes) { 38 // Iterate blocks of text 39 var scores = []; 40 for (let block of blocks) { 41 42 // Skip if block was matched already or it is not a paragraph 43 if (block?.matched) { 44 continue; 45 } 46 47 // Get scores 48 var score = {n:0, m:0}; 49 this.tools.media.same(score, block, node); 50 this.tools.sameWords(score, block.cleantext, node.handle.textContent, 'whitespace'); 51 52 if (score.n == 0 || score.m == 0) 53 continue; 54 scores.push({ node:node, block:block, score:score.m/score.n}); 55 } 56 57 // Sort by score 58 scores.sort((a, b) => (a.score < b.score) ? 1 : -1); 59 60 61 var s = []; for (let score of scores) s.push(score.score); 62 63 // Continue if there are no candidates or both have about the same score 64 if (scores.length == 0) 65 continue; 66 67 // Continue if the top score is too bad 68 score = scores[0]; 69 if (score.score < 0.4) 70 continue; 71 72 // Continue if the top two candidates have about the same score 73 if (scores.length >= 2) { 74 const s0 = scores[0].score; 75 const s1 = scores[1].score; 76 const d = s0-s1; 77 if (d < 0.1) 78 continue; 79 } 80 // Prevent the block from being used again 81 score.block.matched = true; 82 83 // Store the match 84 const type = score.node.type; 85 const handle = score.node.handle; 86 const startline = this.outer.startline + score.block.start; 87 const endline = this.outer.startline + score.block.end; 88 this.outer.outer.matches.add (type, handle, startline, endline, 'paragraph'); 89 } 90 } 91 // ──────────────────────────────────── 92 isIsolatedParagraph (block) { 93 94 // No line should start with these tags 95 const tags = [' ', '|', '^', '>']; 96 for (let line of block.text.split("\n")) 97 for (let tag of tags) 98 if (line.startsWith(tag)) 99 return false; 100 101 // It might be a broken paragraph if it has opening or closing block tags 102 if (this.tools.hasBlockTags(block.text)) 103 return false; 104 105 // Make text matching more accurate and avoid urls like http://images.com/dog.gif from being interpreted as the '//' tag 106 this.tools.media.gatherFromText(block); // Remove media tags from text 107 this.tools.replaceLinks(block); // Replace links with what 'node.textContent' would show 108 109 // It's very likely the paragraph is broken if it has imbalanced inline tags 110 if (this.tools.hasImbaInlineTags(block.nolinktext)) 111 return false; 112 113 // It is likely a paragraph (asuming it is surrounded by empty lines above and below) 114 return true; 115 } 116 117}; // End Class 118