@phuong-tran-redoc/document-engine-angular 0.0.33 → 0.0.35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import {
|
|
2
|
+
import { inject, ElementRef, Input, Directive, Injectable, ApplicationRef, createComponent, HostBinding, InjectionToken, Renderer2, ChangeDetectorRef, forwardRef, ViewEncapsulation, ChangeDetectionStrategy, Component, EventEmitter, Output, HostListener, ContentChildren, ViewChild, ViewChildren, NgZone, ViewContainerRef, EnvironmentInjector, NgModule } from '@angular/core';
|
|
3
3
|
import { Extension, Editor } from '@tiptap/core';
|
|
4
4
|
import Blockquote from '@tiptap/extension-blockquote';
|
|
5
5
|
import Bold from '@tiptap/extension-bold';
|
|
@@ -22,17 +22,17 @@ import { TextStyleKit } from '@tiptap/extension-text-style';
|
|
|
22
22
|
import Underline from '@tiptap/extension-underline';
|
|
23
23
|
import { Dropcursor, Gapcursor, TrailingNode, UndoRedo, CharacterCount, Placeholder } from '@tiptap/extensions';
|
|
24
24
|
import { ResetOnEnter, EditableRegion, RestrictedEditing, CustomOrderedList, StyledTableKit, PageBreak, ResetFormat, Indent, ClearContent, TextCase, NotumHeading, DynamicField, Color, normalizeColor, getActiveMarkRange, getSelectedCells, getCombinedCellAttributeValue, TableDefaultAttributes, getCursorCellInfo, getClosestDomElement } from '@phuong-tran-redoc/document-engine-core';
|
|
25
|
+
import { BubbleMenuPlugin } from '@tiptap/extension-bubble-menu';
|
|
25
26
|
import * as i1 from '@angular/common';
|
|
26
|
-
import {
|
|
27
|
-
import { throttle } from 'lodash-es';
|
|
27
|
+
import { DOCUMENT, CommonModule } from '@angular/common';
|
|
28
28
|
import * as i2 from '@angular/forms';
|
|
29
|
-
import { FormsModule, FormControl, ReactiveFormsModule, FormBuilder, Validators,
|
|
29
|
+
import { NG_VALUE_ACCESSOR, FormsModule, FormControl, ReactiveFormsModule, FormBuilder, Validators, NonNullableFormBuilder } from '@angular/forms';
|
|
30
|
+
import { FloatingMenuPlugin } from '@tiptap/extension-floating-menu';
|
|
31
|
+
import { computePosition, offset, flip, shift, autoUpdate } from '@floating-ui/dom';
|
|
32
|
+
import { throttle } from 'lodash-es';
|
|
30
33
|
import { DomSanitizer } from '@angular/platform-browser';
|
|
31
34
|
import { Subject, debounceTime, distinctUntilChanged, takeUntil } from 'rxjs';
|
|
32
|
-
import { autoUpdate, computePosition, offset, flip, shift } from '@floating-ui/dom';
|
|
33
35
|
import { CellSelection } from '@tiptap/pm/tables';
|
|
34
|
-
import { BubbleMenuPlugin } from '@tiptap/extension-bubble-menu';
|
|
35
|
-
import { FloatingMenuPlugin } from '@tiptap/extension-floating-menu';
|
|
36
36
|
|
|
37
37
|
const DocumentEngineKit = Extension.create({
|
|
38
38
|
name: 'DocumentEngineKit',
|
|
@@ -269,188 +269,952 @@ const DefaultEditorConfig = {
|
|
|
269
269
|
showPrintButton: false,
|
|
270
270
|
};
|
|
271
271
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
* - <button documentEngineButton="filled">Text</button> - default variant (primary)
|
|
300
|
-
* - <button documentEngineButton variant="outline">Text</button> - outline variant
|
|
301
|
-
* - <button documentEngineButton variant="icon" size="icon-sm">Icon</button> - icon button
|
|
302
|
-
*/
|
|
303
|
-
class ButtonDirective {
|
|
304
|
-
variant = 'ghost';
|
|
305
|
-
size = 'default';
|
|
306
|
-
get buttonClasses() {
|
|
307
|
-
const classes = ['btn'];
|
|
308
|
-
classes.push(`btn--${this.variant}`);
|
|
309
|
-
classes.push(`btn--size-${this.size}`);
|
|
310
|
-
return classes.join(' ');
|
|
272
|
+
class TiptapBubbleMenuDirective {
|
|
273
|
+
elRef = inject(ElementRef);
|
|
274
|
+
props;
|
|
275
|
+
editor;
|
|
276
|
+
mountTo;
|
|
277
|
+
shouldShow;
|
|
278
|
+
ngOnInit() {
|
|
279
|
+
const editor = this.editor;
|
|
280
|
+
if (!editor)
|
|
281
|
+
throw new Error('Required: Input `editor`');
|
|
282
|
+
const bubbleMenuElement = this.elRef.nativeElement;
|
|
283
|
+
bubbleMenuElement.style.visibility = 'hidden';
|
|
284
|
+
bubbleMenuElement.style.position = 'absolute';
|
|
285
|
+
// Prevent editor blur when interacting with the bubble content
|
|
286
|
+
// bubbleMenuElement.addEventListener('mousedown', this.preventDefault);
|
|
287
|
+
const { pluginKey, appendTo, updateDelay, resizeDelay, options } = this.props;
|
|
288
|
+
editor.registerPlugin(BubbleMenuPlugin({
|
|
289
|
+
pluginKey,
|
|
290
|
+
appendTo,
|
|
291
|
+
updateDelay,
|
|
292
|
+
resizeDelay,
|
|
293
|
+
editor,
|
|
294
|
+
element: bubbleMenuElement,
|
|
295
|
+
options: this.getFloatingUiOptions(options ?? {}, bubbleMenuElement),
|
|
296
|
+
shouldShow: this.shouldShow,
|
|
297
|
+
getReferencedVirtualElement: this.mountTo,
|
|
298
|
+
}));
|
|
311
299
|
}
|
|
312
|
-
|
|
313
|
-
|
|
300
|
+
ngOnDestroy() {
|
|
301
|
+
if (this.editor && this.props?.pluginKey) {
|
|
302
|
+
this.editor.unregisterPlugin(this.props.pluginKey);
|
|
303
|
+
}
|
|
304
|
+
window.requestAnimationFrame(() => {
|
|
305
|
+
if (this.elRef.nativeElement.parentNode) {
|
|
306
|
+
this.elRef.nativeElement.parentNode.removeChild(this.elRef.nativeElement);
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
this.elRef.nativeElement.removeEventListener('mousedown', this.preventDefault);
|
|
310
|
+
}
|
|
311
|
+
preventDefault(event) {
|
|
312
|
+
// Allow default behavior for focusable elements so inputs can receive focus
|
|
313
|
+
const target = event.target;
|
|
314
|
+
if (target) {
|
|
315
|
+
const tag = target.tagName;
|
|
316
|
+
const isFocusableTag = tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT' || target.isContentEditable;
|
|
317
|
+
if (isFocusableTag)
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
event.preventDefault();
|
|
321
|
+
}
|
|
322
|
+
getFloatingUiOptions(userOptions, element) {
|
|
323
|
+
// Find arrow element if it exists
|
|
324
|
+
// const arrowElement = element.querySelector('[data-arrow]') as HTMLElement | null;
|
|
325
|
+
// Merge with default Floating UI options
|
|
326
|
+
const defaultOptions = {
|
|
327
|
+
placement: 'bottom',
|
|
328
|
+
flip: {
|
|
329
|
+
fallbackPlacements: ['top', 'bottom', 'bottom-start', 'top-end', 'bottom-end'],
|
|
330
|
+
padding: 8,
|
|
331
|
+
},
|
|
332
|
+
// Enable shift middleware to keep bubble within viewport
|
|
333
|
+
shift: { padding: 8 },
|
|
334
|
+
// Add offset to create space between selection and bubble
|
|
335
|
+
offset: 10,
|
|
336
|
+
// Configure arrow if element exists
|
|
337
|
+
// arrow: arrowElement ? { element: arrowElement } : false,
|
|
338
|
+
};
|
|
339
|
+
return { ...defaultOptions, ...userOptions };
|
|
340
|
+
}
|
|
341
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: TiptapBubbleMenuDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
342
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.3.10", type: TiptapBubbleMenuDirective, isStandalone: true, selector: "tiptap-bubble-menu[editor], [tiptapBubbleMenu][editor]", inputs: { props: "props", editor: "editor", mountTo: "mountTo", shouldShow: "shouldShow" }, ngImport: i0 });
|
|
314
343
|
}
|
|
315
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type:
|
|
344
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: TiptapBubbleMenuDirective, decorators: [{
|
|
316
345
|
type: Directive,
|
|
317
346
|
args: [{
|
|
318
|
-
selector: '
|
|
347
|
+
selector: 'tiptap-bubble-menu[editor], [tiptapBubbleMenu][editor]',
|
|
319
348
|
standalone: true,
|
|
320
|
-
host: {
|
|
321
|
-
type: 'button',
|
|
322
|
-
},
|
|
323
349
|
}]
|
|
324
|
-
}], propDecorators: {
|
|
350
|
+
}], propDecorators: { props: [{
|
|
325
351
|
type: Input
|
|
326
|
-
}],
|
|
352
|
+
}], editor: [{
|
|
353
|
+
type: Input
|
|
354
|
+
}], mountTo: [{
|
|
355
|
+
type: Input
|
|
356
|
+
}], shouldShow: [{
|
|
327
357
|
type: Input
|
|
328
|
-
}], buttonClasses: [{
|
|
329
|
-
type: HostBinding,
|
|
330
|
-
args: ['class']
|
|
331
358
|
}] } });
|
|
332
359
|
|
|
333
|
-
|
|
334
|
-
const
|
|
335
|
-
|
|
336
|
-
const
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
const
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
const
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
const
|
|
363
|
-
const
|
|
364
|
-
const
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
const
|
|
370
|
-
const
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
const
|
|
376
|
-
const
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
const
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
360
|
+
function shouldShowFnFactory(params) {
|
|
361
|
+
const { editor, target, forceOpen } = params;
|
|
362
|
+
const requireFocus = target.requireFocus !== false;
|
|
363
|
+
const allowEmptySelection = target.allowEmptySelection === true;
|
|
364
|
+
return () => {
|
|
365
|
+
if (forceOpen())
|
|
366
|
+
return true;
|
|
367
|
+
if (requireFocus && !editor.isFocused)
|
|
368
|
+
return false;
|
|
369
|
+
// Avoid showing during drag/resize
|
|
370
|
+
const dragging = editor.isDragging;
|
|
371
|
+
if (dragging)
|
|
372
|
+
return false;
|
|
373
|
+
// Avoid showing when selection is empty and mark is required
|
|
374
|
+
if (!allowEmptySelection && editor.state.selection.empty && target.kind === 'mark')
|
|
375
|
+
return false;
|
|
376
|
+
// Avoid showing when extension is not active
|
|
377
|
+
const active = Array.isArray(target.name)
|
|
378
|
+
? target.name.some((name) => editor.isActive(name))
|
|
379
|
+
: editor.isActive(target.name);
|
|
380
|
+
if (!active)
|
|
381
|
+
return false;
|
|
382
|
+
// Avoid showing when extra predicate is not met
|
|
383
|
+
if (target.extraPredicate && !target.extraPredicate(editor))
|
|
384
|
+
return false;
|
|
385
|
+
return true;
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
function selectionChangeHandler(params) {
|
|
389
|
+
const { editor, target, onActive, onInactive, listenOnUpdate } = params;
|
|
390
|
+
const run = () => {
|
|
391
|
+
const isActive = Array.isArray(target.name)
|
|
392
|
+
? target.name.some((name) => editor.isActive(name))
|
|
393
|
+
: editor.isActive(target.name);
|
|
394
|
+
if (!isActive)
|
|
395
|
+
return onInactive();
|
|
396
|
+
const activeName = Array.isArray(target.name) ? target.name.find((name) => editor.isActive(name)) : target.name;
|
|
397
|
+
const attrs = target.kind === 'mark'
|
|
398
|
+
? (editor.getAttributes(activeName) ?? {})
|
|
399
|
+
: (editor.state.selection.node?.attrs ?? {});
|
|
400
|
+
onActive(attrs);
|
|
401
|
+
};
|
|
402
|
+
const onSelection = () => run();
|
|
403
|
+
const onUpdate = () => run();
|
|
404
|
+
editor.on('selectionUpdate', onSelection);
|
|
405
|
+
if (listenOnUpdate)
|
|
406
|
+
editor.on('update', onUpdate);
|
|
407
|
+
run(); // initial sync
|
|
408
|
+
return () => {
|
|
409
|
+
editor.off('selectionUpdate', onSelection);
|
|
410
|
+
if (listenOnUpdate)
|
|
411
|
+
editor.off('update', onUpdate);
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
class EventManager {
|
|
416
|
+
document = inject(DOCUMENT);
|
|
417
|
+
keyToEscapeListener = new Map();
|
|
418
|
+
keyToOutsideListener = new Map();
|
|
419
|
+
isAttached(key) {
|
|
420
|
+
return this.keyToOutsideListener.has(key) || this.keyToEscapeListener.has(key);
|
|
421
|
+
}
|
|
422
|
+
attach(key, hostElement, cb, anchorElement) {
|
|
423
|
+
// Avoid duplicate listeners
|
|
424
|
+
this.detach(key);
|
|
425
|
+
const oldOutside = this.keyToOutsideListener.get(key);
|
|
426
|
+
if (!oldOutside) {
|
|
427
|
+
const handler = (event) => {
|
|
428
|
+
const target = event.target;
|
|
429
|
+
const path = event.composedPath?.();
|
|
430
|
+
// Check if click is inside bubble
|
|
431
|
+
const isInsideBubble = !!hostElement && ((!!target && hostElement.contains(target)) || (!!path && path.includes(hostElement)));
|
|
432
|
+
if (isInsideBubble)
|
|
433
|
+
return;
|
|
434
|
+
// Check if click is on the anchor (toolbar button)
|
|
435
|
+
const isInsideAnchor = !!anchorElement && ((!!target && anchorElement.contains(target)) || (!!path && path.includes(anchorElement)));
|
|
436
|
+
if (isInsideAnchor)
|
|
437
|
+
return;
|
|
438
|
+
// Check if click is inside a CDK overlay (e.g., MatSelect dropdown, MatMenu, etc.)
|
|
439
|
+
// These are rendered outside the bubble but should not close it
|
|
440
|
+
if (target instanceof Element) {
|
|
441
|
+
const isInsideOverlay = target.closest('.cdk-overlay-container, .cdk-overlay-pane');
|
|
442
|
+
if (isInsideOverlay)
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
cb(key);
|
|
446
|
+
};
|
|
447
|
+
this.keyToOutsideListener.set(key, handler);
|
|
448
|
+
this.document.addEventListener('pointerdown', handler, true);
|
|
449
|
+
this.document.addEventListener('mousedown', handler, true);
|
|
450
|
+
this.document.addEventListener('click', handler, true);
|
|
451
|
+
}
|
|
452
|
+
const oldEsc = this.keyToEscapeListener.get(key);
|
|
453
|
+
if (!oldEsc) {
|
|
454
|
+
const escHandler = (e) => e.key === 'Escape' && cb(key);
|
|
455
|
+
this.document.addEventListener('keydown', escHandler, true);
|
|
456
|
+
this.keyToEscapeListener.set(key, escHandler);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
detach(key) {
|
|
460
|
+
const outside = this.keyToOutsideListener.get(key);
|
|
461
|
+
if (outside) {
|
|
462
|
+
this.document.removeEventListener('pointerdown', outside, true);
|
|
463
|
+
this.document.removeEventListener('mousedown', outside, true);
|
|
464
|
+
this.document.removeEventListener('click', outside, true);
|
|
465
|
+
this.keyToOutsideListener.delete(key);
|
|
466
|
+
}
|
|
467
|
+
const esc = this.keyToEscapeListener.get(key);
|
|
468
|
+
if (esc) {
|
|
469
|
+
this.document.removeEventListener('keydown', esc, true);
|
|
470
|
+
this.keyToEscapeListener.delete(key);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: EventManager, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
474
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: EventManager, providedIn: 'root' });
|
|
475
|
+
}
|
|
476
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: EventManager, decorators: [{
|
|
477
|
+
type: Injectable,
|
|
478
|
+
args: [{ providedIn: 'root' }]
|
|
479
|
+
}] });
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* Service to manage focus trapping within an element
|
|
483
|
+
* Traps keyboard focus inside a container element for accessibility
|
|
484
|
+
*/
|
|
485
|
+
class FocusTrapService {
|
|
486
|
+
document = inject(DOCUMENT);
|
|
487
|
+
activeTraps = new Map();
|
|
488
|
+
/**
|
|
489
|
+
* Attach focus trap to an element
|
|
490
|
+
* @param element The container element to trap focus within
|
|
491
|
+
* @param autoFocus Whether to automatically focus the first focusable element
|
|
492
|
+
*/
|
|
493
|
+
attach(element, autoFocus = true) {
|
|
494
|
+
if (!element)
|
|
495
|
+
return;
|
|
496
|
+
// Detach existing trap if any
|
|
497
|
+
this.detach(element);
|
|
498
|
+
// Get all focusable elements within the container
|
|
499
|
+
const focusableSelectors = [
|
|
500
|
+
'a[href]',
|
|
501
|
+
'button:not([disabled])',
|
|
502
|
+
'textarea:not([disabled])',
|
|
503
|
+
'input:not([disabled])',
|
|
504
|
+
'select:not([disabled])',
|
|
505
|
+
'[tabindex]:not([tabindex="-1"])',
|
|
506
|
+
].join(', ');
|
|
507
|
+
const focusableElements = Array.from(element.querySelectorAll(focusableSelectors));
|
|
508
|
+
if (focusableElements.length === 0)
|
|
509
|
+
return;
|
|
510
|
+
const firstFocusableElement = focusableElements[0];
|
|
511
|
+
const lastFocusableElement = focusableElements[focusableElements.length - 1];
|
|
512
|
+
// Focus the first element if requested
|
|
513
|
+
if (autoFocus && firstFocusableElement) {
|
|
514
|
+
setTimeout(() => {
|
|
515
|
+
firstFocusableElement.focus();
|
|
516
|
+
}, 0);
|
|
517
|
+
}
|
|
518
|
+
// Handle Tab key to trap focus
|
|
519
|
+
const handler = (event) => {
|
|
520
|
+
if (event.key !== 'Tab')
|
|
521
|
+
return;
|
|
522
|
+
if (!element.contains(this.document.activeElement)) {
|
|
523
|
+
event.preventDefault();
|
|
524
|
+
if (event.shiftKey) {
|
|
525
|
+
lastFocusableElement?.focus();
|
|
526
|
+
}
|
|
527
|
+
else {
|
|
528
|
+
firstFocusableElement?.focus();
|
|
529
|
+
}
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
if (event.shiftKey) {
|
|
533
|
+
// Shift + Tab: going backwards
|
|
534
|
+
if (this.document.activeElement === firstFocusableElement) {
|
|
535
|
+
event.preventDefault();
|
|
536
|
+
lastFocusableElement?.focus();
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
else {
|
|
540
|
+
// Tab: going forwards
|
|
541
|
+
if (this.document.activeElement === lastFocusableElement) {
|
|
542
|
+
event.preventDefault();
|
|
543
|
+
firstFocusableElement?.focus();
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
};
|
|
547
|
+
element.addEventListener('keydown', handler);
|
|
548
|
+
this.activeTraps.set(element, {
|
|
549
|
+
handler,
|
|
550
|
+
firstElement: firstFocusableElement,
|
|
551
|
+
lastElement: lastFocusableElement,
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
/**
|
|
555
|
+
* Detach focus trap from an element
|
|
556
|
+
* @param element The element to remove focus trap from
|
|
557
|
+
*/
|
|
558
|
+
detach(element) {
|
|
559
|
+
if (!element)
|
|
560
|
+
return;
|
|
561
|
+
const trap = this.activeTraps.get(element);
|
|
562
|
+
if (trap) {
|
|
563
|
+
element.removeEventListener('keydown', trap.handler);
|
|
564
|
+
this.activeTraps.delete(element);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
/**
|
|
568
|
+
* Detach all active focus traps
|
|
569
|
+
*/
|
|
570
|
+
detachAll() {
|
|
571
|
+
this.activeTraps.forEach((trap, element) => {
|
|
572
|
+
element.removeEventListener('keydown', trap.handler);
|
|
573
|
+
});
|
|
574
|
+
this.activeTraps.clear();
|
|
575
|
+
}
|
|
576
|
+
/**
|
|
577
|
+
* Re-attach focus trap to an element (useful when content changes)
|
|
578
|
+
* @param element The element to re-attach focus trap to
|
|
579
|
+
* @param autoFocus Whether to automatically focus the first focusable element
|
|
580
|
+
*/
|
|
581
|
+
reattach(element, autoFocus = false) {
|
|
582
|
+
this.detach(element);
|
|
583
|
+
this.attach(element, autoFocus);
|
|
584
|
+
}
|
|
585
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: FocusTrapService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
586
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: FocusTrapService, providedIn: 'root' });
|
|
587
|
+
}
|
|
588
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: FocusTrapService, decorators: [{
|
|
589
|
+
type: Injectable,
|
|
590
|
+
args: [{ providedIn: 'root' }]
|
|
591
|
+
}] });
|
|
592
|
+
|
|
593
|
+
class AngularRenderer {
|
|
594
|
+
applicationRef;
|
|
595
|
+
componentRef;
|
|
596
|
+
constructor(ViewComponent, injector, props) {
|
|
597
|
+
this.applicationRef = injector.get(ApplicationRef);
|
|
598
|
+
this.componentRef = createComponent(ViewComponent, {
|
|
599
|
+
environmentInjector: this.applicationRef.injector,
|
|
600
|
+
elementInjector: injector,
|
|
601
|
+
});
|
|
602
|
+
// set input props to the component
|
|
603
|
+
this.updateProps(props);
|
|
604
|
+
this.applicationRef.attachView(this.componentRef.hostView);
|
|
605
|
+
}
|
|
606
|
+
get instance() {
|
|
607
|
+
return this.componentRef.instance;
|
|
608
|
+
}
|
|
609
|
+
get elementRef() {
|
|
610
|
+
return this.componentRef.injector.get(ElementRef);
|
|
611
|
+
}
|
|
612
|
+
get dom() {
|
|
613
|
+
return this.elementRef.nativeElement;
|
|
614
|
+
}
|
|
615
|
+
updateProps(props) {
|
|
616
|
+
Object.entries(props).forEach(([key, value]) => {
|
|
617
|
+
this.componentRef.setInput(key, value);
|
|
618
|
+
});
|
|
619
|
+
}
|
|
620
|
+
updateAttributes(attributes) {
|
|
621
|
+
Object.keys(attributes).forEach((key) => {
|
|
622
|
+
this.dom.setAttribute(key, attributes[key]);
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
detectChanges() {
|
|
626
|
+
this.componentRef.changeDetectorRef.detectChanges();
|
|
627
|
+
}
|
|
628
|
+
destroy() {
|
|
629
|
+
this.componentRef.destroy();
|
|
630
|
+
this.applicationRef.detachView(this.componentRef.hostView);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
class TiptapDraggableDirective {
|
|
635
|
+
draggable = true;
|
|
636
|
+
handle = '';
|
|
637
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: TiptapDraggableDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
638
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.3.10", type: TiptapDraggableDirective, isStandalone: true, selector: "[tiptapDraggable]", host: { properties: { "attr.draggable": "this.draggable", "attr.data-drag-handle": "this.handle" } }, ngImport: i0 });
|
|
639
|
+
}
|
|
640
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: TiptapDraggableDirective, decorators: [{
|
|
641
|
+
type: Directive,
|
|
642
|
+
args: [{
|
|
643
|
+
selector: '[tiptapDraggable]',
|
|
644
|
+
}]
|
|
645
|
+
}], propDecorators: { draggable: [{
|
|
646
|
+
type: HostBinding,
|
|
647
|
+
args: ['attr.draggable']
|
|
648
|
+
}], handle: [{
|
|
649
|
+
type: HostBinding,
|
|
650
|
+
args: ['attr.data-drag-handle']
|
|
651
|
+
}] } });
|
|
652
|
+
|
|
653
|
+
/**
|
|
654
|
+
* Injection token for HTML preprocessor function.
|
|
655
|
+
* Use this token to provide a custom function that processes HTML content
|
|
656
|
+
* before it's set into the TipTap editor.
|
|
657
|
+
*
|
|
658
|
+
* This is optional - if not provided, the editor will use the HTML content as-is.
|
|
659
|
+
*
|
|
660
|
+
* @example
|
|
661
|
+
* ```typescript
|
|
662
|
+
* // In your component or module providers
|
|
663
|
+
* {
|
|
664
|
+
* provide: EDITOR_HTML_PREPROCESSOR,
|
|
665
|
+
* useValue: (html: string) => {
|
|
666
|
+
* // Your custom preprocessing logic
|
|
667
|
+
* return html.replace(/old/g, 'new');
|
|
668
|
+
* }
|
|
669
|
+
* }
|
|
670
|
+
* ```
|
|
671
|
+
*/
|
|
672
|
+
const EDITOR_HTML_PREPROCESSOR = new InjectionToken('EDITOR_HTML_PREPROCESSOR');
|
|
673
|
+
/**
|
|
674
|
+
* Injection token for content wrapper class.
|
|
675
|
+
* Use this token to provide a custom class name for the content wrapper element.
|
|
676
|
+
*
|
|
677
|
+
* @example
|
|
678
|
+
* ```typescript
|
|
679
|
+
* // In your component or module providers
|
|
680
|
+
* {
|
|
681
|
+
* provide: EDITOR_CONTENT_WRAPPER_CLASS,
|
|
682
|
+
* useValue: 'my-content-wrapper'
|
|
683
|
+
* }
|
|
684
|
+
* ```
|
|
685
|
+
*/
|
|
686
|
+
const EDITOR_CONTENT_WRAPPER_CLASS = new InjectionToken('EDITOR_CONTENT_WRAPPER_CLASS', {
|
|
687
|
+
providedIn: 'root',
|
|
688
|
+
factory: () => null,
|
|
689
|
+
});
|
|
690
|
+
|
|
691
|
+
class TiptapEditorDirective {
|
|
692
|
+
elRef = inject(ElementRef);
|
|
693
|
+
renderer = inject(Renderer2);
|
|
694
|
+
changeDetectorRef = inject(ChangeDetectorRef);
|
|
695
|
+
htmlPreprocessor = inject(EDITOR_HTML_PREPROCESSOR, { optional: true });
|
|
696
|
+
contentWrapperClass = inject(EDITOR_CONTENT_WRAPPER_CLASS, { optional: true });
|
|
697
|
+
_editor;
|
|
698
|
+
initialEditableState;
|
|
699
|
+
set editor(editor) {
|
|
700
|
+
this._editor = editor;
|
|
701
|
+
// Store initial editable state immediately when editor is set
|
|
702
|
+
// This ensures we capture the state before any Forms API calls
|
|
703
|
+
if (this.initialEditableState === undefined && editor) {
|
|
704
|
+
this.initialEditableState = editor.isEditable;
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
get editor() {
|
|
708
|
+
if (!this._editor) {
|
|
709
|
+
throw new Error('Editor must be set before use.');
|
|
710
|
+
}
|
|
711
|
+
return this._editor;
|
|
712
|
+
}
|
|
713
|
+
outputFormat = 'html';
|
|
714
|
+
onChange = () => {
|
|
715
|
+
/** */
|
|
716
|
+
};
|
|
717
|
+
onTouched = () => {
|
|
718
|
+
/** */
|
|
719
|
+
};
|
|
720
|
+
// Writes a new value to the element.
|
|
721
|
+
// This methods is called when programmatic changes from model to view are requested.
|
|
722
|
+
writeValue(value) {
|
|
723
|
+
// Apply preprocessor if value is a string (HTML) and preprocessor is available
|
|
724
|
+
let processedValue = value;
|
|
725
|
+
if (typeof value === 'string' && this.htmlPreprocessor) {
|
|
726
|
+
processedValue = this.htmlPreprocessor(value);
|
|
727
|
+
}
|
|
728
|
+
this.editor.chain().setContent(processedValue, { emitUpdate: false }).run();
|
|
729
|
+
}
|
|
730
|
+
// Registers a callback function that is called when the control's value changes in the UI.
|
|
731
|
+
registerOnChange(fn) {
|
|
732
|
+
this.onChange = fn;
|
|
733
|
+
}
|
|
734
|
+
// Registers a callback function that is called by the forms API on initialization to update the form model on blur.
|
|
735
|
+
registerOnTouched(fn) {
|
|
736
|
+
this.onTouched = fn;
|
|
737
|
+
}
|
|
738
|
+
// Called by the forms api to enable or disable the element
|
|
739
|
+
setDisabledState(isDisabled) {
|
|
740
|
+
// Ensure we have captured the initial state (fallback in case setter wasn't called)
|
|
741
|
+
if (this.initialEditableState === undefined && this.editor) {
|
|
742
|
+
this.initialEditableState = this.editor.isEditable;
|
|
743
|
+
}
|
|
744
|
+
// Only apply disabled state if editor was initially editable
|
|
745
|
+
// If editor was configured as readonly (editable: false), preserve that state
|
|
746
|
+
if (this.initialEditableState === true) {
|
|
747
|
+
this.editor.setEditable(!isDisabled);
|
|
748
|
+
}
|
|
749
|
+
// If initialEditableState === false, do nothing - preserve readonly state
|
|
750
|
+
this.renderer.setProperty(this.elRef.nativeElement, 'disabled', isDisabled);
|
|
751
|
+
}
|
|
752
|
+
handleChange = ({ editor, transaction }) => {
|
|
753
|
+
if (!transaction.docChanged)
|
|
754
|
+
return;
|
|
755
|
+
// Needed for ChangeDetectionStrategy.OnPush to get notified about changes
|
|
756
|
+
this.changeDetectorRef.markForCheck();
|
|
757
|
+
if (this.outputFormat === 'html') {
|
|
758
|
+
const htmlContent = editor.getHTML();
|
|
759
|
+
let finalContent = htmlContent;
|
|
760
|
+
if (this.contentWrapperClass) {
|
|
761
|
+
finalContent = `<div class="${this.contentWrapperClass}">${htmlContent}</div>`;
|
|
762
|
+
}
|
|
763
|
+
this.onChange(finalContent);
|
|
764
|
+
return;
|
|
765
|
+
}
|
|
766
|
+
this.onChange(editor.getJSON());
|
|
767
|
+
};
|
|
768
|
+
ngOnInit() {
|
|
769
|
+
const editor = this.editor;
|
|
770
|
+
// take the inner contents and clear the block
|
|
771
|
+
const { innerHTML } = this.elRef.nativeElement;
|
|
772
|
+
this.elRef.nativeElement.innerHTML = '';
|
|
773
|
+
// insert the editor in the dom
|
|
774
|
+
this.elRef.nativeElement.append(...Array.from(editor.options.element?.childNodes || []));
|
|
775
|
+
// update the options for the editor
|
|
776
|
+
editor.setOptions({ element: this.elRef.nativeElement });
|
|
777
|
+
// update content to the editor
|
|
778
|
+
if (innerHTML) {
|
|
779
|
+
// Apply preprocessor to initial content if available
|
|
780
|
+
const processedContent = this.htmlPreprocessor ? this.htmlPreprocessor(innerHTML) : innerHTML;
|
|
781
|
+
editor.chain().setContent(processedContent, { emitUpdate: false }).run();
|
|
782
|
+
}
|
|
783
|
+
// register blur handler to update `touched` property
|
|
784
|
+
editor.on('blur', () => {
|
|
785
|
+
this.onTouched();
|
|
786
|
+
});
|
|
787
|
+
// register update handler to listen to changes on update
|
|
788
|
+
editor.on('update', this.handleChange);
|
|
789
|
+
// Needed for ChangeDetectionStrategy.OnPush to get notified
|
|
790
|
+
editor.on('selectionUpdate', () => this.changeDetectorRef.markForCheck());
|
|
791
|
+
}
|
|
792
|
+
ngAfterViewInit() {
|
|
793
|
+
this.changeDetectorRef.detectChanges();
|
|
794
|
+
}
|
|
795
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: TiptapEditorDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
796
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.3.10", type: TiptapEditorDirective, isStandalone: true, selector: "tiptap[editor], [tiptap][editor], tiptap-editor[editor], [tiptapEditor][editor]", inputs: { editor: "editor", outputFormat: "outputFormat" }, host: { classAttribute: "tiptap-editor no-twp" }, providers: [
|
|
797
|
+
{
|
|
798
|
+
provide: NG_VALUE_ACCESSOR,
|
|
799
|
+
useExisting: forwardRef(() => TiptapEditorDirective),
|
|
800
|
+
multi: true,
|
|
801
|
+
},
|
|
802
|
+
], ngImport: i0 });
|
|
803
|
+
}
|
|
804
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: TiptapEditorDirective, decorators: [{
|
|
805
|
+
type: Directive,
|
|
806
|
+
args: [{
|
|
807
|
+
selector: 'tiptap[editor], [tiptap][editor], tiptap-editor[editor], [tiptapEditor][editor]',
|
|
808
|
+
providers: [
|
|
809
|
+
{
|
|
810
|
+
provide: NG_VALUE_ACCESSOR,
|
|
811
|
+
useExisting: forwardRef(() => TiptapEditorDirective),
|
|
812
|
+
multi: true,
|
|
813
|
+
},
|
|
814
|
+
],
|
|
815
|
+
standalone: true,
|
|
816
|
+
host: {
|
|
817
|
+
['class']: 'tiptap-editor no-twp',
|
|
818
|
+
},
|
|
819
|
+
}]
|
|
820
|
+
}], propDecorators: { editor: [{
|
|
821
|
+
type: Input
|
|
822
|
+
}], outputFormat: [{
|
|
823
|
+
type: Input
|
|
824
|
+
}] } });
|
|
825
|
+
|
|
826
|
+
class TiptapFloatingMenuDirective {
|
|
827
|
+
elRef;
|
|
828
|
+
constructor(elRef) {
|
|
829
|
+
this.elRef = elRef;
|
|
830
|
+
}
|
|
831
|
+
pluginKey = 'TiptapFloatingMenu';
|
|
832
|
+
editor;
|
|
833
|
+
options = {};
|
|
834
|
+
shouldShow;
|
|
835
|
+
ngOnInit() {
|
|
836
|
+
const editor = this.editor;
|
|
837
|
+
if (!editor) {
|
|
838
|
+
throw new Error('Required: Input `editor`');
|
|
839
|
+
}
|
|
840
|
+
const floatingMenuElement = this.elRef.nativeElement;
|
|
841
|
+
floatingMenuElement.style.visibility = 'hidden';
|
|
842
|
+
floatingMenuElement.style.position = 'absolute';
|
|
843
|
+
editor.registerPlugin(FloatingMenuPlugin({
|
|
844
|
+
pluginKey: this.pluginKey,
|
|
845
|
+
editor,
|
|
846
|
+
element: floatingMenuElement,
|
|
847
|
+
options: this.options,
|
|
848
|
+
shouldShow: this.shouldShow,
|
|
849
|
+
}));
|
|
850
|
+
}
|
|
851
|
+
ngOnDestroy() {
|
|
852
|
+
if (this.editor && this.pluginKey) {
|
|
853
|
+
this.editor.unregisterPlugin(this.pluginKey);
|
|
854
|
+
}
|
|
855
|
+
window.requestAnimationFrame(() => {
|
|
856
|
+
if (this.elRef.nativeElement.parentNode) {
|
|
857
|
+
this.elRef.nativeElement.parentNode.removeChild(this.elRef.nativeElement);
|
|
858
|
+
}
|
|
859
|
+
});
|
|
860
|
+
}
|
|
861
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: TiptapFloatingMenuDirective, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Directive });
|
|
862
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.3.10", type: TiptapFloatingMenuDirective, isStandalone: true, selector: "tiptap-floating-menu[editor], [tiptapFloatingMenu][editor]", inputs: { pluginKey: "pluginKey", editor: "editor", options: "options", shouldShow: "shouldShow" }, ngImport: i0 });
|
|
863
|
+
}
|
|
864
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: TiptapFloatingMenuDirective, decorators: [{
|
|
865
|
+
type: Directive,
|
|
866
|
+
args: [{
|
|
867
|
+
selector: 'tiptap-floating-menu[editor], [tiptapFloatingMenu][editor]',
|
|
868
|
+
}]
|
|
869
|
+
}], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { pluginKey: [{
|
|
870
|
+
type: Input
|
|
871
|
+
}], editor: [{
|
|
872
|
+
type: Input
|
|
873
|
+
}], options: [{
|
|
874
|
+
type: Input
|
|
875
|
+
}], shouldShow: [{
|
|
876
|
+
type: Input
|
|
877
|
+
}] } });
|
|
878
|
+
|
|
879
|
+
/**
|
|
880
|
+
* Directive for positioning a popover/dropdown relative to a trigger element using floating-ui
|
|
881
|
+
* Handles positioning, visibility, and cleanup automatically
|
|
882
|
+
*
|
|
883
|
+
* Usage:
|
|
884
|
+
* <button #trigger>Open</button>
|
|
885
|
+
* <div [popover]="trigger" [isOpen]="isOpen">
|
|
886
|
+
* Content
|
|
887
|
+
* </div>
|
|
888
|
+
*/
|
|
889
|
+
class PopoverDirective {
|
|
890
|
+
elRef = inject(ElementRef);
|
|
891
|
+
cdr = inject(ChangeDetectorRef);
|
|
892
|
+
/**
|
|
893
|
+
* The trigger element reference (ElementRef or HTMLElement)
|
|
894
|
+
*/
|
|
895
|
+
popover;
|
|
896
|
+
/**
|
|
897
|
+
* Whether the popover is open
|
|
898
|
+
*/
|
|
899
|
+
isOpen = false;
|
|
900
|
+
/**
|
|
901
|
+
* Placement of the popover relative to trigger
|
|
902
|
+
*/
|
|
903
|
+
placement = 'bottom-start';
|
|
904
|
+
/**
|
|
905
|
+
* Offset from trigger
|
|
906
|
+
*/
|
|
907
|
+
offset = 8;
|
|
908
|
+
/**
|
|
909
|
+
* Padding for flip/shift middleware
|
|
910
|
+
*/
|
|
911
|
+
padding = 8;
|
|
912
|
+
cleanupAutoUpdate;
|
|
913
|
+
isPositioned = false;
|
|
914
|
+
viewInitialized = false;
|
|
915
|
+
retryCount = 0;
|
|
916
|
+
maxRetries = 10;
|
|
917
|
+
ngAfterViewInit() {
|
|
918
|
+
this.viewInitialized = true;
|
|
919
|
+
this.updateVisibility();
|
|
920
|
+
}
|
|
921
|
+
ngOnChanges(changes) {
|
|
922
|
+
if (this.viewInitialized && (changes['isOpen'] || changes['popover'])) {
|
|
923
|
+
this.updateVisibility();
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
ngOnDestroy() {
|
|
927
|
+
this.cleanup();
|
|
928
|
+
}
|
|
929
|
+
updateVisibility() {
|
|
930
|
+
if (!this.viewInitialized)
|
|
931
|
+
return;
|
|
932
|
+
if (this.isOpen && this.popover) {
|
|
933
|
+
// Wait for next tick to ensure element is rendered
|
|
934
|
+
setTimeout(() => {
|
|
935
|
+
this.setupFloating();
|
|
936
|
+
});
|
|
937
|
+
}
|
|
938
|
+
else {
|
|
939
|
+
this.isPositioned = false;
|
|
940
|
+
this.cleanup();
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
setupFloating() {
|
|
944
|
+
// Reset retry count on each attempt
|
|
945
|
+
this.retryCount = 0;
|
|
946
|
+
this._setupFloatingInternal();
|
|
947
|
+
}
|
|
948
|
+
_setupFloatingInternal() {
|
|
949
|
+
let triggerEl;
|
|
950
|
+
if (!this.popover) {
|
|
951
|
+
if (this.retryCount < this.maxRetries) {
|
|
952
|
+
this.retryCount++;
|
|
953
|
+
setTimeout(() => this._setupFloatingInternal(), 10);
|
|
954
|
+
}
|
|
955
|
+
return;
|
|
956
|
+
}
|
|
957
|
+
if (this.popover instanceof ElementRef) {
|
|
958
|
+
triggerEl = this.popover.nativeElement;
|
|
959
|
+
// If ElementRef exists but nativeElement is not yet available, retry
|
|
960
|
+
if (!triggerEl) {
|
|
961
|
+
if (this.retryCount < this.maxRetries) {
|
|
962
|
+
this.retryCount++;
|
|
963
|
+
setTimeout(() => this._setupFloatingInternal(), 10);
|
|
964
|
+
}
|
|
965
|
+
return;
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
else {
|
|
969
|
+
triggerEl = this.popover;
|
|
970
|
+
}
|
|
971
|
+
const dropdownEl = this.elRef.nativeElement;
|
|
972
|
+
if (!triggerEl || !dropdownEl) {
|
|
973
|
+
// Retry if elements not ready
|
|
974
|
+
if (this.retryCount < this.maxRetries) {
|
|
975
|
+
this.retryCount++;
|
|
976
|
+
setTimeout(() => this._setupFloatingInternal(), 10);
|
|
977
|
+
}
|
|
978
|
+
return;
|
|
979
|
+
}
|
|
980
|
+
// Compute initial position before showing dropdown to prevent flash
|
|
981
|
+
computePosition(triggerEl, dropdownEl, {
|
|
982
|
+
placement: this.placement,
|
|
983
|
+
middleware: [offset(this.offset), flip(), shift({ padding: this.padding })],
|
|
984
|
+
})
|
|
985
|
+
.then(({ x, y }) => {
|
|
986
|
+
// Set position first, then make visible
|
|
987
|
+
Object.assign(dropdownEl.style, { left: `${x}px`, top: `${y}px` });
|
|
988
|
+
this.isPositioned = true;
|
|
989
|
+
// Trigger change detection to update visibility binding
|
|
990
|
+
this.cdr.markForCheck();
|
|
991
|
+
})
|
|
992
|
+
.catch((error) => {
|
|
993
|
+
console.error('[PopoverDirective] Error computing position', error);
|
|
994
|
+
});
|
|
995
|
+
// Use autoUpdate to automatically reposition on scroll/resize
|
|
996
|
+
this.cleanupAutoUpdate = autoUpdate(triggerEl, dropdownEl, () => {
|
|
997
|
+
computePosition(triggerEl, dropdownEl, {
|
|
998
|
+
placement: this.placement,
|
|
999
|
+
middleware: [offset(this.offset), flip(), shift({ padding: this.padding })],
|
|
1000
|
+
}).then(({ x, y }) => {
|
|
1001
|
+
Object.assign(dropdownEl.style, { left: `${x}px`, top: `${y}px` });
|
|
1002
|
+
});
|
|
1003
|
+
});
|
|
1004
|
+
}
|
|
1005
|
+
cleanup() {
|
|
1006
|
+
if (this.cleanupAutoUpdate) {
|
|
1007
|
+
this.cleanupAutoUpdate();
|
|
1008
|
+
this.cleanupAutoUpdate = undefined;
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: PopoverDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
1012
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.3.10", type: PopoverDirective, isStandalone: true, selector: "[popover]", inputs: { popover: "popover", isOpen: "isOpen", placement: "placement", offset: "offset", padding: "padding" }, host: { properties: { "style.position": "\"fixed\"", "style.visibility": "isPositioned ? \"visible\" : \"hidden\"" } }, usesOnChanges: true, ngImport: i0 });
|
|
1013
|
+
}
|
|
1014
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: PopoverDirective, decorators: [{
|
|
1015
|
+
type: Directive,
|
|
1016
|
+
args: [{
|
|
1017
|
+
selector: '[popover]',
|
|
1018
|
+
standalone: true,
|
|
1019
|
+
host: {
|
|
1020
|
+
'[style.position]': '"fixed"',
|
|
1021
|
+
'[style.visibility]': 'isPositioned ? "visible" : "hidden"',
|
|
1022
|
+
},
|
|
1023
|
+
}]
|
|
1024
|
+
}], propDecorators: { popover: [{
|
|
1025
|
+
type: Input
|
|
1026
|
+
}], isOpen: [{
|
|
1027
|
+
type: Input
|
|
1028
|
+
}], placement: [{
|
|
1029
|
+
type: Input
|
|
1030
|
+
}], offset: [{
|
|
1031
|
+
type: Input
|
|
1032
|
+
}], padding: [{
|
|
1033
|
+
type: Input
|
|
1034
|
+
}] } });
|
|
1035
|
+
|
|
1036
|
+
const COLORS = Color.fromArray([
|
|
1037
|
+
{ name: 'Black', value: '#000000', type: 'hex' },
|
|
1038
|
+
{ name: 'Dim gray', value: '#696969', type: 'hex' },
|
|
1039
|
+
{ name: 'Light gray', value: '#d3d3d3', type: 'hex' },
|
|
1040
|
+
{ name: 'White', value: '#ffffff', type: 'hex' },
|
|
1041
|
+
{ name: 'Transparent', value: 'transparent', type: 'hex' },
|
|
1042
|
+
// row 2
|
|
1043
|
+
{ name: 'Red', value: '#ff0000', type: 'hex' },
|
|
1044
|
+
{ name: 'Orange', value: '#ffa500', type: 'hex' },
|
|
1045
|
+
{ name: 'Yellow', value: '#ffff00', type: 'hex' },
|
|
1046
|
+
{ name: 'Light green', value: '#90ee90', type: 'hex' },
|
|
1047
|
+
{ name: 'Green', value: '#008000', type: 'hex' },
|
|
1048
|
+
// row 3
|
|
1049
|
+
{ name: 'Aquamarine', value: '#7fffd4', type: 'hex' },
|
|
1050
|
+
{ name: 'Turquoise', value: '#40e0d0', type: 'hex' },
|
|
1051
|
+
{ name: 'Lightblue', value: '#add8e6', type: 'hex' },
|
|
1052
|
+
{ name: 'Blue', value: '#0000ff', type: 'hex' },
|
|
1053
|
+
{ name: 'Purple', value: '#800080', type: 'hex' },
|
|
1054
|
+
]);
|
|
1055
|
+
|
|
1056
|
+
/**
|
|
1057
|
+
* Custom button directive matching React button component styles
|
|
1058
|
+
* Supports variants: default, destructive, outline, secondary, ghost, link
|
|
1059
|
+
* Supports sizes: default, sm, lg, icon, icon-sm, icon-lg
|
|
1060
|
+
*
|
|
1061
|
+
* Usage:
|
|
1062
|
+
* - <button documentEngineButton>Text</button> - secondary variant, default size
|
|
1063
|
+
* - <button documentEngineButton="filled">Text</button> - default variant (primary)
|
|
1064
|
+
* - <button documentEngineButton variant="outline">Text</button> - outline variant
|
|
1065
|
+
* - <button documentEngineButton variant="icon" size="icon-sm">Icon</button> - icon button
|
|
1066
|
+
*/
|
|
1067
|
+
class ButtonDirective {
|
|
1068
|
+
variant = 'ghost';
|
|
1069
|
+
size = 'default';
|
|
1070
|
+
get buttonClasses() {
|
|
1071
|
+
const classes = ['btn'];
|
|
1072
|
+
classes.push(`btn--${this.variant}`);
|
|
1073
|
+
classes.push(`btn--size-${this.size}`);
|
|
1074
|
+
return classes.join(' ');
|
|
1075
|
+
}
|
|
1076
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: ButtonDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
1077
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.3.10", type: ButtonDirective, isStandalone: true, selector: "button[documentEngineButton]", inputs: { variant: "variant", size: "size" }, host: { attributes: { "type": "button" }, properties: { "class": "this.buttonClasses" } }, ngImport: i0 });
|
|
1078
|
+
}
|
|
1079
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: ButtonDirective, decorators: [{
|
|
1080
|
+
type: Directive,
|
|
1081
|
+
args: [{
|
|
1082
|
+
selector: 'button[documentEngineButton]',
|
|
1083
|
+
standalone: true,
|
|
1084
|
+
host: {
|
|
1085
|
+
type: 'button',
|
|
1086
|
+
},
|
|
1087
|
+
}]
|
|
1088
|
+
}], propDecorators: { variant: [{
|
|
1089
|
+
type: Input
|
|
1090
|
+
}], size: [{
|
|
1091
|
+
type: Input
|
|
1092
|
+
}], buttonClasses: [{
|
|
1093
|
+
type: HostBinding,
|
|
1094
|
+
args: ['class']
|
|
1095
|
+
}] } });
|
|
1096
|
+
|
|
1097
|
+
const DEFAULT = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect></svg>`;
|
|
1098
|
+
const CHECK = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" width="100%" height="100%"><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z"/></svg>`;
|
|
1099
|
+
// Official icons
|
|
1100
|
+
const UNDO = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M280-200v-80h284q63 0 109.5-40T720-420q0-60-46.5-100T564-560H312l104 104-56 56-200-200 200-200 56 56-104 104h252q97 0 166.5 63T800-420q0 94-69.5 157T564-200H280Z"/></svg>`;
|
|
1101
|
+
const REDO = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M396-200q-97 0-166.5-63T160-420q0-94 69.5-157T396-640h252L544-744l56-56 200 200-200 200-56-56 104-104H396q-63 0-109.5 40T240-420q0 60 46.5 100T396-280h284v80H396Z"/></svg>`;
|
|
1102
|
+
const FORMAT_SIZE = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M560-160v-520H360v-120h520v120H680v520H560Zm-360 0v-320H80v-120h360v120H320v320H200Z"/></svg>`;
|
|
1103
|
+
const ARROW_DROP_DOWN = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M480-360 280-560h400L480-360Z"/></svg>`;
|
|
1104
|
+
const FORMAT_LINE_SPACING = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M240-160 80-320l56-56 64 62v-332l-64 62-56-56 160-160 160 160-56 56-64-62v332l64-62 56 56-160 160Zm240-40v-80h400v80H480Zm0-240v-80h400v80H480Zm0-240v-80h400v80H480Z"/></svg>`;
|
|
1105
|
+
const MATCH_CASE = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="m131-252 165-440h79l165 440h-76l-39-112H247l-40 112h-76Zm139-176h131l-64-182h-4l-63 182Zm395 186q-51 0-81-27.5T554-342q0-44 34.5-72.5T677-443q23 0 45 4t38 11v-12q0-29-20.5-47T685-505q-23 0-42 9.5T610-468l-47-35q24-29 54.5-43t68.5-14q69 0 103 32.5t34 97.5v178h-63v-37h-4q-14 23-38 35t-53 12Zm12-54q35 0 59.5-24t24.5-56q-14-8-33.5-12.5T689-393q-32 0-50 14t-18 37q0 20 16 33t40 13Z"/></svg>`;
|
|
1106
|
+
const FORMAT_CLEAR = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" fill="currentColor"><path d="m528-546-93-93-121-121h486v120H568l-40 94ZM792-56 460-388l-80 188H249l119-280L56-792l56-56 736 736-56 56Z"/></svg>`;
|
|
1107
|
+
const FORMAT_COLOR_TEXT = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M80 0v-160h800V0H80Zm140-280 210-560h100l210 560h-96l-50-144H368l-52 144h-96Zm176-224h168l-82-232h-4l-82 232Z"/></svg>`;
|
|
1108
|
+
const FORMAT_COLOR_FILL = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="m247-904 57-56 343 343q23 23 23 57t-23 57L457-313q-23 23-57 23t-57-23L153-503q-23-23-23-57t23-57l190-191-96-96Zm153 153L209-560h382L400-751Zm360 471q-33 0-56.5-23.5T680-360q0-21 12.5-45t27.5-45q9-12 19-25t21-25q11 12 21 25t19 25q15 21 27.5 45t12.5 45q0 33-23.5 56.5T760-280ZM80 0v-160h800V0H80Z"/></svg>`;
|
|
1109
|
+
const FORMAT_BOLD = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M272-200v-560h221q65 0 120 40t55 111q0 51-23 78.5T602-491q25 11 55.5 41t30.5 90q0 89-65 124.5T501-200H272Zm121-112h104q48 0 58.5-24.5T566-372q0-11-10.5-35.5T494-432H393v120Zm0-228h93q33 0 48-17t15-38q0-24-17-39t-44-15h-95v109Z"/></svg>`;
|
|
1110
|
+
const FORMAT_ITALIC = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M200-200v-100h160l120-360H320v-100h400v100H580L460-300h140v100H200Z"/></svg>`;
|
|
1111
|
+
const FORMAT_UNDERLINE = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M200-120v-80h560v80H200Zm280-160q-101 0-157-63t-56-167v-330h103v336q0 56 28 91t82 35q54 0 82-35t28-91v-336h103v330q0 104-56 167t-157 63Z"/></svg>`;
|
|
1112
|
+
const FORMAT_STRIKETHROUGH = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M80-400v-80h800v80H80Zm340-160v-120H200v-120h560v120H540v120H420Zm0 400v-160h120v160H420Z"/></svg>`;
|
|
1113
|
+
const SUBSCRIPT = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M760-160v-80q0-17 11.5-28.5T800-280h80v-40H760v-40h120q17 0 28.5 11.5T920-320v40q0 17-11.5 28.5T880-240h-80v40h120v40H760Zm-525-80 185-291-172-269h106l124 200h4l123-200h107L539-531l186 291H618L482-457h-4L342-240H235Z"/></svg>`;
|
|
1114
|
+
const SUPERSCRIPT = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M760-600v-80q0-17 11.5-28.5T800-720h80v-40H760v-40h120q17 0 28.5 11.5T920-760v40q0 17-11.5 28.5T880-680h-80v40h120v40H760ZM235-160l185-291-172-269h106l124 200h4l123-200h107L539-451l186 291H618L482-377h-4L342-160H235Z"/></svg>`;
|
|
1115
|
+
const CODE = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M320-240 80-480l240-240 57 57-184 184 183 183-56 56Zm320 0-57-57 184-184-183-183 56-56 240 240-240 240Z"/></svg>`;
|
|
1116
|
+
const FORMAT_ALIGN_LEFT = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M120-120v-80h720v80H120Zm0-160v-80h480v80H120Zm0-160v-80h720v80H120Zm0-160v-80h480v80H120Zm0-160v-80h720v80H120Z"/></svg>`;
|
|
1117
|
+
const FORMAT_ALIGN_CENTER = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M120-120v-80h720v80H120Zm160-160v-80h400v80H280ZM120-440v-80h720v80H120Zm160-160v-80h400v80H280ZM120-760v-80h720v80H120Z"/></svg>`;
|
|
1118
|
+
const FORMAT_ALIGN_RIGHT = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M120-760v-80h720v80H120Zm240 160v-80h480v80H360ZM120-440v-80h720v80H120Zm240 160v-80h480v80H360ZM120-120v-80h720v80H120Z"/></svg>`;
|
|
1119
|
+
const FORMAT_ALIGN_JUSTIFY = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M120-120v-80h720v80H120Zm0-160v-80h720v80H120Zm0-160v-80h720v80H120Zm0-160v-80h720v80H120Zm0-160v-80h720v80H120Z"/></svg>`;
|
|
1120
|
+
const FORMAT_INDENT_DECREASE = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M120-120v-80h720v80H120Zm320-160v-80h400v80H440Zm0-160v-80h400v80H440Zm0-160v-80h400v80H440ZM120-760v-80h720v80H120Zm160 440L120-480l160-160v320Z"/></svg>`;
|
|
1121
|
+
const FORMAT_INDENT_INCREASE = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M120-120v-80h720v80H120Zm320-160v-80h400v80H440Zm0-160v-80h400v80H440Zm0-160v-80h400v80H440ZM120-760v-80h720v80H120Zm0 440v-320l160 160-160 160Z"/></svg>`;
|
|
1122
|
+
const FORMAT_LIST_BULLETED = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M360-200v-80h480v80H360Zm0-240v-80h480v80H360Zm0-240v-80h480v80H360ZM200-160q-33 0-56.5-23.5T120-240q0-33 23.5-56.5T200-320q33 0 56.5 23.5T280-240q0 33-23.5 56.5T200-160Zm0-240q-33 0-56.5-23.5T120-480q0-33 23.5-56.5T200-560q33 0 56.5 23.5T280-480q0 33-23.5 56.5T200-400Zm0-240q-33 0-56.5-23.5T120-720q0-33 23.5-56.5T200-800q33 0 56.5 23.5T280-720q0 33-23.5 56.5T200-640Z"/></svg>`;
|
|
1123
|
+
const FORMAT_LIST_NUMBERED = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M120-80v-60h100v-30h-60v-60h60v-30H120v-60h120q17 0 28.5 11.5T280-280v40q0 17-11.5 28.5T240-200q17 0 28.5 11.5T280-160v40q0 17-11.5 28.5T240-80H120Zm0-280v-110q0-17 11.5-28.5T160-510h60v-30H120v-60h120q17 0 28.5 11.5T280-560v70q0 17-11.5 28.5T240-450h-60v30h100v60H120Zm60-280v-180h-60v-60h120v240h-60Zm180 440v-80h480v80H360Zm0-240v-80h480v80H360Zm0-240v-80h480v80H360Z"/></svg>`;
|
|
1124
|
+
const FORMAT_QUOTE = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="m228-240 92-160q-66 0-113-47t-47-113q0-66 47-113t113-47q66 0 113 47t47 113q0 23-5.5 42.5T458-480L320-240h-92Zm360 0 92-160q-66 0-113-47t-47-113q0-66 47-113t113-47q66 0 113 47t47 113q0 23-5.5 42.5T818-480L680-240h-92ZM320-500q25 0 42.5-17.5T380-560q0-25-17.5-42.5T320-620q-25 0-42.5 17.5T260-560q0 25 17.5 42.5T320-500Zm360 0q25 0 42.5-17.5T740-560q0-25-17.5-42.5T680-620q-25 0-42.5 17.5T620-560q0 25 17.5 42.5T680-500Zm0-60Zm-360 0Z"/></svg>`;
|
|
1125
|
+
const TABLE_ROWS = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M760-200v-120H200v120h560Zm0-200v-160H200v160h560Zm0-240v-120H200v120h560ZM200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H200Z"/></svg>`;
|
|
1126
|
+
const LINK = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M440-280H280q-83 0-141.5-58.5T80-480q0-83 58.5-141.5T280-680h160v80H280q-50 0-85 35t-35 85q0 50 35 85t85 35h160v80ZM320-440v-80h320v80H320Zm200 160v-80h160q50 0 85-35t35-85q0-50-35-85t-85-35H520v-80h160q83 0 141.5 58.5T880-480q0 83-58.5 141.5T680-280H520Z"/></svg>`;
|
|
1127
|
+
const LINK_OFF = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="m770-302-60-62q40-11 65-42.5t25-73.5q0-50-35-85t-85-35H520v-80h160q83 0 141.5 58.5T880-480q0 57-29.5 105T770-302ZM634-440l-80-80h86v80h-6ZM792-56 56-792l56-56 736 736-56 56ZM440-280H280q-83 0-141.5-58.5T80-480q0-69 42-123t108-71l74 74h-24q-50 0-85 35t-35 85q0 50 35 85t85 35h160v80ZM320-440v-80h65l79 80H320Z"/></svg>`;
|
|
1128
|
+
const SPECIAL_CHARACTER = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M120-120v-120h200q-84-45-132-125t-48-175q0-142 99-241t241-99q142 0 241 99t99 241q0 95-48 175T640-240h200v120H520v-204q78-14 129-75t51-141q0-92-64-156t-156-64q-92 0-156 64t-64 156q0 80 51 141t129 75v204H120Z"/></svg>`;
|
|
1129
|
+
const CLEAR_ALL = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M120-280v-80h560v80H120Zm80-160v-80h560v80H200Zm80-160v-80h560v80H280Z"/></svg>`;
|
|
1130
|
+
const INSERT_PAGE_BREAK = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M240-80q-33 0-56.5-23.5T160-160v-120h80v120h480v-120h80v120q0 33-23.5 56.5T720-80H240Zm-80-440v-280q0-33 23.5-56.5T240-880h320l240 240v120h-80v-80H520v-200H240v280h-80Zm200 160v-80h240v80H360Zm320 0v-80h240v80H680Zm-640 0v-80h240v80H40Zm440-160Zm0 240Z"/></svg>`;
|
|
1131
|
+
const MARKDOWN = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="m640-360 120-120-42-43-48 48v-125h-60v125l-48-48-42 43 120 120ZM160-160q-33 0-56.5-23.5T80-240v-480q0-33 23.5-56.5T160-800h640q33 0 56.5 23.5T880-720v480q0 33-23.5 56.5T800-160H160Zm0-80h640v-480H160v480Zm0 0v-480 480Zm60-120h60v-180h40v120h60v-120h40v180h60v-200q0-17-11.5-28.5T440-600H260q-17 0-28.5 11.5T220-560v200Z"/></svg>`;
|
|
1132
|
+
const DYNAMIC_FORM = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M160-520q-33 0-56.5-23.5T80-600v-120q0-33 23.5-56.5T160-800h360v280H160Zm0-80h280v-120H160v120Zm0 440q-33 0-56.5-23.5T80-240v-120q0-33 23.5-56.5T160-440h440v280H160Zm0-80h360v-120H160v120Zm520 80v-360h-80v-280h280l-80 200h80L680-160ZM190-270h60v-60h-60v60Zm0-360h60v-60h-60v60Zm-30 30v-120 120Zm0 360v-120 120Z"/></svg>`;
|
|
1133
|
+
const PHOTO = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H200Zm0-80h560v-560H200v560Zm40-80h480L570-480 450-320l-90-120-120 160Zm-40 80v-560 560Z"/></svg>`;
|
|
1134
|
+
const PRINT = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M640-640v-120H320v120h-80v-200h480v200h-80Zm-480 80h640-640Zm560 100q17 0 28.5-11.5T760-500q0-17-11.5-28.5T720-540q-17 0-28.5 11.5T680-500q0 17 11.5 28.5T720-460Zm-80 260v-160H320v160h320Zm80 80H240v-160H80v-240q0-51 35-85.5t85-34.5h560q51 0 85.5 34.5T880-520v240H720v160Zm80-240v-160q0-17-11.5-28.5T760-560H200q-17 0-28.5 11.5T160-520v160h80v-80h480v80h80Z"/></svg>`;
|
|
1135
|
+
const ARCHIVE = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="m480-240 160-160-56-56-64 64v-168h-80v168l-64-64-56 56 160 160ZM200-640v440h560v-440H200Zm0 520q-33 0-56.5-23.5T120-200v-499q0-14 4.5-27t13.5-24l50-61q11-14 27.5-21.5T250-840h460q18 0 34.5 7.5T772-811l50 61q9 11 13.5 24t4.5 27v499q0 33-23.5 56.5T760-120H200Zm16-600h528l-34-40H250l-34 40Zm264 300Z"/></svg>`;
|
|
1136
|
+
// Common Icons
|
|
1137
|
+
const EDIT = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M200-200h57l391-391-57-57-391 391v57Zm-80 80v-170l528-527q12-11 26.5-17t30.5-6q16 0 31 6t26 18l55 56q12 11 17.5 26t5.5 30q0 16-5.5 30.5T817-647L290-120H120Zm640-584-56-56 56 56Zm-141 85-28-29 57 57-29-28Z"/></svg>`;
|
|
1138
|
+
const SETTINGS = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="m370-80-16-128q-13-5-24.5-12T307-235l-119 50L78-375l103-78q-1-7-1-13.5v-27q0-6.5 1-13.5L78-585l110-190 119 50q11-8 23-15t24-12l16-128h220l16 128q13 5 24.5 12t22.5 15l119-50 110 190-103 78q1 7 1 13.5v27q0 6.5-2 13.5l103 78-110 190-118-50q-11 8-23 15t-24 12L590-80H370Zm70-80h79l14-106q31-8 57.5-23.5T639-327l99 41 39-68-86-65q5-14 7-29.5t2-31.5q0-16-2-31.5t-7-29.5l86-65-39-68-99 42q-22-23-48.5-38.5T533-694l-13-106h-79l-14 106q-31 8-57.5 23.5T321-633l-99-41-39 68 86 64q-5 15-7 30t-2 32q0 16 2 31t7 30l-86 65 39 68 99-42q22 23 48.5 38.5T427-266l13 106Zm42-180q58 0 99-41t41-99q0-58-41-99t-99-41q-59 0-99.5 41T342-480q0 58 40.5 99t99.5 41Zm-2-140Z"/></svg>`;
|
|
1139
|
+
const SEARCH = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M784-120 532-372q-30 24-69 38t-83 14q-109 0-184.5-75.5T120-580q0-109 75.5-184.5T380-840q109 0 184.5 75.5T640-580q0 44-14 83t-38 69l252 252-56 56ZM380-400q75 0 127.5-52.5T560-580q0-75-52.5-127.5T380-760q-75 0-127.5 52.5T200-580q0 75 52.5 127.5T380-400Z"/></svg>`;
|
|
1140
|
+
const SEARCH_OFF = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M280-80q-83 0-141.5-58.5T80-280q0-83 58.5-141.5T280-480q83 0 141.5 58.5T480-280q0 83-58.5 141.5T280-80Zm544-40L568-376q-12-13-25.5-26.5T516-428q38-24 61-64t23-88q0-75-52.5-127.5T420-760q-75 0-127.5 52.5T240-580q0 6 .5 11.5T242-557q-18 2-39.5 8T164-535q-2-11-3-22t-1-23q0-109 75.5-184.5T420-840q109 0 184.5 75.5T680-580q0 43-13.5 81.5T629-428l251 252-56 56Zm-615-61 71-71 70 71 29-28-71-71 71-71-28-28-71 71-71-71-28 28 71 71-71 71 28 28Z"/></svg>`;
|
|
1141
|
+
const DESCRIPTION = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M320-240h320v-80H320v80Zm0-160h320v-80H320v80ZM240-80q-33 0-56.5-23.5T160-160v-640q0-33 23.5-56.5T240-880h320l240 240v480q0 33-23.5 56.5T720-80H240Zm280-520v-200H240v640h480v-440H520ZM240-800v200-200 640-640Z"/></svg>`;
|
|
1142
|
+
const CLOSE = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="m256-200-56-56 224-224-224-224 56-56 224 224 224-224 56 56-224 224 224 224-56 56-224-224-224 224Z"/></svg>`;
|
|
1143
|
+
const VERTICAL_ALIGN_TOP = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M160-760v-80h640v80H160Zm280 640v-408L336-424l-56-56 200-200 200 200-56 56-104-104v408h-80Z"/></svg>`;
|
|
1144
|
+
const VERTICAL_ALIGN_CENTER = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M440-80v-168l-64 64-56-56 160-160 160 160-56 56-64-64v168h-80ZM160-440v-80h640v80H160Zm320-120L320-720l56-56 64 64v-168h80v168l64-64 56 56-160 160Z"/></svg>`;
|
|
1145
|
+
const VERTICAL_ALIGN_BOTTOM = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M160-120v-80h640v80H160Zm320-160L280-480l56-56 104 104v-408h80v408l104-104 56 56-200 200Z"/></svg>`;
|
|
1146
|
+
// Custom SVG
|
|
1147
|
+
const RESTRICT_EDIT = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" aria-hidden="true">
|
|
1148
|
+
<g transform="translate(10 10) scale(0.8) translate(-10 -10)">
|
|
1149
|
+
<path d="M6.25 16a.75.75 0 1 1 0 1.5H.75a.75.75 0 1 1 0-1.5zm0-5a.75.75 0 1 1 0 1.5H.75a.75.75 0 1 1 0-1.5zm3-5a.75.75 0 0 1 0 1.5H.75a.75.75 0 0 1 0-1.5zm6-5a.75.75 0 1 1 0 1.5H.75a.75.75 0 0 1 0-1.5zm.25 5.5a3.5 3.5 0 0 1 3.143 1.959.75.75 0 0 1-1.36.636A2 2 0 0 0 13.5 10v2H19a1 1 0 0 1 1 1v5a1 1 0 0 1-1 1h-7a1 1 0 0 1-1-1v-5a1 1 0 0 1 1-1v-2l.005-.192A3.5 3.5 0 0 1 15.5 6.5m0 7.5a.5.5 0 0 0-.492.41L15 14.5v2a.5.5 0 0 0 .992.09L16 16.5v-2a.5.5 0 0 0-.5-.5">
|
|
1150
|
+
</path>
|
|
1151
|
+
</g>
|
|
1152
|
+
</svg>
|
|
1153
|
+
`;
|
|
1154
|
+
const TABLE_CELL_PROPERTY = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" aria-hidden="true">
|
|
1155
|
+
<path d="m11.105 18-.17 1H2.5A1.5 1.5 0 0 1 1 17.5v-15A1.5 1.5 0 0 1 2.5 1h15A1.5 1.5 0 0 1 19 2.5v9.975l-.85-.124-.15-.302V8h-5v4h.021l-.172.351-1.916.28-.151.027c-.287.063-.54.182-.755.341L8 13v5zM2 12h5V8H2zm10-4H8v4h4zM2 2v5h5V2zm0 16h5v-5H2zM13 7h5V2h-5zM8 2v5h4V2z" opacity=".6"></path>
|
|
1156
|
+
<path d="m15.5 11.5 1.323 2.68 2.957.43-2.14 2.085.505 2.946L15.5 18.25l-2.645 1.39.505-2.945-2.14-2.086 2.957-.43zM13 6a1 1 0 0 1 1 1v3.172a2 2 0 0 0-.293.443l-.858 1.736-1.916.28-.151.027A1.98 1.98 0 0 0 9.315 14H7a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1zm-1 2H8v4h4z"></path>
|
|
1157
|
+
</svg>
|
|
1158
|
+
`;
|
|
1159
|
+
const TABLE_COLUMN = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" aria-hidden="true">
|
|
1160
|
+
<path d="M2.5 1h15A1.5 1.5 0 0 1 19 2.5v15a1.5 1.5 0 0 1-1.5 1.5h-15A1.5 1.5 0 0 1 1 17.5v-15A1.5 1.5 0 0 1 2.5 1M2 2v16h16V2z" opacity=".6"></path>
|
|
1161
|
+
<path d="M18 7v1H2V7zm0 5v1H2v-1z" opacity=".6"></path>
|
|
1162
|
+
<path d="M14 1v18a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V1a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1m-2 1H8v4h4zm0 6H8v4h4zm0 6H8v4h4z"></path>
|
|
1163
|
+
</svg>
|
|
1164
|
+
`;
|
|
1165
|
+
const TABLE_MERGE_CELL = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" aria-hidden="true">
|
|
1166
|
+
<path d="M2.5 1h15A1.5 1.5 0 0 1 19 2.5v15a1.5 1.5 0 0 1-1.5 1.5h-15A1.5 1.5 0 0 1 1 17.5v-15A1.5 1.5 0 0 1 2.5 1M2 2v16h16V2z" opacity=".6"></path>
|
|
1167
|
+
<path d="M7 2h1v16H7zm5 0h1v7h-1zm6 5v1H2V7zM8 12v1H2v-1z" opacity=".6"></path>
|
|
1168
|
+
<path d="M7 7h12a1 1 0 0 1 1 1v11a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V8a1 1 0 0 1 1-1m1 2v9h10V9z"></path>
|
|
1169
|
+
</svg>
|
|
1170
|
+
`;
|
|
1171
|
+
const TABLE_PROPERTY = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" aria-hidden="true">
|
|
1172
|
+
<path d="M8 2v5h4V2h1v5h5v1h-5v4h.021l-.172.351-1.916.28-.151.027c-.287.063-.54.182-.755.341L8 13v5H7v-5H2v-1h5V8H2V7h5V2zm4 6H8v4h4z" opacity=".6"></path>
|
|
1173
|
+
<path d="m15.5 11.5 1.323 2.68 2.957.43-2.14 2.085.505 2.946L15.5 18.25l-2.645 1.39.505-2.945-2.14-2.086 2.957-.43zM17 1a2 2 0 0 1 2 2v9.475l-.85-.124-.857-1.736a2 2 0 0 0-.292-.44L17 3H3v14h7.808l.402.392L10.935 19H3a2 2 0 0 1-2-2V3a2 2 0 0 1 2-2z"></path>
|
|
1174
|
+
</svg>
|
|
1175
|
+
`;
|
|
1176
|
+
const TABLE_ROW = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" aria-hidden="true">
|
|
1177
|
+
<path d="M2.5 1h15A1.5 1.5 0 0 1 19 2.5v15a1.5 1.5 0 0 1-1.5 1.5h-15A1.5 1.5 0 0 1 1 17.5v-15A1.5 1.5 0 0 1 2.5 1M2 2v16h16V2z" opacity=".6"></path>
|
|
1178
|
+
<path d="M7 2h1v16H7zm5 0h1v16h-1z" opacity=".6"></path>
|
|
1179
|
+
<path d="M1 6h18a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1H1a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1m1 2v4h4V8zm6 0v4h4V8zm6 0v4h4V8z"></path>
|
|
1180
|
+
</svg>
|
|
1181
|
+
`;
|
|
1182
|
+
|
|
1183
|
+
/**
|
|
1184
|
+
* Icon registry service for managing SVG icons
|
|
1185
|
+
* Similar to Angular Material's MatIconRegistry
|
|
1186
|
+
*/
|
|
1187
|
+
class IconRegistryService {
|
|
1188
|
+
sanitizer = inject(DomSanitizer);
|
|
1189
|
+
icons = new Map();
|
|
1190
|
+
constructor() {
|
|
1191
|
+
this.registerDefaultIcons();
|
|
1192
|
+
}
|
|
1193
|
+
/**
|
|
1194
|
+
* Register a new icon with its SVG content
|
|
1195
|
+
*/
|
|
1196
|
+
registerIcon(name, svgContent) {
|
|
1197
|
+
if (this.icons.has(name)) {
|
|
1198
|
+
throw new Error(`Icon ${name} already registered`);
|
|
1199
|
+
}
|
|
1200
|
+
this.icons.set(name, svgContent);
|
|
1201
|
+
}
|
|
1202
|
+
/**
|
|
1203
|
+
* Get icon SVG content by name
|
|
1204
|
+
* Returns SafeHtml for use in templates with [innerHTML]
|
|
1205
|
+
*/
|
|
1206
|
+
getIcon(name) {
|
|
1207
|
+
const icon = this.icons.get(name);
|
|
1208
|
+
if (!icon) {
|
|
1209
|
+
console.warn(`Icon ${name} not registered`);
|
|
1210
|
+
return this.sanitizer.bypassSecurityTrustHtml(DEFAULT);
|
|
1211
|
+
}
|
|
1212
|
+
return this.sanitizer.bypassSecurityTrustHtml(icon);
|
|
1213
|
+
}
|
|
1214
|
+
/**
|
|
1215
|
+
* Get raw icon SVG string by name
|
|
1216
|
+
* Returns string for direct DOM manipulation
|
|
1217
|
+
*/
|
|
454
1218
|
getIconString(name) {
|
|
455
1219
|
const icon = this.icons.get(name);
|
|
456
1220
|
if (!icon) {
|
|
@@ -2444,214 +3208,57 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImpo
|
|
|
2444
3208
|
<button documentEngineSelectOption value="arrows">Arrows</button>
|
|
2445
3209
|
<button documentEngineSelectOption value="symbols">Symbols</button>
|
|
2446
3210
|
<button documentEngineSelectOption value="punctuation">Punctuation</button>
|
|
2447
|
-
</document-engine-select>
|
|
2448
|
-
</div>
|
|
2449
|
-
|
|
2450
|
-
<!-- Characters Grid -->
|
|
2451
|
-
<div class="special-characters-view__grid-container">
|
|
2452
|
-
<div
|
|
2453
|
-
class="special-characters-view__grid"
|
|
2454
|
-
role="grid"
|
|
2455
|
-
aria-label="Special characters grid"
|
|
2456
|
-
tabindex="-1"
|
|
2457
|
-
(keydown)="onGridKeydown($event)"
|
|
2458
|
-
>
|
|
2459
|
-
<button
|
|
2460
|
-
*ngFor="let char of currentCharacters; let i = index"
|
|
2461
|
-
#charButton
|
|
2462
|
-
type="button"
|
|
2463
|
-
role="gridcell"
|
|
2464
|
-
class="special-characters-view__char-button"
|
|
2465
|
-
(click)="insertCharacter(char.char)"
|
|
2466
|
-
[attr.aria-label]="char.name + (char.code ? ' (' + char.code + ')' : '')"
|
|
2467
|
-
[title]="char.name + (char.code ? ' (' + char.code + ')' : '')"
|
|
2468
|
-
[attr.data-index]="i"
|
|
2469
|
-
>
|
|
2470
|
-
{{ char.char }}
|
|
2471
|
-
</button>
|
|
2472
|
-
</div>
|
|
2473
|
-
</div>
|
|
2474
|
-
|
|
2475
|
-
<!-- Actions -->
|
|
2476
|
-
<div class="special-characters-view__actions">
|
|
2477
|
-
<button type="button" documentEngineButton variant="secondary" (click)="close?.()">Close</button>
|
|
2478
|
-
</div>
|
|
2479
|
-
</div>
|
|
2480
|
-
`, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".special-characters-view{display:flex;flex-direction:column;width:100%}.special-characters-view__header{padding:.5rem 1rem;border-bottom:1px solid var(--border);font-weight:500}.special-characters-view__select{padding:.75rem;width:100%}.special-characters-view__grid-container{padding:.75rem;overflow-y:auto;max-height:20rem}.special-characters-view__grid{display:grid;grid-template-columns:repeat(6,1fr);gap:.25rem}.special-characters-view__char-button{width:2.5rem;height:2.5rem;border-radius:.25rem;border:1px solid var(--border);background-color:transparent;cursor:pointer;font-size:1.125rem;font-weight:500;display:flex;align-items:center;justify-content:center;transition:background-color .2s,color .2s;outline:none}.special-characters-view__char-button:hover{background-color:var(--accent);color:var(--accent-foreground)}.special-characters-view__char-button:focus{outline:none;box-shadow:0 0 0 2px var(--ring);box-shadow-offset:2px}.special-characters-view__actions{display:flex;justify-content:flex-end;padding:.75rem;border-top:1px solid var(--border)}\n"] }]
|
|
2481
|
-
}], propDecorators: { charButtons: [{
|
|
2482
|
-
type: ViewChildren,
|
|
2483
|
-
args: ['charButton']
|
|
2484
|
-
}] } });
|
|
2485
|
-
|
|
2486
|
-
const SpecialCharactersBubbleConfig = {
|
|
2487
|
-
pluginKey: 'SpecialCharactersBubbleMenu',
|
|
2488
|
-
views: [
|
|
2489
|
-
{
|
|
2490
|
-
id: 'characters',
|
|
2491
|
-
component: SpecialCharactersViewComponent,
|
|
2492
|
-
label: 'Special Characters',
|
|
2493
|
-
isDefault: true,
|
|
2494
|
-
},
|
|
2495
|
-
],
|
|
2496
|
-
};
|
|
2497
|
-
|
|
2498
|
-
/**
|
|
2499
|
-
* Directive for positioning a popover/dropdown relative to a trigger element using floating-ui
|
|
2500
|
-
* Handles positioning, visibility, and cleanup automatically
|
|
2501
|
-
*
|
|
2502
|
-
* Usage:
|
|
2503
|
-
* <button #trigger>Open</button>
|
|
2504
|
-
* <div [popover]="trigger" [isOpen]="isOpen">
|
|
2505
|
-
* Content
|
|
2506
|
-
* </div>
|
|
2507
|
-
*/
|
|
2508
|
-
class PopoverDirective {
|
|
2509
|
-
elRef = inject(ElementRef);
|
|
2510
|
-
cdr = inject(ChangeDetectorRef);
|
|
2511
|
-
/**
|
|
2512
|
-
* The trigger element reference (ElementRef or HTMLElement)
|
|
2513
|
-
*/
|
|
2514
|
-
popover;
|
|
2515
|
-
/**
|
|
2516
|
-
* Whether the popover is open
|
|
2517
|
-
*/
|
|
2518
|
-
isOpen = false;
|
|
2519
|
-
/**
|
|
2520
|
-
* Placement of the popover relative to trigger
|
|
2521
|
-
*/
|
|
2522
|
-
placement = 'bottom-start';
|
|
2523
|
-
/**
|
|
2524
|
-
* Offset from trigger
|
|
2525
|
-
*/
|
|
2526
|
-
offset = 8;
|
|
2527
|
-
/**
|
|
2528
|
-
* Padding for flip/shift middleware
|
|
2529
|
-
*/
|
|
2530
|
-
padding = 8;
|
|
2531
|
-
cleanupAutoUpdate;
|
|
2532
|
-
isPositioned = false;
|
|
2533
|
-
viewInitialized = false;
|
|
2534
|
-
retryCount = 0;
|
|
2535
|
-
maxRetries = 10;
|
|
2536
|
-
ngAfterViewInit() {
|
|
2537
|
-
this.viewInitialized = true;
|
|
2538
|
-
this.updateVisibility();
|
|
2539
|
-
}
|
|
2540
|
-
ngOnChanges(changes) {
|
|
2541
|
-
if (this.viewInitialized && (changes['isOpen'] || changes['popover'])) {
|
|
2542
|
-
this.updateVisibility();
|
|
2543
|
-
}
|
|
2544
|
-
}
|
|
2545
|
-
ngOnDestroy() {
|
|
2546
|
-
this.cleanup();
|
|
2547
|
-
}
|
|
2548
|
-
updateVisibility() {
|
|
2549
|
-
if (!this.viewInitialized)
|
|
2550
|
-
return;
|
|
2551
|
-
if (this.isOpen && this.popover) {
|
|
2552
|
-
// Wait for next tick to ensure element is rendered
|
|
2553
|
-
setTimeout(() => {
|
|
2554
|
-
this.setupFloating();
|
|
2555
|
-
});
|
|
2556
|
-
}
|
|
2557
|
-
else {
|
|
2558
|
-
this.isPositioned = false;
|
|
2559
|
-
this.cleanup();
|
|
2560
|
-
}
|
|
2561
|
-
}
|
|
2562
|
-
setupFloating() {
|
|
2563
|
-
// Reset retry count on each attempt
|
|
2564
|
-
this.retryCount = 0;
|
|
2565
|
-
this._setupFloatingInternal();
|
|
2566
|
-
}
|
|
2567
|
-
_setupFloatingInternal() {
|
|
2568
|
-
let triggerEl;
|
|
2569
|
-
if (!this.popover) {
|
|
2570
|
-
if (this.retryCount < this.maxRetries) {
|
|
2571
|
-
this.retryCount++;
|
|
2572
|
-
setTimeout(() => this._setupFloatingInternal(), 10);
|
|
2573
|
-
}
|
|
2574
|
-
return;
|
|
2575
|
-
}
|
|
2576
|
-
if (this.popover instanceof ElementRef) {
|
|
2577
|
-
triggerEl = this.popover.nativeElement;
|
|
2578
|
-
// If ElementRef exists but nativeElement is not yet available, retry
|
|
2579
|
-
if (!triggerEl) {
|
|
2580
|
-
if (this.retryCount < this.maxRetries) {
|
|
2581
|
-
this.retryCount++;
|
|
2582
|
-
setTimeout(() => this._setupFloatingInternal(), 10);
|
|
2583
|
-
}
|
|
2584
|
-
return;
|
|
2585
|
-
}
|
|
2586
|
-
}
|
|
2587
|
-
else {
|
|
2588
|
-
triggerEl = this.popover;
|
|
2589
|
-
}
|
|
2590
|
-
const dropdownEl = this.elRef.nativeElement;
|
|
2591
|
-
if (!triggerEl || !dropdownEl) {
|
|
2592
|
-
// Retry if elements not ready
|
|
2593
|
-
if (this.retryCount < this.maxRetries) {
|
|
2594
|
-
this.retryCount++;
|
|
2595
|
-
setTimeout(() => this._setupFloatingInternal(), 10);
|
|
2596
|
-
}
|
|
2597
|
-
return;
|
|
2598
|
-
}
|
|
2599
|
-
// Compute initial position before showing dropdown to prevent flash
|
|
2600
|
-
computePosition(triggerEl, dropdownEl, {
|
|
2601
|
-
placement: this.placement,
|
|
2602
|
-
middleware: [offset(this.offset), flip(), shift({ padding: this.padding })],
|
|
2603
|
-
})
|
|
2604
|
-
.then(({ x, y }) => {
|
|
2605
|
-
// Set position first, then make visible
|
|
2606
|
-
Object.assign(dropdownEl.style, { left: `${x}px`, top: `${y}px` });
|
|
2607
|
-
this.isPositioned = true;
|
|
2608
|
-
// Trigger change detection to update visibility binding
|
|
2609
|
-
this.cdr.markForCheck();
|
|
2610
|
-
})
|
|
2611
|
-
.catch((error) => {
|
|
2612
|
-
console.error('[PopoverDirective] Error computing position', error);
|
|
2613
|
-
});
|
|
2614
|
-
// Use autoUpdate to automatically reposition on scroll/resize
|
|
2615
|
-
this.cleanupAutoUpdate = autoUpdate(triggerEl, dropdownEl, () => {
|
|
2616
|
-
computePosition(triggerEl, dropdownEl, {
|
|
2617
|
-
placement: this.placement,
|
|
2618
|
-
middleware: [offset(this.offset), flip(), shift({ padding: this.padding })],
|
|
2619
|
-
}).then(({ x, y }) => {
|
|
2620
|
-
Object.assign(dropdownEl.style, { left: `${x}px`, top: `${y}px` });
|
|
2621
|
-
});
|
|
2622
|
-
});
|
|
2623
|
-
}
|
|
2624
|
-
cleanup() {
|
|
2625
|
-
if (this.cleanupAutoUpdate) {
|
|
2626
|
-
this.cleanupAutoUpdate();
|
|
2627
|
-
this.cleanupAutoUpdate = undefined;
|
|
2628
|
-
}
|
|
2629
|
-
}
|
|
2630
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: PopoverDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
2631
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.3.10", type: PopoverDirective, isStandalone: true, selector: "[popover]", inputs: { popover: "popover", isOpen: "isOpen", placement: "placement", offset: "offset", padding: "padding" }, host: { properties: { "style.position": "\"fixed\"", "style.visibility": "isPositioned ? \"visible\" : \"hidden\"" } }, usesOnChanges: true, ngImport: i0 });
|
|
2632
|
-
}
|
|
2633
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: PopoverDirective, decorators: [{
|
|
2634
|
-
type: Directive,
|
|
2635
|
-
args: [{
|
|
2636
|
-
selector: '[popover]',
|
|
2637
|
-
standalone: true,
|
|
2638
|
-
host: {
|
|
2639
|
-
'[style.position]': '"fixed"',
|
|
2640
|
-
'[style.visibility]': 'isPositioned ? "visible" : "hidden"',
|
|
2641
|
-
},
|
|
2642
|
-
}]
|
|
2643
|
-
}], propDecorators: { popover: [{
|
|
2644
|
-
type: Input
|
|
2645
|
-
}], isOpen: [{
|
|
2646
|
-
type: Input
|
|
2647
|
-
}], placement: [{
|
|
2648
|
-
type: Input
|
|
2649
|
-
}], offset: [{
|
|
2650
|
-
type: Input
|
|
2651
|
-
}], padding: [{
|
|
2652
|
-
type: Input
|
|
3211
|
+
</document-engine-select>
|
|
3212
|
+
</div>
|
|
3213
|
+
|
|
3214
|
+
<!-- Characters Grid -->
|
|
3215
|
+
<div class="special-characters-view__grid-container">
|
|
3216
|
+
<div
|
|
3217
|
+
class="special-characters-view__grid"
|
|
3218
|
+
role="grid"
|
|
3219
|
+
aria-label="Special characters grid"
|
|
3220
|
+
tabindex="-1"
|
|
3221
|
+
(keydown)="onGridKeydown($event)"
|
|
3222
|
+
>
|
|
3223
|
+
<button
|
|
3224
|
+
*ngFor="let char of currentCharacters; let i = index"
|
|
3225
|
+
#charButton
|
|
3226
|
+
type="button"
|
|
3227
|
+
role="gridcell"
|
|
3228
|
+
class="special-characters-view__char-button"
|
|
3229
|
+
(click)="insertCharacter(char.char)"
|
|
3230
|
+
[attr.aria-label]="char.name + (char.code ? ' (' + char.code + ')' : '')"
|
|
3231
|
+
[title]="char.name + (char.code ? ' (' + char.code + ')' : '')"
|
|
3232
|
+
[attr.data-index]="i"
|
|
3233
|
+
>
|
|
3234
|
+
{{ char.char }}
|
|
3235
|
+
</button>
|
|
3236
|
+
</div>
|
|
3237
|
+
</div>
|
|
3238
|
+
|
|
3239
|
+
<!-- Actions -->
|
|
3240
|
+
<div class="special-characters-view__actions">
|
|
3241
|
+
<button type="button" documentEngineButton variant="secondary" (click)="close?.()">Close</button>
|
|
3242
|
+
</div>
|
|
3243
|
+
</div>
|
|
3244
|
+
`, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".special-characters-view{display:flex;flex-direction:column;width:100%}.special-characters-view__header{padding:.5rem 1rem;border-bottom:1px solid var(--border);font-weight:500}.special-characters-view__select{padding:.75rem;width:100%}.special-characters-view__grid-container{padding:.75rem;overflow-y:auto;max-height:20rem}.special-characters-view__grid{display:grid;grid-template-columns:repeat(6,1fr);gap:.25rem}.special-characters-view__char-button{width:2.5rem;height:2.5rem;border-radius:.25rem;border:1px solid var(--border);background-color:transparent;cursor:pointer;font-size:1.125rem;font-weight:500;display:flex;align-items:center;justify-content:center;transition:background-color .2s,color .2s;outline:none}.special-characters-view__char-button:hover{background-color:var(--accent);color:var(--accent-foreground)}.special-characters-view__char-button:focus{outline:none;box-shadow:0 0 0 2px var(--ring);box-shadow-offset:2px}.special-characters-view__actions{display:flex;justify-content:flex-end;padding:.75rem;border-top:1px solid var(--border)}\n"] }]
|
|
3245
|
+
}], propDecorators: { charButtons: [{
|
|
3246
|
+
type: ViewChildren,
|
|
3247
|
+
args: ['charButton']
|
|
2653
3248
|
}] } });
|
|
2654
3249
|
|
|
3250
|
+
const SpecialCharactersBubbleConfig = {
|
|
3251
|
+
pluginKey: 'SpecialCharactersBubbleMenu',
|
|
3252
|
+
views: [
|
|
3253
|
+
{
|
|
3254
|
+
id: 'characters',
|
|
3255
|
+
component: SpecialCharactersViewComponent,
|
|
3256
|
+
label: 'Special Characters',
|
|
3257
|
+
isDefault: true,
|
|
3258
|
+
},
|
|
3259
|
+
],
|
|
3260
|
+
};
|
|
3261
|
+
|
|
2655
3262
|
/**
|
|
2656
3263
|
* Toggle button option directive
|
|
2657
3264
|
*/
|
|
@@ -4175,597 +4782,342 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImpo
|
|
|
4175
4782
|
></document-engine-color-picker>
|
|
4176
4783
|
</div>
|
|
4177
4784
|
</div>
|
|
4178
|
-
</div>
|
|
4179
|
-
</div>
|
|
4180
|
-
|
|
4181
|
-
<div class="table-style-view__section">
|
|
4182
|
-
<div class="table-style-view__section-label">Background</div>
|
|
4183
|
-
<div class="table-style-view__field table-style-view__field--color">
|
|
4184
|
-
<div class="table-style-view__field-label">Color</div>
|
|
4185
|
-
|
|
4186
|
-
<div class="table-style-view__color-input">
|
|
4187
|
-
<input documentEngineInput [value]="tableBg" readonly />
|
|
4188
|
-
|
|
4189
|
-
<button
|
|
4190
|
-
*ngIf="tableBg"
|
|
4191
|
-
type="button"
|
|
4192
|
-
documentEngineButton
|
|
4193
|
-
size="icon"
|
|
4194
|
-
variant="ghost"
|
|
4195
|
-
class="table-style-view__color-clear"
|
|
4196
|
-
(click)="tableBg = null"
|
|
4197
|
-
>
|
|
4198
|
-
<document-engine-icon name="close"></document-engine-icon>
|
|
4199
|
-
</button>
|
|
4200
|
-
|
|
4201
|
-
<button
|
|
4202
|
-
#bgColorTrigger
|
|
4203
|
-
type="button"
|
|
4204
|
-
class="table-style-view__color-swatch"
|
|
4205
|
-
[style.background-color]="tableBg || 'transparent'"
|
|
4206
|
-
(click)="toggleColorPicker('bg')"
|
|
4207
|
-
title="Pick color"
|
|
4208
|
-
>
|
|
4209
|
-
<div *ngIf="tableBg === 'transparent'" class="table-style-view__color-checker"></div>
|
|
4210
|
-
</button>
|
|
4211
|
-
</div>
|
|
4212
|
-
|
|
4213
|
-
<!-- Color picker for background -->
|
|
4214
|
-
<div
|
|
4215
|
-
*ngIf="showColorPicker === 'bg'"
|
|
4216
|
-
[popover]="bgColorTrigger"
|
|
4217
|
-
[isOpen]="showColorPicker === 'bg'"
|
|
4218
|
-
class="table-style-view__color-picker-dropdown"
|
|
4219
|
-
(click)="$event.stopPropagation()"
|
|
4220
|
-
>
|
|
4221
|
-
<document-engine-color-picker
|
|
4222
|
-
[colorPalette]="colorPalette"
|
|
4223
|
-
[activeColor]="tableBgObj"
|
|
4224
|
-
(colorSelected)="onTableBgSelected($event)"
|
|
4225
|
-
(colorRemoved)="onTableBgRemoved()"
|
|
4226
|
-
></document-engine-color-picker>
|
|
4227
|
-
</div>
|
|
4228
|
-
</div>
|
|
4229
|
-
</div>
|
|
4230
|
-
</div>
|
|
4231
|
-
|
|
4232
|
-
<!-- Actions -->
|
|
4233
|
-
<div class="table-style-view__actions">
|
|
4234
|
-
<button documentEngineButton variant="ghost" (click)="cancel()">Cancel</button>
|
|
4235
|
-
<button documentEngineButton variant="default" (click)="onSave()">Save</button>
|
|
4236
|
-
</div>
|
|
4237
|
-
</div>
|
|
4238
|
-
`, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".table-style-view{display:flex;flex-direction:column;width:32.5rem}.table-style-view__header{padding:.5rem 1rem;border-bottom:1px solid var(--border);font-weight:500}.table-style-view__content{display:flex;flex-direction:column;gap:1rem;padding:.75rem}.table-style-view__section{display:flex;flex-direction:column;gap:.5rem}.table-style-view__section-label{font-weight:700;color:var(--muted-foreground)}.table-style-view__border-grid{display:grid;grid-template-columns:repeat(8,1fr);gap:.5rem}.table-style-view__field{display:flex;flex-direction:column;gap:.25rem;grid-column:span 2}.table-style-view__field--color{grid-column:span 4;position:relative}.table-style-view__field-label{font-size:.75rem;color:var(--muted-foreground)}.table-style-view__color-input{position:relative;display:flex;align-items:center;gap:.25rem}.table-style-view__color-input input{flex:1;padding-right:3rem}.table-style-view__color-clear{position:absolute;right:2.5rem;z-index:1}.table-style-view__color-swatch{position:absolute;right:.5rem;width:1.75rem;height:1.75rem;border-radius:.25rem;border:1px solid var(--border);cursor:pointer;transition:transform .2s;overflow:hidden;z-index:2}.table-style-view__color-swatch:hover{transform:scale(1.1)}.table-style-view__color-checker{position:absolute;inset:0;opacity:.3;background-image:repeating-conic-gradient(#ccc 0deg 90deg,#fff 90deg 180deg);background-size:4px 4px}.table-style-view__color-picker-dropdown{z-index:50;background:var(--popover);border:1px solid var(--border);border-radius:.5rem;box-shadow:var(--shadow-elevation-2);padding:.5rem}.table-style-view__actions{display:flex;align-items:center;justify-content:flex-end;gap:.5rem;padding:.75rem;border-top:1px solid var(--border)}\n"] }]
|
|
4239
|
-
}], propDecorators: { borderColorTrigger: [{
|
|
4240
|
-
type: ViewChild,
|
|
4241
|
-
args: ['borderColorTrigger', { static: false, read: ElementRef }]
|
|
4242
|
-
}], bgColorTrigger: [{
|
|
4243
|
-
type: ViewChild,
|
|
4244
|
-
args: ['bgColorTrigger', { static: false, read: ElementRef }]
|
|
4245
|
-
}] } });
|
|
4246
|
-
|
|
4247
|
-
const TableBubbleConfig = {
|
|
4248
|
-
props: {
|
|
4249
|
-
pluginKey: 'TableBubbleMenu',
|
|
4250
|
-
mountTo: 'table',
|
|
4251
|
-
},
|
|
4252
|
-
target: {
|
|
4253
|
-
kind: 'node',
|
|
4254
|
-
name: ['tableCell', 'tableHeader'],
|
|
4255
|
-
allowEmptySelection: true,
|
|
4256
|
-
requireFocus: false,
|
|
4257
|
-
},
|
|
4258
|
-
views: [
|
|
4259
|
-
{
|
|
4260
|
-
id: 'main',
|
|
4261
|
-
component: TableMainViewComponent,
|
|
4262
|
-
label: 'Table',
|
|
4263
|
-
isDefault: true,
|
|
4264
|
-
},
|
|
4265
|
-
{
|
|
4266
|
-
id: 'table-style',
|
|
4267
|
-
component: TableStyleViewComponent,
|
|
4268
|
-
label: 'Table Properties',
|
|
4269
|
-
},
|
|
4270
|
-
{
|
|
4271
|
-
id: 'cell-style',
|
|
4272
|
-
component: TableCellStyleViewComponent,
|
|
4273
|
-
label: 'Cell Properties',
|
|
4274
|
-
},
|
|
4275
|
-
],
|
|
4276
|
-
};
|
|
4277
|
-
const TableCreateBubbleConfig = {
|
|
4278
|
-
pluginKey: 'TableCreateBubbleMenu',
|
|
4279
|
-
views: [
|
|
4280
|
-
{
|
|
4281
|
-
id: 'create',
|
|
4282
|
-
component: TableCreateViewComponent,
|
|
4283
|
-
label: 'Create Table',
|
|
4284
|
-
isDefault: true,
|
|
4285
|
-
},
|
|
4286
|
-
],
|
|
4287
|
-
};
|
|
4288
|
-
|
|
4289
|
-
/**
|
|
4290
|
-
* Template view for bubble menu
|
|
4291
|
-
* Shows templates for insertion into the editor
|
|
4292
|
-
* Replaces entire editor content with selected template
|
|
4293
|
-
*/
|
|
4294
|
-
class TemplateViewComponent {
|
|
4295
|
-
cdr = inject(ChangeDetectorRef);
|
|
4296
|
-
editor;
|
|
4297
|
-
close;
|
|
4298
|
-
goBack;
|
|
4299
|
-
navigateTo;
|
|
4300
|
-
searchQuery = '';
|
|
4301
|
-
originalTemplates = [];
|
|
4302
|
-
// Reactive form control for search
|
|
4303
|
-
searchControl = new FormControl('');
|
|
4304
|
-
destroy$ = new Subject();
|
|
4305
|
-
onActivate(attrs) {
|
|
4306
|
-
const templates = attrs['templates'];
|
|
4307
|
-
if (!templates)
|
|
4308
|
-
throw new Error('Templates are required');
|
|
4309
|
-
this.originalTemplates = templates;
|
|
4310
|
-
this.searchQuery = '';
|
|
4311
|
-
this.searchControl.setValue('');
|
|
4312
|
-
this.cdr.markForCheck();
|
|
4313
|
-
}
|
|
4314
|
-
ngOnInit() {
|
|
4315
|
-
// Set up reactive form subscription with debounce
|
|
4316
|
-
this.searchControl.valueChanges
|
|
4317
|
-
.pipe(debounceTime(200), distinctUntilChanged(), takeUntil(this.destroy$))
|
|
4318
|
-
.subscribe((value) => {
|
|
4319
|
-
this.searchQuery = value || '';
|
|
4320
|
-
this.cdr.markForCheck();
|
|
4321
|
-
});
|
|
4322
|
-
}
|
|
4323
|
-
ngOnDestroy() {
|
|
4324
|
-
this.destroy$.next();
|
|
4325
|
-
this.destroy$.complete();
|
|
4326
|
-
}
|
|
4327
|
-
/**
|
|
4328
|
-
* Getter that filters templates based on search query
|
|
4329
|
-
*/
|
|
4330
|
-
get filteredTemplates() {
|
|
4331
|
-
const query = this.searchQuery.toLowerCase().trim();
|
|
4332
|
-
if (!query)
|
|
4333
|
-
return this.originalTemplates;
|
|
4334
|
-
// Filter templates based on search query
|
|
4335
|
-
return this.originalTemplates.filter((template) => template.title.toLowerCase().includes(query) || template.description?.toLowerCase().includes(query));
|
|
4336
|
-
}
|
|
4337
|
-
/**
|
|
4338
|
-
* Insert a template into the editor, replacing entire content
|
|
4339
|
-
*/
|
|
4340
|
-
insertTemplate(template) {
|
|
4341
|
-
if (!this.editor)
|
|
4342
|
-
return;
|
|
4343
|
-
// Replace entire editor content with template data
|
|
4344
|
-
this.editor.chain().focus().setContent(template.data).run();
|
|
4345
|
-
this.close?.();
|
|
4346
|
-
}
|
|
4347
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: TemplateViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4348
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.10", type: TemplateViewComponent, isStandalone: true, selector: "document-engine-template-view", ngImport: i0, template: `
|
|
4349
|
-
<div class="template-view">
|
|
4350
|
-
<!-- Title -->
|
|
4351
|
-
<div class="template-view__header">Templates</div>
|
|
4352
|
-
|
|
4353
|
-
<!-- Search Bar -->
|
|
4354
|
-
<div class="template-view__search">
|
|
4355
|
-
<div class="template-view__search-field">
|
|
4356
|
-
<label documentEngineLabel for="search-input">Search templates</label>
|
|
4357
|
-
<div class="template-view__search-input-wrapper">
|
|
4358
|
-
<document-engine-icon name="search" class="template-view__search-icon"></document-engine-icon>
|
|
4359
|
-
<input
|
|
4360
|
-
documentEngineInput
|
|
4361
|
-
id="search-input"
|
|
4362
|
-
[formControl]="searchControl"
|
|
4363
|
-
placeholder="Search templates..."
|
|
4364
|
-
aria-label="Search templates"
|
|
4365
|
-
/>
|
|
4366
|
-
</div>
|
|
4367
|
-
</div>
|
|
4368
|
-
</div>
|
|
4369
|
-
|
|
4370
|
-
<!-- Templates List -->
|
|
4371
|
-
<div class="template-view__list-container">
|
|
4372
|
-
<div *ngIf="filteredTemplates.length > 0; else noResults" class="template-view__list">
|
|
4373
|
-
<button
|
|
4374
|
-
*ngFor="let template of filteredTemplates"
|
|
4375
|
-
type="button"
|
|
4376
|
-
class="template-view__template-item"
|
|
4377
|
-
(click)="insertTemplate(template)"
|
|
4378
|
-
[attr.aria-label]="template.title + (template.description ? ' - ' + template.description : '')"
|
|
4379
|
-
[title]="template.description || template.title"
|
|
4380
|
-
>
|
|
4381
|
-
<div class="template-view__template-title">{{ template.title }}</div>
|
|
4382
|
-
<div *ngIf="template.description" class="template-view__template-description">
|
|
4383
|
-
{{ template.description }}
|
|
4384
|
-
</div>
|
|
4385
|
-
</button>
|
|
4785
|
+
</div>
|
|
4386
4786
|
</div>
|
|
4387
4787
|
|
|
4388
|
-
<
|
|
4389
|
-
<div class="
|
|
4390
|
-
|
|
4391
|
-
<div class="
|
|
4392
|
-
</div>
|
|
4393
|
-
</ng-template>
|
|
4394
|
-
</div>
|
|
4788
|
+
<div class="table-style-view__section">
|
|
4789
|
+
<div class="table-style-view__section-label">Background</div>
|
|
4790
|
+
<div class="table-style-view__field table-style-view__field--color">
|
|
4791
|
+
<div class="table-style-view__field-label">Color</div>
|
|
4395
4792
|
|
|
4396
|
-
|
|
4397
|
-
|
|
4398
|
-
<button type="button" documentEngineButton variant="secondary" (click)="close?.()">Close</button>
|
|
4399
|
-
</div>
|
|
4400
|
-
</div>
|
|
4401
|
-
`, isInline: true, styles: [".template-view{display:flex;flex-direction:column;width:100%;max-width:28rem}.template-view__header{padding:.5rem 1rem;border-bottom:1px solid var(--border);font-weight:500}.template-view__search{padding:.75rem;border-bottom:1px solid var(--border)}.template-view__search-field{display:flex;flex-direction:column;gap:.25rem;width:100%}.template-view__search-input-wrapper{position:relative;display:flex;align-items:center}.template-view__search-icon{position:absolute;left:.75rem;color:var(--muted-foreground);pointer-events:none;z-index:1}.template-view__search-input-wrapper input{padding-left:2.5rem}.template-view__list-container{overflow-y:auto;max-height:20rem}.template-view__list{padding:.5rem 0}.template-view__template-item{display:flex;flex-direction:column;align-items:flex-start;padding:.75rem;width:100%;text-align:left;background-color:transparent;border:none;cursor:pointer;transition:background-color .2s;outline:none}.template-view__template-item:hover{background-color:var(--accent);color:var(--accent-foreground)}.template-view__template-item:focus{outline:none;box-shadow:0 0 0 2px var(--ring);box-shadow-offset:2px}.template-view__template-title{font-weight:500;font-size:.875rem;color:var(--foreground);margin-bottom:.25rem}.template-view__template-description{font-size:.75rem;color:var(--muted-foreground);line-height:1.4}.template-view__empty{padding:2rem 1rem;text-align:center;color:var(--muted-foreground)}.template-view__empty-icon{margin-bottom:.5rem;font-size:2rem;opacity:.5}.template-view__empty-text{font-size:.875rem}.template-view__actions{display:flex;justify-content:flex-end;padding:.75rem;border-top:1px solid var(--border)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: ButtonDirective, selector: "button[documentEngineButton]", inputs: ["variant", "size"] }, { kind: "directive", type: InputDirective, selector: "input[documentEngineInput]", inputs: ["class"] }, { kind: "directive", type: LabelDirective, selector: "label[documentEngineLabel]", inputs: ["class"] }, { kind: "component", type: IconComponent, selector: "document-engine-icon", inputs: ["name"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4402
|
-
}
|
|
4403
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: TemplateViewComponent, decorators: [{
|
|
4404
|
-
type: Component,
|
|
4405
|
-
args: [{ selector: 'document-engine-template-view', standalone: true, imports: [CommonModule, ReactiveFormsModule, ButtonDirective, InputDirective, LabelDirective, IconComponent], template: `
|
|
4406
|
-
<div class="template-view">
|
|
4407
|
-
<!-- Title -->
|
|
4408
|
-
<div class="template-view__header">Templates</div>
|
|
4793
|
+
<div class="table-style-view__color-input">
|
|
4794
|
+
<input documentEngineInput [value]="tableBg" readonly />
|
|
4409
4795
|
|
|
4410
|
-
|
|
4411
|
-
|
|
4412
|
-
|
|
4413
|
-
|
|
4414
|
-
|
|
4415
|
-
|
|
4416
|
-
|
|
4417
|
-
|
|
4418
|
-
|
|
4419
|
-
|
|
4420
|
-
|
|
4421
|
-
aria-label="Search templates"
|
|
4422
|
-
/>
|
|
4423
|
-
</div>
|
|
4424
|
-
</div>
|
|
4425
|
-
</div>
|
|
4796
|
+
<button
|
|
4797
|
+
*ngIf="tableBg"
|
|
4798
|
+
type="button"
|
|
4799
|
+
documentEngineButton
|
|
4800
|
+
size="icon"
|
|
4801
|
+
variant="ghost"
|
|
4802
|
+
class="table-style-view__color-clear"
|
|
4803
|
+
(click)="tableBg = null"
|
|
4804
|
+
>
|
|
4805
|
+
<document-engine-icon name="close"></document-engine-icon>
|
|
4806
|
+
</button>
|
|
4426
4807
|
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
|
|
4432
|
-
|
|
4433
|
-
|
|
4434
|
-
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
>
|
|
4438
|
-
<div class="template-view__template-title">{{ template.title }}</div>
|
|
4439
|
-
<div *ngIf="template.description" class="template-view__template-description">
|
|
4440
|
-
{{ template.description }}
|
|
4808
|
+
<button
|
|
4809
|
+
#bgColorTrigger
|
|
4810
|
+
type="button"
|
|
4811
|
+
class="table-style-view__color-swatch"
|
|
4812
|
+
[style.background-color]="tableBg || 'transparent'"
|
|
4813
|
+
(click)="toggleColorPicker('bg')"
|
|
4814
|
+
title="Pick color"
|
|
4815
|
+
>
|
|
4816
|
+
<div *ngIf="tableBg === 'transparent'" class="table-style-view__color-checker"></div>
|
|
4817
|
+
</button>
|
|
4441
4818
|
</div>
|
|
4442
|
-
</button>
|
|
4443
|
-
</div>
|
|
4444
4819
|
|
|
4445
|
-
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
|
|
4820
|
+
<!-- Color picker for background -->
|
|
4821
|
+
<div
|
|
4822
|
+
*ngIf="showColorPicker === 'bg'"
|
|
4823
|
+
[popover]="bgColorTrigger"
|
|
4824
|
+
[isOpen]="showColorPicker === 'bg'"
|
|
4825
|
+
class="table-style-view__color-picker-dropdown"
|
|
4826
|
+
(click)="$event.stopPropagation()"
|
|
4827
|
+
>
|
|
4828
|
+
<document-engine-color-picker
|
|
4829
|
+
[colorPalette]="colorPalette"
|
|
4830
|
+
[activeColor]="tableBgObj"
|
|
4831
|
+
(colorSelected)="onTableBgSelected($event)"
|
|
4832
|
+
(colorRemoved)="onTableBgRemoved()"
|
|
4833
|
+
></document-engine-color-picker>
|
|
4834
|
+
</div>
|
|
4449
4835
|
</div>
|
|
4450
|
-
</
|
|
4836
|
+
</div>
|
|
4451
4837
|
</div>
|
|
4452
4838
|
|
|
4453
4839
|
<!-- Actions -->
|
|
4454
|
-
<div class="
|
|
4455
|
-
<button
|
|
4840
|
+
<div class="table-style-view__actions">
|
|
4841
|
+
<button documentEngineButton variant="ghost" (click)="cancel()">Cancel</button>
|
|
4842
|
+
<button documentEngineButton variant="default" (click)="onSave()">Save</button>
|
|
4456
4843
|
</div>
|
|
4457
4844
|
</div>
|
|
4458
|
-
`, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".
|
|
4459
|
-
}]
|
|
4845
|
+
`, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".table-style-view{display:flex;flex-direction:column;width:32.5rem}.table-style-view__header{padding:.5rem 1rem;border-bottom:1px solid var(--border);font-weight:500}.table-style-view__content{display:flex;flex-direction:column;gap:1rem;padding:.75rem}.table-style-view__section{display:flex;flex-direction:column;gap:.5rem}.table-style-view__section-label{font-weight:700;color:var(--muted-foreground)}.table-style-view__border-grid{display:grid;grid-template-columns:repeat(8,1fr);gap:.5rem}.table-style-view__field{display:flex;flex-direction:column;gap:.25rem;grid-column:span 2}.table-style-view__field--color{grid-column:span 4;position:relative}.table-style-view__field-label{font-size:.75rem;color:var(--muted-foreground)}.table-style-view__color-input{position:relative;display:flex;align-items:center;gap:.25rem}.table-style-view__color-input input{flex:1;padding-right:3rem}.table-style-view__color-clear{position:absolute;right:2.5rem;z-index:1}.table-style-view__color-swatch{position:absolute;right:.5rem;width:1.75rem;height:1.75rem;border-radius:.25rem;border:1px solid var(--border);cursor:pointer;transition:transform .2s;overflow:hidden;z-index:2}.table-style-view__color-swatch:hover{transform:scale(1.1)}.table-style-view__color-checker{position:absolute;inset:0;opacity:.3;background-image:repeating-conic-gradient(#ccc 0deg 90deg,#fff 90deg 180deg);background-size:4px 4px}.table-style-view__color-picker-dropdown{z-index:50;background:var(--popover);border:1px solid var(--border);border-radius:.5rem;box-shadow:var(--shadow-elevation-2);padding:.5rem}.table-style-view__actions{display:flex;align-items:center;justify-content:flex-end;gap:.5rem;padding:.75rem;border-top:1px solid var(--border)}\n"] }]
|
|
4846
|
+
}], propDecorators: { borderColorTrigger: [{
|
|
4847
|
+
type: ViewChild,
|
|
4848
|
+
args: ['borderColorTrigger', { static: false, read: ElementRef }]
|
|
4849
|
+
}], bgColorTrigger: [{
|
|
4850
|
+
type: ViewChild,
|
|
4851
|
+
args: ['bgColorTrigger', { static: false, read: ElementRef }]
|
|
4852
|
+
}] } });
|
|
4460
4853
|
|
|
4461
|
-
const
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
|
|
4854
|
+
const TableBubbleConfig = {
|
|
4855
|
+
props: {
|
|
4856
|
+
pluginKey: 'TableBubbleMenu',
|
|
4857
|
+
mountTo: 'table',
|
|
4858
|
+
},
|
|
4859
|
+
target: {
|
|
4860
|
+
kind: 'node',
|
|
4861
|
+
name: ['tableCell', 'tableHeader'],
|
|
4862
|
+
allowEmptySelection: true,
|
|
4863
|
+
requireFocus: false,
|
|
4864
|
+
},
|
|
4465
4865
|
views: [
|
|
4466
4866
|
{
|
|
4467
|
-
id: '
|
|
4468
|
-
component:
|
|
4469
|
-
label: '
|
|
4867
|
+
id: 'main',
|
|
4868
|
+
component: TableMainViewComponent,
|
|
4869
|
+
label: 'Table',
|
|
4470
4870
|
isDefault: true,
|
|
4471
4871
|
},
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
|
|
4477
|
-
|
|
4478
|
-
|
|
4479
|
-
|
|
4480
|
-
|
|
4481
|
-
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
|
|
4486
|
-
|
|
4487
|
-
|
|
4488
|
-
|
|
4489
|
-
|
|
4490
|
-
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
|
|
4496
|
-
|
|
4497
|
-
|
|
4498
|
-
|
|
4499
|
-
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
|
|
4503
|
-
|
|
4504
|
-
|
|
4505
|
-
|
|
4506
|
-
|
|
4507
|
-
|
|
4508
|
-
|
|
4509
|
-
|
|
4510
|
-
|
|
4511
|
-
|
|
4512
|
-
|
|
4513
|
-
];
|
|
4514
|
-
|
|
4515
|
-
|
|
4516
|
-
|
|
4517
|
-
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
|
|
4526
|
-
|
|
4527
|
-
|
|
4528
|
-
if (!element)
|
|
4529
|
-
return;
|
|
4530
|
-
// Detach existing trap if any
|
|
4531
|
-
this.detach(element);
|
|
4532
|
-
// Get all focusable elements within the container
|
|
4533
|
-
const focusableSelectors = [
|
|
4534
|
-
'a[href]',
|
|
4535
|
-
'button:not([disabled])',
|
|
4536
|
-
'textarea:not([disabled])',
|
|
4537
|
-
'input:not([disabled])',
|
|
4538
|
-
'select:not([disabled])',
|
|
4539
|
-
'[tabindex]:not([tabindex="-1"])',
|
|
4540
|
-
].join(', ');
|
|
4541
|
-
const focusableElements = Array.from(element.querySelectorAll(focusableSelectors));
|
|
4542
|
-
if (focusableElements.length === 0)
|
|
4543
|
-
return;
|
|
4544
|
-
const firstFocusableElement = focusableElements[0];
|
|
4545
|
-
const lastFocusableElement = focusableElements[focusableElements.length - 1];
|
|
4546
|
-
// Focus the first element if requested
|
|
4547
|
-
if (autoFocus && firstFocusableElement) {
|
|
4548
|
-
setTimeout(() => {
|
|
4549
|
-
firstFocusableElement.focus();
|
|
4550
|
-
}, 0);
|
|
4551
|
-
}
|
|
4552
|
-
// Handle Tab key to trap focus
|
|
4553
|
-
const handler = (event) => {
|
|
4554
|
-
if (event.key !== 'Tab')
|
|
4555
|
-
return;
|
|
4556
|
-
if (!element.contains(this.document.activeElement)) {
|
|
4557
|
-
event.preventDefault();
|
|
4558
|
-
if (event.shiftKey) {
|
|
4559
|
-
lastFocusableElement?.focus();
|
|
4560
|
-
}
|
|
4561
|
-
else {
|
|
4562
|
-
firstFocusableElement?.focus();
|
|
4563
|
-
}
|
|
4564
|
-
return;
|
|
4565
|
-
}
|
|
4566
|
-
if (event.shiftKey) {
|
|
4567
|
-
// Shift + Tab: going backwards
|
|
4568
|
-
if (this.document.activeElement === firstFocusableElement) {
|
|
4569
|
-
event.preventDefault();
|
|
4570
|
-
lastFocusableElement?.focus();
|
|
4571
|
-
}
|
|
4572
|
-
}
|
|
4573
|
-
else {
|
|
4574
|
-
// Tab: going forwards
|
|
4575
|
-
if (this.document.activeElement === lastFocusableElement) {
|
|
4576
|
-
event.preventDefault();
|
|
4577
|
-
firstFocusableElement?.focus();
|
|
4578
|
-
}
|
|
4579
|
-
}
|
|
4580
|
-
};
|
|
4581
|
-
element.addEventListener('keydown', handler);
|
|
4582
|
-
this.activeTraps.set(element, {
|
|
4583
|
-
handler,
|
|
4584
|
-
firstElement: firstFocusableElement,
|
|
4585
|
-
lastElement: lastFocusableElement,
|
|
4872
|
+
{
|
|
4873
|
+
id: 'table-style',
|
|
4874
|
+
component: TableStyleViewComponent,
|
|
4875
|
+
label: 'Table Properties',
|
|
4876
|
+
},
|
|
4877
|
+
{
|
|
4878
|
+
id: 'cell-style',
|
|
4879
|
+
component: TableCellStyleViewComponent,
|
|
4880
|
+
label: 'Cell Properties',
|
|
4881
|
+
},
|
|
4882
|
+
],
|
|
4883
|
+
};
|
|
4884
|
+
const TableCreateBubbleConfig = {
|
|
4885
|
+
pluginKey: 'TableCreateBubbleMenu',
|
|
4886
|
+
views: [
|
|
4887
|
+
{
|
|
4888
|
+
id: 'create',
|
|
4889
|
+
component: TableCreateViewComponent,
|
|
4890
|
+
label: 'Create Table',
|
|
4891
|
+
isDefault: true,
|
|
4892
|
+
},
|
|
4893
|
+
],
|
|
4894
|
+
};
|
|
4895
|
+
|
|
4896
|
+
/**
|
|
4897
|
+
* Template view for bubble menu
|
|
4898
|
+
* Shows templates for insertion into the editor
|
|
4899
|
+
* Replaces entire editor content with selected template
|
|
4900
|
+
*/
|
|
4901
|
+
class TemplateViewComponent {
|
|
4902
|
+
cdr = inject(ChangeDetectorRef);
|
|
4903
|
+
editor;
|
|
4904
|
+
close;
|
|
4905
|
+
goBack;
|
|
4906
|
+
navigateTo;
|
|
4907
|
+
searchQuery = '';
|
|
4908
|
+
originalTemplates = [];
|
|
4909
|
+
// Reactive form control for search
|
|
4910
|
+
searchControl = new FormControl('');
|
|
4911
|
+
destroy$ = new Subject();
|
|
4912
|
+
onActivate(attrs) {
|
|
4913
|
+
const templates = attrs['templates'];
|
|
4914
|
+
if (!templates)
|
|
4915
|
+
throw new Error('Templates are required');
|
|
4916
|
+
this.originalTemplates = templates;
|
|
4917
|
+
this.searchQuery = '';
|
|
4918
|
+
this.searchControl.setValue('');
|
|
4919
|
+
this.cdr.markForCheck();
|
|
4920
|
+
}
|
|
4921
|
+
ngOnInit() {
|
|
4922
|
+
// Set up reactive form subscription with debounce
|
|
4923
|
+
this.searchControl.valueChanges
|
|
4924
|
+
.pipe(debounceTime(200), distinctUntilChanged(), takeUntil(this.destroy$))
|
|
4925
|
+
.subscribe((value) => {
|
|
4926
|
+
this.searchQuery = value || '';
|
|
4927
|
+
this.cdr.markForCheck();
|
|
4586
4928
|
});
|
|
4587
4929
|
}
|
|
4588
|
-
|
|
4589
|
-
|
|
4590
|
-
|
|
4591
|
-
*/
|
|
4592
|
-
detach(element) {
|
|
4593
|
-
if (!element)
|
|
4594
|
-
return;
|
|
4595
|
-
const trap = this.activeTraps.get(element);
|
|
4596
|
-
if (trap) {
|
|
4597
|
-
element.removeEventListener('keydown', trap.handler);
|
|
4598
|
-
this.activeTraps.delete(element);
|
|
4599
|
-
}
|
|
4930
|
+
ngOnDestroy() {
|
|
4931
|
+
this.destroy$.next();
|
|
4932
|
+
this.destroy$.complete();
|
|
4600
4933
|
}
|
|
4601
4934
|
/**
|
|
4602
|
-
*
|
|
4935
|
+
* Getter that filters templates based on search query
|
|
4603
4936
|
*/
|
|
4604
|
-
|
|
4605
|
-
this.
|
|
4606
|
-
|
|
4607
|
-
|
|
4608
|
-
|
|
4937
|
+
get filteredTemplates() {
|
|
4938
|
+
const query = this.searchQuery.toLowerCase().trim();
|
|
4939
|
+
if (!query)
|
|
4940
|
+
return this.originalTemplates;
|
|
4941
|
+
// Filter templates based on search query
|
|
4942
|
+
return this.originalTemplates.filter((template) => template.title.toLowerCase().includes(query) || template.description?.toLowerCase().includes(query));
|
|
4609
4943
|
}
|
|
4610
4944
|
/**
|
|
4611
|
-
*
|
|
4612
|
-
* @param element The element to re-attach focus trap to
|
|
4613
|
-
* @param autoFocus Whether to automatically focus the first focusable element
|
|
4945
|
+
* Insert a template into the editor, replacing entire content
|
|
4614
4946
|
*/
|
|
4615
|
-
|
|
4616
|
-
this.
|
|
4617
|
-
|
|
4947
|
+
insertTemplate(template) {
|
|
4948
|
+
if (!this.editor)
|
|
4949
|
+
return;
|
|
4950
|
+
// Replace entire editor content with template data
|
|
4951
|
+
this.editor.chain().focus().setContent(template.data).run();
|
|
4952
|
+
this.close?.();
|
|
4618
4953
|
}
|
|
4619
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type:
|
|
4620
|
-
static
|
|
4621
|
-
|
|
4622
|
-
|
|
4623
|
-
|
|
4624
|
-
args: [{ providedIn: 'root' }]
|
|
4625
|
-
}] });
|
|
4954
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: TemplateViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4955
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.10", type: TemplateViewComponent, isStandalone: true, selector: "document-engine-template-view", ngImport: i0, template: `
|
|
4956
|
+
<div class="template-view">
|
|
4957
|
+
<!-- Title -->
|
|
4958
|
+
<div class="template-view__header">Templates</div>
|
|
4626
4959
|
|
|
4627
|
-
|
|
4628
|
-
|
|
4629
|
-
|
|
4630
|
-
|
|
4631
|
-
|
|
4632
|
-
|
|
4633
|
-
|
|
4634
|
-
|
|
4635
|
-
|
|
4636
|
-
|
|
4637
|
-
|
|
4638
|
-
|
|
4639
|
-
|
|
4640
|
-
|
|
4641
|
-
|
|
4642
|
-
|
|
4643
|
-
// Avoid showing when extension is not active
|
|
4644
|
-
const active = Array.isArray(target.name)
|
|
4645
|
-
? target.name.some((name) => editor.isActive(name))
|
|
4646
|
-
: editor.isActive(target.name);
|
|
4647
|
-
if (!active)
|
|
4648
|
-
return false;
|
|
4649
|
-
// Avoid showing when extra predicate is not met
|
|
4650
|
-
if (target.extraPredicate && !target.extraPredicate(editor))
|
|
4651
|
-
return false;
|
|
4652
|
-
return true;
|
|
4653
|
-
};
|
|
4654
|
-
}
|
|
4655
|
-
function selectionChangeHandler(params) {
|
|
4656
|
-
const { editor, target, onActive, onInactive, listenOnUpdate } = params;
|
|
4657
|
-
const run = () => {
|
|
4658
|
-
const isActive = Array.isArray(target.name)
|
|
4659
|
-
? target.name.some((name) => editor.isActive(name))
|
|
4660
|
-
: editor.isActive(target.name);
|
|
4661
|
-
if (!isActive)
|
|
4662
|
-
return onInactive();
|
|
4663
|
-
const activeName = Array.isArray(target.name) ? target.name.find((name) => editor.isActive(name)) : target.name;
|
|
4664
|
-
const attrs = target.kind === 'mark'
|
|
4665
|
-
? (editor.getAttributes(activeName) ?? {})
|
|
4666
|
-
: (editor.state.selection.node?.attrs ?? {});
|
|
4667
|
-
onActive(attrs);
|
|
4668
|
-
};
|
|
4669
|
-
const onSelection = () => run();
|
|
4670
|
-
const onUpdate = () => run();
|
|
4671
|
-
editor.on('selectionUpdate', onSelection);
|
|
4672
|
-
if (listenOnUpdate)
|
|
4673
|
-
editor.on('update', onUpdate);
|
|
4674
|
-
run(); // initial sync
|
|
4675
|
-
return () => {
|
|
4676
|
-
editor.off('selectionUpdate', onSelection);
|
|
4677
|
-
if (listenOnUpdate)
|
|
4678
|
-
editor.off('update', onUpdate);
|
|
4679
|
-
};
|
|
4680
|
-
}
|
|
4960
|
+
<!-- Search Bar -->
|
|
4961
|
+
<div class="template-view__search">
|
|
4962
|
+
<div class="template-view__search-field">
|
|
4963
|
+
<label documentEngineLabel for="search-input">Search templates</label>
|
|
4964
|
+
<div class="template-view__search-input-wrapper">
|
|
4965
|
+
<document-engine-icon name="search" class="template-view__search-icon"></document-engine-icon>
|
|
4966
|
+
<input
|
|
4967
|
+
documentEngineInput
|
|
4968
|
+
id="search-input"
|
|
4969
|
+
[formControl]="searchControl"
|
|
4970
|
+
placeholder="Search templates..."
|
|
4971
|
+
aria-label="Search templates"
|
|
4972
|
+
/>
|
|
4973
|
+
</div>
|
|
4974
|
+
</div>
|
|
4975
|
+
</div>
|
|
4681
4976
|
|
|
4682
|
-
|
|
4683
|
-
|
|
4684
|
-
|
|
4685
|
-
|
|
4686
|
-
|
|
4687
|
-
|
|
4688
|
-
|
|
4689
|
-
|
|
4690
|
-
|
|
4691
|
-
|
|
4692
|
-
|
|
4693
|
-
|
|
4694
|
-
|
|
4695
|
-
|
|
4696
|
-
|
|
4697
|
-
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
|
|
4701
|
-
|
|
4702
|
-
|
|
4703
|
-
|
|
4704
|
-
|
|
4705
|
-
|
|
4706
|
-
|
|
4707
|
-
|
|
4708
|
-
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
window.requestAnimationFrame(() => {
|
|
4715
|
-
if (this.elRef.nativeElement.parentNode) {
|
|
4716
|
-
this.elRef.nativeElement.parentNode.removeChild(this.elRef.nativeElement);
|
|
4717
|
-
}
|
|
4718
|
-
});
|
|
4719
|
-
this.elRef.nativeElement.removeEventListener('mousedown', this.preventDefault);
|
|
4720
|
-
}
|
|
4721
|
-
preventDefault(event) {
|
|
4722
|
-
// Allow default behavior for focusable elements so inputs can receive focus
|
|
4723
|
-
const target = event.target;
|
|
4724
|
-
if (target) {
|
|
4725
|
-
const tag = target.tagName;
|
|
4726
|
-
const isFocusableTag = tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT' || target.isContentEditable;
|
|
4727
|
-
if (isFocusableTag)
|
|
4728
|
-
return;
|
|
4729
|
-
}
|
|
4730
|
-
event.preventDefault();
|
|
4731
|
-
}
|
|
4732
|
-
getFloatingUiOptions(userOptions, element) {
|
|
4733
|
-
// Find arrow element if it exists
|
|
4734
|
-
// const arrowElement = element.querySelector('[data-arrow]') as HTMLElement | null;
|
|
4735
|
-
// Merge with default Floating UI options
|
|
4736
|
-
const defaultOptions = {
|
|
4737
|
-
placement: 'bottom',
|
|
4738
|
-
flip: {
|
|
4739
|
-
fallbackPlacements: ['top', 'bottom', 'bottom-start', 'top-end', 'bottom-end'],
|
|
4740
|
-
padding: 8,
|
|
4741
|
-
},
|
|
4742
|
-
// Enable shift middleware to keep bubble within viewport
|
|
4743
|
-
shift: { padding: 8 },
|
|
4744
|
-
// Add offset to create space between selection and bubble
|
|
4745
|
-
offset: 10,
|
|
4746
|
-
// Configure arrow if element exists
|
|
4747
|
-
// arrow: arrowElement ? { element: arrowElement } : false,
|
|
4748
|
-
};
|
|
4749
|
-
return { ...defaultOptions, ...userOptions };
|
|
4750
|
-
}
|
|
4751
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: TiptapBubbleMenuDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
4752
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.3.10", type: TiptapBubbleMenuDirective, isStandalone: true, selector: "tiptap-bubble-menu[editor], [tiptapBubbleMenu][editor]", inputs: { props: "props", editor: "editor", mountTo: "mountTo", shouldShow: "shouldShow" }, ngImport: i0 });
|
|
4977
|
+
<!-- Templates List -->
|
|
4978
|
+
<div class="template-view__list-container">
|
|
4979
|
+
<div *ngIf="filteredTemplates.length > 0; else noResults" class="template-view__list">
|
|
4980
|
+
<button
|
|
4981
|
+
*ngFor="let template of filteredTemplates"
|
|
4982
|
+
type="button"
|
|
4983
|
+
class="template-view__template-item"
|
|
4984
|
+
(click)="insertTemplate(template)"
|
|
4985
|
+
[attr.aria-label]="template.title + (template.description ? ' - ' + template.description : '')"
|
|
4986
|
+
[title]="template.description || template.title"
|
|
4987
|
+
>
|
|
4988
|
+
<div class="template-view__template-title">{{ template.title }}</div>
|
|
4989
|
+
<div *ngIf="template.description" class="template-view__template-description">
|
|
4990
|
+
{{ template.description }}
|
|
4991
|
+
</div>
|
|
4992
|
+
</button>
|
|
4993
|
+
</div>
|
|
4994
|
+
|
|
4995
|
+
<ng-template #noResults>
|
|
4996
|
+
<div class="template-view__empty">
|
|
4997
|
+
<document-engine-icon name="search_off" class="template-view__empty-icon"></document-engine-icon>
|
|
4998
|
+
<div class="template-view__empty-text">No templates found matching "{{ searchQuery }}"</div>
|
|
4999
|
+
</div>
|
|
5000
|
+
</ng-template>
|
|
5001
|
+
</div>
|
|
5002
|
+
|
|
5003
|
+
<!-- Actions -->
|
|
5004
|
+
<div class="template-view__actions">
|
|
5005
|
+
<button type="button" documentEngineButton variant="secondary" (click)="close?.()">Close</button>
|
|
5006
|
+
</div>
|
|
5007
|
+
</div>
|
|
5008
|
+
`, isInline: true, styles: [".template-view{display:flex;flex-direction:column;width:100%;max-width:28rem}.template-view__header{padding:.5rem 1rem;border-bottom:1px solid var(--border);font-weight:500}.template-view__search{padding:.75rem;border-bottom:1px solid var(--border)}.template-view__search-field{display:flex;flex-direction:column;gap:.25rem;width:100%}.template-view__search-input-wrapper{position:relative;display:flex;align-items:center}.template-view__search-icon{position:absolute;left:.75rem;color:var(--muted-foreground);pointer-events:none;z-index:1}.template-view__search-input-wrapper input{padding-left:2.5rem}.template-view__list-container{overflow-y:auto;max-height:20rem}.template-view__list{padding:.5rem 0}.template-view__template-item{display:flex;flex-direction:column;align-items:flex-start;padding:.75rem;width:100%;text-align:left;background-color:transparent;border:none;cursor:pointer;transition:background-color .2s;outline:none}.template-view__template-item:hover{background-color:var(--accent);color:var(--accent-foreground)}.template-view__template-item:focus{outline:none;box-shadow:0 0 0 2px var(--ring);box-shadow-offset:2px}.template-view__template-title{font-weight:500;font-size:.875rem;color:var(--foreground);margin-bottom:.25rem}.template-view__template-description{font-size:.75rem;color:var(--muted-foreground);line-height:1.4}.template-view__empty{padding:2rem 1rem;text-align:center;color:var(--muted-foreground)}.template-view__empty-icon{margin-bottom:.5rem;font-size:2rem;opacity:.5}.template-view__empty-text{font-size:.875rem}.template-view__actions{display:flex;justify-content:flex-end;padding:.75rem;border-top:1px solid var(--border)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: ButtonDirective, selector: "button[documentEngineButton]", inputs: ["variant", "size"] }, { kind: "directive", type: InputDirective, selector: "input[documentEngineInput]", inputs: ["class"] }, { kind: "directive", type: LabelDirective, selector: "label[documentEngineLabel]", inputs: ["class"] }, { kind: "component", type: IconComponent, selector: "document-engine-icon", inputs: ["name"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4753
5009
|
}
|
|
4754
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type:
|
|
4755
|
-
type:
|
|
4756
|
-
args: [{
|
|
4757
|
-
|
|
4758
|
-
|
|
4759
|
-
|
|
4760
|
-
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
|
|
4765
|
-
|
|
4766
|
-
|
|
4767
|
-
|
|
4768
|
-
|
|
5010
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: TemplateViewComponent, decorators: [{
|
|
5011
|
+
type: Component,
|
|
5012
|
+
args: [{ selector: 'document-engine-template-view', standalone: true, imports: [CommonModule, ReactiveFormsModule, ButtonDirective, InputDirective, LabelDirective, IconComponent], template: `
|
|
5013
|
+
<div class="template-view">
|
|
5014
|
+
<!-- Title -->
|
|
5015
|
+
<div class="template-view__header">Templates</div>
|
|
5016
|
+
|
|
5017
|
+
<!-- Search Bar -->
|
|
5018
|
+
<div class="template-view__search">
|
|
5019
|
+
<div class="template-view__search-field">
|
|
5020
|
+
<label documentEngineLabel for="search-input">Search templates</label>
|
|
5021
|
+
<div class="template-view__search-input-wrapper">
|
|
5022
|
+
<document-engine-icon name="search" class="template-view__search-icon"></document-engine-icon>
|
|
5023
|
+
<input
|
|
5024
|
+
documentEngineInput
|
|
5025
|
+
id="search-input"
|
|
5026
|
+
[formControl]="searchControl"
|
|
5027
|
+
placeholder="Search templates..."
|
|
5028
|
+
aria-label="Search templates"
|
|
5029
|
+
/>
|
|
5030
|
+
</div>
|
|
5031
|
+
</div>
|
|
5032
|
+
</div>
|
|
5033
|
+
|
|
5034
|
+
<!-- Templates List -->
|
|
5035
|
+
<div class="template-view__list-container">
|
|
5036
|
+
<div *ngIf="filteredTemplates.length > 0; else noResults" class="template-view__list">
|
|
5037
|
+
<button
|
|
5038
|
+
*ngFor="let template of filteredTemplates"
|
|
5039
|
+
type="button"
|
|
5040
|
+
class="template-view__template-item"
|
|
5041
|
+
(click)="insertTemplate(template)"
|
|
5042
|
+
[attr.aria-label]="template.title + (template.description ? ' - ' + template.description : '')"
|
|
5043
|
+
[title]="template.description || template.title"
|
|
5044
|
+
>
|
|
5045
|
+
<div class="template-view__template-title">{{ template.title }}</div>
|
|
5046
|
+
<div *ngIf="template.description" class="template-view__template-description">
|
|
5047
|
+
{{ template.description }}
|
|
5048
|
+
</div>
|
|
5049
|
+
</button>
|
|
5050
|
+
</div>
|
|
5051
|
+
|
|
5052
|
+
<ng-template #noResults>
|
|
5053
|
+
<div class="template-view__empty">
|
|
5054
|
+
<document-engine-icon name="search_off" class="template-view__empty-icon"></document-engine-icon>
|
|
5055
|
+
<div class="template-view__empty-text">No templates found matching "{{ searchQuery }}"</div>
|
|
5056
|
+
</div>
|
|
5057
|
+
</ng-template>
|
|
5058
|
+
</div>
|
|
5059
|
+
|
|
5060
|
+
<!-- Actions -->
|
|
5061
|
+
<div class="template-view__actions">
|
|
5062
|
+
<button type="button" documentEngineButton variant="secondary" (click)="close?.()">Close</button>
|
|
5063
|
+
</div>
|
|
5064
|
+
</div>
|
|
5065
|
+
`, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".template-view{display:flex;flex-direction:column;width:100%;max-width:28rem}.template-view__header{padding:.5rem 1rem;border-bottom:1px solid var(--border);font-weight:500}.template-view__search{padding:.75rem;border-bottom:1px solid var(--border)}.template-view__search-field{display:flex;flex-direction:column;gap:.25rem;width:100%}.template-view__search-input-wrapper{position:relative;display:flex;align-items:center}.template-view__search-icon{position:absolute;left:.75rem;color:var(--muted-foreground);pointer-events:none;z-index:1}.template-view__search-input-wrapper input{padding-left:2.5rem}.template-view__list-container{overflow-y:auto;max-height:20rem}.template-view__list{padding:.5rem 0}.template-view__template-item{display:flex;flex-direction:column;align-items:flex-start;padding:.75rem;width:100%;text-align:left;background-color:transparent;border:none;cursor:pointer;transition:background-color .2s;outline:none}.template-view__template-item:hover{background-color:var(--accent);color:var(--accent-foreground)}.template-view__template-item:focus{outline:none;box-shadow:0 0 0 2px var(--ring);box-shadow-offset:2px}.template-view__template-title{font-weight:500;font-size:.875rem;color:var(--foreground);margin-bottom:.25rem}.template-view__template-description{font-size:.75rem;color:var(--muted-foreground);line-height:1.4}.template-view__empty{padding:2rem 1rem;text-align:center;color:var(--muted-foreground)}.template-view__empty-icon{margin-bottom:.5rem;font-size:2rem;opacity:.5}.template-view__empty-text{font-size:.875rem}.template-view__actions{display:flex;justify-content:flex-end;padding:.75rem;border-top:1px solid var(--border)}\n"] }]
|
|
5066
|
+
}] });
|
|
5067
|
+
|
|
5068
|
+
const TemplateBubbleConfig = {
|
|
5069
|
+
pluginKey: 'TemplateBubbleMenu',
|
|
5070
|
+
maxWidth: '400px',
|
|
5071
|
+
width: '400px',
|
|
5072
|
+
views: [
|
|
5073
|
+
{
|
|
5074
|
+
id: 'templates',
|
|
5075
|
+
component: TemplateViewComponent,
|
|
5076
|
+
label: 'Templates',
|
|
5077
|
+
isDefault: true,
|
|
5078
|
+
},
|
|
5079
|
+
],
|
|
5080
|
+
};
|
|
5081
|
+
|
|
5082
|
+
const FONT_SIZE_OPTIONS = [
|
|
5083
|
+
{ value: '12px', label: '12px' },
|
|
5084
|
+
{ value: '14px', label: '14px' },
|
|
5085
|
+
{ value: '16px', label: '16px' },
|
|
5086
|
+
{ value: '18px', label: '18px' },
|
|
5087
|
+
{ value: '20px', label: '20px' },
|
|
5088
|
+
];
|
|
5089
|
+
const LINE_HEIGHT_OPTIONS = [
|
|
5090
|
+
{ value: null, label: 'Default' },
|
|
5091
|
+
{ value: '1', label: '1' },
|
|
5092
|
+
{ value: '1.5', label: '1.5' },
|
|
5093
|
+
{ value: '2', label: '2' },
|
|
5094
|
+
{ value: '2.5', label: '2.5' },
|
|
5095
|
+
{ value: '3', label: '3' },
|
|
5096
|
+
];
|
|
5097
|
+
const TEXT_CASE_OPTIONS = [
|
|
5098
|
+
{ value: 'uppercase', label: 'UPPERCASE' },
|
|
5099
|
+
{ value: 'lowercase', label: 'lowercase' },
|
|
5100
|
+
{ value: 'capitalize', label: 'Capitalize' },
|
|
5101
|
+
];
|
|
5102
|
+
const HEADING_OPTIONS = [
|
|
5103
|
+
{ value: null, label: 'Normal text', class: '' },
|
|
5104
|
+
{ value: 1, label: 'Heading 1', class: 'h1' },
|
|
5105
|
+
{ value: 2, label: 'Heading 2', class: 'h2' },
|
|
5106
|
+
{ value: 3, label: 'Heading 3', class: 'h3' },
|
|
5107
|
+
];
|
|
5108
|
+
const TEXT_ALIGN_OPTIONS = [
|
|
5109
|
+
{ value: 'left', label: 'Align Left', icon: 'format_align_left' },
|
|
5110
|
+
{ value: 'center', label: 'Align Center', icon: 'format_align_center' },
|
|
5111
|
+
{ value: 'right', label: 'Align Right', icon: 'format_align_right' },
|
|
5112
|
+
{ value: 'justify', label: 'Align Justify', icon: 'format_align_justify' },
|
|
5113
|
+
];
|
|
5114
|
+
|
|
5115
|
+
const LIST_STYLE_OPTIONS = [
|
|
5116
|
+
{ value: 'decimal', label: 'Decimal', example: '1.' },
|
|
5117
|
+
{ value: 'lower-alpha-dot', label: 'Lower Alpha', example: 'a.' },
|
|
5118
|
+
{ value: 'lower-alpha-parens', label: 'Lower Alpha (Parentheses)', example: '(a)' },
|
|
5119
|
+
{ value: 'lower-roman-parens', label: 'Lower Roman', example: '(i)' },
|
|
5120
|
+
];
|
|
4769
5121
|
|
|
4770
5122
|
class EditorBubbleMenuComponent {
|
|
4771
5123
|
editor;
|
|
@@ -5086,121 +5438,55 @@ class EditorBubbleMenuComponent {
|
|
|
5086
5438
|
}
|
|
5087
5439
|
return s;
|
|
5088
5440
|
}
|
|
5089
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: EditorBubbleMenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
5090
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.10", type: EditorBubbleMenuComponent, isStandalone: true, selector: "document-engine-editor-bubble-menu", inputs: { editor: "editor", config: "config" }, viewQueries: [{ propertyName: "viewContainer", first: true, predicate: ["viewContainer"], descendants: true, read: ViewContainerRef }, { propertyName: "bubbleElement", first: true, predicate: ["bubbleElement"], descendants: true }], ngImport: i0, template: `
|
|
5091
|
-
<div
|
|
5092
|
-
#bubbleElement
|
|
5093
|
-
class="bubble-menu-wrapper rounded border bg-card text-card-foreground border-border shadow-elevation-2 z-
|
|
5094
|
-
tiptapBubbleMenu
|
|
5095
|
-
[editor]="editor"
|
|
5096
|
-
[props]="config.props!"
|
|
5097
|
-
[mountTo]="mountTo"
|
|
5098
|
-
[shouldShow]="shouldShowFn"
|
|
5099
|
-
>
|
|
5100
|
-
<!-- View component will be dynamically inserted here -->
|
|
5101
|
-
<ng-container #viewContainer></ng-container>
|
|
5102
|
-
</div>
|
|
5103
|
-
`, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: TiptapBubbleMenuDirective, selector: "tiptap-bubble-menu[editor], [tiptapBubbleMenu][editor]", inputs: ["props", "editor", "mountTo", "shouldShow"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
5104
|
-
}
|
|
5105
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: EditorBubbleMenuComponent, decorators: [{
|
|
5106
|
-
type: Component,
|
|
5107
|
-
args: [{
|
|
5108
|
-
selector: 'document-engine-editor-bubble-menu',
|
|
5109
|
-
standalone: true,
|
|
5110
|
-
imports: [CommonModule, TiptapBubbleMenuDirective],
|
|
5111
|
-
template: `
|
|
5112
|
-
<div
|
|
5113
|
-
#bubbleElement
|
|
5114
|
-
class="bubble-menu-wrapper rounded border bg-card text-card-foreground border-border shadow-elevation-2 z-10"
|
|
5115
|
-
tiptapBubbleMenu
|
|
5116
|
-
[editor]="editor"
|
|
5117
|
-
[props]="config.props!"
|
|
5118
|
-
[mountTo]="mountTo"
|
|
5119
|
-
[shouldShow]="shouldShowFn"
|
|
5120
|
-
>
|
|
5121
|
-
<!-- View component will be dynamically inserted here -->
|
|
5122
|
-
<ng-container #viewContainer></ng-container>
|
|
5123
|
-
</div>
|
|
5124
|
-
`,
|
|
5125
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
5126
|
-
}]
|
|
5127
|
-
}], propDecorators: { editor: [{
|
|
5128
|
-
type: Input
|
|
5129
|
-
}], config: [{
|
|
5130
|
-
type: Input
|
|
5131
|
-
}], viewContainer: [{
|
|
5132
|
-
type: ViewChild,
|
|
5133
|
-
args: ['viewContainer', { read: ViewContainerRef }]
|
|
5134
|
-
}], bubbleElement: [{
|
|
5135
|
-
type: ViewChild,
|
|
5136
|
-
args: ['bubbleElement']
|
|
5137
|
-
}] } });
|
|
5138
|
-
|
|
5139
|
-
class EventManager {
|
|
5140
|
-
document = inject(DOCUMENT);
|
|
5141
|
-
keyToEscapeListener = new Map();
|
|
5142
|
-
keyToOutsideListener = new Map();
|
|
5143
|
-
isAttached(key) {
|
|
5144
|
-
return this.keyToOutsideListener.has(key) || this.keyToEscapeListener.has(key);
|
|
5145
|
-
}
|
|
5146
|
-
attach(key, hostElement, cb, anchorElement) {
|
|
5147
|
-
// Avoid duplicate listeners
|
|
5148
|
-
this.detach(key);
|
|
5149
|
-
const oldOutside = this.keyToOutsideListener.get(key);
|
|
5150
|
-
if (!oldOutside) {
|
|
5151
|
-
const handler = (event) => {
|
|
5152
|
-
const target = event.target;
|
|
5153
|
-
const path = event.composedPath?.();
|
|
5154
|
-
// Check if click is inside bubble
|
|
5155
|
-
const isInsideBubble = !!hostElement && ((!!target && hostElement.contains(target)) || (!!path && path.includes(hostElement)));
|
|
5156
|
-
if (isInsideBubble)
|
|
5157
|
-
return;
|
|
5158
|
-
// Check if click is on the anchor (toolbar button)
|
|
5159
|
-
const isInsideAnchor = !!anchorElement && ((!!target && anchorElement.contains(target)) || (!!path && path.includes(anchorElement)));
|
|
5160
|
-
if (isInsideAnchor)
|
|
5161
|
-
return;
|
|
5162
|
-
// Check if click is inside a CDK overlay (e.g., MatSelect dropdown, MatMenu, etc.)
|
|
5163
|
-
// These are rendered outside the bubble but should not close it
|
|
5164
|
-
if (target instanceof Element) {
|
|
5165
|
-
const isInsideOverlay = target.closest('.cdk-overlay-container, .cdk-overlay-pane');
|
|
5166
|
-
if (isInsideOverlay)
|
|
5167
|
-
return;
|
|
5168
|
-
}
|
|
5169
|
-
cb(key);
|
|
5170
|
-
};
|
|
5171
|
-
this.keyToOutsideListener.set(key, handler);
|
|
5172
|
-
this.document.addEventListener('pointerdown', handler, true);
|
|
5173
|
-
this.document.addEventListener('mousedown', handler, true);
|
|
5174
|
-
this.document.addEventListener('click', handler, true);
|
|
5175
|
-
}
|
|
5176
|
-
const oldEsc = this.keyToEscapeListener.get(key);
|
|
5177
|
-
if (!oldEsc) {
|
|
5178
|
-
const escHandler = (e) => e.key === 'Escape' && cb(key);
|
|
5179
|
-
this.document.addEventListener('keydown', escHandler, true);
|
|
5180
|
-
this.keyToEscapeListener.set(key, escHandler);
|
|
5181
|
-
}
|
|
5182
|
-
}
|
|
5183
|
-
detach(key) {
|
|
5184
|
-
const outside = this.keyToOutsideListener.get(key);
|
|
5185
|
-
if (outside) {
|
|
5186
|
-
this.document.removeEventListener('pointerdown', outside, true);
|
|
5187
|
-
this.document.removeEventListener('mousedown', outside, true);
|
|
5188
|
-
this.document.removeEventListener('click', outside, true);
|
|
5189
|
-
this.keyToOutsideListener.delete(key);
|
|
5190
|
-
}
|
|
5191
|
-
const esc = this.keyToEscapeListener.get(key);
|
|
5192
|
-
if (esc) {
|
|
5193
|
-
this.document.removeEventListener('keydown', esc, true);
|
|
5194
|
-
this.keyToEscapeListener.delete(key);
|
|
5195
|
-
}
|
|
5196
|
-
}
|
|
5197
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: EventManager, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
5198
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: EventManager, providedIn: 'root' });
|
|
5441
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: EditorBubbleMenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
5442
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.10", type: EditorBubbleMenuComponent, isStandalone: true, selector: "document-engine-editor-bubble-menu", inputs: { editor: "editor", config: "config" }, viewQueries: [{ propertyName: "viewContainer", first: true, predicate: ["viewContainer"], descendants: true, read: ViewContainerRef }, { propertyName: "bubbleElement", first: true, predicate: ["bubbleElement"], descendants: true }], ngImport: i0, template: `
|
|
5443
|
+
<div
|
|
5444
|
+
#bubbleElement
|
|
5445
|
+
class="bubble-menu-wrapper rounded border bg-card text-card-foreground border-border shadow-elevation-2 z-30"
|
|
5446
|
+
tiptapBubbleMenu
|
|
5447
|
+
[editor]="editor"
|
|
5448
|
+
[props]="config.props!"
|
|
5449
|
+
[mountTo]="mountTo"
|
|
5450
|
+
[shouldShow]="shouldShowFn"
|
|
5451
|
+
>
|
|
5452
|
+
<!-- View component will be dynamically inserted here -->
|
|
5453
|
+
<ng-container #viewContainer></ng-container>
|
|
5454
|
+
</div>
|
|
5455
|
+
`, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: TiptapBubbleMenuDirective, selector: "tiptap-bubble-menu[editor], [tiptapBubbleMenu][editor]", inputs: ["props", "editor", "mountTo", "shouldShow"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
5199
5456
|
}
|
|
5200
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type:
|
|
5201
|
-
type:
|
|
5202
|
-
args: [{
|
|
5203
|
-
|
|
5457
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: EditorBubbleMenuComponent, decorators: [{
|
|
5458
|
+
type: Component,
|
|
5459
|
+
args: [{
|
|
5460
|
+
selector: 'document-engine-editor-bubble-menu',
|
|
5461
|
+
standalone: true,
|
|
5462
|
+
imports: [CommonModule, TiptapBubbleMenuDirective],
|
|
5463
|
+
template: `
|
|
5464
|
+
<div
|
|
5465
|
+
#bubbleElement
|
|
5466
|
+
class="bubble-menu-wrapper rounded border bg-card text-card-foreground border-border shadow-elevation-2 z-30"
|
|
5467
|
+
tiptapBubbleMenu
|
|
5468
|
+
[editor]="editor"
|
|
5469
|
+
[props]="config.props!"
|
|
5470
|
+
[mountTo]="mountTo"
|
|
5471
|
+
[shouldShow]="shouldShowFn"
|
|
5472
|
+
>
|
|
5473
|
+
<!-- View component will be dynamically inserted here -->
|
|
5474
|
+
<ng-container #viewContainer></ng-container>
|
|
5475
|
+
</div>
|
|
5476
|
+
`,
|
|
5477
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
5478
|
+
}]
|
|
5479
|
+
}], propDecorators: { editor: [{
|
|
5480
|
+
type: Input
|
|
5481
|
+
}], config: [{
|
|
5482
|
+
type: Input
|
|
5483
|
+
}], viewContainer: [{
|
|
5484
|
+
type: ViewChild,
|
|
5485
|
+
args: ['viewContainer', { read: ViewContainerRef }]
|
|
5486
|
+
}], bubbleElement: [{
|
|
5487
|
+
type: ViewChild,
|
|
5488
|
+
args: ['bubbleElement']
|
|
5489
|
+
}] } });
|
|
5204
5490
|
|
|
5205
5491
|
/**
|
|
5206
5492
|
* A bubble menu wrapper specifically for toolbar buttons
|
|
@@ -5629,690 +5915,429 @@ class ToolbarComponent {
|
|
|
5629
5915
|
else {
|
|
5630
5916
|
this.editor.chain().focus().unsetFontSize().run();
|
|
5631
5917
|
}
|
|
5632
|
-
this.toolbarService.emit('setFontSize', size);
|
|
5633
|
-
}
|
|
5634
|
-
setLineHeight(lineHeight) {
|
|
5635
|
-
if (lineHeight) {
|
|
5636
|
-
this.editor.chain().focus().setLineHeight(lineHeight).run();
|
|
5637
|
-
}
|
|
5638
|
-
else {
|
|
5639
|
-
this.editor.chain().focus().unsetLineHeight().run();
|
|
5640
|
-
}
|
|
5641
|
-
this.toolbarService.emit('setLineHeight', lineHeight);
|
|
5642
|
-
}
|
|
5643
|
-
setHeading(level) {
|
|
5644
|
-
if (!level) {
|
|
5645
|
-
this.editor.chain().focus().removeHeading().run();
|
|
5646
|
-
}
|
|
5647
|
-
else {
|
|
5648
|
-
const levelNumber = parseInt(level, 10);
|
|
5649
|
-
this.editor.chain().focus().toggleHeading({ level: levelNumber }).run();
|
|
5650
|
-
}
|
|
5651
|
-
this.toolbarService.emit('setHeading', level);
|
|
5652
|
-
}
|
|
5653
|
-
setTextAlign(align) {
|
|
5654
|
-
if (align) {
|
|
5655
|
-
this.editor
|
|
5656
|
-
.chain()
|
|
5657
|
-
.focus()
|
|
5658
|
-
.setTextAlign(align)
|
|
5659
|
-
.run();
|
|
5660
|
-
}
|
|
5661
|
-
else {
|
|
5662
|
-
// Reset to default (left)
|
|
5663
|
-
this.editor.chain().focus().setTextAlign('left').run();
|
|
5664
|
-
}
|
|
5665
|
-
this.toolbarService.emit('setTextAlign', align);
|
|
5666
|
-
}
|
|
5667
|
-
setListStyle(listStyleType) {
|
|
5668
|
-
if (this.editor.isActive('customOrderedList')) {
|
|
5669
|
-
// If there's an active ordered list, update its style
|
|
5670
|
-
this.editor.chain().focus().setListStyle(listStyleType).run();
|
|
5671
|
-
}
|
|
5672
|
-
else {
|
|
5673
|
-
// If no active list, create a new one with the selected style
|
|
5674
|
-
this.editor.chain().focus().toggleOrderedList().run();
|
|
5675
|
-
this.editor.chain().focus().setListStyle(listStyleType).run();
|
|
5676
|
-
}
|
|
5677
|
-
this.toolbarService.emit('setListStyle', listStyleType);
|
|
5678
|
-
}
|
|
5679
|
-
openColorBubble(event, type) {
|
|
5680
|
-
const targetElement = (event.currentTarget || event.target);
|
|
5681
|
-
this.colorBubbleMenu?.toggleBubble(targetElement, { type });
|
|
5682
|
-
this.toolbarService.emit('openColorBubble', { type });
|
|
5683
|
-
}
|
|
5684
|
-
toggleLink() {
|
|
5685
|
-
if (this.editor.isActive('link')) {
|
|
5686
|
-
// If link is active, remove it
|
|
5687
|
-
this.editor.chain().focus().unsetLink().run();
|
|
5688
|
-
}
|
|
5689
|
-
else {
|
|
5690
|
-
// If no link, open the bubble menu
|
|
5691
|
-
this.linkBubbleWrapper?.showBubble();
|
|
5692
|
-
}
|
|
5693
|
-
this.toolbarService.emit('toggleLink');
|
|
5694
|
-
}
|
|
5695
|
-
insertImage(event) {
|
|
5696
|
-
const targetElement = (event.currentTarget || event.target);
|
|
5697
|
-
this.imageBubbleMenu?.toggleBubble(targetElement);
|
|
5698
|
-
this.toolbarService.emit('insertImage');
|
|
5699
|
-
}
|
|
5700
|
-
openSpecialCharsBubble(event) {
|
|
5701
|
-
const targetElement = (event.currentTarget || event.target);
|
|
5702
|
-
this.specialCharsBubbleMenu?.toggleBubble(targetElement);
|
|
5703
|
-
this.toolbarService.emit('openSpecialCharsBubble');
|
|
5704
|
-
}
|
|
5705
|
-
openDynamicFieldsBubble(event) {
|
|
5706
|
-
const targetElement = (event.currentTarget || event.target);
|
|
5707
|
-
this.dynamicFieldsBubbleMenu?.toggleBubble(targetElement, {
|
|
5708
|
-
categories: this.config?.dynamicFieldsCategories || [],
|
|
5709
|
-
});
|
|
5710
|
-
this.toolbarService.emit('openDynamicFieldsBubble');
|
|
5711
|
-
}
|
|
5712
|
-
openTemplateBubble(event) {
|
|
5713
|
-
const targetElement = (event.currentTarget || event.target);
|
|
5714
|
-
this.templateBubbleMenu?.toggleBubble(targetElement, {
|
|
5715
|
-
templates: this.config?.templates || [],
|
|
5716
|
-
});
|
|
5717
|
-
this.toolbarService.emit('openTemplateBubble');
|
|
5718
|
-
}
|
|
5719
|
-
openCreateTableBubble(event) {
|
|
5720
|
-
const targetElement = (event.currentTarget || event.target);
|
|
5721
|
-
this.tableCreateBubbleMenu?.toggleBubble(targetElement);
|
|
5722
|
-
this.toolbarService.emit('openCreateTableBubble');
|
|
5723
|
-
}
|
|
5724
|
-
// downloadAsMarkdown(filename?: string): void {
|
|
5725
|
-
// const fileName = filename ? `${filename}.md` : 'export.md';
|
|
5726
|
-
// const markdown = renderToMarkdown({
|
|
5727
|
-
// content: this.editor.getJSON(),
|
|
5728
|
-
// extensions: this.editor.options.extensions,
|
|
5729
|
-
// options: {
|
|
5730
|
-
// nodeMapping: {
|
|
5731
|
-
// pageBreak: () => 'Page break',
|
|
5732
|
-
// },
|
|
5733
|
-
// },
|
|
5734
|
-
// });
|
|
5735
|
-
// const blob = new Blob([markdown], { type: 'text/markdown' });
|
|
5736
|
-
// const url = URL.createObjectURL(blob);
|
|
5737
|
-
// const a = document.createElement('a');
|
|
5738
|
-
// a.href = url;
|
|
5739
|
-
// a.download = fileName;
|
|
5740
|
-
// a.click();
|
|
5741
|
-
// URL.revokeObjectURL(url);
|
|
5742
|
-
// }
|
|
5743
|
-
trackByValue(index, item) {
|
|
5744
|
-
return item.value;
|
|
5745
|
-
}
|
|
5746
|
-
trackByIndex(index) {
|
|
5747
|
-
return index;
|
|
5748
|
-
}
|
|
5749
|
-
get customToolbarButtons() {
|
|
5750
|
-
return this.config?.customToolbarButtons || [];
|
|
5751
|
-
}
|
|
5752
|
-
handleCustomIconButton(button) {
|
|
5753
|
-
if (button.type === 'icon-button') {
|
|
5754
|
-
button.callback();
|
|
5755
|
-
this.toolbarService.emit('customIconButton', { label: button.label, icon: button.icon });
|
|
5756
|
-
}
|
|
5757
|
-
}
|
|
5758
|
-
handleCustomSelectButton(button, value) {
|
|
5759
|
-
if (button.type === 'select-button') {
|
|
5760
|
-
button.callback();
|
|
5761
|
-
this.toolbarService.emit('customSelectButton', { label: button.label, value });
|
|
5762
|
-
}
|
|
5763
|
-
}
|
|
5764
|
-
printValue() {
|
|
5765
|
-
console.log('html', this.editor.getHTML());
|
|
5766
|
-
console.log('json', this.editor.getJSON());
|
|
5767
|
-
console.log('State', this.editor.state);
|
|
5768
|
-
this.toolbarService.emit('printValue');
|
|
5769
|
-
}
|
|
5770
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: ToolbarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
5771
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.10", type: ToolbarComponent, isStandalone: true, selector: "document-engine-toolbar", inputs: { editor: "editor", capabilities: "capabilities", config: "config" }, outputs: { action: "action" }, viewQueries: [{ propertyName: "colorBubbleMenu", first: true, predicate: ["colorBubbleMenu"], descendants: true }, { propertyName: "linkBubbleWrapper", first: true, predicate: ["linkBubbleWrapper"], descendants: true }, { propertyName: "imageBubbleMenu", first: true, predicate: ["imageBubbleMenu"], descendants: true }, { propertyName: "specialCharsBubbleMenu", first: true, predicate: ["specialCharsBubbleMenu"], descendants: true }, { propertyName: "dynamicFieldsBubbleMenu", first: true, predicate: ["dynamicFieldsBubbleMenu"], descendants: true }, { propertyName: "templateBubbleMenu", first: true, predicate: ["templateBubbleMenu"], descendants: true }, { propertyName: "tableCreateBubbleMenu", first: true, predicate: ["tableCreateBubbleMenu"], descendants: true }], ngImport: i0, template: "<div class=\"document-engine-document-editor__toolbar\">\n <div class=\"document-engine-document-editor__toolbar-content\">\n <div class=\"document-engine-document-editor__toolbar-left\">\n <!-- History: Undo/Redo -->\n <div *ngIf=\"capabilities.hasHistory\" class=\"document-engine-document-editor__toolbar-group\" data-group=\"history\">\n <button\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canUndo\"\n (click)=\"runAndEmit('undo', editor.chain().focus().undo().run)\"\n title=\"Undo (Ctrl+Z)\"\n >\n <document-engine-icon name=\"undo\"></document-engine-icon>\n </button>\n\n <button\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canRedo\"\n (click)=\"runAndEmit('redo', editor.chain().focus().redo().run)\"\n title=\"Redo\"\n >\n <document-engine-icon name=\"redo\"></document-engine-icon>\n </button>\n </div>\n\n <!-- Text Formatting: Font Size, Line Height, Text Case, Heading -->\n <div\n *ngIf=\"capabilities.hasTextProperty\"\n class=\"document-engine-document-editor__toolbar-group\"\n data-group=\"text-property\"\n >\n <!-- Font Size -->\n <document-engine-select\n *ngIf=\"capabilities.hasFontSize\"\n [value]=\"activeFontSize\"\n placeholder=\"Font Size\"\n labelMode=\"static\"\n [disabled]=\"toolbarState.isReadOnly\"\n title=\"Font Size\"\n >\n <document-engine-icon documentEngineSelectLabel name=\"format_size\"></document-engine-icon>\n\n <button documentEngineSelectOption [value]=\"null\">Default</button>\n\n <button\n *ngFor=\"let option of fontSizeOptions; trackBy: trackByValue\"\n documentEngineSelectOption\n [value]=\"option.value\"\n (click)=\"setFontSize(option.value)\"\n >\n {{ option.label }}\n </button>\n </document-engine-select>\n\n <!-- Line Height -->\n <document-engine-select\n *ngIf=\"capabilities.hasLineHeight\"\n [value]=\"activeLineHeight\"\n placeholder=\"Line Height\"\n labelMode=\"static\"\n [disabled]=\"toolbarState.isReadOnly\"\n title=\"Line Height\"\n >\n <document-engine-icon documentEngineSelectLabel name=\"format_line_spacing\"></document-engine-icon>\n\n <button\n *ngFor=\"let option of lineHeightOptions; trackBy: trackByValue\"\n documentEngineSelectOption\n [value]=\"option.value\"\n (click)=\"setLineHeight(option.value)\"\n >\n {{ option.label }}\n </button>\n </document-engine-select>\n\n <!-- Text Case -->\n <document-engine-select\n *ngIf=\"capabilities.hasTextCase\"\n placeholder=\"Text Case\"\n labelMode=\"static\"\n [disabled]=\"toolbarState.isReadOnly\"\n title=\"Text Case\"\n >\n <document-engine-icon documentEngineSelectLabel name=\"match_case\"></document-engine-icon>\n\n <button\n *ngFor=\"let option of textCaseOptions; trackBy: trackByValue\"\n documentEngineSelectOption\n [value]=\"option.value\"\n (click)=\"runAndEmit('setTextCase', editor.chain().focus().textCase(option.value).run)\"\n >\n {{ option.label }}\n </button>\n </document-engine-select>\n\n <!-- Heading -->\n <document-engine-select\n *ngIf=\"capabilities.hasHeading\"\n [value]=\"activeHeading?.toString() || null\"\n placeholder=\"Normal text\"\n [disabled]=\"toolbarState.isReadOnly\"\n title=\"Heading\"\n >\n <button\n *ngFor=\"let option of headingOptions; trackBy: trackByValue\"\n documentEngineSelectOption\n [value]=\"option.value?.toString() || null\"\n [class]=\"option.class\"\n (click)=\"setHeading(option.value?.toString() || null)\"\n >\n {{ option.label }}\n </button>\n </document-engine-select>\n </div>\n\n <!-- Format Utilities: Reset Formatting & Colors -->\n <div\n *ngIf=\"capabilities.hasFormatUtilities\"\n class=\"document-engine-document-editor__toolbar-group\"\n data-group=\"format-utilities\"\n >\n <!-- Reset Formatting -->\n <button\n *ngIf=\"capabilities.hasResetFormat\"\n documentEngineButton\n size=\"icon\"\n (click)=\"runAndEmit('resetFormat', editor.chain().focus().resetFormat().run)\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canResetFormat\"\n title=\"Clear Formatting\"\n >\n <document-engine-icon name=\"format_clear\"></document-engine-icon>\n </button>\n\n <!-- Colors: Text/Background -->\n <button\n *ngIf=\"capabilities.hasColors\"\n documentEngineButton\n size=\"icon\"\n title=\"Text Color\"\n [disabled]=\"toolbarState.isReadOnly\"\n (click)=\"openColorBubble($event, 'text')\"\n >\n <document-engine-icon name=\"format_color_text\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasColors\"\n documentEngineButton\n size=\"icon\"\n title=\"Background Color\"\n [disabled]=\"toolbarState.isReadOnly\"\n (click)=\"openColorBubble($event, 'fill')\"\n >\n <document-engine-icon name=\"format_color_fill\"></document-engine-icon>\n </button>\n </div>\n\n <!-- Text Styles: Bold/Italic/Underline/Strike -->\n <div\n *ngIf=\"capabilities.basicInline\"\n class=\"document-engine-document-editor__toolbar-group\"\n data-group=\"basic-inline\"\n >\n <button\n *ngIf=\"capabilities.hasBold\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleBold\"\n (click)=\"runAndEmit('toggleBold', editor.chain().focus().toggleBold().run)\"\n [class.active]=\"editor.isActive('bold')\"\n title=\"Bold\"\n >\n <document-engine-icon name=\"format_bold\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasItalic\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleItalic\"\n (click)=\"runAndEmit('toggleItalic', editor.chain().focus().toggleItalic().run)\"\n [class.active]=\"editor.isActive('italic')\"\n title=\"Italic\"\n >\n <document-engine-icon name=\"format_italic\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasUnderline\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleUnderline\"\n (click)=\"runAndEmit('toggleUnderline', editor.chain().focus().toggleUnderline().run)\"\n [class.active]=\"editor.isActive('underline')\"\n title=\"Underline\"\n >\n <document-engine-icon name=\"format_underline\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasStrike\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleStrike\"\n (click)=\"runAndEmit('toggleStrike', editor.chain().focus().toggleStrike().run)\"\n [class.active]=\"editor.isActive('strike')\"\n title=\"Strikethrough\"\n >\n <document-engine-icon name=\"format_strikethrough\"></document-engine-icon>\n </button>\n </div>\n\n <!-- Text Styles: Sub/Super/Code/Link -->\n <div\n *ngIf=\"capabilities.advancedInline\"\n class=\"document-engine-document-editor__toolbar-group\"\n data-group=\"advanced-inline\"\n >\n <button\n *ngIf=\"capabilities.hasSubscript\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleSubscript\"\n (click)=\"runAndEmit('toggleSubscript', editor.chain().focus().toggleSubscript().run)\"\n [class.active]=\"editor.isActive('subscript')\"\n title=\"Subscript\"\n >\n <document-engine-icon name=\"subscript\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasSuperscript\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleSuperscript\"\n (click)=\"runAndEmit('toggleSuperscript', editor.chain().focus().toggleSuperscript().run)\"\n [class.active]=\"editor.isActive('superscript')\"\n title=\"Superscript\"\n >\n <document-engine-icon name=\"superscript\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasCode\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleCode\"\n (click)=\"runAndEmit('toggleCode', editor.chain().focus().toggleCode().run)\"\n [class.active]=\"editor.isActive('code')\"\n title=\"Code\"\n >\n <document-engine-icon name=\"code\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasLink\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleLink\"\n (click)=\"toggleLink()\"\n [class.active]=\"editor.isActive('link')\"\n [title]=\"editor.isActive('link') ? 'Unlink' : 'Link'\"\n >\n <document-engine-icon [name]=\"editor.isActive('link') ? 'link_off' : 'link'\"></document-engine-icon>\n </button>\n </div>\n\n <!-- Alignment & Layout: Text Align, Indent, Lists -->\n <div\n *ngIf=\"capabilities.hasAlignmentLayout\"\n class=\"document-engine-document-editor__toolbar-group\"\n data-group=\"alignment-layout\"\n >\n <!-- Text Alignment -->\n <document-engine-select\n *ngIf=\"capabilities.hasTextAlign\"\n [value]=\"activeTextAlign\"\n (valueChange)=\"setTextAlign($event)\"\n placeholder=\"Text Align\"\n labelMode=\"static\"\n [disabled]=\"toolbarState.isReadOnly\"\n title=\"Text Align\"\n >\n <document-engine-icon documentEngineSelectLabel [name]=\"activeTextAlignIcon\"></document-engine-icon>\n\n <button\n *ngFor=\"let option of textAlignOptions; trackBy: trackByValue\"\n documentEngineSelectOption\n [value]=\"option.value\"\n >\n <document-engine-icon [name]=\"option.icon\"></document-engine-icon>\n <span>{{ option.label }}</span>\n </button>\n </document-engine-select>\n\n <!-- Indent -->\n <button\n *ngIf=\"capabilities.hasIndent\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canDecreaseIndent\"\n (click)=\"runAndEmit('decreaseIndent', editor.chain().focus().decreaseIndent().run)\"\n title=\"Decrease Indent\"\n >\n <document-engine-icon name=\"format_indent_decrease\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasIndent\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canIncreaseIndent\"\n (click)=\"runAndEmit('increaseIndent', editor.chain().focus().increaseIndent().run)\"\n title=\"Increase Indent\"\n >\n <document-engine-icon name=\"format_indent_increase\"></document-engine-icon>\n </button>\n\n <!-- Lists: Bullet, Numbered (with style) -->\n <button\n *ngIf=\"capabilities.hasLists\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleBulletList\"\n (click)=\"runAndEmit('toggleBulletList', editor.chain().focus().toggleBulletList().run)\"\n [class.active]=\"editor.isActive('bulletList')\"\n title=\"Bullet List\"\n >\n <document-engine-icon name=\"format_list_bulleted\"></document-engine-icon>\n </button>\n\n <!-- Numbered List with Style -->\n <document-engine-select\n *ngIf=\"capabilities.hasLists\"\n placeholder=\"Numbered List\"\n labelMode=\"static\"\n [value]=\"\n editor.isActive('customOrderedList')\n ? editor.getAttributes('customOrderedList')['data-list-style-type'] || 'decimal'\n : null\n \"\n [disabled]=\"toolbarState.isReadOnly\"\n title=\"Numbered List\"\n >\n <document-engine-icon documentEngineSelectLabel name=\"format_list_numbered\"></document-engine-icon>\n\n <button\n *ngFor=\"let option of listStyleOptions; trackBy: trackByValue\"\n documentEngineSelectOption\n [value]=\"option.value\"\n (click)=\"setListStyle(option.value)\"\n [class.active]=\"\n editor.isActive('customOrderedList') &&\n editor.getAttributes('customOrderedList')['data-list-style-type'] === option.value\n \"\n >\n <span>{{ option.example }}</span>\n <span>{{ option.label }}</span>\n </button>\n </document-engine-select>\n </div>\n\n <!-- Block Types: Code Block, Blockquote -->\n <div\n *ngIf=\"capabilities.hasBlockTypes\"\n class=\"document-engine-document-editor__toolbar-group\"\n data-group=\"block-types\"\n >\n <!-- Blockquote -->\n <button\n *ngIf=\"capabilities.hasBlockquote\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleBlockquote\"\n (click)=\"runAndEmit('toggleBlockquote', editor.chain().focus().toggleBlockquote().run)\"\n [class.active]=\"editor.isActive('blockquote')\"\n title=\"Quote\"\n >\n <document-engine-icon name=\"format_quote\"></document-engine-icon>\n </button>\n </div>\n\n <!-- Insert Object: Table, Images, Special Chars, Page Break -->\n <div\n *ngIf=\"capabilities.hasInsertObject\"\n class=\"document-engine-document-editor__toolbar-group\"\n data-group=\"insert-object\"\n >\n <button\n *ngIf=\"capabilities.hasTable\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly\"\n (click)=\"openCreateTableBubble($event)\"\n [class.active]=\"editor.isActive('table')\"\n title=\"Table\"\n >\n <document-engine-icon name=\"table_rows\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasImages\"\n documentEngineButton\n size=\"icon\"\n title=\"Insert Image\"\n [disabled]=\"toolbarState.isReadOnly\"\n (click)=\"insertImage($event)\"\n >\n <document-engine-icon name=\"photo\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasSpecialChars\"\n documentEngineButton\n size=\"icon\"\n title=\"Special Character\"\n [disabled]=\"toolbarState.isReadOnly\"\n (click)=\"openSpecialCharsBubble($event)\"\n >\n <document-engine-icon name=\"special_character\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasPageBreak\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canInsertPageBreak\"\n (click)=\"runAndEmit('insertPageBreak', editor.chain().focus().insertPageBreak().run)\"\n title=\"Insert page break\"\n >\n <document-engine-icon name=\"insert_page_break\"></document-engine-icon>\n </button>\n </div>\n\n <!-- Business Features: Templates, Dynamic Fields, Editable Region -->\n <div\n *ngIf=\"capabilities.hasBusinessFeatures\"\n class=\"document-engine-document-editor__toolbar-group\"\n data-group=\"business-features\"\n >\n <button\n *ngIf=\"capabilities.hasDynamicFields\"\n documentEngineButton\n size=\"icon\"\n title=\"Dynamic Field\"\n [disabled]=\"toolbarState.isReadOnly\"\n (click)=\"openDynamicFieldsBubble($event)\"\n >\n <document-engine-icon name=\"dynamic_form\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasTemplates\"\n documentEngineButton\n size=\"icon\"\n title=\"Template\"\n [disabled]=\"toolbarState.isReadOnly\"\n (click)=\"openTemplateBubble($event)\"\n >\n <document-engine-icon name=\"description\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasEditableRegion\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"!toolbarState.canToggleEditableRegion || toolbarState.isReadOnly\"\n (click)=\"runAndEmit('toggleEditableRegion', editor.chain().focus().toggleEditableRegion().run)\"\n [class.active]=\"editor.isActive('editableRegion')\"\n title=\"Toggle editable region\"\n >\n <document-engine-icon name=\"restrict_edit\"></document-engine-icon>\n </button>\n </div>\n\n <!-- Actions: Clear Content, Markdown -->\n <div *ngIf=\"capabilities.hasActions\" class=\"document-engine-document-editor__toolbar-group\" data-group=\"actions\">\n <button\n *ngIf=\"capabilities.hasClearContent\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canClearContent\"\n (click)=\"runAndEmit('clear', editor.chain().focus().clear().run)\"\n title=\"Clear all content\"\n >\n <document-engine-icon name=\"clear_all\"></document-engine-icon>\n </button>\n\n <!-- <button\n *ngIf=\"capabilities.hasMarkdown\"\n documentEngineButton\n size=\"icon\"\n (click)=\"downloadAsMarkdown()\"\n title=\"Download as Markdown\"\n >\n <document-engine-icon name=\"markdown\"></document-engine-icon>\n </button> -->\n </div>\n\n <!-- Custom Toolbar Buttons -->\n <div\n *ngIf=\"customToolbarButtons.length > 0\"\n class=\"document-engine-document-editor__toolbar-group\"\n data-group=\"custom-buttons\"\n >\n <ng-container *ngFor=\"let button of customToolbarButtons; trackBy: trackByIndex\">\n <!-- Icon Button -->\n <button\n *ngIf=\"button.type === 'icon-button'\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly\"\n (click)=\"handleCustomIconButton(button)\"\n [title]=\"button.label || ''\"\n >\n <document-engine-icon [name]=\"button.icon\"></document-engine-icon>\n </button>\n\n <!-- Select Button -->\n <document-engine-select\n *ngIf=\"button.type === 'select-button'\"\n [value]=\"null\"\n labelMode=\"static\"\n [disabled]=\"toolbarState.isReadOnly\"\n [title]=\"button.label\"\n >\n <span documentEngineSelectLabel>{{ button.label }}</span>\n\n <button\n *ngFor=\"let option of button.options; trackBy: trackByValue\"\n documentEngineSelectOption\n [value]=\"option.value\"\n (click)=\"handleCustomSelectButton(button, option.value)\"\n >\n {{ option.label }}\n </button>\n </document-engine-select>\n </ng-container>\n </div>\n </div>\n\n <!-- Right side actions -->\n <div class=\"document-engine-document-editor__toolbar-right\">\n <button\n *ngIf=\"capabilities.hasPrintButton\"\n documentEngineButton\n size=\"icon\"\n (click)=\"printValue()\"\n title=\"Print Value\"\n >\n <document-engine-icon name=\"print\"></document-engine-icon>\n </button>\n </div>\n </div>\n\n <!-- Color Bubble Menu -->\n <document-engine-toolbar-bubble-menu\n #colorBubbleMenu\n [editor]=\"editor\"\n [config]=\"colorBubbleConfig\"\n ></document-engine-toolbar-bubble-menu>\n\n <!-- Link Bubble Menu -->\n <document-engine-editor-bubble-menu\n *ngIf=\"capabilities.hasLink\"\n #linkBubbleWrapper\n [editor]=\"editor\"\n [config]=\"linkBubbleConfig\"\n ></document-engine-editor-bubble-menu>\n\n <!-- Table Bubble Menu -->\n <document-engine-editor-bubble-menu\n *ngIf=\"capabilities.hasTable\"\n #tableBubbleWrapper\n [editor]=\"editor\"\n [config]=\"tableBubbleConfig\"\n ></document-engine-editor-bubble-menu>\n\n <!-- Image Bubble Menu -->\n <document-engine-toolbar-bubble-menu\n *ngIf=\"capabilities.hasImages\"\n #imageBubbleMenu\n [editor]=\"editor\"\n [config]=\"imageBubbleConfig\"\n ></document-engine-toolbar-bubble-menu>\n\n <!-- Special Characters Bubble Menu -->\n <document-engine-toolbar-bubble-menu\n *ngIf=\"capabilities.hasSpecialChars\"\n #specialCharsBubbleMenu\n [editor]=\"editor\"\n [config]=\"specialCharsBubbleConfig\"\n ></document-engine-toolbar-bubble-menu>\n\n <!-- Dynamic Fields Bubble Menu -->\n <document-engine-toolbar-bubble-menu\n *ngIf=\"capabilities.hasDynamicFields\"\n #dynamicFieldsBubbleMenu\n [editor]=\"editor\"\n [config]=\"dynamicFieldsBubbleConfig\"\n ></document-engine-toolbar-bubble-menu>\n\n <!-- Template Bubble Menu -->\n <document-engine-toolbar-bubble-menu\n *ngIf=\"capabilities.hasTemplates\"\n #templateBubbleMenu\n [editor]=\"editor\"\n [config]=\"templateBubbleConfig\"\n ></document-engine-toolbar-bubble-menu>\n\n <!-- Table Create Bubble Menu -->\n <document-engine-toolbar-bubble-menu\n *ngIf=\"capabilities.hasTable\"\n #tableCreateBubbleMenu\n [editor]=\"editor\"\n [config]=\"tableCreateBubbleConfig\"\n ></document-engine-toolbar-bubble-menu>\n</div>\n", styles: [":host{position:sticky;top:0;z-index:20}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: IconComponent, selector: "document-engine-icon", inputs: ["name"] }, { kind: "directive", type: ButtonDirective, selector: "button[documentEngineButton]", inputs: ["variant", "size"] }, { kind: "component", type: SelectComponent, selector: "document-engine-select", inputs: ["value", "disabled", "labelMode", "variant"], outputs: ["valueChange"] }, { kind: "directive", type: SelectLabelDirective, selector: "[documentEngineSelectLabel]" }, { kind: "directive", type: SelectOptionDirective, selector: "[documentEngineSelectOption]", inputs: ["value", "label", "disabled"] }, { kind: "component", type: ToolbarBubbleMenuComponent, selector: "document-engine-toolbar-bubble-menu", inputs: ["editor", "config"] }, { kind: "component", type: EditorBubbleMenuComponent, selector: "document-engine-editor-bubble-menu", inputs: ["editor", "config"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
5772
|
-
}
|
|
5773
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: ToolbarComponent, decorators: [{
|
|
5774
|
-
type: Component,
|
|
5775
|
-
args: [{ selector: 'document-engine-toolbar', standalone: true, imports: [
|
|
5776
|
-
CommonModule,
|
|
5777
|
-
IconComponent,
|
|
5778
|
-
ButtonDirective,
|
|
5779
|
-
SelectComponent,
|
|
5780
|
-
SelectLabelDirective,
|
|
5781
|
-
SelectOptionDirective,
|
|
5782
|
-
ToolbarBubbleMenuComponent,
|
|
5783
|
-
EditorBubbleMenuComponent,
|
|
5784
|
-
], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"document-engine-document-editor__toolbar\">\n <div class=\"document-engine-document-editor__toolbar-content\">\n <div class=\"document-engine-document-editor__toolbar-left\">\n <!-- History: Undo/Redo -->\n <div *ngIf=\"capabilities.hasHistory\" class=\"document-engine-document-editor__toolbar-group\" data-group=\"history\">\n <button\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canUndo\"\n (click)=\"runAndEmit('undo', editor.chain().focus().undo().run)\"\n title=\"Undo (Ctrl+Z)\"\n >\n <document-engine-icon name=\"undo\"></document-engine-icon>\n </button>\n\n <button\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canRedo\"\n (click)=\"runAndEmit('redo', editor.chain().focus().redo().run)\"\n title=\"Redo\"\n >\n <document-engine-icon name=\"redo\"></document-engine-icon>\n </button>\n </div>\n\n <!-- Text Formatting: Font Size, Line Height, Text Case, Heading -->\n <div\n *ngIf=\"capabilities.hasTextProperty\"\n class=\"document-engine-document-editor__toolbar-group\"\n data-group=\"text-property\"\n >\n <!-- Font Size -->\n <document-engine-select\n *ngIf=\"capabilities.hasFontSize\"\n [value]=\"activeFontSize\"\n placeholder=\"Font Size\"\n labelMode=\"static\"\n [disabled]=\"toolbarState.isReadOnly\"\n title=\"Font Size\"\n >\n <document-engine-icon documentEngineSelectLabel name=\"format_size\"></document-engine-icon>\n\n <button documentEngineSelectOption [value]=\"null\">Default</button>\n\n <button\n *ngFor=\"let option of fontSizeOptions; trackBy: trackByValue\"\n documentEngineSelectOption\n [value]=\"option.value\"\n (click)=\"setFontSize(option.value)\"\n >\n {{ option.label }}\n </button>\n </document-engine-select>\n\n <!-- Line Height -->\n <document-engine-select\n *ngIf=\"capabilities.hasLineHeight\"\n [value]=\"activeLineHeight\"\n placeholder=\"Line Height\"\n labelMode=\"static\"\n [disabled]=\"toolbarState.isReadOnly\"\n title=\"Line Height\"\n >\n <document-engine-icon documentEngineSelectLabel name=\"format_line_spacing\"></document-engine-icon>\n\n <button\n *ngFor=\"let option of lineHeightOptions; trackBy: trackByValue\"\n documentEngineSelectOption\n [value]=\"option.value\"\n (click)=\"setLineHeight(option.value)\"\n >\n {{ option.label }}\n </button>\n </document-engine-select>\n\n <!-- Text Case -->\n <document-engine-select\n *ngIf=\"capabilities.hasTextCase\"\n placeholder=\"Text Case\"\n labelMode=\"static\"\n [disabled]=\"toolbarState.isReadOnly\"\n title=\"Text Case\"\n >\n <document-engine-icon documentEngineSelectLabel name=\"match_case\"></document-engine-icon>\n\n <button\n *ngFor=\"let option of textCaseOptions; trackBy: trackByValue\"\n documentEngineSelectOption\n [value]=\"option.value\"\n (click)=\"runAndEmit('setTextCase', editor.chain().focus().textCase(option.value).run)\"\n >\n {{ option.label }}\n </button>\n </document-engine-select>\n\n <!-- Heading -->\n <document-engine-select\n *ngIf=\"capabilities.hasHeading\"\n [value]=\"activeHeading?.toString() || null\"\n placeholder=\"Normal text\"\n [disabled]=\"toolbarState.isReadOnly\"\n title=\"Heading\"\n >\n <button\n *ngFor=\"let option of headingOptions; trackBy: trackByValue\"\n documentEngineSelectOption\n [value]=\"option.value?.toString() || null\"\n [class]=\"option.class\"\n (click)=\"setHeading(option.value?.toString() || null)\"\n >\n {{ option.label }}\n </button>\n </document-engine-select>\n </div>\n\n <!-- Format Utilities: Reset Formatting & Colors -->\n <div\n *ngIf=\"capabilities.hasFormatUtilities\"\n class=\"document-engine-document-editor__toolbar-group\"\n data-group=\"format-utilities\"\n >\n <!-- Reset Formatting -->\n <button\n *ngIf=\"capabilities.hasResetFormat\"\n documentEngineButton\n size=\"icon\"\n (click)=\"runAndEmit('resetFormat', editor.chain().focus().resetFormat().run)\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canResetFormat\"\n title=\"Clear Formatting\"\n >\n <document-engine-icon name=\"format_clear\"></document-engine-icon>\n </button>\n\n <!-- Colors: Text/Background -->\n <button\n *ngIf=\"capabilities.hasColors\"\n documentEngineButton\n size=\"icon\"\n title=\"Text Color\"\n [disabled]=\"toolbarState.isReadOnly\"\n (click)=\"openColorBubble($event, 'text')\"\n >\n <document-engine-icon name=\"format_color_text\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasColors\"\n documentEngineButton\n size=\"icon\"\n title=\"Background Color\"\n [disabled]=\"toolbarState.isReadOnly\"\n (click)=\"openColorBubble($event, 'fill')\"\n >\n <document-engine-icon name=\"format_color_fill\"></document-engine-icon>\n </button>\n </div>\n\n <!-- Text Styles: Bold/Italic/Underline/Strike -->\n <div\n *ngIf=\"capabilities.basicInline\"\n class=\"document-engine-document-editor__toolbar-group\"\n data-group=\"basic-inline\"\n >\n <button\n *ngIf=\"capabilities.hasBold\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleBold\"\n (click)=\"runAndEmit('toggleBold', editor.chain().focus().toggleBold().run)\"\n [class.active]=\"editor.isActive('bold')\"\n title=\"Bold\"\n >\n <document-engine-icon name=\"format_bold\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasItalic\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleItalic\"\n (click)=\"runAndEmit('toggleItalic', editor.chain().focus().toggleItalic().run)\"\n [class.active]=\"editor.isActive('italic')\"\n title=\"Italic\"\n >\n <document-engine-icon name=\"format_italic\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasUnderline\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleUnderline\"\n (click)=\"runAndEmit('toggleUnderline', editor.chain().focus().toggleUnderline().run)\"\n [class.active]=\"editor.isActive('underline')\"\n title=\"Underline\"\n >\n <document-engine-icon name=\"format_underline\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasStrike\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleStrike\"\n (click)=\"runAndEmit('toggleStrike', editor.chain().focus().toggleStrike().run)\"\n [class.active]=\"editor.isActive('strike')\"\n title=\"Strikethrough\"\n >\n <document-engine-icon name=\"format_strikethrough\"></document-engine-icon>\n </button>\n </div>\n\n <!-- Text Styles: Sub/Super/Code/Link -->\n <div\n *ngIf=\"capabilities.advancedInline\"\n class=\"document-engine-document-editor__toolbar-group\"\n data-group=\"advanced-inline\"\n >\n <button\n *ngIf=\"capabilities.hasSubscript\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleSubscript\"\n (click)=\"runAndEmit('toggleSubscript', editor.chain().focus().toggleSubscript().run)\"\n [class.active]=\"editor.isActive('subscript')\"\n title=\"Subscript\"\n >\n <document-engine-icon name=\"subscript\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasSuperscript\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleSuperscript\"\n (click)=\"runAndEmit('toggleSuperscript', editor.chain().focus().toggleSuperscript().run)\"\n [class.active]=\"editor.isActive('superscript')\"\n title=\"Superscript\"\n >\n <document-engine-icon name=\"superscript\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasCode\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleCode\"\n (click)=\"runAndEmit('toggleCode', editor.chain().focus().toggleCode().run)\"\n [class.active]=\"editor.isActive('code')\"\n title=\"Code\"\n >\n <document-engine-icon name=\"code\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasLink\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleLink\"\n (click)=\"toggleLink()\"\n [class.active]=\"editor.isActive('link')\"\n [title]=\"editor.isActive('link') ? 'Unlink' : 'Link'\"\n >\n <document-engine-icon [name]=\"editor.isActive('link') ? 'link_off' : 'link'\"></document-engine-icon>\n </button>\n </div>\n\n <!-- Alignment & Layout: Text Align, Indent, Lists -->\n <div\n *ngIf=\"capabilities.hasAlignmentLayout\"\n class=\"document-engine-document-editor__toolbar-group\"\n data-group=\"alignment-layout\"\n >\n <!-- Text Alignment -->\n <document-engine-select\n *ngIf=\"capabilities.hasTextAlign\"\n [value]=\"activeTextAlign\"\n (valueChange)=\"setTextAlign($event)\"\n placeholder=\"Text Align\"\n labelMode=\"static\"\n [disabled]=\"toolbarState.isReadOnly\"\n title=\"Text Align\"\n >\n <document-engine-icon documentEngineSelectLabel [name]=\"activeTextAlignIcon\"></document-engine-icon>\n\n <button\n *ngFor=\"let option of textAlignOptions; trackBy: trackByValue\"\n documentEngineSelectOption\n [value]=\"option.value\"\n >\n <document-engine-icon [name]=\"option.icon\"></document-engine-icon>\n <span>{{ option.label }}</span>\n </button>\n </document-engine-select>\n\n <!-- Indent -->\n <button\n *ngIf=\"capabilities.hasIndent\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canDecreaseIndent\"\n (click)=\"runAndEmit('decreaseIndent', editor.chain().focus().decreaseIndent().run)\"\n title=\"Decrease Indent\"\n >\n <document-engine-icon name=\"format_indent_decrease\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasIndent\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canIncreaseIndent\"\n (click)=\"runAndEmit('increaseIndent', editor.chain().focus().increaseIndent().run)\"\n title=\"Increase Indent\"\n >\n <document-engine-icon name=\"format_indent_increase\"></document-engine-icon>\n </button>\n\n <!-- Lists: Bullet, Numbered (with style) -->\n <button\n *ngIf=\"capabilities.hasLists\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleBulletList\"\n (click)=\"runAndEmit('toggleBulletList', editor.chain().focus().toggleBulletList().run)\"\n [class.active]=\"editor.isActive('bulletList')\"\n title=\"Bullet List\"\n >\n <document-engine-icon name=\"format_list_bulleted\"></document-engine-icon>\n </button>\n\n <!-- Numbered List with Style -->\n <document-engine-select\n *ngIf=\"capabilities.hasLists\"\n placeholder=\"Numbered List\"\n labelMode=\"static\"\n [value]=\"\n editor.isActive('customOrderedList')\n ? editor.getAttributes('customOrderedList')['data-list-style-type'] || 'decimal'\n : null\n \"\n [disabled]=\"toolbarState.isReadOnly\"\n title=\"Numbered List\"\n >\n <document-engine-icon documentEngineSelectLabel name=\"format_list_numbered\"></document-engine-icon>\n\n <button\n *ngFor=\"let option of listStyleOptions; trackBy: trackByValue\"\n documentEngineSelectOption\n [value]=\"option.value\"\n (click)=\"setListStyle(option.value)\"\n [class.active]=\"\n editor.isActive('customOrderedList') &&\n editor.getAttributes('customOrderedList')['data-list-style-type'] === option.value\n \"\n >\n <span>{{ option.example }}</span>\n <span>{{ option.label }}</span>\n </button>\n </document-engine-select>\n </div>\n\n <!-- Block Types: Code Block, Blockquote -->\n <div\n *ngIf=\"capabilities.hasBlockTypes\"\n class=\"document-engine-document-editor__toolbar-group\"\n data-group=\"block-types\"\n >\n <!-- Blockquote -->\n <button\n *ngIf=\"capabilities.hasBlockquote\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleBlockquote\"\n (click)=\"runAndEmit('toggleBlockquote', editor.chain().focus().toggleBlockquote().run)\"\n [class.active]=\"editor.isActive('blockquote')\"\n title=\"Quote\"\n >\n <document-engine-icon name=\"format_quote\"></document-engine-icon>\n </button>\n </div>\n\n <!-- Insert Object: Table, Images, Special Chars, Page Break -->\n <div\n *ngIf=\"capabilities.hasInsertObject\"\n class=\"document-engine-document-editor__toolbar-group\"\n data-group=\"insert-object\"\n >\n <button\n *ngIf=\"capabilities.hasTable\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly\"\n (click)=\"openCreateTableBubble($event)\"\n [class.active]=\"editor.isActive('table')\"\n title=\"Table\"\n >\n <document-engine-icon name=\"table_rows\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasImages\"\n documentEngineButton\n size=\"icon\"\n title=\"Insert Image\"\n [disabled]=\"toolbarState.isReadOnly\"\n (click)=\"insertImage($event)\"\n >\n <document-engine-icon name=\"photo\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasSpecialChars\"\n documentEngineButton\n size=\"icon\"\n title=\"Special Character\"\n [disabled]=\"toolbarState.isReadOnly\"\n (click)=\"openSpecialCharsBubble($event)\"\n >\n <document-engine-icon name=\"special_character\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasPageBreak\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canInsertPageBreak\"\n (click)=\"runAndEmit('insertPageBreak', editor.chain().focus().insertPageBreak().run)\"\n title=\"Insert page break\"\n >\n <document-engine-icon name=\"insert_page_break\"></document-engine-icon>\n </button>\n </div>\n\n <!-- Business Features: Templates, Dynamic Fields, Editable Region -->\n <div\n *ngIf=\"capabilities.hasBusinessFeatures\"\n class=\"document-engine-document-editor__toolbar-group\"\n data-group=\"business-features\"\n >\n <button\n *ngIf=\"capabilities.hasDynamicFields\"\n documentEngineButton\n size=\"icon\"\n title=\"Dynamic Field\"\n [disabled]=\"toolbarState.isReadOnly\"\n (click)=\"openDynamicFieldsBubble($event)\"\n >\n <document-engine-icon name=\"dynamic_form\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasTemplates\"\n documentEngineButton\n size=\"icon\"\n title=\"Template\"\n [disabled]=\"toolbarState.isReadOnly\"\n (click)=\"openTemplateBubble($event)\"\n >\n <document-engine-icon name=\"description\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasEditableRegion\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"!toolbarState.canToggleEditableRegion || toolbarState.isReadOnly\"\n (click)=\"runAndEmit('toggleEditableRegion', editor.chain().focus().toggleEditableRegion().run)\"\n [class.active]=\"editor.isActive('editableRegion')\"\n title=\"Toggle editable region\"\n >\n <document-engine-icon name=\"restrict_edit\"></document-engine-icon>\n </button>\n </div>\n\n <!-- Actions: Clear Content, Markdown -->\n <div *ngIf=\"capabilities.hasActions\" class=\"document-engine-document-editor__toolbar-group\" data-group=\"actions\">\n <button\n *ngIf=\"capabilities.hasClearContent\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canClearContent\"\n (click)=\"runAndEmit('clear', editor.chain().focus().clear().run)\"\n title=\"Clear all content\"\n >\n <document-engine-icon name=\"clear_all\"></document-engine-icon>\n </button>\n\n <!-- <button\n *ngIf=\"capabilities.hasMarkdown\"\n documentEngineButton\n size=\"icon\"\n (click)=\"downloadAsMarkdown()\"\n title=\"Download as Markdown\"\n >\n <document-engine-icon name=\"markdown\"></document-engine-icon>\n </button> -->\n </div>\n\n <!-- Custom Toolbar Buttons -->\n <div\n *ngIf=\"customToolbarButtons.length > 0\"\n class=\"document-engine-document-editor__toolbar-group\"\n data-group=\"custom-buttons\"\n >\n <ng-container *ngFor=\"let button of customToolbarButtons; trackBy: trackByIndex\">\n <!-- Icon Button -->\n <button\n *ngIf=\"button.type === 'icon-button'\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly\"\n (click)=\"handleCustomIconButton(button)\"\n [title]=\"button.label || ''\"\n >\n <document-engine-icon [name]=\"button.icon\"></document-engine-icon>\n </button>\n\n <!-- Select Button -->\n <document-engine-select\n *ngIf=\"button.type === 'select-button'\"\n [value]=\"null\"\n labelMode=\"static\"\n [disabled]=\"toolbarState.isReadOnly\"\n [title]=\"button.label\"\n >\n <span documentEngineSelectLabel>{{ button.label }}</span>\n\n <button\n *ngFor=\"let option of button.options; trackBy: trackByValue\"\n documentEngineSelectOption\n [value]=\"option.value\"\n (click)=\"handleCustomSelectButton(button, option.value)\"\n >\n {{ option.label }}\n </button>\n </document-engine-select>\n </ng-container>\n </div>\n </div>\n\n <!-- Right side actions -->\n <div class=\"document-engine-document-editor__toolbar-right\">\n <button\n *ngIf=\"capabilities.hasPrintButton\"\n documentEngineButton\n size=\"icon\"\n (click)=\"printValue()\"\n title=\"Print Value\"\n >\n <document-engine-icon name=\"print\"></document-engine-icon>\n </button>\n </div>\n </div>\n\n <!-- Color Bubble Menu -->\n <document-engine-toolbar-bubble-menu\n #colorBubbleMenu\n [editor]=\"editor\"\n [config]=\"colorBubbleConfig\"\n ></document-engine-toolbar-bubble-menu>\n\n <!-- Link Bubble Menu -->\n <document-engine-editor-bubble-menu\n *ngIf=\"capabilities.hasLink\"\n #linkBubbleWrapper\n [editor]=\"editor\"\n [config]=\"linkBubbleConfig\"\n ></document-engine-editor-bubble-menu>\n\n <!-- Table Bubble Menu -->\n <document-engine-editor-bubble-menu\n *ngIf=\"capabilities.hasTable\"\n #tableBubbleWrapper\n [editor]=\"editor\"\n [config]=\"tableBubbleConfig\"\n ></document-engine-editor-bubble-menu>\n\n <!-- Image Bubble Menu -->\n <document-engine-toolbar-bubble-menu\n *ngIf=\"capabilities.hasImages\"\n #imageBubbleMenu\n [editor]=\"editor\"\n [config]=\"imageBubbleConfig\"\n ></document-engine-toolbar-bubble-menu>\n\n <!-- Special Characters Bubble Menu -->\n <document-engine-toolbar-bubble-menu\n *ngIf=\"capabilities.hasSpecialChars\"\n #specialCharsBubbleMenu\n [editor]=\"editor\"\n [config]=\"specialCharsBubbleConfig\"\n ></document-engine-toolbar-bubble-menu>\n\n <!-- Dynamic Fields Bubble Menu -->\n <document-engine-toolbar-bubble-menu\n *ngIf=\"capabilities.hasDynamicFields\"\n #dynamicFieldsBubbleMenu\n [editor]=\"editor\"\n [config]=\"dynamicFieldsBubbleConfig\"\n ></document-engine-toolbar-bubble-menu>\n\n <!-- Template Bubble Menu -->\n <document-engine-toolbar-bubble-menu\n *ngIf=\"capabilities.hasTemplates\"\n #templateBubbleMenu\n [editor]=\"editor\"\n [config]=\"templateBubbleConfig\"\n ></document-engine-toolbar-bubble-menu>\n\n <!-- Table Create Bubble Menu -->\n <document-engine-toolbar-bubble-menu\n *ngIf=\"capabilities.hasTable\"\n #tableCreateBubbleMenu\n [editor]=\"editor\"\n [config]=\"tableCreateBubbleConfig\"\n ></document-engine-toolbar-bubble-menu>\n</div>\n", styles: [":host{position:sticky;top:0;z-index:20}\n"] }]
|
|
5785
|
-
}], propDecorators: { editor: [{
|
|
5786
|
-
type: Input
|
|
5787
|
-
}], capabilities: [{
|
|
5788
|
-
type: Input
|
|
5789
|
-
}], config: [{
|
|
5790
|
-
type: Input
|
|
5791
|
-
}], action: [{
|
|
5792
|
-
type: Output
|
|
5793
|
-
}], colorBubbleMenu: [{
|
|
5794
|
-
type: ViewChild,
|
|
5795
|
-
args: ['colorBubbleMenu']
|
|
5796
|
-
}], linkBubbleWrapper: [{
|
|
5797
|
-
type: ViewChild,
|
|
5798
|
-
args: ['linkBubbleWrapper']
|
|
5799
|
-
}], imageBubbleMenu: [{
|
|
5800
|
-
type: ViewChild,
|
|
5801
|
-
args: ['imageBubbleMenu']
|
|
5802
|
-
}], specialCharsBubbleMenu: [{
|
|
5803
|
-
type: ViewChild,
|
|
5804
|
-
args: ['specialCharsBubbleMenu']
|
|
5805
|
-
}], dynamicFieldsBubbleMenu: [{
|
|
5806
|
-
type: ViewChild,
|
|
5807
|
-
args: ['dynamicFieldsBubbleMenu']
|
|
5808
|
-
}], templateBubbleMenu: [{
|
|
5809
|
-
type: ViewChild,
|
|
5810
|
-
args: ['templateBubbleMenu']
|
|
5811
|
-
}], tableCreateBubbleMenu: [{
|
|
5812
|
-
type: ViewChild,
|
|
5813
|
-
args: ['tableCreateBubbleMenu']
|
|
5814
|
-
}] } });
|
|
5815
|
-
|
|
5816
|
-
/**
|
|
5817
|
-
* Character Count component for document editor footer
|
|
5818
|
-
* Displays character/word count with optional limit and color coding
|
|
5819
|
-
*/
|
|
5820
|
-
class CharacterCountComponent {
|
|
5821
|
-
cdr = inject(ChangeDetectorRef);
|
|
5822
|
-
editor;
|
|
5823
|
-
config;
|
|
5824
|
-
mode = 'characters';
|
|
5825
|
-
count = 0;
|
|
5826
|
-
limit = null;
|
|
5827
|
-
updateCount = () => {
|
|
5828
|
-
if (!this.editor) {
|
|
5829
|
-
return;
|
|
5830
|
-
}
|
|
5831
|
-
// Get count from editor storage if available
|
|
5832
|
-
const characterCountStorage = this.editor.storage.characterCount;
|
|
5833
|
-
if (characterCountStorage) {
|
|
5834
|
-
if (this.mode === 'characters') {
|
|
5835
|
-
this.count = characterCountStorage.characters?.() || 0;
|
|
5836
|
-
}
|
|
5837
|
-
else {
|
|
5838
|
-
this.count = characterCountStorage.words?.() || 0;
|
|
5839
|
-
}
|
|
5918
|
+
this.toolbarService.emit('setFontSize', size);
|
|
5919
|
+
}
|
|
5920
|
+
setLineHeight(lineHeight) {
|
|
5921
|
+
if (lineHeight) {
|
|
5922
|
+
this.editor.chain().focus().setLineHeight(lineHeight).run();
|
|
5840
5923
|
}
|
|
5841
5924
|
else {
|
|
5842
|
-
|
|
5843
|
-
const text = this.editor.getText();
|
|
5844
|
-
if (this.mode === 'characters') {
|
|
5845
|
-
this.count = text.length;
|
|
5846
|
-
}
|
|
5847
|
-
else {
|
|
5848
|
-
this.count = text.trim() ? text.trim().split(/\s+/).length : 0;
|
|
5849
|
-
}
|
|
5925
|
+
this.editor.chain().focus().unsetLineHeight().run();
|
|
5850
5926
|
}
|
|
5851
|
-
|
|
5852
|
-
|
|
5853
|
-
|
|
5854
|
-
|
|
5927
|
+
this.toolbarService.emit('setLineHeight', lineHeight);
|
|
5928
|
+
}
|
|
5929
|
+
setHeading(level) {
|
|
5930
|
+
if (!level) {
|
|
5931
|
+
this.editor.chain().focus().removeHeading().run();
|
|
5855
5932
|
}
|
|
5856
5933
|
else {
|
|
5857
|
-
|
|
5858
|
-
|
|
5859
|
-
this.cdr.markForCheck();
|
|
5860
|
-
};
|
|
5861
|
-
ngOnInit() {
|
|
5862
|
-
if (!this.editor) {
|
|
5863
|
-
return;
|
|
5934
|
+
const levelNumber = parseInt(level, 10);
|
|
5935
|
+
this.editor.chain().focus().toggleHeading({ level: levelNumber }).run();
|
|
5864
5936
|
}
|
|
5865
|
-
|
|
5866
|
-
this.editor.on('update', this.updateCount);
|
|
5867
|
-
this.editor.on('transaction', this.updateCount);
|
|
5868
|
-
// Initialize count once
|
|
5869
|
-
this.updateCount();
|
|
5937
|
+
this.toolbarService.emit('setHeading', level);
|
|
5870
5938
|
}
|
|
5871
|
-
|
|
5872
|
-
if (
|
|
5873
|
-
this.editor
|
|
5874
|
-
|
|
5939
|
+
setTextAlign(align) {
|
|
5940
|
+
if (align) {
|
|
5941
|
+
this.editor
|
|
5942
|
+
.chain()
|
|
5943
|
+
.focus()
|
|
5944
|
+
.setTextAlign(align)
|
|
5945
|
+
.run();
|
|
5875
5946
|
}
|
|
5876
|
-
|
|
5877
|
-
|
|
5878
|
-
|
|
5879
|
-
return false;
|
|
5947
|
+
else {
|
|
5948
|
+
// Reset to default (left)
|
|
5949
|
+
this.editor.chain().focus().setTextAlign('left').run();
|
|
5880
5950
|
}
|
|
5881
|
-
|
|
5882
|
-
return this.count >= this.limit * 0.9 && this.count < this.limit;
|
|
5951
|
+
this.toolbarService.emit('setTextAlign', align);
|
|
5883
5952
|
}
|
|
5884
|
-
|
|
5885
|
-
if (
|
|
5886
|
-
|
|
5953
|
+
setListStyle(listStyleType) {
|
|
5954
|
+
if (this.editor.isActive('customOrderedList')) {
|
|
5955
|
+
// If there's an active ordered list, update its style
|
|
5956
|
+
this.editor.chain().focus().setListStyle(listStyleType).run();
|
|
5887
5957
|
}
|
|
5888
|
-
|
|
5889
|
-
|
|
5890
|
-
|
|
5891
|
-
|
|
5892
|
-
}
|
|
5893
|
-
|
|
5894
|
-
type: Component,
|
|
5895
|
-
args: [{ selector: 'document-engine-character-count', standalone: true, imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"document-engine-character-count\">\n <span\n class=\"document-engine-character-count__text\"\n [class.document-engine-character-count__text--muted]=\"!isNearLimit() && !isOverLimit()\"\n [class.document-engine-character-count__text--warning]=\"isNearLimit() && !isOverLimit()\"\n [class.document-engine-character-count__text--error]=\"isOverLimit()\"\n >\n <ng-container *ngIf=\"mode === 'characters'\">\n <ng-container *ngIf=\"limit; else noLimit\"> {{ count }} / {{ limit }} characters. </ng-container>\n <ng-template #noLimit> {{ count }} {{ count === 1 ? 'character' : 'characters' }}. </ng-template>\n </ng-container>\n <ng-container *ngIf=\"mode === 'words'\"> {{ count }} {{ count === 1 ? 'word' : 'words' }}. </ng-container>\n </span>\n</div>\n", styles: [".document-engine-character-count{display:flex;justify-content:flex-end}.document-engine-character-count__text{font-size:.875rem;transition:color .2s ease-in-out}.document-engine-character-count__text--muted{color:var(--muted-foreground, #6b7280)}.document-engine-character-count__text--warning{color:var(--warning, #d97706)}.document-engine-character-count__text--error{color:var(--destructive, #dc2626)}\n"] }]
|
|
5896
|
-
}], propDecorators: { editor: [{
|
|
5897
|
-
type: Input
|
|
5898
|
-
}], config: [{
|
|
5899
|
-
type: Input
|
|
5900
|
-
}], mode: [{
|
|
5901
|
-
type: Input
|
|
5902
|
-
}] } });
|
|
5903
|
-
|
|
5904
|
-
/**
|
|
5905
|
-
* Footer component for document editor
|
|
5906
|
-
* Container for footer components like character count
|
|
5907
|
-
*/
|
|
5908
|
-
class FooterComponent {
|
|
5909
|
-
editor;
|
|
5910
|
-
config;
|
|
5911
|
-
// @Input() editable = true;
|
|
5912
|
-
hasCharacterCount() {
|
|
5913
|
-
const config = this.config ?? {};
|
|
5914
|
-
return config.characterCount !== false;
|
|
5915
|
-
}
|
|
5916
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: FooterComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
5917
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.10", type: FooterComponent, isStandalone: true, selector: "document-engine-footer", inputs: { editor: "editor", config: "config" }, ngImport: i0, template: "<div class=\"document-engine-document-editor__footer\">\n <div class=\"document-engine-document-editor__footer-content\">\n <div class=\"document-engine-document-editor__footer-left\">\n <!-- Additional footer components can be added here -->\n </div>\n\n <div class=\"document-engine-document-editor__footer-right\">\n <document-engine-character-count\n *ngIf=\"hasCharacterCount()\"\n [editor]=\"editor\"\n [config]=\"config\"\n mode=\"characters\"\n ></document-engine-character-count>\n </div>\n </div>\n</div>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: CharacterCountComponent, selector: "document-engine-character-count", inputs: ["editor", "config", "mode"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
5918
|
-
}
|
|
5919
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: FooterComponent, decorators: [{
|
|
5920
|
-
type: Component,
|
|
5921
|
-
args: [{ selector: 'document-engine-footer', standalone: true, imports: [CommonModule, CharacterCountComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"document-engine-document-editor__footer\">\n <div class=\"document-engine-document-editor__footer-content\">\n <div class=\"document-engine-document-editor__footer-left\">\n <!-- Additional footer components can be added here -->\n </div>\n\n <div class=\"document-engine-document-editor__footer-right\">\n <document-engine-character-count\n *ngIf=\"hasCharacterCount()\"\n [editor]=\"editor\"\n [config]=\"config\"\n mode=\"characters\"\n ></document-engine-character-count>\n </div>\n </div>\n</div>\n" }]
|
|
5922
|
-
}], propDecorators: { editor: [{
|
|
5923
|
-
type: Input
|
|
5924
|
-
}], config: [{
|
|
5925
|
-
type: Input
|
|
5926
|
-
}] } });
|
|
5927
|
-
|
|
5928
|
-
/**
|
|
5929
|
-
* Document Editor Component
|
|
5930
|
-
* High-level wrapper that encapsulates TiptapEditor and DocumentEngineKit
|
|
5931
|
-
* Provides a simple API for consumers without exposing internal dependencies
|
|
5932
|
-
*
|
|
5933
|
-
* Usage:
|
|
5934
|
-
* <document-engine-editor
|
|
5935
|
-
* #editor
|
|
5936
|
-
* [config]="editorConfig"
|
|
5937
|
-
*
|
|
5938
|
-
* (editorReady)="editor = docEditor.editor"
|
|
5939
|
-
* >
|
|
5940
|
-
* <tiptap-editor
|
|
5941
|
-
* [editor]="docEditor.editor"
|
|
5942
|
-
* [(ngModel)]="content"
|
|
5943
|
-
* ></tiptap-editor>
|
|
5944
|
-
* </document-engine-editor>
|
|
5945
|
-
*/
|
|
5946
|
-
class DocumentEditorComponent {
|
|
5947
|
-
ngZone = inject(NgZone);
|
|
5948
|
-
config;
|
|
5949
|
-
editorReady = new EventEmitter();
|
|
5950
|
-
// Expose editor property for external access (re-export)
|
|
5951
|
-
editor;
|
|
5952
|
-
// Store merged config for toolbar and helper methods
|
|
5953
|
-
get mergedConfig() {
|
|
5954
|
-
return this._mergedConfig ?? this.getMergedConfig();
|
|
5955
|
-
}
|
|
5956
|
-
_mergedConfig;
|
|
5957
|
-
// Capabilities instance for toolbar
|
|
5958
|
-
capabilities;
|
|
5959
|
-
updateCapabilities() {
|
|
5960
|
-
this.capabilities = new EditorCapabilities(this.mergedConfig);
|
|
5961
|
-
}
|
|
5962
|
-
ngOnChanges(changes) {
|
|
5963
|
-
if (!changes['config'])
|
|
5964
|
-
return;
|
|
5965
|
-
if (!this.editor)
|
|
5966
|
-
return;
|
|
5967
|
-
this._mergedConfig = this.getMergedConfig();
|
|
5968
|
-
this.updateCapabilities();
|
|
5969
|
-
this.editor.setEditable(this._mergedConfig.editable ?? true);
|
|
5970
|
-
}
|
|
5971
|
-
ngOnInit() {
|
|
5972
|
-
this.initializeEditor();
|
|
5958
|
+
else {
|
|
5959
|
+
// If no active list, create a new one with the selected style
|
|
5960
|
+
this.editor.chain().focus().toggleOrderedList().run();
|
|
5961
|
+
this.editor.chain().focus().setListStyle(listStyleType).run();
|
|
5962
|
+
}
|
|
5963
|
+
this.toolbarService.emit('setListStyle', listStyleType);
|
|
5973
5964
|
}
|
|
5974
|
-
|
|
5975
|
-
|
|
5976
|
-
|
|
5965
|
+
openColorBubble(event, type) {
|
|
5966
|
+
const targetElement = (event.currentTarget || event.target);
|
|
5967
|
+
this.colorBubbleMenu?.toggleBubble(targetElement, { type });
|
|
5968
|
+
this.toolbarService.emit('openColorBubble', { type });
|
|
5977
5969
|
}
|
|
5978
|
-
|
|
5979
|
-
|
|
5980
|
-
|
|
5981
|
-
|
|
5982
|
-
|
|
5983
|
-
|
|
5984
|
-
|
|
5985
|
-
|
|
5986
|
-
|
|
5987
|
-
|
|
5988
|
-
editable: this._mergedConfig.editable,
|
|
5989
|
-
extensions: [DocumentEngineKit.configure(this._mergedConfig)],
|
|
5990
|
-
});
|
|
5991
|
-
// Emit editorReady event back inside Angular zone
|
|
5992
|
-
this.ngZone.run(() => {
|
|
5993
|
-
// Create capabilities instance after editor is initialized
|
|
5994
|
-
this.updateCapabilities();
|
|
5995
|
-
this.editorReady.emit(this.editor);
|
|
5996
|
-
});
|
|
5997
|
-
});
|
|
5970
|
+
toggleLink() {
|
|
5971
|
+
if (this.editor.isActive('link')) {
|
|
5972
|
+
// If link is active, remove it
|
|
5973
|
+
this.editor.chain().focus().unsetLink().run();
|
|
5974
|
+
}
|
|
5975
|
+
else {
|
|
5976
|
+
// If no link, open the bubble menu
|
|
5977
|
+
this.linkBubbleWrapper?.showBubble();
|
|
5978
|
+
}
|
|
5979
|
+
this.toolbarService.emit('toggleLink');
|
|
5998
5980
|
}
|
|
5999
|
-
|
|
6000
|
-
|
|
6001
|
-
|
|
6002
|
-
|
|
6003
|
-
getMergedConfig() {
|
|
6004
|
-
// Merge user config with defaults, preferring user config when provided
|
|
6005
|
-
return this.mergeConfig(DefaultEditorConfig, this.config);
|
|
5981
|
+
insertImage(event) {
|
|
5982
|
+
const targetElement = (event.currentTarget || event.target);
|
|
5983
|
+
this.imageBubbleMenu?.toggleBubble(targetElement);
|
|
5984
|
+
this.toolbarService.emit('insertImage');
|
|
6006
5985
|
}
|
|
6007
|
-
|
|
6008
|
-
|
|
6009
|
-
|
|
6010
|
-
|
|
6011
|
-
if (!userConfig)
|
|
6012
|
-
return defaultConfig;
|
|
6013
|
-
const merged = { ...defaultConfig };
|
|
6014
|
-
const keys = Object.keys(userConfig);
|
|
6015
|
-
for (const key of keys) {
|
|
6016
|
-
const value = userConfig[key];
|
|
6017
|
-
if (value !== undefined)
|
|
6018
|
-
merged[key] = value;
|
|
6019
|
-
}
|
|
6020
|
-
return merged;
|
|
5986
|
+
openSpecialCharsBubble(event) {
|
|
5987
|
+
const targetElement = (event.currentTarget || event.target);
|
|
5988
|
+
this.specialCharsBubbleMenu?.toggleBubble(targetElement);
|
|
5989
|
+
this.toolbarService.emit('openSpecialCharsBubble');
|
|
6021
5990
|
}
|
|
6022
|
-
|
|
6023
|
-
|
|
6024
|
-
|
|
6025
|
-
|
|
6026
|
-
type: Component,
|
|
6027
|
-
args: [{ selector: 'document-engine-editor', standalone: false, changeDetection: ChangeDetectionStrategy.OnPush, host: {
|
|
6028
|
-
class: 'no-twp',
|
|
6029
|
-
}, template: "<div class=\"document-engine-document-editor\">\n <document-engine-toolbar\n *ngIf=\"mergedConfig.showToolbar\"\n [editor]=\"editor\"\n [capabilities]=\"capabilities\"\n [config]=\"mergedConfig\"\n ></document-engine-toolbar>\n\n <div class=\"document-engine-document-editor__content\">\n <ng-content></ng-content>\n </div>\n\n <document-engine-footer\n *ngIf=\"editor && mergedConfig.showFooter\"\n [editor]=\"editor\"\n [config]=\"mergedConfig\"\n ></document-engine-footer>\n</div>\n", styles: [":is() *,:is() *:before,:is() *:after{box-sizing:border-box}\n"] }]
|
|
6030
|
-
}], propDecorators: { config: [{
|
|
6031
|
-
type: Input
|
|
6032
|
-
}], editorReady: [{
|
|
6033
|
-
type: Output
|
|
6034
|
-
}] } });
|
|
6035
|
-
|
|
6036
|
-
class DocumentEditorModule {
|
|
6037
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: DocumentEditorModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
|
6038
|
-
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.10", ngImport: i0, type: DocumentEditorModule, declarations: [DocumentEditorComponent], imports: [CommonModule, ToolbarComponent, FooterComponent], exports: [DocumentEditorComponent] });
|
|
6039
|
-
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: DocumentEditorModule, imports: [CommonModule, ToolbarComponent, FooterComponent] });
|
|
6040
|
-
}
|
|
6041
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: DocumentEditorModule, decorators: [{
|
|
6042
|
-
type: NgModule,
|
|
6043
|
-
args: [{
|
|
6044
|
-
declarations: [DocumentEditorComponent],
|
|
6045
|
-
imports: [CommonModule, ToolbarComponent, FooterComponent],
|
|
6046
|
-
exports: [DocumentEditorComponent],
|
|
6047
|
-
}]
|
|
6048
|
-
}] });
|
|
6049
|
-
|
|
6050
|
-
class AngularRenderer {
|
|
6051
|
-
applicationRef;
|
|
6052
|
-
componentRef;
|
|
6053
|
-
constructor(ViewComponent, injector, props) {
|
|
6054
|
-
this.applicationRef = injector.get(ApplicationRef);
|
|
6055
|
-
this.componentRef = createComponent(ViewComponent, {
|
|
6056
|
-
environmentInjector: this.applicationRef.injector,
|
|
6057
|
-
elementInjector: injector,
|
|
5991
|
+
openDynamicFieldsBubble(event) {
|
|
5992
|
+
const targetElement = (event.currentTarget || event.target);
|
|
5993
|
+
this.dynamicFieldsBubbleMenu?.toggleBubble(targetElement, {
|
|
5994
|
+
categories: this.config?.dynamicFieldsCategories || [],
|
|
6058
5995
|
});
|
|
6059
|
-
|
|
6060
|
-
this.updateProps(props);
|
|
6061
|
-
this.applicationRef.attachView(this.componentRef.hostView);
|
|
5996
|
+
this.toolbarService.emit('openDynamicFieldsBubble');
|
|
6062
5997
|
}
|
|
6063
|
-
|
|
6064
|
-
|
|
5998
|
+
openTemplateBubble(event) {
|
|
5999
|
+
const targetElement = (event.currentTarget || event.target);
|
|
6000
|
+
this.templateBubbleMenu?.toggleBubble(targetElement, {
|
|
6001
|
+
templates: this.config?.templates || [],
|
|
6002
|
+
});
|
|
6003
|
+
this.toolbarService.emit('openTemplateBubble');
|
|
6065
6004
|
}
|
|
6066
|
-
|
|
6067
|
-
|
|
6005
|
+
openCreateTableBubble(event) {
|
|
6006
|
+
const targetElement = (event.currentTarget || event.target);
|
|
6007
|
+
this.tableCreateBubbleMenu?.toggleBubble(targetElement);
|
|
6008
|
+
this.toolbarService.emit('openCreateTableBubble');
|
|
6068
6009
|
}
|
|
6069
|
-
|
|
6070
|
-
|
|
6010
|
+
// downloadAsMarkdown(filename?: string): void {
|
|
6011
|
+
// const fileName = filename ? `${filename}.md` : 'export.md';
|
|
6012
|
+
// const markdown = renderToMarkdown({
|
|
6013
|
+
// content: this.editor.getJSON(),
|
|
6014
|
+
// extensions: this.editor.options.extensions,
|
|
6015
|
+
// options: {
|
|
6016
|
+
// nodeMapping: {
|
|
6017
|
+
// pageBreak: () => 'Page break',
|
|
6018
|
+
// },
|
|
6019
|
+
// },
|
|
6020
|
+
// });
|
|
6021
|
+
// const blob = new Blob([markdown], { type: 'text/markdown' });
|
|
6022
|
+
// const url = URL.createObjectURL(blob);
|
|
6023
|
+
// const a = document.createElement('a');
|
|
6024
|
+
// a.href = url;
|
|
6025
|
+
// a.download = fileName;
|
|
6026
|
+
// a.click();
|
|
6027
|
+
// URL.revokeObjectURL(url);
|
|
6028
|
+
// }
|
|
6029
|
+
trackByValue(index, item) {
|
|
6030
|
+
return item.value;
|
|
6071
6031
|
}
|
|
6072
|
-
|
|
6073
|
-
|
|
6074
|
-
this.componentRef.setInput(key, value);
|
|
6075
|
-
});
|
|
6032
|
+
trackByIndex(index) {
|
|
6033
|
+
return index;
|
|
6076
6034
|
}
|
|
6077
|
-
|
|
6078
|
-
|
|
6079
|
-
this.dom.setAttribute(key, attributes[key]);
|
|
6080
|
-
});
|
|
6035
|
+
get customToolbarButtons() {
|
|
6036
|
+
return this.config?.customToolbarButtons || [];
|
|
6081
6037
|
}
|
|
6082
|
-
|
|
6083
|
-
|
|
6038
|
+
handleCustomIconButton(button) {
|
|
6039
|
+
if (button.type === 'icon-button') {
|
|
6040
|
+
button.callback();
|
|
6041
|
+
this.toolbarService.emit('customIconButton', { label: button.label, icon: button.icon });
|
|
6042
|
+
}
|
|
6084
6043
|
}
|
|
6085
|
-
|
|
6086
|
-
|
|
6087
|
-
|
|
6044
|
+
handleCustomSelectButton(button, value) {
|
|
6045
|
+
if (button.type === 'select-button') {
|
|
6046
|
+
button.callback();
|
|
6047
|
+
this.toolbarService.emit('customSelectButton', { label: button.label, value });
|
|
6048
|
+
}
|
|
6088
6049
|
}
|
|
6050
|
+
printValue() {
|
|
6051
|
+
console.log('html', this.editor.getHTML());
|
|
6052
|
+
console.log('json', this.editor.getJSON());
|
|
6053
|
+
console.log('State', this.editor.state);
|
|
6054
|
+
this.toolbarService.emit('printValue');
|
|
6055
|
+
}
|
|
6056
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: ToolbarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
6057
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.10", type: ToolbarComponent, isStandalone: true, selector: "document-engine-toolbar", inputs: { editor: "editor", capabilities: "capabilities", config: "config" }, outputs: { action: "action" }, viewQueries: [{ propertyName: "colorBubbleMenu", first: true, predicate: ["colorBubbleMenu"], descendants: true }, { propertyName: "linkBubbleWrapper", first: true, predicate: ["linkBubbleWrapper"], descendants: true }, { propertyName: "imageBubbleMenu", first: true, predicate: ["imageBubbleMenu"], descendants: true }, { propertyName: "specialCharsBubbleMenu", first: true, predicate: ["specialCharsBubbleMenu"], descendants: true }, { propertyName: "dynamicFieldsBubbleMenu", first: true, predicate: ["dynamicFieldsBubbleMenu"], descendants: true }, { propertyName: "templateBubbleMenu", first: true, predicate: ["templateBubbleMenu"], descendants: true }, { propertyName: "tableCreateBubbleMenu", first: true, predicate: ["tableCreateBubbleMenu"], descendants: true }], ngImport: i0, template: "<div class=\"document-engine-document-editor__toolbar\">\n <div class=\"document-engine-document-editor__toolbar-content\">\n <div class=\"document-engine-document-editor__toolbar-left\">\n <!-- History: Undo/Redo -->\n <div *ngIf=\"capabilities.hasHistory\" class=\"document-engine-document-editor__toolbar-group\" data-group=\"history\">\n <button\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canUndo\"\n (click)=\"runAndEmit('undo', editor.chain().focus().undo().run)\"\n title=\"Undo (Ctrl+Z)\"\n >\n <document-engine-icon name=\"undo\"></document-engine-icon>\n </button>\n\n <button\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canRedo\"\n (click)=\"runAndEmit('redo', editor.chain().focus().redo().run)\"\n title=\"Redo\"\n >\n <document-engine-icon name=\"redo\"></document-engine-icon>\n </button>\n </div>\n\n <!-- Text Formatting: Font Size, Line Height, Text Case, Heading -->\n <div\n *ngIf=\"capabilities.hasTextProperty\"\n class=\"document-engine-document-editor__toolbar-group\"\n data-group=\"text-property\"\n >\n <!-- Font Size -->\n <document-engine-select\n *ngIf=\"capabilities.hasFontSize\"\n [value]=\"activeFontSize\"\n placeholder=\"Font Size\"\n labelMode=\"static\"\n [disabled]=\"toolbarState.isReadOnly\"\n title=\"Font Size\"\n >\n <document-engine-icon documentEngineSelectLabel name=\"format_size\"></document-engine-icon>\n\n <button documentEngineSelectOption [value]=\"null\">Default</button>\n\n <button\n *ngFor=\"let option of fontSizeOptions; trackBy: trackByValue\"\n documentEngineSelectOption\n [value]=\"option.value\"\n (click)=\"setFontSize(option.value)\"\n >\n {{ option.label }}\n </button>\n </document-engine-select>\n\n <!-- Line Height -->\n <document-engine-select\n *ngIf=\"capabilities.hasLineHeight\"\n [value]=\"activeLineHeight\"\n placeholder=\"Line Height\"\n labelMode=\"static\"\n [disabled]=\"toolbarState.isReadOnly\"\n title=\"Line Height\"\n >\n <document-engine-icon documentEngineSelectLabel name=\"format_line_spacing\"></document-engine-icon>\n\n <button\n *ngFor=\"let option of lineHeightOptions; trackBy: trackByValue\"\n documentEngineSelectOption\n [value]=\"option.value\"\n (click)=\"setLineHeight(option.value)\"\n >\n {{ option.label }}\n </button>\n </document-engine-select>\n\n <!-- Text Case -->\n <document-engine-select\n *ngIf=\"capabilities.hasTextCase\"\n placeholder=\"Text Case\"\n labelMode=\"static\"\n [disabled]=\"toolbarState.isReadOnly\"\n title=\"Text Case\"\n >\n <document-engine-icon documentEngineSelectLabel name=\"match_case\"></document-engine-icon>\n\n <button\n *ngFor=\"let option of textCaseOptions; trackBy: trackByValue\"\n documentEngineSelectOption\n [value]=\"option.value\"\n (click)=\"runAndEmit('setTextCase', editor.chain().focus().textCase(option.value).run)\"\n >\n {{ option.label }}\n </button>\n </document-engine-select>\n\n <!-- Heading -->\n <document-engine-select\n *ngIf=\"capabilities.hasHeading\"\n [value]=\"activeHeading?.toString() || null\"\n placeholder=\"Normal text\"\n [disabled]=\"toolbarState.isReadOnly\"\n title=\"Heading\"\n >\n <button\n *ngFor=\"let option of headingOptions; trackBy: trackByValue\"\n documentEngineSelectOption\n [value]=\"option.value?.toString() || null\"\n [class]=\"option.class\"\n (click)=\"setHeading(option.value?.toString() || null)\"\n >\n {{ option.label }}\n </button>\n </document-engine-select>\n </div>\n\n <!-- Format Utilities: Reset Formatting & Colors -->\n <div\n *ngIf=\"capabilities.hasFormatUtilities\"\n class=\"document-engine-document-editor__toolbar-group\"\n data-group=\"format-utilities\"\n >\n <!-- Reset Formatting -->\n <button\n *ngIf=\"capabilities.hasResetFormat\"\n documentEngineButton\n size=\"icon\"\n (click)=\"runAndEmit('resetFormat', editor.chain().focus().resetFormat().run)\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canResetFormat\"\n title=\"Clear Formatting\"\n >\n <document-engine-icon name=\"format_clear\"></document-engine-icon>\n </button>\n\n <!-- Colors: Text/Background -->\n <button\n *ngIf=\"capabilities.hasColors\"\n documentEngineButton\n size=\"icon\"\n title=\"Text Color\"\n [disabled]=\"toolbarState.isReadOnly\"\n (click)=\"openColorBubble($event, 'text')\"\n >\n <document-engine-icon name=\"format_color_text\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasColors\"\n documentEngineButton\n size=\"icon\"\n title=\"Background Color\"\n [disabled]=\"toolbarState.isReadOnly\"\n (click)=\"openColorBubble($event, 'fill')\"\n >\n <document-engine-icon name=\"format_color_fill\"></document-engine-icon>\n </button>\n </div>\n\n <!-- Text Styles: Bold/Italic/Underline/Strike -->\n <div\n *ngIf=\"capabilities.basicInline\"\n class=\"document-engine-document-editor__toolbar-group\"\n data-group=\"basic-inline\"\n >\n <button\n *ngIf=\"capabilities.hasBold\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleBold\"\n (click)=\"runAndEmit('toggleBold', editor.chain().focus().toggleBold().run)\"\n [class.active]=\"editor.isActive('bold')\"\n title=\"Bold\"\n >\n <document-engine-icon name=\"format_bold\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasItalic\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleItalic\"\n (click)=\"runAndEmit('toggleItalic', editor.chain().focus().toggleItalic().run)\"\n [class.active]=\"editor.isActive('italic')\"\n title=\"Italic\"\n >\n <document-engine-icon name=\"format_italic\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasUnderline\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleUnderline\"\n (click)=\"runAndEmit('toggleUnderline', editor.chain().focus().toggleUnderline().run)\"\n [class.active]=\"editor.isActive('underline')\"\n title=\"Underline\"\n >\n <document-engine-icon name=\"format_underline\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasStrike\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleStrike\"\n (click)=\"runAndEmit('toggleStrike', editor.chain().focus().toggleStrike().run)\"\n [class.active]=\"editor.isActive('strike')\"\n title=\"Strikethrough\"\n >\n <document-engine-icon name=\"format_strikethrough\"></document-engine-icon>\n </button>\n </div>\n\n <!-- Text Styles: Sub/Super/Code/Link -->\n <div\n *ngIf=\"capabilities.advancedInline\"\n class=\"document-engine-document-editor__toolbar-group\"\n data-group=\"advanced-inline\"\n >\n <button\n *ngIf=\"capabilities.hasSubscript\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleSubscript\"\n (click)=\"runAndEmit('toggleSubscript', editor.chain().focus().toggleSubscript().run)\"\n [class.active]=\"editor.isActive('subscript')\"\n title=\"Subscript\"\n >\n <document-engine-icon name=\"subscript\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasSuperscript\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleSuperscript\"\n (click)=\"runAndEmit('toggleSuperscript', editor.chain().focus().toggleSuperscript().run)\"\n [class.active]=\"editor.isActive('superscript')\"\n title=\"Superscript\"\n >\n <document-engine-icon name=\"superscript\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasCode\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleCode\"\n (click)=\"runAndEmit('toggleCode', editor.chain().focus().toggleCode().run)\"\n [class.active]=\"editor.isActive('code')\"\n title=\"Code\"\n >\n <document-engine-icon name=\"code\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasLink\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleLink\"\n (click)=\"toggleLink()\"\n [class.active]=\"editor.isActive('link')\"\n [title]=\"editor.isActive('link') ? 'Unlink' : 'Link'\"\n >\n <document-engine-icon [name]=\"editor.isActive('link') ? 'link_off' : 'link'\"></document-engine-icon>\n </button>\n </div>\n\n <!-- Alignment & Layout: Text Align, Indent -->\n <div\n *ngIf=\"capabilities.hasAlignmentLayout\"\n class=\"document-engine-document-editor__toolbar-group\"\n data-group=\"alignment-layout\"\n >\n <!-- Text Alignment -->\n <document-engine-select\n *ngIf=\"capabilities.hasTextAlign\"\n [value]=\"activeTextAlign\"\n (valueChange)=\"setTextAlign($event)\"\n placeholder=\"Text Align\"\n labelMode=\"static\"\n [disabled]=\"toolbarState.isReadOnly\"\n title=\"Text Align\"\n >\n <document-engine-icon documentEngineSelectLabel [name]=\"activeTextAlignIcon\"></document-engine-icon>\n\n <button\n *ngFor=\"let option of textAlignOptions; trackBy: trackByValue\"\n documentEngineSelectOption\n [value]=\"option.value\"\n >\n <document-engine-icon [name]=\"option.icon\"></document-engine-icon>\n <span>{{ option.label }}</span>\n </button>\n </document-engine-select>\n\n <!-- Indent -->\n <button\n *ngIf=\"capabilities.hasIndent\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canDecreaseIndent\"\n (click)=\"runAndEmit('decreaseIndent', editor.chain().focus().decreaseIndent().run)\"\n title=\"Decrease Indent\"\n >\n <document-engine-icon name=\"format_indent_decrease\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasIndent\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canIncreaseIndent\"\n (click)=\"runAndEmit('increaseIndent', editor.chain().focus().increaseIndent().run)\"\n title=\"Increase Indent\"\n >\n <document-engine-icon name=\"format_indent_increase\"></document-engine-icon>\n </button>\n </div>\n\n <!-- Lists -->\n <div\n *ngIf=\"capabilities.hasAlignmentLayout\"\n class=\"document-engine-document-editor__toolbar-group\"\n data-group=\"alignment-layout\"\n >\n <!-- Lists: Bullet, Numbered (with style) -->\n <button\n *ngIf=\"capabilities.hasLists\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleBulletList\"\n (click)=\"runAndEmit('toggleBulletList', editor.chain().focus().toggleBulletList().run)\"\n [class.active]=\"editor.isActive('bulletList')\"\n title=\"Bullet List\"\n >\n <document-engine-icon name=\"format_list_bulleted\"></document-engine-icon>\n </button>\n\n <!-- Numbered List with Style -->\n <document-engine-select\n *ngIf=\"capabilities.hasLists\"\n placeholder=\"Numbered List\"\n labelMode=\"static\"\n [value]=\"\n editor.isActive('customOrderedList')\n ? editor.getAttributes('customOrderedList')['data-list-style-type'] || 'decimal'\n : null\n \"\n [disabled]=\"toolbarState.isReadOnly\"\n title=\"Numbered List\"\n >\n <document-engine-icon documentEngineSelectLabel name=\"format_list_numbered\"></document-engine-icon>\n\n <button\n *ngFor=\"let option of listStyleOptions; trackBy: trackByValue\"\n documentEngineSelectOption\n [value]=\"option.value\"\n (click)=\"setListStyle(option.value)\"\n [class.active]=\"\n editor.isActive('customOrderedList') &&\n editor.getAttributes('customOrderedList')['data-list-style-type'] === option.value\n \"\n >\n <span>{{ option.example }}</span>\n <span>{{ option.label }}</span>\n </button>\n </document-engine-select>\n </div>\n\n <!-- Block Types: Code Block, Blockquote -->\n <div\n *ngIf=\"capabilities.hasBlockTypes\"\n class=\"document-engine-document-editor__toolbar-group\"\n data-group=\"block-types\"\n >\n <!-- Blockquote -->\n <button\n *ngIf=\"capabilities.hasBlockquote\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleBlockquote\"\n (click)=\"runAndEmit('toggleBlockquote', editor.chain().focus().toggleBlockquote().run)\"\n [class.active]=\"editor.isActive('blockquote')\"\n title=\"Quote\"\n >\n <document-engine-icon name=\"format_quote\"></document-engine-icon>\n </button>\n </div>\n\n <!-- Insert Object: Table, Images, Special Chars, Page Break -->\n <div\n *ngIf=\"capabilities.hasInsertObject\"\n class=\"document-engine-document-editor__toolbar-group\"\n data-group=\"insert-object\"\n >\n <button\n *ngIf=\"capabilities.hasTable\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly\"\n (click)=\"openCreateTableBubble($event)\"\n [class.active]=\"editor.isActive('table')\"\n title=\"Table\"\n >\n <document-engine-icon name=\"table_rows\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasImages\"\n documentEngineButton\n size=\"icon\"\n title=\"Insert Image\"\n [disabled]=\"toolbarState.isReadOnly\"\n (click)=\"insertImage($event)\"\n >\n <document-engine-icon name=\"photo\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasSpecialChars\"\n documentEngineButton\n size=\"icon\"\n title=\"Special Character\"\n [disabled]=\"toolbarState.isReadOnly\"\n (click)=\"openSpecialCharsBubble($event)\"\n >\n <document-engine-icon name=\"special_character\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasPageBreak\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canInsertPageBreak\"\n (click)=\"runAndEmit('insertPageBreak', editor.chain().focus().insertPageBreak().run)\"\n title=\"Insert page break\"\n >\n <document-engine-icon name=\"insert_page_break\"></document-engine-icon>\n </button>\n </div>\n\n <!-- Business Features: Templates, Dynamic Fields, Editable Region -->\n <div\n *ngIf=\"capabilities.hasBusinessFeatures\"\n class=\"document-engine-document-editor__toolbar-group\"\n data-group=\"business-features\"\n >\n <button\n *ngIf=\"capabilities.hasDynamicFields\"\n documentEngineButton\n size=\"icon\"\n title=\"Dynamic Field\"\n [disabled]=\"toolbarState.isReadOnly\"\n (click)=\"openDynamicFieldsBubble($event)\"\n >\n <document-engine-icon name=\"dynamic_form\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasTemplates\"\n documentEngineButton\n size=\"icon\"\n title=\"Template\"\n [disabled]=\"toolbarState.isReadOnly\"\n (click)=\"openTemplateBubble($event)\"\n >\n <document-engine-icon name=\"description\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasEditableRegion\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"!toolbarState.canToggleEditableRegion || toolbarState.isReadOnly\"\n (click)=\"runAndEmit('toggleEditableRegion', editor.chain().focus().toggleEditableRegion().run)\"\n [class.active]=\"editor.isActive('editableRegion')\"\n title=\"Toggle editable region\"\n >\n <document-engine-icon name=\"restrict_edit\"></document-engine-icon>\n </button>\n </div>\n\n <!-- Actions: Clear Content, Markdown -->\n <div *ngIf=\"capabilities.hasActions\" class=\"document-engine-document-editor__toolbar-group\" data-group=\"actions\">\n <button\n *ngIf=\"capabilities.hasClearContent\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canClearContent\"\n (click)=\"runAndEmit('clear', editor.chain().focus().clear().run)\"\n title=\"Clear all content\"\n >\n <document-engine-icon name=\"clear_all\"></document-engine-icon>\n </button>\n\n <!-- <button\n *ngIf=\"capabilities.hasMarkdown\"\n documentEngineButton\n size=\"icon\"\n (click)=\"downloadAsMarkdown()\"\n title=\"Download as Markdown\"\n >\n <document-engine-icon name=\"markdown\"></document-engine-icon>\n </button> -->\n </div>\n\n <!-- Custom Toolbar Buttons -->\n <div\n *ngIf=\"customToolbarButtons.length > 0\"\n class=\"document-engine-document-editor__toolbar-group\"\n data-group=\"custom-buttons\"\n >\n <ng-container *ngFor=\"let button of customToolbarButtons; trackBy: trackByIndex\">\n <!-- Icon Button -->\n <button\n *ngIf=\"button.type === 'icon-button'\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly\"\n (click)=\"handleCustomIconButton(button)\"\n [title]=\"button.label || ''\"\n >\n <document-engine-icon [name]=\"button.icon\"></document-engine-icon>\n </button>\n\n <!-- Select Button -->\n <document-engine-select\n *ngIf=\"button.type === 'select-button'\"\n [value]=\"null\"\n labelMode=\"static\"\n [disabled]=\"toolbarState.isReadOnly\"\n [title]=\"button.label\"\n >\n <span documentEngineSelectLabel>{{ button.label }}</span>\n\n <button\n *ngFor=\"let option of button.options; trackBy: trackByValue\"\n documentEngineSelectOption\n [value]=\"option.value\"\n (click)=\"handleCustomSelectButton(button, option.value)\"\n >\n {{ option.label }}\n </button>\n </document-engine-select>\n </ng-container>\n </div>\n </div>\n\n <!-- Right side actions -->\n <div class=\"document-engine-document-editor__toolbar-right\">\n <button\n *ngIf=\"capabilities.hasPrintButton\"\n documentEngineButton\n size=\"icon\"\n (click)=\"printValue()\"\n title=\"Print Value\"\n >\n <document-engine-icon name=\"print\"></document-engine-icon>\n </button>\n </div>\n </div>\n\n <!-- Color Bubble Menu -->\n <document-engine-toolbar-bubble-menu\n #colorBubbleMenu\n [editor]=\"editor\"\n [config]=\"colorBubbleConfig\"\n ></document-engine-toolbar-bubble-menu>\n\n <!-- Link Bubble Menu -->\n <document-engine-editor-bubble-menu\n *ngIf=\"capabilities.hasLink\"\n #linkBubbleWrapper\n [editor]=\"editor\"\n [config]=\"linkBubbleConfig\"\n ></document-engine-editor-bubble-menu>\n\n <!-- Table Bubble Menu -->\n <document-engine-editor-bubble-menu\n *ngIf=\"capabilities.hasTable\"\n #tableBubbleWrapper\n [editor]=\"editor\"\n [config]=\"tableBubbleConfig\"\n ></document-engine-editor-bubble-menu>\n\n <!-- Image Bubble Menu -->\n <document-engine-toolbar-bubble-menu\n *ngIf=\"capabilities.hasImages\"\n #imageBubbleMenu\n [editor]=\"editor\"\n [config]=\"imageBubbleConfig\"\n ></document-engine-toolbar-bubble-menu>\n\n <!-- Special Characters Bubble Menu -->\n <document-engine-toolbar-bubble-menu\n *ngIf=\"capabilities.hasSpecialChars\"\n #specialCharsBubbleMenu\n [editor]=\"editor\"\n [config]=\"specialCharsBubbleConfig\"\n ></document-engine-toolbar-bubble-menu>\n\n <!-- Dynamic Fields Bubble Menu -->\n <document-engine-toolbar-bubble-menu\n *ngIf=\"capabilities.hasDynamicFields\"\n #dynamicFieldsBubbleMenu\n [editor]=\"editor\"\n [config]=\"dynamicFieldsBubbleConfig\"\n ></document-engine-toolbar-bubble-menu>\n\n <!-- Template Bubble Menu -->\n <document-engine-toolbar-bubble-menu\n *ngIf=\"capabilities.hasTemplates\"\n #templateBubbleMenu\n [editor]=\"editor\"\n [config]=\"templateBubbleConfig\"\n ></document-engine-toolbar-bubble-menu>\n\n <!-- Table Create Bubble Menu -->\n <document-engine-toolbar-bubble-menu\n *ngIf=\"capabilities.hasTable\"\n #tableCreateBubbleMenu\n [editor]=\"editor\"\n [config]=\"tableCreateBubbleConfig\"\n ></document-engine-toolbar-bubble-menu>\n</div>\n", styles: [":host{position:sticky;top:0;z-index:20}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: IconComponent, selector: "document-engine-icon", inputs: ["name"] }, { kind: "directive", type: ButtonDirective, selector: "button[documentEngineButton]", inputs: ["variant", "size"] }, { kind: "component", type: SelectComponent, selector: "document-engine-select", inputs: ["value", "disabled", "labelMode", "variant"], outputs: ["valueChange"] }, { kind: "directive", type: SelectLabelDirective, selector: "[documentEngineSelectLabel]" }, { kind: "directive", type: SelectOptionDirective, selector: "[documentEngineSelectOption]", inputs: ["value", "label", "disabled"] }, { kind: "component", type: ToolbarBubbleMenuComponent, selector: "document-engine-toolbar-bubble-menu", inputs: ["editor", "config"] }, { kind: "component", type: EditorBubbleMenuComponent, selector: "document-engine-editor-bubble-menu", inputs: ["editor", "config"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
6089
6058
|
}
|
|
6090
|
-
|
|
6091
|
-
|
|
6092
|
-
|
|
6093
|
-
|
|
6094
|
-
|
|
6095
|
-
|
|
6096
|
-
|
|
6097
|
-
|
|
6098
|
-
|
|
6099
|
-
|
|
6100
|
-
|
|
6101
|
-
}]
|
|
6102
|
-
}], propDecorators: {
|
|
6103
|
-
type:
|
|
6104
|
-
|
|
6105
|
-
|
|
6106
|
-
|
|
6107
|
-
|
|
6059
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: ToolbarComponent, decorators: [{
|
|
6060
|
+
type: Component,
|
|
6061
|
+
args: [{ selector: 'document-engine-toolbar', standalone: true, imports: [
|
|
6062
|
+
CommonModule,
|
|
6063
|
+
IconComponent,
|
|
6064
|
+
ButtonDirective,
|
|
6065
|
+
SelectComponent,
|
|
6066
|
+
SelectLabelDirective,
|
|
6067
|
+
SelectOptionDirective,
|
|
6068
|
+
ToolbarBubbleMenuComponent,
|
|
6069
|
+
EditorBubbleMenuComponent,
|
|
6070
|
+
], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"document-engine-document-editor__toolbar\">\n <div class=\"document-engine-document-editor__toolbar-content\">\n <div class=\"document-engine-document-editor__toolbar-left\">\n <!-- History: Undo/Redo -->\n <div *ngIf=\"capabilities.hasHistory\" class=\"document-engine-document-editor__toolbar-group\" data-group=\"history\">\n <button\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canUndo\"\n (click)=\"runAndEmit('undo', editor.chain().focus().undo().run)\"\n title=\"Undo (Ctrl+Z)\"\n >\n <document-engine-icon name=\"undo\"></document-engine-icon>\n </button>\n\n <button\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canRedo\"\n (click)=\"runAndEmit('redo', editor.chain().focus().redo().run)\"\n title=\"Redo\"\n >\n <document-engine-icon name=\"redo\"></document-engine-icon>\n </button>\n </div>\n\n <!-- Text Formatting: Font Size, Line Height, Text Case, Heading -->\n <div\n *ngIf=\"capabilities.hasTextProperty\"\n class=\"document-engine-document-editor__toolbar-group\"\n data-group=\"text-property\"\n >\n <!-- Font Size -->\n <document-engine-select\n *ngIf=\"capabilities.hasFontSize\"\n [value]=\"activeFontSize\"\n placeholder=\"Font Size\"\n labelMode=\"static\"\n [disabled]=\"toolbarState.isReadOnly\"\n title=\"Font Size\"\n >\n <document-engine-icon documentEngineSelectLabel name=\"format_size\"></document-engine-icon>\n\n <button documentEngineSelectOption [value]=\"null\">Default</button>\n\n <button\n *ngFor=\"let option of fontSizeOptions; trackBy: trackByValue\"\n documentEngineSelectOption\n [value]=\"option.value\"\n (click)=\"setFontSize(option.value)\"\n >\n {{ option.label }}\n </button>\n </document-engine-select>\n\n <!-- Line Height -->\n <document-engine-select\n *ngIf=\"capabilities.hasLineHeight\"\n [value]=\"activeLineHeight\"\n placeholder=\"Line Height\"\n labelMode=\"static\"\n [disabled]=\"toolbarState.isReadOnly\"\n title=\"Line Height\"\n >\n <document-engine-icon documentEngineSelectLabel name=\"format_line_spacing\"></document-engine-icon>\n\n <button\n *ngFor=\"let option of lineHeightOptions; trackBy: trackByValue\"\n documentEngineSelectOption\n [value]=\"option.value\"\n (click)=\"setLineHeight(option.value)\"\n >\n {{ option.label }}\n </button>\n </document-engine-select>\n\n <!-- Text Case -->\n <document-engine-select\n *ngIf=\"capabilities.hasTextCase\"\n placeholder=\"Text Case\"\n labelMode=\"static\"\n [disabled]=\"toolbarState.isReadOnly\"\n title=\"Text Case\"\n >\n <document-engine-icon documentEngineSelectLabel name=\"match_case\"></document-engine-icon>\n\n <button\n *ngFor=\"let option of textCaseOptions; trackBy: trackByValue\"\n documentEngineSelectOption\n [value]=\"option.value\"\n (click)=\"runAndEmit('setTextCase', editor.chain().focus().textCase(option.value).run)\"\n >\n {{ option.label }}\n </button>\n </document-engine-select>\n\n <!-- Heading -->\n <document-engine-select\n *ngIf=\"capabilities.hasHeading\"\n [value]=\"activeHeading?.toString() || null\"\n placeholder=\"Normal text\"\n [disabled]=\"toolbarState.isReadOnly\"\n title=\"Heading\"\n >\n <button\n *ngFor=\"let option of headingOptions; trackBy: trackByValue\"\n documentEngineSelectOption\n [value]=\"option.value?.toString() || null\"\n [class]=\"option.class\"\n (click)=\"setHeading(option.value?.toString() || null)\"\n >\n {{ option.label }}\n </button>\n </document-engine-select>\n </div>\n\n <!-- Format Utilities: Reset Formatting & Colors -->\n <div\n *ngIf=\"capabilities.hasFormatUtilities\"\n class=\"document-engine-document-editor__toolbar-group\"\n data-group=\"format-utilities\"\n >\n <!-- Reset Formatting -->\n <button\n *ngIf=\"capabilities.hasResetFormat\"\n documentEngineButton\n size=\"icon\"\n (click)=\"runAndEmit('resetFormat', editor.chain().focus().resetFormat().run)\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canResetFormat\"\n title=\"Clear Formatting\"\n >\n <document-engine-icon name=\"format_clear\"></document-engine-icon>\n </button>\n\n <!-- Colors: Text/Background -->\n <button\n *ngIf=\"capabilities.hasColors\"\n documentEngineButton\n size=\"icon\"\n title=\"Text Color\"\n [disabled]=\"toolbarState.isReadOnly\"\n (click)=\"openColorBubble($event, 'text')\"\n >\n <document-engine-icon name=\"format_color_text\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasColors\"\n documentEngineButton\n size=\"icon\"\n title=\"Background Color\"\n [disabled]=\"toolbarState.isReadOnly\"\n (click)=\"openColorBubble($event, 'fill')\"\n >\n <document-engine-icon name=\"format_color_fill\"></document-engine-icon>\n </button>\n </div>\n\n <!-- Text Styles: Bold/Italic/Underline/Strike -->\n <div\n *ngIf=\"capabilities.basicInline\"\n class=\"document-engine-document-editor__toolbar-group\"\n data-group=\"basic-inline\"\n >\n <button\n *ngIf=\"capabilities.hasBold\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleBold\"\n (click)=\"runAndEmit('toggleBold', editor.chain().focus().toggleBold().run)\"\n [class.active]=\"editor.isActive('bold')\"\n title=\"Bold\"\n >\n <document-engine-icon name=\"format_bold\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasItalic\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleItalic\"\n (click)=\"runAndEmit('toggleItalic', editor.chain().focus().toggleItalic().run)\"\n [class.active]=\"editor.isActive('italic')\"\n title=\"Italic\"\n >\n <document-engine-icon name=\"format_italic\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasUnderline\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleUnderline\"\n (click)=\"runAndEmit('toggleUnderline', editor.chain().focus().toggleUnderline().run)\"\n [class.active]=\"editor.isActive('underline')\"\n title=\"Underline\"\n >\n <document-engine-icon name=\"format_underline\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasStrike\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleStrike\"\n (click)=\"runAndEmit('toggleStrike', editor.chain().focus().toggleStrike().run)\"\n [class.active]=\"editor.isActive('strike')\"\n title=\"Strikethrough\"\n >\n <document-engine-icon name=\"format_strikethrough\"></document-engine-icon>\n </button>\n </div>\n\n <!-- Text Styles: Sub/Super/Code/Link -->\n <div\n *ngIf=\"capabilities.advancedInline\"\n class=\"document-engine-document-editor__toolbar-group\"\n data-group=\"advanced-inline\"\n >\n <button\n *ngIf=\"capabilities.hasSubscript\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleSubscript\"\n (click)=\"runAndEmit('toggleSubscript', editor.chain().focus().toggleSubscript().run)\"\n [class.active]=\"editor.isActive('subscript')\"\n title=\"Subscript\"\n >\n <document-engine-icon name=\"subscript\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasSuperscript\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleSuperscript\"\n (click)=\"runAndEmit('toggleSuperscript', editor.chain().focus().toggleSuperscript().run)\"\n [class.active]=\"editor.isActive('superscript')\"\n title=\"Superscript\"\n >\n <document-engine-icon name=\"superscript\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasCode\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleCode\"\n (click)=\"runAndEmit('toggleCode', editor.chain().focus().toggleCode().run)\"\n [class.active]=\"editor.isActive('code')\"\n title=\"Code\"\n >\n <document-engine-icon name=\"code\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasLink\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleLink\"\n (click)=\"toggleLink()\"\n [class.active]=\"editor.isActive('link')\"\n [title]=\"editor.isActive('link') ? 'Unlink' : 'Link'\"\n >\n <document-engine-icon [name]=\"editor.isActive('link') ? 'link_off' : 'link'\"></document-engine-icon>\n </button>\n </div>\n\n <!-- Alignment & Layout: Text Align, Indent -->\n <div\n *ngIf=\"capabilities.hasAlignmentLayout\"\n class=\"document-engine-document-editor__toolbar-group\"\n data-group=\"alignment-layout\"\n >\n <!-- Text Alignment -->\n <document-engine-select\n *ngIf=\"capabilities.hasTextAlign\"\n [value]=\"activeTextAlign\"\n (valueChange)=\"setTextAlign($event)\"\n placeholder=\"Text Align\"\n labelMode=\"static\"\n [disabled]=\"toolbarState.isReadOnly\"\n title=\"Text Align\"\n >\n <document-engine-icon documentEngineSelectLabel [name]=\"activeTextAlignIcon\"></document-engine-icon>\n\n <button\n *ngFor=\"let option of textAlignOptions; trackBy: trackByValue\"\n documentEngineSelectOption\n [value]=\"option.value\"\n >\n <document-engine-icon [name]=\"option.icon\"></document-engine-icon>\n <span>{{ option.label }}</span>\n </button>\n </document-engine-select>\n\n <!-- Indent -->\n <button\n *ngIf=\"capabilities.hasIndent\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canDecreaseIndent\"\n (click)=\"runAndEmit('decreaseIndent', editor.chain().focus().decreaseIndent().run)\"\n title=\"Decrease Indent\"\n >\n <document-engine-icon name=\"format_indent_decrease\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasIndent\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canIncreaseIndent\"\n (click)=\"runAndEmit('increaseIndent', editor.chain().focus().increaseIndent().run)\"\n title=\"Increase Indent\"\n >\n <document-engine-icon name=\"format_indent_increase\"></document-engine-icon>\n </button>\n </div>\n\n <!-- Lists -->\n <div\n *ngIf=\"capabilities.hasAlignmentLayout\"\n class=\"document-engine-document-editor__toolbar-group\"\n data-group=\"alignment-layout\"\n >\n <!-- Lists: Bullet, Numbered (with style) -->\n <button\n *ngIf=\"capabilities.hasLists\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleBulletList\"\n (click)=\"runAndEmit('toggleBulletList', editor.chain().focus().toggleBulletList().run)\"\n [class.active]=\"editor.isActive('bulletList')\"\n title=\"Bullet List\"\n >\n <document-engine-icon name=\"format_list_bulleted\"></document-engine-icon>\n </button>\n\n <!-- Numbered List with Style -->\n <document-engine-select\n *ngIf=\"capabilities.hasLists\"\n placeholder=\"Numbered List\"\n labelMode=\"static\"\n [value]=\"\n editor.isActive('customOrderedList')\n ? editor.getAttributes('customOrderedList')['data-list-style-type'] || 'decimal'\n : null\n \"\n [disabled]=\"toolbarState.isReadOnly\"\n title=\"Numbered List\"\n >\n <document-engine-icon documentEngineSelectLabel name=\"format_list_numbered\"></document-engine-icon>\n\n <button\n *ngFor=\"let option of listStyleOptions; trackBy: trackByValue\"\n documentEngineSelectOption\n [value]=\"option.value\"\n (click)=\"setListStyle(option.value)\"\n [class.active]=\"\n editor.isActive('customOrderedList') &&\n editor.getAttributes('customOrderedList')['data-list-style-type'] === option.value\n \"\n >\n <span>{{ option.example }}</span>\n <span>{{ option.label }}</span>\n </button>\n </document-engine-select>\n </div>\n\n <!-- Block Types: Code Block, Blockquote -->\n <div\n *ngIf=\"capabilities.hasBlockTypes\"\n class=\"document-engine-document-editor__toolbar-group\"\n data-group=\"block-types\"\n >\n <!-- Blockquote -->\n <button\n *ngIf=\"capabilities.hasBlockquote\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleBlockquote\"\n (click)=\"runAndEmit('toggleBlockquote', editor.chain().focus().toggleBlockquote().run)\"\n [class.active]=\"editor.isActive('blockquote')\"\n title=\"Quote\"\n >\n <document-engine-icon name=\"format_quote\"></document-engine-icon>\n </button>\n </div>\n\n <!-- Insert Object: Table, Images, Special Chars, Page Break -->\n <div\n *ngIf=\"capabilities.hasInsertObject\"\n class=\"document-engine-document-editor__toolbar-group\"\n data-group=\"insert-object\"\n >\n <button\n *ngIf=\"capabilities.hasTable\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly\"\n (click)=\"openCreateTableBubble($event)\"\n [class.active]=\"editor.isActive('table')\"\n title=\"Table\"\n >\n <document-engine-icon name=\"table_rows\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasImages\"\n documentEngineButton\n size=\"icon\"\n title=\"Insert Image\"\n [disabled]=\"toolbarState.isReadOnly\"\n (click)=\"insertImage($event)\"\n >\n <document-engine-icon name=\"photo\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasSpecialChars\"\n documentEngineButton\n size=\"icon\"\n title=\"Special Character\"\n [disabled]=\"toolbarState.isReadOnly\"\n (click)=\"openSpecialCharsBubble($event)\"\n >\n <document-engine-icon name=\"special_character\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasPageBreak\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canInsertPageBreak\"\n (click)=\"runAndEmit('insertPageBreak', editor.chain().focus().insertPageBreak().run)\"\n title=\"Insert page break\"\n >\n <document-engine-icon name=\"insert_page_break\"></document-engine-icon>\n </button>\n </div>\n\n <!-- Business Features: Templates, Dynamic Fields, Editable Region -->\n <div\n *ngIf=\"capabilities.hasBusinessFeatures\"\n class=\"document-engine-document-editor__toolbar-group\"\n data-group=\"business-features\"\n >\n <button\n *ngIf=\"capabilities.hasDynamicFields\"\n documentEngineButton\n size=\"icon\"\n title=\"Dynamic Field\"\n [disabled]=\"toolbarState.isReadOnly\"\n (click)=\"openDynamicFieldsBubble($event)\"\n >\n <document-engine-icon name=\"dynamic_form\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasTemplates\"\n documentEngineButton\n size=\"icon\"\n title=\"Template\"\n [disabled]=\"toolbarState.isReadOnly\"\n (click)=\"openTemplateBubble($event)\"\n >\n <document-engine-icon name=\"description\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasEditableRegion\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"!toolbarState.canToggleEditableRegion || toolbarState.isReadOnly\"\n (click)=\"runAndEmit('toggleEditableRegion', editor.chain().focus().toggleEditableRegion().run)\"\n [class.active]=\"editor.isActive('editableRegion')\"\n title=\"Toggle editable region\"\n >\n <document-engine-icon name=\"restrict_edit\"></document-engine-icon>\n </button>\n </div>\n\n <!-- Actions: Clear Content, Markdown -->\n <div *ngIf=\"capabilities.hasActions\" class=\"document-engine-document-editor__toolbar-group\" data-group=\"actions\">\n <button\n *ngIf=\"capabilities.hasClearContent\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canClearContent\"\n (click)=\"runAndEmit('clear', editor.chain().focus().clear().run)\"\n title=\"Clear all content\"\n >\n <document-engine-icon name=\"clear_all\"></document-engine-icon>\n </button>\n\n <!-- <button\n *ngIf=\"capabilities.hasMarkdown\"\n documentEngineButton\n size=\"icon\"\n (click)=\"downloadAsMarkdown()\"\n title=\"Download as Markdown\"\n >\n <document-engine-icon name=\"markdown\"></document-engine-icon>\n </button> -->\n </div>\n\n <!-- Custom Toolbar Buttons -->\n <div\n *ngIf=\"customToolbarButtons.length > 0\"\n class=\"document-engine-document-editor__toolbar-group\"\n data-group=\"custom-buttons\"\n >\n <ng-container *ngFor=\"let button of customToolbarButtons; trackBy: trackByIndex\">\n <!-- Icon Button -->\n <button\n *ngIf=\"button.type === 'icon-button'\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly\"\n (click)=\"handleCustomIconButton(button)\"\n [title]=\"button.label || ''\"\n >\n <document-engine-icon [name]=\"button.icon\"></document-engine-icon>\n </button>\n\n <!-- Select Button -->\n <document-engine-select\n *ngIf=\"button.type === 'select-button'\"\n [value]=\"null\"\n labelMode=\"static\"\n [disabled]=\"toolbarState.isReadOnly\"\n [title]=\"button.label\"\n >\n <span documentEngineSelectLabel>{{ button.label }}</span>\n\n <button\n *ngFor=\"let option of button.options; trackBy: trackByValue\"\n documentEngineSelectOption\n [value]=\"option.value\"\n (click)=\"handleCustomSelectButton(button, option.value)\"\n >\n {{ option.label }}\n </button>\n </document-engine-select>\n </ng-container>\n </div>\n </div>\n\n <!-- Right side actions -->\n <div class=\"document-engine-document-editor__toolbar-right\">\n <button\n *ngIf=\"capabilities.hasPrintButton\"\n documentEngineButton\n size=\"icon\"\n (click)=\"printValue()\"\n title=\"Print Value\"\n >\n <document-engine-icon name=\"print\"></document-engine-icon>\n </button>\n </div>\n </div>\n\n <!-- Color Bubble Menu -->\n <document-engine-toolbar-bubble-menu\n #colorBubbleMenu\n [editor]=\"editor\"\n [config]=\"colorBubbleConfig\"\n ></document-engine-toolbar-bubble-menu>\n\n <!-- Link Bubble Menu -->\n <document-engine-editor-bubble-menu\n *ngIf=\"capabilities.hasLink\"\n #linkBubbleWrapper\n [editor]=\"editor\"\n [config]=\"linkBubbleConfig\"\n ></document-engine-editor-bubble-menu>\n\n <!-- Table Bubble Menu -->\n <document-engine-editor-bubble-menu\n *ngIf=\"capabilities.hasTable\"\n #tableBubbleWrapper\n [editor]=\"editor\"\n [config]=\"tableBubbleConfig\"\n ></document-engine-editor-bubble-menu>\n\n <!-- Image Bubble Menu -->\n <document-engine-toolbar-bubble-menu\n *ngIf=\"capabilities.hasImages\"\n #imageBubbleMenu\n [editor]=\"editor\"\n [config]=\"imageBubbleConfig\"\n ></document-engine-toolbar-bubble-menu>\n\n <!-- Special Characters Bubble Menu -->\n <document-engine-toolbar-bubble-menu\n *ngIf=\"capabilities.hasSpecialChars\"\n #specialCharsBubbleMenu\n [editor]=\"editor\"\n [config]=\"specialCharsBubbleConfig\"\n ></document-engine-toolbar-bubble-menu>\n\n <!-- Dynamic Fields Bubble Menu -->\n <document-engine-toolbar-bubble-menu\n *ngIf=\"capabilities.hasDynamicFields\"\n #dynamicFieldsBubbleMenu\n [editor]=\"editor\"\n [config]=\"dynamicFieldsBubbleConfig\"\n ></document-engine-toolbar-bubble-menu>\n\n <!-- Template Bubble Menu -->\n <document-engine-toolbar-bubble-menu\n *ngIf=\"capabilities.hasTemplates\"\n #templateBubbleMenu\n [editor]=\"editor\"\n [config]=\"templateBubbleConfig\"\n ></document-engine-toolbar-bubble-menu>\n\n <!-- Table Create Bubble Menu -->\n <document-engine-toolbar-bubble-menu\n *ngIf=\"capabilities.hasTable\"\n #tableCreateBubbleMenu\n [editor]=\"editor\"\n [config]=\"tableCreateBubbleConfig\"\n ></document-engine-toolbar-bubble-menu>\n</div>\n", styles: [":host{position:sticky;top:0;z-index:20}\n"] }]
|
|
6071
|
+
}], propDecorators: { editor: [{
|
|
6072
|
+
type: Input
|
|
6073
|
+
}], capabilities: [{
|
|
6074
|
+
type: Input
|
|
6075
|
+
}], config: [{
|
|
6076
|
+
type: Input
|
|
6077
|
+
}], action: [{
|
|
6078
|
+
type: Output
|
|
6079
|
+
}], colorBubbleMenu: [{
|
|
6080
|
+
type: ViewChild,
|
|
6081
|
+
args: ['colorBubbleMenu']
|
|
6082
|
+
}], linkBubbleWrapper: [{
|
|
6083
|
+
type: ViewChild,
|
|
6084
|
+
args: ['linkBubbleWrapper']
|
|
6085
|
+
}], imageBubbleMenu: [{
|
|
6086
|
+
type: ViewChild,
|
|
6087
|
+
args: ['imageBubbleMenu']
|
|
6088
|
+
}], specialCharsBubbleMenu: [{
|
|
6089
|
+
type: ViewChild,
|
|
6090
|
+
args: ['specialCharsBubbleMenu']
|
|
6091
|
+
}], dynamicFieldsBubbleMenu: [{
|
|
6092
|
+
type: ViewChild,
|
|
6093
|
+
args: ['dynamicFieldsBubbleMenu']
|
|
6094
|
+
}], templateBubbleMenu: [{
|
|
6095
|
+
type: ViewChild,
|
|
6096
|
+
args: ['templateBubbleMenu']
|
|
6097
|
+
}], tableCreateBubbleMenu: [{
|
|
6098
|
+
type: ViewChild,
|
|
6099
|
+
args: ['tableCreateBubbleMenu']
|
|
6108
6100
|
}] } });
|
|
6109
6101
|
|
|
6110
6102
|
/**
|
|
6111
|
-
*
|
|
6112
|
-
*
|
|
6113
|
-
* before it's set into the TipTap editor.
|
|
6114
|
-
*
|
|
6115
|
-
* This is optional - if not provided, the editor will use the HTML content as-is.
|
|
6116
|
-
*
|
|
6117
|
-
* @example
|
|
6118
|
-
* ```typescript
|
|
6119
|
-
* // In your component or module providers
|
|
6120
|
-
* {
|
|
6121
|
-
* provide: EDITOR_HTML_PREPROCESSOR,
|
|
6122
|
-
* useValue: (html: string) => {
|
|
6123
|
-
* // Your custom preprocessing logic
|
|
6124
|
-
* return html.replace(/old/g, 'new');
|
|
6125
|
-
* }
|
|
6126
|
-
* }
|
|
6127
|
-
* ```
|
|
6103
|
+
* Character Count component for document editor footer
|
|
6104
|
+
* Displays character/word count with optional limit and color coding
|
|
6128
6105
|
*/
|
|
6129
|
-
|
|
6130
|
-
|
|
6131
|
-
|
|
6132
|
-
|
|
6133
|
-
|
|
6134
|
-
|
|
6135
|
-
|
|
6136
|
-
|
|
6137
|
-
|
|
6138
|
-
|
|
6139
|
-
this._editor = editor;
|
|
6140
|
-
// Store initial editable state immediately when editor is set
|
|
6141
|
-
// This ensures we capture the state before any Forms API calls
|
|
6142
|
-
if (this.initialEditableState === undefined && editor) {
|
|
6143
|
-
this.initialEditableState = editor.isEditable;
|
|
6144
|
-
}
|
|
6145
|
-
}
|
|
6146
|
-
get editor() {
|
|
6147
|
-
if (!this._editor) {
|
|
6148
|
-
throw new Error('Editor must be set before use.');
|
|
6106
|
+
class CharacterCountComponent {
|
|
6107
|
+
cdr = inject(ChangeDetectorRef);
|
|
6108
|
+
editor;
|
|
6109
|
+
config;
|
|
6110
|
+
mode = 'characters';
|
|
6111
|
+
count = 0;
|
|
6112
|
+
limit = null;
|
|
6113
|
+
updateCount = () => {
|
|
6114
|
+
if (!this.editor) {
|
|
6115
|
+
return;
|
|
6149
6116
|
}
|
|
6150
|
-
|
|
6151
|
-
|
|
6152
|
-
|
|
6153
|
-
|
|
6154
|
-
|
|
6155
|
-
|
|
6156
|
-
|
|
6157
|
-
|
|
6158
|
-
|
|
6159
|
-
// Writes a new value to the element.
|
|
6160
|
-
// This methods is called when programmatic changes from model to view are requested.
|
|
6161
|
-
writeValue(value) {
|
|
6162
|
-
// Apply preprocessor if value is a string (HTML) and preprocessor is available
|
|
6163
|
-
let processedValue = value;
|
|
6164
|
-
if (typeof value === 'string' && this.htmlPreprocessor) {
|
|
6165
|
-
processedValue = this.htmlPreprocessor(value);
|
|
6117
|
+
// Get count from editor storage if available
|
|
6118
|
+
const characterCountStorage = this.editor.storage.characterCount;
|
|
6119
|
+
if (characterCountStorage) {
|
|
6120
|
+
if (this.mode === 'characters') {
|
|
6121
|
+
this.count = characterCountStorage.characters?.() || 0;
|
|
6122
|
+
}
|
|
6123
|
+
else {
|
|
6124
|
+
this.count = characterCountStorage.words?.() || 0;
|
|
6125
|
+
}
|
|
6166
6126
|
}
|
|
6167
|
-
|
|
6168
|
-
|
|
6169
|
-
|
|
6170
|
-
|
|
6171
|
-
|
|
6172
|
-
|
|
6173
|
-
|
|
6174
|
-
|
|
6175
|
-
|
|
6176
|
-
}
|
|
6177
|
-
// Called by the forms api to enable or disable the element
|
|
6178
|
-
setDisabledState(isDisabled) {
|
|
6179
|
-
// Ensure we have captured the initial state (fallback in case setter wasn't called)
|
|
6180
|
-
if (this.initialEditableState === undefined && this.editor) {
|
|
6181
|
-
this.initialEditableState = this.editor.isEditable;
|
|
6127
|
+
else {
|
|
6128
|
+
// Fallback: calculate from text
|
|
6129
|
+
const text = this.editor.getText();
|
|
6130
|
+
if (this.mode === 'characters') {
|
|
6131
|
+
this.count = text.length;
|
|
6132
|
+
}
|
|
6133
|
+
else {
|
|
6134
|
+
this.count = text.trim() ? text.trim().split(/\s+/).length : 0;
|
|
6135
|
+
}
|
|
6182
6136
|
}
|
|
6183
|
-
//
|
|
6184
|
-
|
|
6185
|
-
if (
|
|
6186
|
-
this.
|
|
6137
|
+
// Get limit from config
|
|
6138
|
+
const characterCountConfig = this.config?.characterCount;
|
|
6139
|
+
if (characterCountConfig && typeof characterCountConfig === 'object') {
|
|
6140
|
+
this.limit = characterCountConfig.limit || null;
|
|
6187
6141
|
}
|
|
6188
|
-
|
|
6189
|
-
|
|
6190
|
-
}
|
|
6191
|
-
handleChange = ({ editor, transaction }) => {
|
|
6192
|
-
if (!transaction.docChanged)
|
|
6193
|
-
return;
|
|
6194
|
-
// Needed for ChangeDetectionStrategy.OnPush to get notified about changes
|
|
6195
|
-
this.changeDetectorRef.markForCheck();
|
|
6196
|
-
if (this.outputFormat === 'html') {
|
|
6197
|
-
this.onChange(editor.getHTML());
|
|
6198
|
-
return;
|
|
6142
|
+
else {
|
|
6143
|
+
this.limit = null;
|
|
6199
6144
|
}
|
|
6200
|
-
this.
|
|
6145
|
+
this.cdr.markForCheck();
|
|
6201
6146
|
};
|
|
6202
6147
|
ngOnInit() {
|
|
6203
|
-
|
|
6204
|
-
|
|
6205
|
-
const { innerHTML } = this.elRef.nativeElement;
|
|
6206
|
-
this.elRef.nativeElement.innerHTML = '';
|
|
6207
|
-
// insert the editor in the dom
|
|
6208
|
-
this.elRef.nativeElement.append(...Array.from(editor.options.element?.childNodes || []));
|
|
6209
|
-
// update the options for the editor
|
|
6210
|
-
editor.setOptions({ element: this.elRef.nativeElement });
|
|
6211
|
-
// update content to the editor
|
|
6212
|
-
if (innerHTML) {
|
|
6213
|
-
// Apply preprocessor to initial content if available
|
|
6214
|
-
const processedContent = this.htmlPreprocessor ? this.htmlPreprocessor(innerHTML) : innerHTML;
|
|
6215
|
-
editor.chain().setContent(processedContent, { emitUpdate: false }).run();
|
|
6148
|
+
if (!this.editor) {
|
|
6149
|
+
return;
|
|
6216
6150
|
}
|
|
6217
|
-
//
|
|
6218
|
-
editor.on('
|
|
6219
|
-
|
|
6220
|
-
|
|
6221
|
-
|
|
6222
|
-
editor.on('update', this.handleChange);
|
|
6223
|
-
// Needed for ChangeDetectionStrategy.OnPush to get notified
|
|
6224
|
-
editor.on('selectionUpdate', () => this.changeDetectorRef.markForCheck());
|
|
6151
|
+
// Subscribe to update events to keep count in sync
|
|
6152
|
+
this.editor.on('update', this.updateCount);
|
|
6153
|
+
this.editor.on('transaction', this.updateCount);
|
|
6154
|
+
// Initialize count once
|
|
6155
|
+
this.updateCount();
|
|
6225
6156
|
}
|
|
6226
|
-
|
|
6227
|
-
this.
|
|
6157
|
+
ngOnDestroy() {
|
|
6158
|
+
if (this.editor) {
|
|
6159
|
+
this.editor.off('update', this.updateCount);
|
|
6160
|
+
this.editor.off('transaction', this.updateCount);
|
|
6161
|
+
}
|
|
6228
6162
|
}
|
|
6229
|
-
|
|
6230
|
-
|
|
6231
|
-
|
|
6232
|
-
|
|
6233
|
-
|
|
6234
|
-
|
|
6235
|
-
|
|
6236
|
-
|
|
6163
|
+
isNearLimit() {
|
|
6164
|
+
if (!this.limit) {
|
|
6165
|
+
return false;
|
|
6166
|
+
}
|
|
6167
|
+
// Consider near limit when >= 90% of limit
|
|
6168
|
+
return this.count >= this.limit * 0.9 && this.count < this.limit;
|
|
6169
|
+
}
|
|
6170
|
+
isOverLimit() {
|
|
6171
|
+
if (!this.limit) {
|
|
6172
|
+
return false;
|
|
6173
|
+
}
|
|
6174
|
+
return this.count > this.limit;
|
|
6175
|
+
}
|
|
6176
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: CharacterCountComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
6177
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.10", type: CharacterCountComponent, isStandalone: true, selector: "document-engine-character-count", inputs: { editor: "editor", config: "config", mode: "mode" }, ngImport: i0, template: "<div class=\"document-engine-character-count\">\n <span\n class=\"document-engine-character-count__text\"\n [class.document-engine-character-count__text--muted]=\"!isNearLimit() && !isOverLimit()\"\n [class.document-engine-character-count__text--warning]=\"isNearLimit() && !isOverLimit()\"\n [class.document-engine-character-count__text--error]=\"isOverLimit()\"\n >\n <ng-container *ngIf=\"mode === 'characters'\">\n <ng-container *ngIf=\"limit; else noLimit\"> {{ count }} / {{ limit }} characters. </ng-container>\n <ng-template #noLimit> {{ count }} {{ count === 1 ? 'character' : 'characters' }}. </ng-template>\n </ng-container>\n <ng-container *ngIf=\"mode === 'words'\"> {{ count }} {{ count === 1 ? 'word' : 'words' }}. </ng-container>\n </span>\n</div>\n", styles: [".document-engine-character-count{display:flex;justify-content:flex-end}.document-engine-character-count__text{font-size:.875rem;transition:color .2s ease-in-out}.document-engine-character-count__text--muted{color:var(--muted-foreground, #6b7280)}.document-engine-character-count__text--warning{color:var(--warning, #d97706)}.document-engine-character-count__text--error{color:var(--destructive, #dc2626)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
6237
6178
|
}
|
|
6238
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type:
|
|
6239
|
-
type:
|
|
6240
|
-
args: [{
|
|
6241
|
-
selector: 'tiptap[editor], [tiptap][editor], tiptap-editor[editor], [tiptapEditor][editor]',
|
|
6242
|
-
providers: [
|
|
6243
|
-
{
|
|
6244
|
-
provide: NG_VALUE_ACCESSOR,
|
|
6245
|
-
useExisting: forwardRef(() => TiptapEditorDirective),
|
|
6246
|
-
multi: true,
|
|
6247
|
-
},
|
|
6248
|
-
],
|
|
6249
|
-
standalone: true,
|
|
6250
|
-
host: {
|
|
6251
|
-
['class']: 'tiptap-editor no-twp',
|
|
6252
|
-
},
|
|
6253
|
-
}]
|
|
6179
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: CharacterCountComponent, decorators: [{
|
|
6180
|
+
type: Component,
|
|
6181
|
+
args: [{ selector: 'document-engine-character-count', standalone: true, imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"document-engine-character-count\">\n <span\n class=\"document-engine-character-count__text\"\n [class.document-engine-character-count__text--muted]=\"!isNearLimit() && !isOverLimit()\"\n [class.document-engine-character-count__text--warning]=\"isNearLimit() && !isOverLimit()\"\n [class.document-engine-character-count__text--error]=\"isOverLimit()\"\n >\n <ng-container *ngIf=\"mode === 'characters'\">\n <ng-container *ngIf=\"limit; else noLimit\"> {{ count }} / {{ limit }} characters. </ng-container>\n <ng-template #noLimit> {{ count }} {{ count === 1 ? 'character' : 'characters' }}. </ng-template>\n </ng-container>\n <ng-container *ngIf=\"mode === 'words'\"> {{ count }} {{ count === 1 ? 'word' : 'words' }}. </ng-container>\n </span>\n</div>\n", styles: [".document-engine-character-count{display:flex;justify-content:flex-end}.document-engine-character-count__text{font-size:.875rem;transition:color .2s ease-in-out}.document-engine-character-count__text--muted{color:var(--muted-foreground, #6b7280)}.document-engine-character-count__text--warning{color:var(--warning, #d97706)}.document-engine-character-count__text--error{color:var(--destructive, #dc2626)}\n"] }]
|
|
6254
6182
|
}], propDecorators: { editor: [{
|
|
6255
6183
|
type: Input
|
|
6256
|
-
}],
|
|
6184
|
+
}], config: [{
|
|
6185
|
+
type: Input
|
|
6186
|
+
}], mode: [{
|
|
6257
6187
|
type: Input
|
|
6258
6188
|
}] } });
|
|
6259
6189
|
|
|
6260
|
-
|
|
6261
|
-
|
|
6262
|
-
|
|
6263
|
-
|
|
6190
|
+
/**
|
|
6191
|
+
* Footer component for document editor
|
|
6192
|
+
* Container for footer components like character count
|
|
6193
|
+
*/
|
|
6194
|
+
class FooterComponent {
|
|
6195
|
+
editor;
|
|
6196
|
+
config;
|
|
6197
|
+
// @Input() editable = true;
|
|
6198
|
+
hasCharacterCount() {
|
|
6199
|
+
const config = this.config ?? {};
|
|
6200
|
+
return config.characterCount !== false;
|
|
6264
6201
|
}
|
|
6265
|
-
|
|
6202
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: FooterComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
6203
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.10", type: FooterComponent, isStandalone: true, selector: "document-engine-footer", inputs: { editor: "editor", config: "config" }, ngImport: i0, template: "<div class=\"document-engine-document-editor__footer\">\n <div class=\"document-engine-document-editor__footer-content\">\n <div class=\"document-engine-document-editor__footer-left\">\n <!-- Additional footer components can be added here -->\n </div>\n\n <div class=\"document-engine-document-editor__footer-right\">\n <document-engine-character-count\n *ngIf=\"hasCharacterCount()\"\n [editor]=\"editor\"\n [config]=\"config\"\n mode=\"characters\"\n ></document-engine-character-count>\n </div>\n </div>\n</div>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: CharacterCountComponent, selector: "document-engine-character-count", inputs: ["editor", "config", "mode"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
6204
|
+
}
|
|
6205
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: FooterComponent, decorators: [{
|
|
6206
|
+
type: Component,
|
|
6207
|
+
args: [{ selector: 'document-engine-footer', standalone: true, imports: [CommonModule, CharacterCountComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"document-engine-document-editor__footer\">\n <div class=\"document-engine-document-editor__footer-content\">\n <div class=\"document-engine-document-editor__footer-left\">\n <!-- Additional footer components can be added here -->\n </div>\n\n <div class=\"document-engine-document-editor__footer-right\">\n <document-engine-character-count\n *ngIf=\"hasCharacterCount()\"\n [editor]=\"editor\"\n [config]=\"config\"\n mode=\"characters\"\n ></document-engine-character-count>\n </div>\n </div>\n</div>\n" }]
|
|
6208
|
+
}], propDecorators: { editor: [{
|
|
6209
|
+
type: Input
|
|
6210
|
+
}], config: [{
|
|
6211
|
+
type: Input
|
|
6212
|
+
}] } });
|
|
6213
|
+
|
|
6214
|
+
/**
|
|
6215
|
+
* Document Editor Component
|
|
6216
|
+
* High-level wrapper that encapsulates TiptapEditor and DocumentEngineKit
|
|
6217
|
+
* Provides a simple API for consumers without exposing internal dependencies
|
|
6218
|
+
*
|
|
6219
|
+
* Usage:
|
|
6220
|
+
* <document-engine-editor
|
|
6221
|
+
* #editor
|
|
6222
|
+
* [config]="editorConfig"
|
|
6223
|
+
*
|
|
6224
|
+
* (editorReady)="editor = docEditor.editor"
|
|
6225
|
+
* >
|
|
6226
|
+
* <tiptap-editor
|
|
6227
|
+
* [editor]="docEditor.editor"
|
|
6228
|
+
* [(ngModel)]="content"
|
|
6229
|
+
* ></tiptap-editor>
|
|
6230
|
+
* </document-engine-editor>
|
|
6231
|
+
*/
|
|
6232
|
+
class DocumentEditorComponent {
|
|
6233
|
+
ngZone = inject(NgZone);
|
|
6234
|
+
contentWrapperClass = inject(EDITOR_CONTENT_WRAPPER_CLASS, { optional: true });
|
|
6235
|
+
config;
|
|
6236
|
+
editorReady = new EventEmitter();
|
|
6237
|
+
// Expose editor property for external access (re-export)
|
|
6266
6238
|
editor;
|
|
6267
|
-
|
|
6268
|
-
|
|
6239
|
+
// Store merged config for toolbar and helper methods
|
|
6240
|
+
get mergedConfig() {
|
|
6241
|
+
return this._mergedConfig ?? this.getMergedConfig();
|
|
6242
|
+
}
|
|
6243
|
+
_mergedConfig;
|
|
6244
|
+
// Capabilities instance for toolbar
|
|
6245
|
+
capabilities;
|
|
6246
|
+
updateCapabilities() {
|
|
6247
|
+
this.capabilities = new EditorCapabilities(this.mergedConfig);
|
|
6248
|
+
}
|
|
6249
|
+
ngOnChanges(changes) {
|
|
6250
|
+
if (!changes['config'])
|
|
6251
|
+
return;
|
|
6252
|
+
if (!this.editor)
|
|
6253
|
+
return;
|
|
6254
|
+
this._mergedConfig = this.getMergedConfig();
|
|
6255
|
+
this.updateCapabilities();
|
|
6256
|
+
this.editor.setEditable(this._mergedConfig.editable ?? true);
|
|
6257
|
+
}
|
|
6269
6258
|
ngOnInit() {
|
|
6270
|
-
|
|
6271
|
-
if (!editor) {
|
|
6272
|
-
throw new Error('Required: Input `editor`');
|
|
6273
|
-
}
|
|
6274
|
-
const floatingMenuElement = this.elRef.nativeElement;
|
|
6275
|
-
floatingMenuElement.style.visibility = 'hidden';
|
|
6276
|
-
floatingMenuElement.style.position = 'absolute';
|
|
6277
|
-
editor.registerPlugin(FloatingMenuPlugin({
|
|
6278
|
-
pluginKey: this.pluginKey,
|
|
6279
|
-
editor,
|
|
6280
|
-
element: floatingMenuElement,
|
|
6281
|
-
options: this.options,
|
|
6282
|
-
shouldShow: this.shouldShow,
|
|
6283
|
-
}));
|
|
6259
|
+
this.initializeEditor();
|
|
6284
6260
|
}
|
|
6285
6261
|
ngOnDestroy() {
|
|
6286
|
-
if (this.editor
|
|
6287
|
-
this.editor.
|
|
6288
|
-
|
|
6289
|
-
|
|
6290
|
-
|
|
6291
|
-
|
|
6292
|
-
|
|
6262
|
+
if (this.editor)
|
|
6263
|
+
this.editor.destroy();
|
|
6264
|
+
}
|
|
6265
|
+
// Internal methods
|
|
6266
|
+
initializeEditor() {
|
|
6267
|
+
// Run editor initialization outside Angular zone for better performance
|
|
6268
|
+
// Editor operations (especially typing) don't need to trigger Angular change detection
|
|
6269
|
+
this.ngZone.runOutsideAngular(() => {
|
|
6270
|
+
// Merge user config with default full-featured config
|
|
6271
|
+
this._mergedConfig = this.getMergedConfig();
|
|
6272
|
+
// Initialize editor with config
|
|
6273
|
+
// Initial content will be set by TiptapEditorDirective via writeValue
|
|
6274
|
+
this.editor = new Editor({
|
|
6275
|
+
editable: this._mergedConfig.editable,
|
|
6276
|
+
extensions: [DocumentEngineKit.configure(this._mergedConfig)],
|
|
6277
|
+
editorProps: { attributes: { class: this.contentWrapperClass ?? '' } },
|
|
6278
|
+
});
|
|
6279
|
+
// Emit editorReady event back inside Angular zone
|
|
6280
|
+
this.ngZone.run(() => {
|
|
6281
|
+
// Create capabilities instance after editor is initialized
|
|
6282
|
+
this.updateCapabilities();
|
|
6283
|
+
this.editorReady.emit(this.editor);
|
|
6284
|
+
});
|
|
6293
6285
|
});
|
|
6294
6286
|
}
|
|
6295
|
-
|
|
6296
|
-
|
|
6287
|
+
/**
|
|
6288
|
+
* Get merged config with all extensions enabled by default
|
|
6289
|
+
* User config will override defaults
|
|
6290
|
+
*/
|
|
6291
|
+
getMergedConfig() {
|
|
6292
|
+
// Merge user config with defaults, preferring user config when provided
|
|
6293
|
+
return this.mergeConfig(DefaultEditorConfig, this.config);
|
|
6294
|
+
}
|
|
6295
|
+
/**
|
|
6296
|
+
* Merge two config objects, preferring userConfig values when provided
|
|
6297
|
+
*/
|
|
6298
|
+
mergeConfig(defaultConfig, userConfig) {
|
|
6299
|
+
if (!userConfig)
|
|
6300
|
+
return defaultConfig;
|
|
6301
|
+
const merged = { ...defaultConfig };
|
|
6302
|
+
const keys = Object.keys(userConfig);
|
|
6303
|
+
for (const key of keys) {
|
|
6304
|
+
const value = userConfig[key];
|
|
6305
|
+
if (value !== undefined)
|
|
6306
|
+
merged[key] = value;
|
|
6307
|
+
}
|
|
6308
|
+
return merged;
|
|
6309
|
+
}
|
|
6310
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: DocumentEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
6311
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.10", type: DocumentEditorComponent, isStandalone: false, selector: "document-engine-editor", inputs: { config: "config" }, outputs: { editorReady: "editorReady" }, host: { classAttribute: "no-twp" }, usesOnChanges: true, ngImport: i0, template: "<div class=\"document-engine-document-editor\">\n <document-engine-toolbar\n *ngIf=\"mergedConfig.showToolbar\"\n [editor]=\"editor\"\n [capabilities]=\"capabilities\"\n [config]=\"mergedConfig\"\n ></document-engine-toolbar>\n\n <div class=\"document-engine-document-editor__content\">\n <ng-content></ng-content>\n </div>\n\n <document-engine-footer\n *ngIf=\"editor && mergedConfig.showFooter\"\n [editor]=\"editor\"\n [config]=\"mergedConfig\"\n ></document-engine-footer>\n</div>\n", styles: [":is() *,:is() *:before,:is() *:after{box-sizing:border-box}\n"], dependencies: [{ kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: ToolbarComponent, selector: "document-engine-toolbar", inputs: ["editor", "capabilities", "config"], outputs: ["action"] }, { kind: "component", type: FooterComponent, selector: "document-engine-footer", inputs: ["editor", "config"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
6297
6312
|
}
|
|
6298
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type:
|
|
6299
|
-
type:
|
|
6300
|
-
args: [{
|
|
6301
|
-
|
|
6302
|
-
|
|
6303
|
-
}],
|
|
6304
|
-
type: Input
|
|
6305
|
-
}], editor: [{
|
|
6306
|
-
type: Input
|
|
6307
|
-
}], options: [{
|
|
6308
|
-
type: Input
|
|
6309
|
-
}], shouldShow: [{
|
|
6313
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: DocumentEditorComponent, decorators: [{
|
|
6314
|
+
type: Component,
|
|
6315
|
+
args: [{ selector: 'document-engine-editor', standalone: false, changeDetection: ChangeDetectionStrategy.OnPush, host: {
|
|
6316
|
+
class: 'no-twp',
|
|
6317
|
+
}, template: "<div class=\"document-engine-document-editor\">\n <document-engine-toolbar\n *ngIf=\"mergedConfig.showToolbar\"\n [editor]=\"editor\"\n [capabilities]=\"capabilities\"\n [config]=\"mergedConfig\"\n ></document-engine-toolbar>\n\n <div class=\"document-engine-document-editor__content\">\n <ng-content></ng-content>\n </div>\n\n <document-engine-footer\n *ngIf=\"editor && mergedConfig.showFooter\"\n [editor]=\"editor\"\n [config]=\"mergedConfig\"\n ></document-engine-footer>\n</div>\n", styles: [":is() *,:is() *:before,:is() *:after{box-sizing:border-box}\n"] }]
|
|
6318
|
+
}], propDecorators: { config: [{
|
|
6310
6319
|
type: Input
|
|
6320
|
+
}], editorReady: [{
|
|
6321
|
+
type: Output
|
|
6311
6322
|
}] } });
|
|
6312
6323
|
|
|
6324
|
+
class DocumentEditorModule {
|
|
6325
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: DocumentEditorModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
|
6326
|
+
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.10", ngImport: i0, type: DocumentEditorModule, declarations: [DocumentEditorComponent], imports: [CommonModule, ToolbarComponent, FooterComponent, TiptapEditorDirective], exports: [DocumentEditorComponent, TiptapEditorDirective] });
|
|
6327
|
+
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: DocumentEditorModule, imports: [CommonModule, ToolbarComponent, FooterComponent] });
|
|
6328
|
+
}
|
|
6329
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: DocumentEditorModule, decorators: [{
|
|
6330
|
+
type: NgModule,
|
|
6331
|
+
args: [{
|
|
6332
|
+
declarations: [DocumentEditorComponent],
|
|
6333
|
+
imports: [CommonModule, ToolbarComponent, FooterComponent, TiptapEditorDirective],
|
|
6334
|
+
exports: [DocumentEditorComponent, TiptapEditorDirective],
|
|
6335
|
+
}]
|
|
6336
|
+
}] });
|
|
6337
|
+
|
|
6313
6338
|
/**
|
|
6314
6339
|
* Generated bundle index. Do not edit.
|
|
6315
6340
|
*/
|
|
6316
6341
|
|
|
6317
|
-
export { ARCHIVE, ARROW_DROP_DOWN, AngularRenderer, ButtonDirective, CHECK, CLEAR_ALL, CLOSE, CODE, CharacterCountComponent, CheckboxComponent, ColorPickerComponent, ColorPickerViewComponent, DEFAULT, DEFAULT_TOOLBAR_STATE, DESCRIPTION, DYNAMIC_FORM, DocumentEditorComponent, DocumentEditorModule, DocumentEngineKit, DynamicFieldsViewComponent, EDIT, EDITOR_HTML_PREPROCESSOR, EditorBubbleMenuComponent, EditorCapabilities, ErrorMessageComponent, EventManager, FORMAT_ALIGN_CENTER, FORMAT_ALIGN_JUSTIFY, FORMAT_ALIGN_LEFT, FORMAT_ALIGN_RIGHT, FORMAT_BOLD, FORMAT_CLEAR, FORMAT_COLOR_FILL, FORMAT_COLOR_TEXT, FORMAT_INDENT_DECREASE, FORMAT_INDENT_INCREASE, FORMAT_ITALIC, FORMAT_LINE_SPACING, FORMAT_LIST_BULLETED, FORMAT_LIST_NUMBERED, FORMAT_QUOTE, FORMAT_SIZE, FORMAT_STRIKETHROUGH, FORMAT_UNDERLINE, FocusTrapService, FooterComponent, INSERT_PAGE_BREAK, IconComponent, IconRegistryService, ImageInsertViewComponent, InputDirective, LINK, LINK_OFF, LabelDirective, LinkEditViewComponent, LinkMainViewComponent, LinkPropertiesViewComponent, MARKDOWN, MATCH_CASE, PHOTO, PRINT, PopoverDirective, REDO, RESTRICT_EDIT, SEARCH, SEARCH_OFF, SETTINGS, SPECIAL_CHARACTER, SUBSCRIPT, SUPERSCRIPT, SelectComponent, SelectLabelDirective, SelectOptionDirective, SpecialCharactersViewComponent, TABLE_CELL_PROPERTY, TABLE_COLUMN, TABLE_MERGE_CELL, TABLE_PROPERTY, TABLE_ROW, TABLE_ROWS, TableCellStyleViewComponent, TableCreateViewComponent, TableMainViewComponent, TableStyleViewComponent, TemplateViewComponent, TiptapBubbleMenuDirective, TiptapDraggableDirective, TiptapEditorDirective, TiptapFloatingMenuDirective, ToggleGroupComponent, ToggleOptionDirective, ToolbarBubbleMenuComponent, ToolbarComponent, ToolbarService, UNDO, VERTICAL_ALIGN_BOTTOM, VERTICAL_ALIGN_CENTER, VERTICAL_ALIGN_TOP, buildToolbarState, selectionChangeHandler, shouldShowFnFactory, urlValidator };
|
|
6342
|
+
export { ARCHIVE, ARROW_DROP_DOWN, AngularRenderer, ButtonDirective, CHECK, CLEAR_ALL, CLOSE, CODE, CharacterCountComponent, CheckboxComponent, ColorPickerComponent, ColorPickerViewComponent, DEFAULT, DEFAULT_TOOLBAR_STATE, DESCRIPTION, DYNAMIC_FORM, DocumentEditorComponent, DocumentEditorModule, DocumentEngineKit, DynamicFieldsViewComponent, EDIT, EDITOR_CONTENT_WRAPPER_CLASS, EDITOR_HTML_PREPROCESSOR, EditorBubbleMenuComponent, EditorCapabilities, ErrorMessageComponent, EventManager, FORMAT_ALIGN_CENTER, FORMAT_ALIGN_JUSTIFY, FORMAT_ALIGN_LEFT, FORMAT_ALIGN_RIGHT, FORMAT_BOLD, FORMAT_CLEAR, FORMAT_COLOR_FILL, FORMAT_COLOR_TEXT, FORMAT_INDENT_DECREASE, FORMAT_INDENT_INCREASE, FORMAT_ITALIC, FORMAT_LINE_SPACING, FORMAT_LIST_BULLETED, FORMAT_LIST_NUMBERED, FORMAT_QUOTE, FORMAT_SIZE, FORMAT_STRIKETHROUGH, FORMAT_UNDERLINE, FocusTrapService, FooterComponent, INSERT_PAGE_BREAK, IconComponent, IconRegistryService, ImageInsertViewComponent, InputDirective, LINK, LINK_OFF, LabelDirective, LinkEditViewComponent, LinkMainViewComponent, LinkPropertiesViewComponent, MARKDOWN, MATCH_CASE, PHOTO, PRINT, PopoverDirective, REDO, RESTRICT_EDIT, SEARCH, SEARCH_OFF, SETTINGS, SPECIAL_CHARACTER, SUBSCRIPT, SUPERSCRIPT, SelectComponent, SelectLabelDirective, SelectOptionDirective, SpecialCharactersViewComponent, TABLE_CELL_PROPERTY, TABLE_COLUMN, TABLE_MERGE_CELL, TABLE_PROPERTY, TABLE_ROW, TABLE_ROWS, TableCellStyleViewComponent, TableCreateViewComponent, TableMainViewComponent, TableStyleViewComponent, TemplateViewComponent, TiptapBubbleMenuDirective, TiptapDraggableDirective, TiptapEditorDirective, TiptapFloatingMenuDirective, ToggleGroupComponent, ToggleOptionDirective, ToolbarBubbleMenuComponent, ToolbarComponent, ToolbarService, UNDO, VERTICAL_ALIGN_BOTTOM, VERTICAL_ALIGN_CENTER, VERTICAL_ALIGN_TOP, buildToolbarState, selectionChangeHandler, shouldShowFnFactory, urlValidator };
|
|
6318
6343
|
//# sourceMappingURL=phuong-tran-redoc-document-engine-angular.mjs.map
|