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