/** * Quill-Modul: Drag-Resize für Bilder im Editor. * * Aktivierung pro Editor-Instanz via: * modules: { imageResize: true } * * Verhalten: * - Click auf im Editor → 4 Ecken-Handles als position:fixed-Overlay * - mousedown/touchstart auf einem Handle startet den Drag * - Drag aktualisiert width-Attribut + Inline-Style mit !important am * (Inline-Style ist nötig, weil die globale `img { max-width:100% }`-Regel * im Intranet das HTML-width-Attribut überstimmt — Spezifität 0 verliert * gegen jede Author-CSS-Regel auf ) * - Aspect Ratio über CSS `height: auto` gehalten * * Persistenz-Pipeline: * 1) ResizableImage-Blot (unten registriert) whitelisted alt/width/height in * Quills formats/image-Override, damit die Attribute den Quill-Save-Pfad * (`quill.update`) überleben. * 2) Pro Quill-Instanz: Clipboard-Matcher für IMG + Monkey-Patch um * `dangerouslyPasteHTML`. Die reparieren den Lade-Pfad, weil Quill * 2.0.0-dev.3 beim HTML→Delta-Parse die Attribute trotz Blot droppt. * Der Monkey-Patch reappliziert width-Attribut UND Inline-!important-Style * am Editor-DOM (gleiches Pattern wie Live-Drag). * * Bewusst klein gehalten. Keine Abhängigkeit zu third-party Resize-Modulen, * die für Quill 1.3.x geschrieben wurden und in 2.0.0-dev.3 unstet sind. */ (function (global) { 'use strict'; if (typeof Quill === 'undefined') { console.error('[quill_resize] Quill ist nicht geladen.'); return; } // ResizableImage-Blot: Quill 2.0.0-dev.3' nativer formats/image strippt // beim Re-Parse alle Attribute außer src. Dieser Blot whitelisted alt, // width, height, damit die per Drag gesetzte Größe den Roundtrip überlebt. // // Quill 2.x' Blots sind ES6-Klassen — Vererbung MUSS via `class ... extends` // erfolgen, nicht via Prototype-Chain (Image.apply würde TypeError werfen, // weil ES6-Classes nicht ohne `new` aufgerufen werden können). // // try/catch: Falls Quill in einer Version vorliegt, die diese Override-API // nicht unterstützt, kippt der Editor nicht — wir loggen und fallen // stillschweigend auf den nativen Image-Blot zurück. if (!global.__ResizableImageRegistered) { try { var Image = Quill.import('formats/image'); var ATTRS = ['alt', 'width', 'height']; var ResizableImage = class extends Image { static create(value) { var src = value; var extras = null; if (value && typeof value === 'object') { src = value.src || value.url || ''; extras = value; } var node = super.create(src); if (extras) { ATTRS.forEach(function (attr) { if (extras[attr] !== undefined && extras[attr] !== null && extras[attr] !== '') { node.setAttribute(attr, extras[attr]); } }); } return node; } static formats(domNode) { var base = (typeof super.formats === 'function') ? super.formats(domNode) : {}; ATTRS.forEach(function (attr) { if (domNode.hasAttribute(attr)) { base[attr] = domNode.getAttribute(attr); } }); return base; } format(name, value) { if (ATTRS.indexOf(name) > -1) { if (value) { this.domNode.setAttribute(name, value); } else { this.domNode.removeAttribute(name); } } else { super.format(name, value); } } }; ResizableImage.blotName = 'image'; ResizableImage.tagName = 'IMG'; Quill.register(ResizableImage, true); global.__ResizableImageRegistered = true; global.ResizableImage = ResizableImage; } catch (err) { console.error('[quill_resize] ResizableImage-Blot konnte nicht registriert werden — fallback auf nativen Image-Blot. Resize-Größe geht beim Re-Edit verloren.', err); } } var MIN_WIDTH = 50; function ImageResize(quill, options) { this.quill = quill; this.editor = quill.root; this.options = options || {}; this.activeImg = null; this.overlay = null; this.dragState = null; // Methoden binden, damit removeEventListener funktioniert this.onEditorClick = this.onEditorClick.bind(this); this.onDocPointerDown = this.onDocPointerDown.bind(this); this.onReposition = this.onReposition.bind(this); this.onMove = this.onMove.bind(this); this.onUp = this.onUp.bind(this); this.editor.addEventListener('click', this.onEditorClick); document.addEventListener('mousedown', this.onDocPointerDown, true); document.addEventListener('touchstart', this.onDocPointerDown, true); window.addEventListener('resize', this.onReposition); window.addEventListener('scroll', this.onReposition, true); // Bei Text-Changes Position anpassen (z.B. wenn Bild verschoben wird) quill.on('text-change', this.onReposition); // Quill 2.0.0-dev.3 strippt beim dangerouslyPasteHTML alle -Attribute // außer src. Reader rendert das gespeicherte HTML direkt — also korrekt; // aber im Editor ist die Größe nach Re-Open weg. Zwei Schutzschichten: // // (a) Clipboard-Matcher: Beim HTML→Delta-Parse die width/height/alt // als delta.attributes mitführen. Bei korrekt greifendem // ResizableImage-Blot überlebt das den Render zurück ans DOM. // // (b) Monkey-Patch um dangerouslyPasteHTML: Falls (a) in dev.3 nicht // durchgereicht wird (Embed-format-Hook ignoriert), reapplizieren // wir die Attribute nach dem Paste direkt am Editor-DOM. Idempotent // pro Quill-Instanz via Marker-Property. try { quill.clipboard.addMatcher('IMG', function (node, delta) { if (!node || !delta || !delta.ops || !delta.ops.length) return delta; var op = delta.ops[0]; if (!op || !op.insert) return delta; var attrs = op.attributes || {}; ['alt', 'width', 'height'].forEach(function (a) { if (node.hasAttribute(a) && attrs[a] == null) { attrs[a] = node.getAttribute(a); } }); if (Object.keys(attrs).length) op.attributes = attrs; return delta; }); } catch (e) { console.warn('[quill_resize] Clipboard-Matcher Setup fehlgeschlagen', e); } if (!quill.__pasteHtmlPatched && quill.clipboard && typeof quill.clipboard.dangerouslyPasteHTML === 'function') { var origPaste = quill.clipboard.dangerouslyPasteHTML; var PASTE_ATTRS = ['alt', 'width', 'height']; quill.clipboard.dangerouslyPasteHTML = function () { var args = Array.prototype.slice.call(arguments); var htmlArg = (typeof args[0] === 'string') ? args[0] : args[1]; var seq = []; if (typeof htmlArg === 'string' && htmlArg.indexOf('