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.