PageSourceSearch

https://www.aulaformacion.org/campus/mieditor/moodle-ckeditor.js

js aulaformacion.org collected 2026-10-04 13:25:23 UTC 13,496 bytes, 327 lines download raw bytes

1/**
2 * Integracion de CKEditor 4 en Moodle 1.9
3 * ---------------------------------------
4 * Sustituye los <textarea> en plano por CKEditor sin tocar el core de Moodle.
5 * Requisito: el editor HTML de Moodle debe estar DESACTIVADO
6 * (Administracion del sitio > Apariencia > Editor HTML > htmleditor = No).
7 *
8 * Coloca este archivo en:  <raiz_moodle>/mieditor/moodle-ckeditor.js
9 */
10
11(function () {
12
13    /* =====================================================================
14     * 1. CONFIGURACION - ajusta esta seccion a tu sitio
15     * ===================================================================== */
16
17    var CFG = {
18
19        // Altura del area de edicion, en pixeles.
20        altura: 260,
21
22        // Textareas que NO deben convertirse en editor.
23        // Se excluye por id exacto o por atributo name exacto.
24        excluirIds: [
25            'id_configtext',      // campos de configuracion del administrador
26            'id_customcss',
27            'id_frontpageloggedin'
28        ],
29        excluirNombres: [
30            'config',
31            'additionalhtmlhead',
32            'code',
33            'sql'
34        ],
35
36        // Excluir tambien cualquier textarea con la clase CSS "no-editor".
37        claseExcluir: 'no-editor',
38
39        // Quitar los saltos de linea del HTML justo antes de guardarlo.
40        // Moodle 1.9, cuando trata un campo como "formato Moodle automatico",
41        // convierte cada salto de linea del codigo en un <br> extra al mostrarlo,
42        // y el texto acaba con doble espaciado. Se limpian solo los saltos que
43        // CKEditor pone ENTRE etiquetas, que son de formateo y no significan
44        // nada: en pantalla el codigo se sigue viendo indentado y legible.
45        limpiarSaltosAlGuardar: true,
46
47        // true  = Intro inserta <br>; Mayus+Intro crea un parrafo.
48        // false = Intro crea un parrafo <p>; Mayus+Intro inserta <br>.
49        //         Es lo correcto en HTML: el espaciado se regula por CSS.
50        saltoConBr: false,
51
52        // Poner la etiqueta del campo ENCIMA del editor, a ancho completo,
53        // en lugar de en una columna a la izquierda que empuja el cuadro
54        // hacia la derecha. Solo afecta a los campos con editor.
55        etiquetaEncima: true,
56
57        // Forzar el desplegable "Formato" a HTML y ocultarlo.
58        // Recomendado: sin esto, Moodle puede guardar el contenido como
59        // "formato Moodle" y destrozar el HTML del editor al mostrarlo.
60        forzarFormatoHtml: true,
61
62        // Barra de herramientas: solo lo basico.
63        // Para anadir mas botones, basta con ponerlos en esta lista: el paquete
64        // Full ya trae todos los plugins cargados. Ejemplos que puedes anadir:
65        // 'TextColor', 'BGColor', 'JustifyLeft', 'JustifyCenter', 'Format',
66        // 'Font', 'FontSize', 'Strike', 'Subscript', 'Superscript', 'Maximize'.
67        toolbar: [
68            { name: 'edicion',  items: ['Undo', 'Redo'] },
69            { name: 'fuente',  items: ['Source'] },
70            { name: 'estilo',   items: ['Bold', 'Italic', 'Underline', 'RemoveFormat'] },
71            { name: 'parrafo',  items: ['NumberedList', 'BulletedList'] },
72            { name: 'insertar', items: ['Link', 'Unlink', 'Image', 'Table'] },
73            { name: 'pegar',    items: ['PasteText', 'PasteFromWord'] },
74            { name: 'sistema',  items: ['Maximize'] }
75        ]
76    };
77
78
79    /* =====================================================================
80     * 2. LOGICA - normalmente no hace falta tocar nada a partir de aqui
81     * ===================================================================== */
82
83    function tieneClase(el, clase) {
84        return (' ' + (el.className || '') + ' ').indexOf(' ' + clase + ' ') !== -1;
85    }
86
87    function estaExcluido(ta) {
88        if (tieneClase(ta, CFG.claseExcluir)) { return true; }
89        if (ta.id && indiceEn(CFG.excluirIds, ta.id) !== -1) { return true; }
90        if (ta.name && indiceEn(CFG.excluirNombres, ta.name) !== -1) { return true; }
91        return false;
92    }
93
94    // indexOf de array compatible con navegadores antiguos.
95    function indiceEn(arr, valor) {
96        for (var i = 0; i < arr.length; i++) {
97            if (arr[i] === valor) { return i; }
98        }
99        return -1;
100    }
101
102    // Sube por el DOM hasta la fila del formulario (TR de Moodle 1.9 o DIV.fitem
103    // de los formularios mform) para poder ocultarla entera.
104    function filaContenedora(el) {
105        var nodo = el;
106        for (var i = 0; i < 6 && nodo && nodo.parentNode; i++) {
107            nodo = nodo.parentNode;
108            if (!nodo.tagName) { break; }
109            if (nodo.tagName.toUpperCase() === 'TR') { return nodo; }
110            if (tieneClase(nodo, 'fitem')) { return nodo; }
111        }
112        return null;
113    }
114
115    /**
116     * Pone a "HTML" (valor 1 = FORMAT_HTML) todo desplegable de formato y lo
117     * oculta. Se comprueba el texto de la opcion ademas del valor para no tocar
118     * por error otros selects llamados "format" (p. ej. el formato del curso).
119     */
120    function ajustarSelectsDeFormato() {
121        var selects = document.getElementsByTagName('select');
122        for (var i = 0; i < selects.length; i++) {
123            var s = selects[i];
124            var nombre = (s.name || '') + ' ' + (s.id || '');
125            if (!/format/i.test(nombre)) { continue; }
126
127            var encontrada = -1;
128            for (var j = 0; j < s.options.length; j++) {
129                if (s.options[j].value === '1' && /html/i.test(s.options[j].text)) {
130                    encontrada = j;
131                    break;
132                }
133            }
134            if (encontrada === -1) { continue; }   // no es un selector de formato de texto
135
136            s.selectedIndex = encontrada;
137            var fila = filaContenedora(s);
138            if (fila) {
139                fila.style.display = 'none';
140            } else {
141                s.style.display = 'none';
142            }
143        }
144    }
145
146    /**
147     * Inyecta el CSS que pone la etiqueta del campo encima del editor y deja
148     * el cuadro a ancho completo. Se aplica solo a las filas marcadas con
149     * la clase ck-fitem, es decir, las que contienen un editor.
150     */
151    function inyectarEstilos() {
152        var css =
153            '.ck-fitem { display: block !important; float: none !important;' +
154            '  width: auto !important; overflow: hidden; margin-bottom: 14px; }' +
155            '.ck-fitem .fitemtitle { display: block !important; float: none !important;' +
156            '  width: auto !important; text-align: left !important;' +
157            '  margin: 0 0 6px 0 !important; padding: 0 !important; }' +
158            '.ck-fitem .fitemtitle label { font-weight: bold; }' +
159            '.ck-fitem .felement { display: block !important; float: none !important;' +
160            '  width: auto !important; margin-left: 0 !important; padding: 0 !important; }' +
161            '.ck-fitem .cke { width: 100% !important; }';
162
163        var st = document.createElement('style');
164        st.type = 'text/css';
165        if (st.styleSheet) {
166            st.styleSheet.cssText = css;          // IE antiguo
167        } else {
168            st.appendChild(document.createTextNode(css));
169        }
170        (document.getElementsByTagName('head')[0] || document.documentElement).appendChild(st);
171    }
172
173    /**
174     * Bandera: solo se limpian los saltos cuando el HTML va camino del
175     * <textarea>. Asi la vista "Fuente HTML" sigue mostrando el codigo
176     * indentado y legible, que es como resulta comodo editarlo.
177     */
178    var modoLimpieza = false;
179
180    /**
181     * Quita los saltos de linea que CKEditor pone junto a las etiquetas al
182     * formatear. Solo se tocan los saltos pegados a un ">" o a un "<": un
183     * espacio escrito por el usuario entre dos etiquetas no lleva salto,
184     * asi que no se pierde.
185     */
186    function limpiarSaltos(html) {
187        if (typeof html !== 'string') { return html; }
188        // Dentro de <pre> los saltos si son significativos.
189        if (/<pre[\s>]/i.test(html)) { return html; }
190        return html
191            .replace(/>[\t ]*\r?\n[\t ]*/g, '>')
192            .replace(/[\t ]*\r?\n[\t ]*</g, '<');
193    }
194
195    /**
196     * Vuelca una instancia a su <textarea> con la limpieza activada.
197     */
198    function volcar(editor) {
199        modoLimpieza = true;
200        try { editor.updateElement(); } catch (e) {}
201        modoLimpieza = false;
202    }
203
204    /**
205     * Vuelca el contenido de todas las instancias a sus <textarea>.
206     * Moodle 1.9 valida los campos obligatorios leyendo el textarea, y CKEditor
207     * solo lo actualiza al enviar el formulario: sin esto, un campo obligatorio
208     * sale en rojo la primera vez aunque tenga contenido.
209     */
210    function sincronizarTodo() {
211        // Se deja la bandera puesta durante toda la secuencia de envio: CKEditor
212        // vuelve a volcar por su cuenta al enviarse el formulario, y ese volcado
213        // tambien tiene que salir limpio. Se desactiva al terminar la tarea.
214        modoLimpieza = true;
215        for (var nombre in CKEDITOR.instances) {
216            try { CKEDITOR.instances[nombre].updateElement(); } catch (e) {}
217        }
218        window.setTimeout(function () { modoLimpieza = false; }, 0);
219    }
220
221    function arrancar() {
222        if (typeof CKEDITOR === 'undefined') {
223            if (window.console && console.warn) {
224                console.warn('CKEditor no se ha cargado: revisa la ruta de ckeditor.js');
225            }
226            return;
227        }
228
229        // Desactiva el filtro de contenido de CKEditor: quien decide que HTML
230        // es valido es Moodle (clean_text), no el editor.
231        CKEDITOR.config.allowedContent = true;
232        CKEDITOR.config.entities = false;
233        CKEDITOR.config.language = 'es';
234
235        // Oculta el aviso "This CKEditor version is not secure".
236        // La 4.22.1 es la ultima version libre; la 4.25-lts exige licencia comercial.
237        CKEDITOR.config.versionCheck = false;
238
239        if (CFG.saltoConBr) {
240            CKEDITOR.config.enterMode = CKEDITOR.ENTER_BR;
241            CKEDITOR.config.shiftEnterMode = CKEDITOR.ENTER_P;
242            CKEDITOR.config.autoParagraph = false;
243        }
244
245        var textareas = document.getElementsByTagName('textarea');
246        // Copia estatica: al reemplazar, la coleccion viva puede cambiar.
247        var lista = [];
248        for (var i = 0; i < textareas.length; i++) { lista.push(textareas[i]); }
249
250        var convertidos = 0;
251        for (var k = 0; k < lista.length; k++) {
252            var ta = lista[k];
253            if (estaExcluido(ta)) { continue; }
254            if (!ta.id) { ta.id = 'mdl_ckeditor_' + k; }
255
256            var opciones = {
257                height: CFG.altura,
258                removePlugins: 'elementspath'
259            };
260            if (CFG.toolbar) { opciones.toolbar = CFG.toolbar; }
261
262            try {
263                var ed = CKEDITOR.replace(ta.id, opciones);
264
265                // Limpieza en el propio flujo de datos de CKEditor: cubre
266                // tambien el volcado que hace el editor al enviar el formulario.
267                if (CFG.limpiarSaltosAlGuardar) {
268                    ed.on('getData', function (ev) {
269                        if (modoLimpieza) {
270                            ev.data.dataValue = limpiarSaltos(ev.data.dataValue);
271                        }
272                    });
273                }
274
275                // Mantiene el textarea al dia mientras se escribe.
276                ed.on('change', function () { volcar(this); });
277                ed.on('blur',   function () { volcar(this); });
278                // 'change' no se dispara en modo Fuente HTML; al cambiar de modo si.
279                ed.on('mode',   function () { volcar(this); });
280
281                // Marca la fila del formulario para el CSS de maquetacion.
282                if (CFG.etiquetaEncima) {
283                    var fila = filaContenedora(ta);
284                    if (fila && fila.tagName &&
285                        fila.tagName.toUpperCase() !== 'TR' &&
286                        !tieneClase(fila, 'ck-fitem')) {
287                        fila.className = (fila.className ? fila.className + ' ' : '') + 'ck-fitem';
288                    }
289                }
290
291                convertidos++;
292            } catch (e) {
293                if (window.console && console.error) { console.error(e); }
294            }
295        }
296
297        if (convertidos > 0 && CFG.etiquetaEncima) {
298            inyectarEstilos();
299        }
300
301        if (convertidos > 0 && CFG.forzarFormatoHtml) {
302            ajustarSelectsDeFormato();
303        }
304
305        if (convertidos > 0 && document.addEventListener) {
306            // Fase de captura en document: se ejecuta ANTES que el onsubmit del
307            // formulario, que es donde Moodle 1.9 valida los campos obligatorios.
308            document.addEventListener('submit', sincronizarTodo, true);
309
310            // Red de seguridad: mousedown ocurre antes del click y del submit,
311            // y cubre los envios que Moodle dispara por JavaScript.
312            document.addEventListener('mousedown', function (ev) {
313                var t = ev.target || ev.srcElement;
314                if (!t || !t.type) { return; }
315                var tipo = String(t.type).toLowerCase();
316                if (tipo === 'submit' || tipo === 'image') { sincronizarTodo(); }
317            }, true);
318        }
319    }
320
321    if (window.addEventListener) {
322        window.addEventListener('load', arrancar, false);
323    } else if (window.attachEvent) {
324        window.attachEvent('onload', arrancar);
325    }
326
327})();

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.