diff --git a/module/shared_components/quill/quill_resize.css b/module/shared_components/quill/quill_resize.css
index ba03566..50e3612 100644
--- a/module/shared_components/quill/quill_resize.css
+++ b/module/shared_components/quill/quill_resize.css
@@ -1,20 +1,59 @@
-.ql-img-resize-overlay {
- position: absolute;
- border: 1px dashed #3C7DD9;
+/* Drag-Handle Overlay für Quill Image Resize */
+
+.ql-image-overlay {
box-sizing: border-box;
+ border: 1px dashed #2684ff;
pointer-events: none;
- z-index: 100;
}
-.ql-img-resize-handle {
+.ql-image-handle {
position: absolute;
- bottom: -5px;
- right: -5px;
- width: 10px;
- height: 10px;
- background: #3C7DD9;
- border: 1px solid #fff;
+ width: 12px;
+ height: 12px;
+ background: #ffffff;
+ border: 2px solid #2684ff;
border-radius: 2px;
- cursor: nwse-resize;
- pointer-events: all;
+ pointer-events: auto;
+ z-index: 10001;
+ box-shadow: 0 0 2px rgba(0, 0, 0, 0.3);
}
+
+.ql-image-handle:hover {
+ background: #2684ff;
+}
+
+.ql-image-handle-nw {
+ left: -6px;
+ top: -6px;
+ cursor: nwse-resize;
+}
+
+.ql-image-handle-ne {
+ right: -6px;
+ top: -6px;
+ cursor: nesw-resize;
+}
+
+.ql-image-handle-sw {
+ left: -6px;
+ bottom: -6px;
+ cursor: nesw-resize;
+}
+
+.ql-image-handle-se {
+ right: -6px;
+ bottom: -6px;
+ cursor: nwse-resize;
+}
+
+/* Während des Drags Text-Selection global unterdrücken */
+.ql-image-overlay.is-dragging,
+.ql-image-overlay.is-dragging * {
+ user-select: none;
+ -webkit-user-select: none;
+}
+
+/* Im Editor selbst soll das Bild visuell ein leichtes Outline bekommen,
+ wenn es aktiv ist — aber CSS kann nicht aus dem Overlay zurückgreifen,
+ daher: ein dünner Schatten ist genug; der gestrichelte Rand am Overlay
+ markiert das aktive Bild klar genug. */
diff --git a/module/shared_components/quill/quill_resize.js b/module/shared_components/quill/quill_resize.js
index a1c05d9..a691a95 100644
--- a/module/shared_components/quill/quill_resize.js
+++ b/module/shared_components/quill/quill_resize.js
@@ -1,96 +1,363 @@
-(function () {
+/**
+ * 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') return;
-
- function ImageResizeModule(quill, options) {
- this.quill = quill;
- this.overlay = null;
- this.activeImg = null;
- this._startX = 0;
- this._startW = 0;
-
- this._onImgClick = this._onImgClick.bind(this);
- this._onDocClick = this._onDocClick.bind(this);
- this._onMouseMove = this._onMouseMove.bind(this);
- this._onMouseUp = this._onMouseUp.bind(this);
-
- quill.root.addEventListener('click', this._onImgClick);
- document.addEventListener('click', this._onDocClick);
+ if (typeof Quill === 'undefined') {
+ console.error('[quill_resize] Quill ist nicht geladen.');
+ return;
}
- ImageResizeModule.prototype._onImgClick = function (e) {
- if (e.target && e.target.tagName === 'IMG') {
- e.stopPropagation();
- this._select(e.target);
+ // 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('
-1; });
+ // if (idx > -1) t[idx] = ['code-block'];
+ // return t;
+ // },
+ //
+ // minimal: function () {
+ // return [
+ // [{ header: [2, 3, false] }],
+ // ['bold', 'italic', 'underline'],
+ // [{ list: 'ordered' }, { list: 'bullet' }],
+ // ['link'],
+ // ['clean']
+ // ];
+ // }
+ };
+})(window);