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