refactor(quill): Toolbar-Presets als Factory-Funktionen, Resize-Plugin überarbeitet
Toolbar-Presets liefern jetzt frische Arrays (Factory-Pattern), damit mehrere Editor-Instanzen auf einer Seite sich nicht gegenseitig beeinflussen. Resize-Modul und CSS angepasst. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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. */
|
||||
|
||||
@@ -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 <img> 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 <img>
|
||||
* (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 <img>)
|
||||
* - 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;
|
||||
}
|
||||
};
|
||||
|
||||
ImageResizeModule.prototype._onDocClick = function (e) {
|
||||
if (this.overlay && e.target !== this.activeImg && !this.overlay.contains(e.target)) {
|
||||
this._deselect();
|
||||
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]);
|
||||
}
|
||||
};
|
||||
|
||||
ImageResizeModule.prototype._select = function (img) {
|
||||
this._deselect();
|
||||
this.activeImg = img;
|
||||
|
||||
var overlay = document.createElement('div');
|
||||
overlay.className = 'ql-img-resize-overlay';
|
||||
|
||||
var handle = document.createElement('div');
|
||||
handle.className = 'ql-img-resize-handle';
|
||||
overlay.appendChild(handle);
|
||||
|
||||
this.quill.root.style.position = 'relative';
|
||||
this.quill.root.appendChild(overlay);
|
||||
this.overlay = overlay;
|
||||
|
||||
this._reposition();
|
||||
|
||||
var self = this;
|
||||
handle.addEventListener('mousedown', function (e) {
|
||||
e.preventDefault();
|
||||
self._startX = e.clientX;
|
||||
self._startW = self.activeImg.offsetWidth || self.activeImg.getBoundingClientRect().width;
|
||||
document.addEventListener('mousemove', self._onMouseMove);
|
||||
document.addEventListener('mouseup', self._onMouseUp);
|
||||
});
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ImageResizeModule.prototype._reposition = function () {
|
||||
if (!this.overlay || !this.activeImg) return;
|
||||
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 <img>-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('<img') !== -1) {
|
||||
var tmp = document.createElement('div');
|
||||
tmp.innerHTML = htmlArg;
|
||||
Array.prototype.forEach.call(tmp.querySelectorAll('img'), function (img) {
|
||||
var entry = { src: img.getAttribute('src') || '', attrs: {} };
|
||||
PASTE_ATTRS.forEach(function (a) {
|
||||
if (img.hasAttribute(a)) entry.attrs[a] = img.getAttribute(a);
|
||||
});
|
||||
seq.push(entry);
|
||||
});
|
||||
}
|
||||
|
||||
var result = origPaste.apply(this, args);
|
||||
|
||||
if (seq.length) {
|
||||
var domImgs = quill.root.querySelectorAll('img');
|
||||
var ix = 0;
|
||||
for (var di = 0; di < domImgs.length && ix < seq.length; di++) {
|
||||
var dimg = domImgs[di];
|
||||
var dsrc = dimg.getAttribute('src') || '';
|
||||
while (ix < seq.length && seq[ix].src !== dsrc) ix++;
|
||||
if (ix < seq.length && seq[ix].src === dsrc) {
|
||||
var entryAttrs = seq[ix].attrs;
|
||||
Object.keys(entryAttrs).forEach(function (k) {
|
||||
if (!dimg.hasAttribute(k)) {
|
||||
dimg.setAttribute(k, entryAttrs[k]);
|
||||
}
|
||||
});
|
||||
// Inline-Style mit !important: das HTML-width-Attribut
|
||||
// hat Spezifität 0 und wird im Intranet von der globalen
|
||||
// mysyde.css-img-Regel überstimmt (oder von Quills internem
|
||||
// Embed-Wrapping). Inline-!important gewinnt garantiert.
|
||||
// Spiegelt das Verhalten von onMove (live-drag) wider.
|
||||
if (entryAttrs.width) {
|
||||
dimg.style.setProperty('width', entryAttrs.width + 'px', 'important');
|
||||
dimg.style.setProperty('height', 'auto', 'important');
|
||||
}
|
||||
ix++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
quill.__pasteHtmlPatched = true;
|
||||
}
|
||||
}
|
||||
|
||||
ImageResize.prototype.onEditorClick = function (e) {
|
||||
if (e.target && e.target.tagName === 'IMG' && this.editor.contains(e.target)) {
|
||||
this.attachTo(e.target);
|
||||
} else {
|
||||
this.detach();
|
||||
}
|
||||
};
|
||||
|
||||
ImageResize.prototype.attachTo = function (img) {
|
||||
if (this.activeImg === img && this.overlay) return;
|
||||
this.detach();
|
||||
this.activeImg = img;
|
||||
this.overlay = this.buildOverlay();
|
||||
document.body.appendChild(this.overlay);
|
||||
this.reposition();
|
||||
};
|
||||
|
||||
ImageResize.prototype.buildOverlay = function () {
|
||||
var self = this;
|
||||
var box = document.createElement('div');
|
||||
box.className = 'ql-image-overlay';
|
||||
|
||||
['nw', 'ne', 'sw', 'se'].forEach(function (corner) {
|
||||
var h = document.createElement('div');
|
||||
h.className = 'ql-image-handle ql-image-handle-' + corner;
|
||||
h.dataset.corner = corner;
|
||||
|
||||
// Corner explizit über Closure binden, nicht über e.currentTarget
|
||||
// (currentTarget kann nach Listener-Return null sein, je nach Browser).
|
||||
var startDrag = function (e) { self.onHandleDown(e, corner); };
|
||||
h.addEventListener('mousedown', startDrag);
|
||||
h.addEventListener('touchstart', startDrag, { passive: false });
|
||||
|
||||
box.appendChild(h);
|
||||
});
|
||||
|
||||
return box;
|
||||
};
|
||||
|
||||
ImageResize.prototype.reposition = function () {
|
||||
if (!this.activeImg || !this.overlay) return;
|
||||
var r = this.activeImg.getBoundingClientRect();
|
||||
var er = this.quill.root.getBoundingClientRect();
|
||||
this.overlay.style.left = (r.left - er.left + this.quill.root.scrollLeft) + 'px';
|
||||
this.overlay.style.top = (r.top - er.top + this.quill.root.scrollTop) + 'px';
|
||||
this.overlay.style.width = r.width + 'px';
|
||||
this.overlay.style.height = r.height + 'px';
|
||||
var s = this.overlay.style;
|
||||
s.position = 'fixed';
|
||||
s.left = r.left + 'px';
|
||||
s.top = r.top + 'px';
|
||||
s.width = r.width + 'px';
|
||||
s.height = r.height + 'px';
|
||||
// KEIN pointer-events:none hier — die Handles brauchen Klick-Empfang.
|
||||
// Der gestrichelte Rand des Overlays liegt im CSS auf
|
||||
// `.ql-image-overlay` mit pointer-events:none direkt im CSS.
|
||||
s.zIndex = '10000';
|
||||
};
|
||||
|
||||
ImageResizeModule.prototype._onMouseMove = function (e) {
|
||||
ImageResize.prototype.onReposition = function () {
|
||||
this.reposition();
|
||||
};
|
||||
|
||||
ImageResize.prototype.onHandleDown = function (e, corner) {
|
||||
if (!this.activeImg) return;
|
||||
var dx = e.clientX - this._startX;
|
||||
var newW = Math.max(40, this._startW + dx);
|
||||
this.activeImg.style.width = newW + 'px';
|
||||
this.activeImg.setAttribute('width', newW);
|
||||
this._reposition();
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
var p = (e.touches && e.touches[0]) ? e.touches[0] : e;
|
||||
var r = this.activeImg.getBoundingClientRect();
|
||||
|
||||
this.dragState = {
|
||||
startX: p.clientX,
|
||||
startY: p.clientY,
|
||||
startW: r.width,
|
||||
startH: r.height,
|
||||
corner: corner
|
||||
};
|
||||
|
||||
ImageResizeModule.prototype._onMouseUp = function () {
|
||||
document.removeEventListener('mousemove', this._onMouseMove);
|
||||
document.removeEventListener('mouseup', this._onMouseUp);
|
||||
if (this.quill) this.quill.update();
|
||||
document.addEventListener('mousemove', this.onMove);
|
||||
document.addEventListener('mouseup', this.onUp);
|
||||
document.addEventListener('touchmove', this.onMove, { passive: false });
|
||||
document.addEventListener('touchend', this.onUp);
|
||||
document.addEventListener('touchcancel', this.onUp);
|
||||
};
|
||||
|
||||
ImageResizeModule.prototype._deselect = function () {
|
||||
ImageResize.prototype.onMove = function (e) {
|
||||
if (!this.dragState || !this.activeImg) return;
|
||||
if (e.cancelable) e.preventDefault();
|
||||
|
||||
var p = (e.touches && e.touches[0]) ? e.touches[0] : e;
|
||||
var dx = p.clientX - this.dragState.startX;
|
||||
|
||||
// Links liegende Handles invertieren das Vorzeichen:
|
||||
// rechts ziehen → dx positiv → image schrumpft (left handle)
|
||||
if (this.dragState.corner === 'nw' || this.dragState.corner === 'sw') {
|
||||
dx = -dx;
|
||||
}
|
||||
|
||||
var editorR = this.editor.getBoundingClientRect();
|
||||
var maxW = Math.max(MIN_WIDTH, editorR.width - 8);
|
||||
var newW = Math.max(MIN_WIDTH, Math.min(maxW, this.dragState.startW + dx));
|
||||
newW = Math.round(newW);
|
||||
|
||||
// width sowohl als HTML-Attribut als auch als Inline-Style setzen.
|
||||
// Inline-Style ist nötig, weil im Intranet eine CSS-Regel mit höherer
|
||||
// Spezifität als das Attribut existiert (an dieser Stelle nicht zu
|
||||
// finden ohne Live-DevTools). Inline-Spezifität (1000) gewinnt gegen
|
||||
// alle nicht-!important CSS-Regeln. setProperty mit 'important' als
|
||||
// Fallback, falls eine !important-Regel im Spiel ist.
|
||||
var widthPx = newW + 'px';
|
||||
this.activeImg.setAttribute('width', String(newW));
|
||||
this.activeImg.style.setProperty('width', widthPx, 'important');
|
||||
this.activeImg.style.setProperty('height', 'auto', 'important');
|
||||
this.activeImg.removeAttribute('height');
|
||||
this.reposition();
|
||||
};
|
||||
|
||||
ImageResize.prototype.onUp = function () {
|
||||
document.removeEventListener('mousemove', this.onMove);
|
||||
document.removeEventListener('mouseup', this.onUp);
|
||||
document.removeEventListener('touchmove', this.onMove);
|
||||
document.removeEventListener('touchend', this.onUp);
|
||||
document.removeEventListener('touchcancel', this.onUp);
|
||||
|
||||
if (this.dragState) {
|
||||
this.dragState = null;
|
||||
try { this.quill.update('user'); } catch (e) {}
|
||||
}
|
||||
};
|
||||
|
||||
ImageResize.prototype.onDocPointerDown = function (e) {
|
||||
if (!this.activeImg) return;
|
||||
if (this.overlay && this.overlay.contains(e.target)) return;
|
||||
if (e.target === this.activeImg) return;
|
||||
this.detach();
|
||||
};
|
||||
|
||||
ImageResize.prototype.detach = function () {
|
||||
if (this.overlay && this.overlay.parentNode) {
|
||||
this.overlay.parentNode.removeChild(this.overlay);
|
||||
}
|
||||
this.overlay = null;
|
||||
this.activeImg = null;
|
||||
this.dragState = null;
|
||||
};
|
||||
|
||||
Quill.register('modules/imageResize', ImageResizeModule);
|
||||
})();
|
||||
Quill.register('modules/imageResize', ImageResize);
|
||||
global.QuillImageResize = ImageResize;
|
||||
})(window);
|
||||
|
||||
@@ -1,46 +1,108 @@
|
||||
window.QuillToolbarPresets = {
|
||||
/**
|
||||
* Geteilte Toolbar-Presets für Quill-Editoren (Wiki, Forum).
|
||||
*
|
||||
* Jeder Preset ist eine Factory-Funktion, die ein frisches Array
|
||||
* liefert. Grund: Quill kann das Modules-Objekt während der Init
|
||||
* mutieren — bei mehreren Editor-Instanzen auf einer Seite (z.B.
|
||||
* Wiki mit Sprach-Tabs) würden sie sich sonst gegenseitig
|
||||
* verändern.
|
||||
*
|
||||
* Verwendung pro Editor:
|
||||
*
|
||||
* container: window.QuillToolbarPresets.full()
|
||||
*
|
||||
* Bewusst nicht enthalten: `code` (inline) — nur `code-block`,
|
||||
* weil Multi-Line-Code die häufige Nutzung ist.
|
||||
*/
|
||||
(function (global) {
|
||||
'use strict';
|
||||
|
||||
global.QuillToolbarPresets = {
|
||||
// Vollständige Toolbar — Goldstandard für Wiki und Forum
|
||||
full: function () {
|
||||
return [
|
||||
[{ header: [1, 2, 3, 4, 5, 6, false] }],
|
||||
[{ header: [1, 2, 3, false] }],
|
||||
['bold', 'italic', 'underline', 'strike'],
|
||||
[{ script: 'sub' }, { script: 'super' }],
|
||||
[{ color: [] }, { background: [] }],
|
||||
[{ list: 'ordered' }, { list: 'bullet' }],
|
||||
[{ indent: '-1' }, { indent: '+1' }],
|
||||
[{ align: [] }],
|
||||
['blockquote', 'code-block'],
|
||||
['link', 'image'],
|
||||
['table'],
|
||||
[
|
||||
{ list: 'ordered' }, { list: 'bullet' },
|
||||
{ indent: '-1' }, { indent: '+1' }
|
||||
],
|
||||
['link', 'image', 'video'],
|
||||
['code-block', 'table'],
|
||||
['clean']
|
||||
];
|
||||
},
|
||||
|
||||
// Compact-Toolbar — wie full(), aber OHNE Image und Video.
|
||||
// Genutzt in Modulen, die kein Image-Backend haben (Tasks, Products).
|
||||
compact: function () {
|
||||
return [
|
||||
[{ header: [1, 2, 3, false] }],
|
||||
['bold', 'italic', 'underline'],
|
||||
[{ list: 'ordered' }, { list: 'bullet' }],
|
||||
['link', 'image'],
|
||||
['table'],
|
||||
['bold', 'italic', 'underline', 'strike'],
|
||||
[{ script: 'sub' }, { script: 'super' }],
|
||||
[{ color: [] }, { background: [] }],
|
||||
[{ align: [] }],
|
||||
[
|
||||
{ list: 'ordered' }, { list: 'bullet' },
|
||||
{ indent: '-1' }, { indent: '+1' }
|
||||
],
|
||||
['link'],
|
||||
['code-block', 'table'],
|
||||
['clean']
|
||||
];
|
||||
},
|
||||
|
||||
// Better-Table-Modul-Konfiguration. Identisch über alle Editoren —
|
||||
// deutsche Operations-Menu-Labels und der Hintergrundfarben-Picker
|
||||
// für Tabellenzellen.
|
||||
betterTableConfig: function () {
|
||||
return {
|
||||
operationMenu: {
|
||||
items: {
|
||||
insertColumnRight: { text: 'Spalte rechts' },
|
||||
insertColumnLeft: { text: 'Spalte links' },
|
||||
insertRowUp: { text: 'Zeile oben' },
|
||||
insertRowDown: { text: 'Zeile unten' },
|
||||
mergeCells: { text: 'Zellen verbinden' },
|
||||
unmergeCells: { text: 'Zellen trennen' },
|
||||
deleteColumn: { text: 'Spalte löschen' },
|
||||
insertRowUp: { text: 'Zeile oberhalb einfügen' },
|
||||
insertRowDown: { text: 'Zeile unterhalb einfügen' },
|
||||
insertColumnLeft: { text: 'Spalte links einfügen' },
|
||||
insertColumnRight: { text: 'Spalte rechts einfügen' },
|
||||
mergeCells: { text: 'Zellen zusammenführen' },
|
||||
unmergeCells: { text: 'Zellen teilen' },
|
||||
deleteRow: { text: 'Zeile löschen' },
|
||||
deleteColumn: { text: 'Spalte löschen' },
|
||||
deleteTable: { text: 'Tabelle löschen' }
|
||||
},
|
||||
color: {
|
||||
colors: [
|
||||
'#f8f9fa', '#e9ecef', '#dee2e6', '#ced4da',
|
||||
'#adb5bd', '#6c757d',
|
||||
'green', 'red', 'yellow', 'blue',
|
||||
'white', 'black', 'orange'
|
||||
],
|
||||
text: 'Hintergrundfarbe'
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// Vorlage für künftige Erweiterungen — derzeit nicht aktiv:
|
||||
//
|
||||
// compact: function () {
|
||||
// // wie full, aber ohne 'table' — z.B. für Comment-Felder
|
||||
// var t = this.full();
|
||||
// var idx = t.findIndex(function (row) { return row.indexOf('table') > -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);
|
||||
|
||||
Reference in New Issue
Block a user