Proyectos de Subversion Moodle

Rev

Rev 1 | | Comparar con el anterior | Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
// This file is part of Moodle - http://moodle.org/
2
//
3
// Moodle is free software: you can redistribute it and/or modify
4
// it under the terms of the GNU General Public License as published by
5
// the Free Software Foundation, either version 3 of the License, or
6
// (at your option) any later version.
7
//
8
// Moodle is distributed in the hope that it will be useful,
9
// but WITHOUT ANY WARRANTY; without even the implied warranty of
10
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
// GNU General Public License for more details.
12
//
13
// You should have received a copy of the GNU General Public License
14
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
15
 
16
/**
17
 * Helper for Tiny noautolink plugin.
18
 *
19
 * @module      tiny_noautolink/noautolink
20
 * @copyright   2023 Meirza <meirza.arson@moodle.com>
21
 * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
22
 */
23
 
24
import Pending from 'core/pending';
25
 
26
const noautolinkClassName = 'nolink';
27
const noautolinkTagHTML = 'span';
28
const notificationTimeout = 2000;
29
 
30
/**
31
 * Handle action.
32
 *
33
 * @param {TinyMCE} editor
34
 * @param {object} messages
35
 */
36
export const handleAction = (editor, messages) => {
37
    const toggleState = isInAnchor(editor, editor.selection.getNode());
38
    const urlString = getSelectedContent(editor);
39
    if (!toggleState && urlString !== '') {
40
        setNoAutoLink(editor, messages, urlString);
41
    } else if (toggleState) {
42
        unsetNoAutoLink(editor, messages, urlString);
43
    } else {
44
        editor.notificationManager.open({text: messages.infoEmptySelection, type: 'info', timeout: notificationTimeout});
45
    }
46
};
47
 
48
/**
49
 * Display notification feedback when applying the noautolink to the selected text.
50
 *
51
 * @param {TinyMCE} editor
52
 * @param {object} messages
53
 * @param {String} urlString
54
 */
55
const setNoAutoLink = (editor, messages, urlString) => {
11 efrain 56
    const pendingPromise = new Pending('tiny_noautolink/setNoautolink');
57
    // Applying the auto-link prevention.
58
    setNoautolinkOnSelection(editor, urlString)
59
    .catch(error => {
60
        editor.notificationManager.open({text: error, type: 'error', timeout: notificationTimeout});
61
    })
62
    .finally(() => {
63
        editor.notificationManager.open({text: messages.infoAddSuccess, type: 'success', timeout: notificationTimeout});
64
        pendingPromise.resolve();
65
    });
1 efrain 66
};
67
 
68
/**
69
 * Display notification feedback when removing the noautolink to the selected text.
70
 *
71
 * @param {TinyMCE} editor
72
 * @param {object} messages
73
 */
74
const unsetNoAutoLink = (editor, messages) => {
75
    const nodeString = editor.selection.getNode().outerHTML.trim();
76
    // Convert HTML string to DOM element to get nolink class.
77
    const wrapper = document.createElement('div');
78
    wrapper.innerHTML = nodeString;
79
    const tempElement = wrapper.firstChild;
80
    if (tempElement.classList.contains('nolink')) {
81
        const pendingPromise = new Pending('tiny_noautolink/setNoautolink');
82
        // Removing the auto-link prevention.
83
        unsetNoautolinkOnSelection(editor, nodeString)
84
        .catch(error => {
85
            editor.notificationManager.open({text: error, type: 'error', timeout: notificationTimeout});
86
            pendingPromise.reject(error); // Handle the error as needed.
87
        })
88
        .finally(() => {
89
            editor.notificationManager.open({text: messages.infoRemoveSuccess, type: 'success', timeout: notificationTimeout});
90
            pendingPromise.resolve();
91
        });
92
    }
93
};
94
 
95
/**
96
 * Return the full string based on the position of the cursor within the string.
97
 *
98
 * @param {TinyMCE} editor
99
 * @returns {String}
100
 */
101
const getSelectedContent = (editor) => {
102
    const selection = editor.selection; // Get the selection object.
103
    let content = selection.getContent({format: 'text'}).trim();
104
    if (content == '') {
105
        const range = selection.getRng(); // Get the range object.
106
 
107
        // Check if the cursor is within a text node.
108
        if (range.startContainer.nodeType === Node.TEXT_NODE) {
109
            const textContent = range.startContainer.textContent;
110
            const cursorOffset = range.startOffset;
111
 
112
            // Find the word boundaries around the cursor.
113
            let wordStart = cursorOffset;
114
            while (wordStart > 0 && /\S/.test(textContent[wordStart - 1])) {
115
                wordStart--;
116
            }
117
 
118
            let wordEnd = cursorOffset;
119
            while (wordEnd < textContent.length && /\S/.test(textContent[wordEnd])) {
120
                wordEnd++;
121
            }
122
 
123
            // Set the selection range to the word.
124
            selection.setRng({
125
                startContainer: range.startContainer,
126
                startOffset: wordStart,
127
                endContainer: range.startContainer,
128
                endOffset: wordEnd,
129
            });
130
            content = selection.getContent({format: 'text'}).trim();
131
        }
132
    }
133
    return content;
134
};
135
 
136
/**
137
 * Wrap the selection with the nolink class.
138
 *
139
 * @param {TinyMCE} editor
140
 * @param {String} url URL the link will point to.
141
 */
142
const setNoautolinkOnSelection = async(editor, url) => {
143
    const newContent = `<${noautolinkTagHTML} class="${noautolinkClassName}">${url}</${noautolinkTagHTML}>`;
144
    editor.selection.setContent(newContent);
145
 
146
    // Select the new content.
147
    const currentNode = editor.selection.getNode();
148
    const currentDOM = editor.dom.select(`${noautolinkTagHTML}.${noautolinkClassName}`, currentNode);
149
    currentDOM.forEach(function(value, index) {
150
        if (value.outerHTML == newContent) {
151
            editor.selection.select(currentDOM[index]);
152
            return;
153
        }
154
    });
155
};
156
 
157
/**
158
 * Remove the nolink on the selection.
159
 *
160
 * @param {TinyMCE} editor
161
 * @param {String} url URL the link will point to.
162
 */
163
const unsetNoautolinkOnSelection = async(editor, url) => {
164
    const regex = new RegExp(`</?${noautolinkTagHTML}[^>]*>`, "g");
165
    url = url.replace(regex, "");
166
    const currentSpan = editor.dom.getParent(editor.selection.getNode(), noautolinkTagHTML);
167
    currentSpan.outerHTML = url;
168
};
169
 
170
/**
171
 * Get anchor element.
172
 *
173
 * @param {TinyMCE} editor
174
 * @param {Element} selectedElm
175
 * @returns {Element}
176
 */
177
const getAnchorElement = (editor, selectedElm) => {
178
    selectedElm = selectedElm || editor.selection.getNode();
179
    return editor.dom.getParent(selectedElm, `${noautolinkTagHTML}.${noautolinkClassName}`);
180
};
181
 
182
 
183
/**
184
 * Check the current selected element is an anchor or not.
185
 *
186
 * @param {TinyMCE} editor
187
 * @param {Element} selectedElm
188
 * @returns {boolean}
189
 */
190
const isInAnchor = (editor, selectedElm) => getAnchorElement(editor, selectedElm) !== null;
191
 
192
/**
193
 * Change state of button.
194
 *
195
 * @param {TinyMCE} editor
196
 * @param {function()} toggler
197
 * @returns {function()}
198
 */
199
const toggleState = (editor, toggler) => {
200
    editor.on('NodeChange', toggler);
201
    return () => editor.off('NodeChange', toggler);
202
};
203
 
204
/**
205
 * Change the active state of button.
206
 *
207
 * @param {TinyMCE} editor
208
 * @returns {function(*): function(): *}
209
 */
210
export const toggleActiveState = (editor) => (api) => {
211
    const updateState = () => api.setActive(!editor.mode.isReadOnly() && isInAnchor(editor, editor.selection.getNode()));
212
    updateState();
213
    return toggleState(editor, updateState);
11 efrain 214
};