Proyectos de Subversion Moodle

Rev

| 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
import {renderForPromise} from 'core/templates';
17
import {getFilePicker} from './options';
18
import {getString} from 'core/str';
19
 
20
/**
21
 * Get the image path for the specified image.
22
 *
23
 * @param {string} identifier The name of the image
24
 * @param {string} component The component name
25
 * @return {string} The image URL path
26
 */
27
export const getImagePath = (identifier, component = 'editor_tiny') => Promise.resolve(M.util.image_url(identifier, component));
28
 
29
export const getButtonImage = async(identifier, component = 'editor_tiny') => renderForPromise('editor_tiny/toolbar_button', {
30
    image: await getImagePath(identifier, component),
31
});
32
 
33
/**
34
 * Helper to display a filepicker and return a Promise.
35
 *
36
 * The Promise will resolve when a file is selected, or reject if the file type is not found.
37
 *
38
 * @param {TinyMCE} editor
39
 * @param {string} filetype
40
 * @returns {Promise<object>} The file object returned by the filepicker
41
 */
42
export const displayFilepicker = (editor, filetype) => new Promise((resolve, reject) => {
43
    const configuration = getFilePicker(editor, filetype);
44
    if (configuration) {
45
        const options = {
46
            ...configuration,
47
            formcallback: resolve,
48
        };
49
        M.core_filepicker.show(Y, options);
50
        return;
51
    }
52
    reject(`Unknown filetype ${filetype}`);
53
});
54
 
55
/**
56
 * Given a TinyMCE Toolbar configuration, add the specified button to the named section.
57
 *
58
 * @param {object} toolbar
59
 * @param {string} section
60
 * @param {string} button
61
 * @param {string|null} [after=null]
62
 * @returns {object} The toolbar configuration
63
 */
64
export const addToolbarButton = (toolbar, section, button, after = null) => {
65
    if (!toolbar) {
66
        return [{
67
            name: section,
68
            items: [button],
69
        }];
70
    }
71
 
72
    const mutatedToolbar = JSON.parse(JSON.stringify(toolbar));
73
    return mutatedToolbar.map((item) => {
74
        if (item.name === section) {
75
            if (after) {
76
                // Insert new button after the specified button.
77
                let index = item.items.findIndex(value => value == after);
78
                if (index !== -1) {
79
                    item.items.splice(index + 1, 0, button);
80
                }
81
            } else {
82
                // Append button to end of button section.
83
                item.items.push(button);
84
            }
85
        }
86
 
87
        return item;
88
    });
89
};
90
 
91
/**
92
 * Given a TinyMCE Toolbar configuration, add the specified buttons to the named section.
93
 *
94
 * @param {object} toolbar
95
 * @param {string} section
96
 * @param {Array} buttons
97
 * @returns {object} The toolbar configuration
98
 */
99
export const addToolbarButtons = (toolbar, section, buttons) => {
100
    if (!toolbar) {
101
        return [{
102
            name: section,
103
            items: buttons,
104
        }];
105
    }
106
 
107
    const mutatedToolbar = JSON.parse(JSON.stringify(toolbar));
108
    return mutatedToolbar.map((item) => {
109
        if (item.name === section) {
110
            buttons.forEach(button => item.items.push(button));
111
        }
112
 
113
        return item;
114
    });
115
};
116
 
117
/**
118
 * Insert a new section into the toolbar.
119
 *
120
 * @param {array} toolbar The TinyMCE.editor.settings.toolbar configuration
121
 * @param {string} name The new section name to add
122
 * @param {string} relativeTo Insert relative to this section name
123
 * @param {boolean} append Append or Prepend
124
 * @returns {array}
125
 */
126
export const addToolbarSection = (toolbar, name, relativeTo, append = true) => {
127
    const newSection = {
128
        name,
129
        items: [],
130
    };
131
    const sectionInserted = toolbar.some((section, index) => {
132
        if (section.name === relativeTo) {
133
            if (append) {
134
                toolbar.splice(index + 1, 0, newSection);
135
            } else {
136
                toolbar.splice(index, 0, newSection);
137
            }
138
            return true;
139
        }
140
        return false;
141
    });
142
 
143
    if (!sectionInserted) {
144
        // Relative section not found.
145
        if (append) {
146
            toolbar.push(newSection);
147
        } else {
148
            toolbar.unshift(newSection);
149
        }
150
    }
151
 
152
    return toolbar;
153
};
154
 
155
/**
156
 * Given a TinyMCE Menubar configuration, add the specified button to the named section.
157
 *
158
 * @param {object} menubar
159
 * @param {string} section
160
 * @param {string} menuitem
161
 * @param {string|null} [after=null]
162
 * @returns {object}
163
 */
164
export const addMenubarItem = (menubar, section, menuitem, after = null) => {
165
    if (!menubar) {
166
        const emptyMenubar = {};
167
        emptyMenubar[section] = {
168
            title: section,
169
            items: menuitem,
170
        };
171
    }
172
 
173
    const mutatedMenubar = JSON.parse(JSON.stringify(menubar));
174
    Array.from(Object.entries(mutatedMenubar)).forEach(([name, menu]) => {
175
        if (name === section) {
176
            if (after) {
177
                // Insert new item after the specified menu item.
178
                let index = menu.items.indexOf(after);
179
                if (index !== -1) {
180
                    index += after.length;
181
                    menu.items = menu.items.slice(0, index) + ` ${menuitem}` + menu.items.slice(index);
182
                }
183
            } else {
184
                // Append item to end of the menu section.
185
                menu.items = `${menu.items} ${menuitem}`;
186
            }
187
        }
188
    });
189
 
190
    return mutatedMenubar;
191
};
192
 
193
/**
194
 * Given a TinyMCE contextmenu configuration, add the specified button to the end.
195
 *
196
 * @param {string} contextmenu
197
 * @param {string[]} menuitems
198
 * @returns {string}
199
 */
200
export const addContextmenuItem = (contextmenu, ...menuitems) => {
201
    const contextmenuItems = (contextmenu ?? '').split(' ');
202
 
203
    return contextmenuItems
204
        .concat(menuitems)
205
        .filter((item) => item !== '')
206
        .join(' ');
207
};
208
 
209
/**
210
 * Given a TinyMCE quickbars configuration, add items to the menu.
211
 *
212
 * @param {string} quicktoolbar
213
 * @param {string[]} toolbaritems
214
 * @returns {string}
215
 */
216
export const addQuickbarsToolbarItem = (quicktoolbar, ...toolbaritems) => {
217
    const quicktoolbarItems = (quicktoolbar ?? '').split(' ');
218
 
219
    return quicktoolbarItems
220
        .concat(toolbaritems)
221
        .filter((item) => item !== '')
222
        .join(' ');
223
};
224
 
225
/**
226
 * Get the link to the user documentation for the named plugin.
227
 *
228
 * @param {string} pluginName
229
 * @returns {string}
230
 */
231
export const getDocumentationLink = (pluginName) => `https://docs.moodle.org/en/editor_tiny/${pluginName}`;
232
 
233
/**
234
 * Get the default plugin metadata for the named plugin.
235
 * If no URL is provided, then a URL is generated pointing to the standard Moodle Documentation.
236
 *
237
 * @param {string} component The component name
238
 * @param {string} pluginName The plugin name
239
 * @param {string|null} [url=null] An optional URL to the plugin documentation
240
 * @returns {object}
241
 */
242
export const getPluginMetadata = async(component, pluginName, url = null) => {
243
    const name = await getString('helplinktext', component);
244
    return {
245
        getMetadata: () => ({
246
            name,
247
            url: url ?? getDocumentationLink(pluginName),
248
        }),
249
    };
250
};
251
 
252
/**
253
 * Ensure that the editor is still in the DOM, removing it if it is not.
254
 *
255
 * @param {TinyMCE} editor
256
 * @returns {TinyMCE|null}
257
 */
258
export const ensureEditorIsValid = (editor) => {
259
    // TinyMCE uses the element ID as a map key internally, even if the target has changed.
260
    // In cases such as where an editor is in a modal form which has been detached from the DOM, but the editor not removed,
261
    // we need to manually destroy the editor.
262
    // We could theoretically do this with a Mutation Observer, but in some cases the Node may be moved,
263
    // or added back elsewhere in the DOM.
264
    if (!editor.getElement().isConnected) {
265
        return null;
266
    }
267
 
268
    return editor;
269
};
270
 
271
/**
272
 * Given a TinyMCE Toolbar configuration, remove the specified button from the named section.
273
 *
274
 * @param {object} toolbar
275
 * @param {string} section
276
 * @param {string} button
277
 * @returns {object} The toolbar configuration
278
 */
279
 export const removeToolbarButton = (toolbar, section, button) => {
280
    if (!toolbar) {
281
        return [{
282
            name: section,
283
            items: [button],
284
        }];
285
    }
286
 
287
    const mutatedToolbar = JSON.parse(JSON.stringify(toolbar));
288
    return mutatedToolbar.map((item) => {
289
        if (item.name === section) {
290
            item.items.splice(item.items.indexOf(button), 1);
291
        }
292
 
293
        return item;
294
    });
295
};
296
 
297
/**
298
 * Given a TinyMCE Toolbar configuration, remove the specified buttons from the named section.
299
 *
300
 * @param {object} toolbar
301
 * @param {string} section
302
 * @param {Array} buttons
303
 * @returns {object} The toolbar configuration
304
 */
305
 export const removeToolbarButtons = (toolbar, section, buttons) => {
306
    if (!toolbar) {
307
        return [{
308
            name: section,
309
            items: buttons,
310
        }];
311
    }
312
 
313
    const mutatedToolbar = JSON.parse(JSON.stringify(toolbar));
314
    return mutatedToolbar.map((item) => {
315
        if (item.name === section) {
316
            buttons.forEach(button => item.items.splice(item.items.indexOf(button), 1));
317
        }
318
 
319
        return item;
320
    });
321
};
322
 
323
/**
324
 * Remove the specified sub-menu item from the named section.
325
 * Recreate a menu with the same sub-menu items but remove the specified item.
326
 *
327
 * @param {TinyMCE} editor
328
 * @param {string} section
329
 * @param {string} submenuitem The text of sub-menu that we want to removed
330
 */
331
export const removeSubmenuItem = async(editor, section, submenuitem) => {
332
    // Get menu items.
333
    const menuItems = editor.ui.registry.getAll().menuItems[section];
334
 
335
    // Because we will match between title strings,
336
    // we make sure no problems arise while applying multi-language.
337
    const submenuitemtitle = await getString(submenuitem, 'editor_tiny');
338
 
339
    // Overriding the menu items,
340
    // by recreating them but excluding the specified sub-menu.
341
    if (menuItems) {
342
        editor.ui.registry.addNestedMenuItem(
343
            section,
344
            {
345
                text: menuItems.text,
346
                getSubmenuItems: () => {
347
                    let newSubmenu = [];
348
                    menuItems.getSubmenuItems().forEach((item) => {
349
                        // Need to trim the text because some of the sub-menus use space to replace an icon.
350
                        if (item.text.trim() != submenuitemtitle) {
351
                            newSubmenu.push(item);
352
                        }
353
                    });
354
                    return newSubmenu;
355
                }
356
            }
357
        );
358
    }
359
};
360
 
361
/**
362
 * Given a TinyMCE Menubar configuration, remove the specified menu from the named section.
363
 *
364
 * @param {string} menubar
365
 * @param {string} section
366
 * @param {string} menuitem
367
 * @returns {object}
368
 */
369
export const removeMenubarItem = (menubar, section, menuitem) => {
370
    menubar[section].items = menubar[section].items
371
        .replace(menuitem, '');
372
 
373
    return menubar;
374
};
375
 
376
/**
377
 * Given a TinyMCE Menubar configuration, remove the specified menu from the named section.
378
 *
379
 * @param {string} menubar
380
 * @param {string} section
381
 * @param {Array} menuitems
382
 * @returns {object}
383
 */
384
export const removeMenubarItems = (menubar, section, menuitems) => {
385
    // Create RegExp pattern.
386
    const regexPattern = new RegExp(menuitems.join('|'), "ig");
387
 
388
    // Remove menuitems.
389
    menubar[section].items = menubar[section].items.replace(regexPattern, '');
390
 
391
    return menubar;
392
};
393
 
394
/**
395
 * Updates the state of the editor.
396
 *
397
 * @param {TinyMCE} editor
398
 * @param {HTMLElement} target
399
 */
400
export const updateEditorState = (editor, target) => {
401
    if (target.hasAttribute('readonly')) {
402
        editor.mode.set("readonly");
403
    } else {
404
        editor.mode.set("design");
405
    }
406
};