PageSourceSearch

https://edtechbooks.org/js/etb-editor/editor.utilities.js

js edtechbooks.org collected 2026-09-24 08:52:17 UTC 9,400 bytes, 263 lines download raw bytes

1// Expression helpers
2$.expr[':'].block = function (element) {
3    return $(element).css('display') === 'block' ||
4        $(element).css('display') === 'inline-block' ||
5        $(element).css('display') === 'table' ||
6        $(element).is('li,code');
7};
8$.expr[':'].heading = function (element) {
9    return $(element).is('h1,h2,h3,h4,h5,h6');
10};
11$.expr[':'].next = function (element) {
12    return !!$(element).next().length;
13};
14// Functions
15function MoveCaretToElement(e) {
16    const range = document.createRange();
17    const sel = window.getSelection();
18    range.selectNodeContents($(e)[0]);
19    range.collapse(true);
20    sel.removeAllRanges();
21    sel.addRange(range);
22}
23
24function RevertElement(btn) {
25    let input = $(btn).parent().next();
26    let o = $(input).next();
27    if (o.is('editable-element-original')) {
28        let editingCode = ($(input).attr('data-format') == 'code');
29        if (editingCode) DisableCodeEditor(input);
30        $(input).html(o.html());
31        if (editingCode) EnableCodeEditor(input);
32        SetEditorClean(input);
33    } else {
34        Toast('Revert Failed', 'There was an error attempting to revert the content.', 'bg-danger text-light', 'bi-alert-triangle')
35    }
36    $(input).html();
37}
38function DeleteElement(btn) {
39    let input = $(btn).parent().next();
40    $(input).empty();
41}
42/*
43function DeselectElement() {
44    $('.editor-selected').removeClass('editor-selected');
45    selectedElement = null;
46}
47function SelectElement(e) {
48    let editor = $(e).parents('[contenteditable][data-editor-style="full"]');
49    if(editor.length>0) {
50        SetDirty({target: editor});
51        selectedElement = e;
52        $(selectedElement).addClass('editor-selected');
53    }
54}
55*/
56function UniqueID(prefix = '') {
57    let lastUniqueID = prefix + '_' + (Math.random() + 1).toString(36).substring(7);
58    return lastUniqueID;
59}
60function UnixTime() {
61    return Math.floor(Date.now() / 1000);
62}
63
64function Clamp(number, min, max) {
65    return Math.min(Math.max(number, min), max);
66}
67
68function getClassInteger(element, prefix) {
69    let classes = $(element).attr('class').split(' ');
70    for (let i = 0; i < classes.length; i++) {
71        let match = classes[i].match(new RegExp('^' + prefix + '-(\\d+)$'));
72        if (match) {
73            return parseInt(match[1]);
74        }
75    }
76    return null;
77}
78
79function decodeHtml(html) {
80    let doc = new DOMParser().parseFromString(html, "text/html");
81    return doc.documentElement.textContent;
82}
83
84function EmphasizeAPATitle(html) {
85    if(html.includes('<em>')) return html;
86    if((html.match(/\(/g) || []).length<2 || html.includes('(Ed')) return html; // Only apply to fully formed journal articles
87    let arr = html.split(/([.?])/);
88    html = '';
89    let yearEnded = 0;
90    let titleStarted = false;
91    let titleEnded = false;
92    for(let item of arr) {
93        if(titleStarted && !titleEnded && item.endsWith('.')) {
94            html += '</em>';
95            titleEnded = true;
96        }
97        if(yearEnded > 3 && !titleStarted) {
98            html += '<em>';
99            titleStarted = true;
100            
101            if(item.includes('(')) {
102                item = item.replace('(','</em>(');
103                item += '</em>';
104                titleEnded = true;
105            }
106        }
107        html += item;
108        if(yearEnded>0) yearEnded++;
109        if(item.endsWith(')')) yearEnded = 1;
110    }
111    if(titleStarted && !titleEnded) html += '</em>';
112    return html;
113}
114function formatHTML(html) {
115    html = html.replace(/&nbsp;/g, ' ');
116    html = html.replace(/\s\s+/g, ' ');
117    html = html.replace(/[\u00A0-\u9999<>&]/gim, function (i) {
118        return '&#' + i.charCodeAt(0) + ';';
119    });
120    //html = html.replace(/data-type="(.*?)"/g, "");
121    html = html.replace(/data-selected="(.*?)"/g, "");
122    html = html.replace(/&#62;/g, '&#62;[br]');
123    html = html.replace(/&#60;/g, '[br]&#60;');
124    html = html.replace(/\[br\]/g, '</div><div>');
125    html = '<div>' + html + '</div>';
126    //html = html.replace(/&#62;(.*?)&#60;/g, '&#62;<div>$1</div>&#60;');
127    html = html.replace(/class="(.*?)"/g, 'class="<span class="code-editor-class">$1</span>"');
128    html = html.replace(/src="(.*?)"/g, 'src="<span class="code-editor-src">$1</span>"');
129    html = html.replace(/href="(.*?)"/g, 'href="<span class="code-editor-href">$1</span>"');
130    html = html.replace(/id="(.*?)"/g, 'id="<span class="code-editor-id">$1</span>"');
131    html = html.replace(/alt="(.*?)"/g, 'alt="<span class="code-editor-alt">$1</span>"');
132    indent = -1;
133    let obj = $('<div>' + html + '</div>');
134    $(obj).find('div').each(function () {
135        let css = {
136            'margin-left': ((indent + 1) * 20) + 'px'
137        };
138        if ($(this).text().substring(0, 2) == '</') {
139            indent--;
140        } else if ($(this).text().substring(0, 1) == '<' && $(this).text().substring(0, 5) !== '<img ' && $(this).text().substring(0, 7) !== '<input ' && $(this).text().substring(0, 3) !== '<br') {
141            indent++;
142        }
143        $(this).css(css);
144    });
145    return $(obj).html();
146}
147function UpdateDataTypes(e) {
148    let elements = e.children();
149    elements.each(function () {
150        UpdateDataType(this);
151    });
152    /*
153    let elements = $('main [data-field="text"]').children();
154    elements.each(function () {
155        UpdateDataType(this);
156    });
157    elements.on('mouseup', function (event) {
158        SelectElement(this);
159        
160        if ($(this).is('img,figure,input')) {
161            SelectElement(this);
162        } else {
163            SelectElement();
164        }
165        
166    });*/
167}
168
169function ExitEditor() {
170    //window.location.href = window.location.href.split('?')[0];
171}
172function TFToInt(tf) {
173    if (tf) return 1;
174    return 0;
175}
176
177function FormatHTML(html) {
178    html = html.replace(/&nbsp;/g, ' ');
179    html = html.replace(/[\u00A0-\u9999<>&]/gim, function (i) {
180        return '&#' + i.charCodeAt(0) + ';';
181    });
182    html = '<div>' + html + '</div>';
183    //html = html.replace(/&#62;(.*?)&#60;/g, '&#62;<div>$1</div>&#60;');
184    html = html.replace(/class="(.*?)"/g, 'class="<span class="highlight">$1</span>"');
185    html = html.replace(/src="(.*?)"/g, 'src="<span class="highlight">$1</span>"');
186    html = html.replace(/href="(.*?)"/g, 'href="<span class="highlight">$1</span>"');
187    html = html.replace(/id="(.*?)"/g, 'id="<span class="highlight">$1</span>"');
188    html = html.replace(/alt="(.*?)"/g, 'alt="<span class="highlight">$1</span>"');
189    html = html.replace(/&#62;/g, '</span>&#62;[br]');
190    html = html.replace(/&#60;/g, '[br]&#60;<span class="highlight">');
191    html = html.replace(/\[br\]/g, '</div><div>');
192    indent = -1;
193    let obj = $('<div>' + html + '</div>');
194    $(obj).find('div').each(function () {
195        let css = {
196            'margin-left': ((indent + 1) * 20) + 'px'
197        };
198        if ($(this).text().substring(0, 2) == '</') {
199            indent--;
200        } else if ($(this).text().substring(0, 1) == '<' && $(this).text().substring(0, 5) !== '<img ' && $(this).text().substring(0, 7) !== '<input ' && $(this).text().substring(0, 3) !== '<br') {
201            indent++;
202        }
203        $(this).css(css);
204    });
205    return $(obj).html();
206}
207
208
209function WrapLinksInATags(html) {
210    // Create a DOM parser to handle the HTML
211    const parser = new DOMParser();
212    const doc = parser.parseFromString(html, "text/html");
213
214    // Regex to identify potential links (e.g., URLs or domain names)
215    const linkRegex = /\b(?:https?:\/\/|www\.)[^\s<>]+(?:\.[^\s<>]+)*(?:\/[^\s<>]*)?/gi;
216
217    // Recursive function to process child nodes
218    function processNode(node) {
219        // Only process text nodes
220        if (node.nodeType === Node.TEXT_NODE) {
221            const matches = [...node.textContent.matchAll(linkRegex)];
222            if (matches.length > 0) {
223                const fragment = document.createDocumentFragment();
224                let lastIndex = 0;
225
226                matches.forEach(match => {
227                    const start = match.index;
228                    const end = start + match[0].length;
229
230                    // Add text before the match
231                    if (start > lastIndex) {
232                        fragment.appendChild(document.createTextNode(node.textContent.slice(lastIndex, start)));
233                    }
234
235                    // Create the <a> tag for the match
236                    const link = document.createElement("a");
237                    link.href = match[0].startsWith("http") ? match[0] : "http://" + match[0];
238                    link.textContent = match[0];
239                    fragment.appendChild(link);
240
241                    lastIndex = end;
242                });
243
244                // Add remaining text
245                if (lastIndex < node.textContent.length) {
246                    fragment.appendChild(document.createTextNode(node.textContent.slice(lastIndex)));
247                }
248
249                // Replace the original text node with the fragment
250                node.replaceWith(fragment);
251            }
252        } else if (node.nodeType === Node.ELEMENT_NODE && node.tagName.toLowerCase() !== "a") {
253            // Recursively process child nodes of non-<a> elements
254            Array.from(node.childNodes).forEach(processNode);
255        }
256    }
257
258    // Process all child nodes of the body
259    Array.from(doc.body.childNodes).forEach(processNode);
260
261    // Return the modified HTML as a string
262    return doc.body.innerHTML;
263}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.