@flogeez/angular-tiptap-editor 0.2.7 → 0.3.1
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 { input, output, Component,
|
|
2
|
+
import { input, output, Component, computed, effect, ViewChild, signal, Injectable, inject, Directive, viewChild, DestroyRef } from '@angular/core';
|
|
3
3
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
4
4
|
import { Node, nodeInputRule, mergeAttributes, Extension, Editor } from '@tiptap/core';
|
|
5
5
|
import StarterKit from '@tiptap/starter-kit';
|
|
@@ -14,7 +14,13 @@ import Highlight from '@tiptap/extension-highlight';
|
|
|
14
14
|
import OfficePaste from '@intevation/tiptap-extension-office-paste';
|
|
15
15
|
import { Plugin, PluginKey } from '@tiptap/pm/state';
|
|
16
16
|
import { DecorationSet, Decoration } from '@tiptap/pm/view';
|
|
17
|
+
import Table from '@tiptap/extension-table';
|
|
18
|
+
import TableRow from '@tiptap/extension-table-row';
|
|
19
|
+
import TableCell from '@tiptap/extension-table-cell';
|
|
20
|
+
import TableHeader from '@tiptap/extension-table-header';
|
|
17
21
|
import tippy from 'tippy.js';
|
|
22
|
+
import { CellSelection } from '@tiptap/pm/tables';
|
|
23
|
+
import { CommonModule } from '@angular/common';
|
|
18
24
|
import { Plugin as Plugin$1, PluginKey as PluginKey$1 } from 'prosemirror-state';
|
|
19
25
|
import { NG_VALUE_ACCESSOR, NgControl } from '@angular/forms';
|
|
20
26
|
import { concat, defer, of, tap } from 'rxjs';
|
|
@@ -488,6 +494,30 @@ const UploadProgress = Extension.create({
|
|
|
488
494
|
},
|
|
489
495
|
});
|
|
490
496
|
|
|
497
|
+
const TableExtension = Extension.create({
|
|
498
|
+
name: "tableExtension",
|
|
499
|
+
addExtensions() {
|
|
500
|
+
return [
|
|
501
|
+
Table.configure({
|
|
502
|
+
resizable: true,
|
|
503
|
+
handleWidth: 5,
|
|
504
|
+
cellMinWidth: 100,
|
|
505
|
+
}),
|
|
506
|
+
TableRow,
|
|
507
|
+
TableHeader.configure({
|
|
508
|
+
HTMLAttributes: {
|
|
509
|
+
class: "table-header",
|
|
510
|
+
},
|
|
511
|
+
}),
|
|
512
|
+
TableCell.configure({
|
|
513
|
+
HTMLAttributes: {
|
|
514
|
+
class: "table-cell",
|
|
515
|
+
},
|
|
516
|
+
}),
|
|
517
|
+
];
|
|
518
|
+
},
|
|
519
|
+
});
|
|
520
|
+
|
|
491
521
|
class TiptapButtonComponent {
|
|
492
522
|
constructor() {
|
|
493
523
|
// Inputs
|
|
@@ -562,6 +592,393 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.0", ngImpor
|
|
|
562
592
|
`, styles: [".tiptap-button{display:flex;align-items:center;justify-content:center;width:32px;height:32px;border:none;background:transparent;border-radius:8px;cursor:pointer;transition:all .2s cubic-bezier(.4,0,.2,1);color:#64748b;position:relative;overflow:hidden}.tiptap-button:before{content:\"\";position:absolute;inset:0;background:linear-gradient(135deg,#6366f1,#8b5cf6);opacity:0;transition:opacity .2s ease;border-radius:8px}.tiptap-button:hover{color:#6366f1;transform:translateY(-1px)}.tiptap-button:hover:before{opacity:.1}.tiptap-button:active{transform:translateY(0)}.tiptap-button.is-active{color:#6366f1;background:#6366f11a}.tiptap-button.is-active:before{opacity:.15}.tiptap-button.is-active:hover{background:#6366f126}.tiptap-button:disabled{opacity:.5;cursor:not-allowed;pointer-events:none}.tiptap-button:disabled:hover{transform:none;color:#64748b}.tiptap-button:disabled:before{opacity:0}.tiptap-button .material-symbols-outlined{font-size:20px;position:relative;z-index:1}.tiptap-button .material-symbols-outlined.icon-small{font-size:16px}.tiptap-button .material-symbols-outlined.icon-medium{font-size:20px}.tiptap-button .material-symbols-outlined.icon-large{font-size:24px}.tiptap-button.text-button{width:auto;padding:0 12px;font-size:14px;font-weight:500}.tiptap-button.color-button{width:28px;height:28px;border-radius:50%;border:2px solid transparent;transition:all .2s ease}.tiptap-button.color-button:hover{border-color:#e2e8f0;transform:scale(1.1)}.tiptap-button.color-button.is-active{border-color:#6366f1;box-shadow:0 0 0 2px #6366f133}.tiptap-button.primary{background:linear-gradient(135deg,#6366f1,#8b5cf6);color:#fff}.tiptap-button.primary:hover{background:linear-gradient(135deg,#5b21b6,#7c3aed);color:#fff}.tiptap-button.secondary{background:#f1f5f9;color:#64748b}.tiptap-button.secondary:hover{background:#e2e8f0;color:#475569}.tiptap-button.danger{color:#ef4444}.tiptap-button.danger:hover{color:#dc2626;background:#ef44441a}.tiptap-button.danger:before{background:linear-gradient(135deg,#ef4444,#dc2626)}.tiptap-button.small{width:24px;height:24px}.tiptap-button.medium{width:32px;height:32px}.tiptap-button.large{width:40px;height:40px}.tiptap-button.has-badge{position:relative}.tiptap-button .badge{position:absolute;top:-4px;right:-4px;background:#ef4444;color:#fff;font-size:10px;padding:2px 4px;border-radius:8px;min-width:16px;text-align:center;line-height:1}.tiptap-button.has-tooltip{position:relative}.tiptap-button .tooltip{position:absolute;bottom:-30px;left:50%;transform:translate(-50%);background:#000c;color:#fff;padding:4px 8px;border-radius:4px;font-size:12px;white-space:nowrap;opacity:0;visibility:hidden;transition:all .2s ease;z-index:1000}.tiptap-button:hover .tooltip{opacity:1;visibility:visible}@keyframes pulse{0%,to{box-shadow:0 0 #6366f166}50%{box-shadow:0 0 0 4px #6366f100}}.tiptap-button.is-active.pulse{animation:pulse 2s infinite}@media (max-width: 768px){.tiptap-button{width:32px;height:32px}.tiptap-button .material-symbols-outlined{font-size:18px}.tiptap-button.text-button{padding:0 8px;font-size:13px}}\n"] }]
|
|
563
593
|
}] });
|
|
564
594
|
|
|
595
|
+
class TiptapBubbleMenuComponent {
|
|
596
|
+
// Effect comme propriété de classe pour éviter l'erreur d'injection context
|
|
597
|
+
constructor() {
|
|
598
|
+
this.editor = input.required();
|
|
599
|
+
this.config = input({
|
|
600
|
+
bold: true,
|
|
601
|
+
italic: true,
|
|
602
|
+
underline: true,
|
|
603
|
+
strike: true,
|
|
604
|
+
code: true,
|
|
605
|
+
superscript: false,
|
|
606
|
+
subscript: false,
|
|
607
|
+
highlight: true,
|
|
608
|
+
link: true,
|
|
609
|
+
separator: true,
|
|
610
|
+
});
|
|
611
|
+
this.tippyInstance = null;
|
|
612
|
+
this.updateTimeout = null;
|
|
613
|
+
this.bubbleMenuConfig = computed(() => ({
|
|
614
|
+
bold: true,
|
|
615
|
+
italic: true,
|
|
616
|
+
underline: true,
|
|
617
|
+
strike: true,
|
|
618
|
+
code: true,
|
|
619
|
+
superscript: false,
|
|
620
|
+
subscript: false,
|
|
621
|
+
highlight: true,
|
|
622
|
+
link: true,
|
|
623
|
+
separator: true,
|
|
624
|
+
...this.config(),
|
|
625
|
+
}));
|
|
626
|
+
this.updateMenu = () => {
|
|
627
|
+
// Debounce pour éviter les appels trop fréquents
|
|
628
|
+
if (this.updateTimeout) {
|
|
629
|
+
clearTimeout(this.updateTimeout);
|
|
630
|
+
}
|
|
631
|
+
this.updateTimeout = setTimeout(() => {
|
|
632
|
+
const ed = this.editor();
|
|
633
|
+
if (!ed)
|
|
634
|
+
return;
|
|
635
|
+
const { selection } = ed.state;
|
|
636
|
+
const { from, to } = selection;
|
|
637
|
+
const hasTextSelection = from !== to && !(selection instanceof CellSelection);
|
|
638
|
+
const isImageSelected = ed.isActive("image") || ed.isActive("resizableImage");
|
|
639
|
+
const isTableCellSelected = ed.isActive("tableCell") || ed.isActive("tableHeader");
|
|
640
|
+
const hasCellSelection = selection instanceof CellSelection;
|
|
641
|
+
console.log("TextBubbleMenu - updateMenu:", {
|
|
642
|
+
hasTextSelection,
|
|
643
|
+
isImageSelected,
|
|
644
|
+
isTableCellSelected,
|
|
645
|
+
hasCellSelection,
|
|
646
|
+
selectionType: selection.constructor.name,
|
|
647
|
+
selectionEmpty: selection.empty,
|
|
648
|
+
from,
|
|
649
|
+
to,
|
|
650
|
+
});
|
|
651
|
+
// Ne montrer le menu texte que si :
|
|
652
|
+
// - Il y a une sélection de texte (pas une sélection de cellules multiples)
|
|
653
|
+
// - Aucune image n'est sélectionnée (priorité aux images)
|
|
654
|
+
// - Ce n'est pas une sélection de cellules multiples (CellSelection)
|
|
655
|
+
// - L'éditeur est éditable
|
|
656
|
+
// Note: Le texte dans une cellule est autorisé (isTableCellSelected peut être true)
|
|
657
|
+
const shouldShow = hasTextSelection &&
|
|
658
|
+
!isImageSelected &&
|
|
659
|
+
!hasCellSelection &&
|
|
660
|
+
ed.isEditable;
|
|
661
|
+
if (shouldShow) {
|
|
662
|
+
this.showTippy();
|
|
663
|
+
}
|
|
664
|
+
else {
|
|
665
|
+
this.hideTippy();
|
|
666
|
+
}
|
|
667
|
+
}, 10);
|
|
668
|
+
};
|
|
669
|
+
this.handleBlur = () => {
|
|
670
|
+
// Masquer le menu quand l'éditeur perd le focus
|
|
671
|
+
setTimeout(() => {
|
|
672
|
+
this.hideTippy();
|
|
673
|
+
}, 100);
|
|
674
|
+
};
|
|
675
|
+
effect(() => {
|
|
676
|
+
const ed = this.editor();
|
|
677
|
+
if (!ed)
|
|
678
|
+
return;
|
|
679
|
+
// Nettoyer les anciens listeners
|
|
680
|
+
ed.off("selectionUpdate", this.updateMenu);
|
|
681
|
+
ed.off("transaction", this.updateMenu);
|
|
682
|
+
ed.off("focus", this.updateMenu);
|
|
683
|
+
ed.off("blur", this.handleBlur);
|
|
684
|
+
// Ajouter les nouveaux listeners
|
|
685
|
+
ed.on("selectionUpdate", this.updateMenu);
|
|
686
|
+
ed.on("transaction", this.updateMenu);
|
|
687
|
+
ed.on("focus", this.updateMenu);
|
|
688
|
+
ed.on("blur", this.handleBlur);
|
|
689
|
+
// Ne pas appeler updateMenu() ici pour éviter l'affichage prématuré
|
|
690
|
+
// Il sera appelé automatiquement quand l'éditeur sera prêt
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
ngOnInit() {
|
|
694
|
+
// Initialiser Tippy de manière synchrone après que le component soit ready
|
|
695
|
+
this.initTippy();
|
|
696
|
+
}
|
|
697
|
+
ngOnDestroy() {
|
|
698
|
+
const ed = this.editor();
|
|
699
|
+
if (ed) {
|
|
700
|
+
ed.off("selectionUpdate", this.updateMenu);
|
|
701
|
+
ed.off("transaction", this.updateMenu);
|
|
702
|
+
ed.off("focus", this.updateMenu);
|
|
703
|
+
ed.off("blur", this.handleBlur);
|
|
704
|
+
}
|
|
705
|
+
// Nettoyer les timeouts
|
|
706
|
+
if (this.updateTimeout) {
|
|
707
|
+
clearTimeout(this.updateTimeout);
|
|
708
|
+
}
|
|
709
|
+
// Nettoyer Tippy
|
|
710
|
+
if (this.tippyInstance) {
|
|
711
|
+
this.tippyInstance.destroy();
|
|
712
|
+
this.tippyInstance = null;
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
initTippy() {
|
|
716
|
+
// Attendre que l'élément soit disponible
|
|
717
|
+
if (!this.menuRef?.nativeElement) {
|
|
718
|
+
setTimeout(() => this.initTippy(), 50);
|
|
719
|
+
return;
|
|
720
|
+
}
|
|
721
|
+
const menuElement = this.menuRef.nativeElement;
|
|
722
|
+
// S'assurer qu'il n'y a pas déjà une instance
|
|
723
|
+
if (this.tippyInstance) {
|
|
724
|
+
this.tippyInstance.destroy();
|
|
725
|
+
}
|
|
726
|
+
// Créer l'instance Tippy
|
|
727
|
+
this.tippyInstance = tippy(document.body, {
|
|
728
|
+
content: menuElement,
|
|
729
|
+
trigger: "manual",
|
|
730
|
+
placement: "top-start",
|
|
731
|
+
appendTo: () => document.body,
|
|
732
|
+
interactive: true,
|
|
733
|
+
arrow: false,
|
|
734
|
+
offset: [0, 8],
|
|
735
|
+
hideOnClick: false,
|
|
736
|
+
onShow: (instance) => {
|
|
737
|
+
// S'assurer que les autres menus sont fermés
|
|
738
|
+
this.hideOtherMenus();
|
|
739
|
+
},
|
|
740
|
+
getReferenceClientRect: () => this.getSelectionRect(),
|
|
741
|
+
// Améliorer le positionnement avec scroll
|
|
742
|
+
popperOptions: {
|
|
743
|
+
modifiers: [
|
|
744
|
+
{
|
|
745
|
+
name: "preventOverflow",
|
|
746
|
+
options: {
|
|
747
|
+
boundary: "viewport",
|
|
748
|
+
padding: 8,
|
|
749
|
+
},
|
|
750
|
+
},
|
|
751
|
+
{
|
|
752
|
+
name: "flip",
|
|
753
|
+
options: {
|
|
754
|
+
fallbackPlacements: ["bottom-start", "top-end", "bottom-end"],
|
|
755
|
+
},
|
|
756
|
+
},
|
|
757
|
+
],
|
|
758
|
+
},
|
|
759
|
+
});
|
|
760
|
+
// Maintenant que Tippy est initialisé, faire un premier check
|
|
761
|
+
this.updateMenu();
|
|
762
|
+
}
|
|
763
|
+
getSelectionRect() {
|
|
764
|
+
const selection = window.getSelection();
|
|
765
|
+
if (!selection || selection.rangeCount === 0) {
|
|
766
|
+
return new DOMRect(0, 0, 0, 0);
|
|
767
|
+
}
|
|
768
|
+
const range = selection.getRangeAt(0);
|
|
769
|
+
return range.getBoundingClientRect();
|
|
770
|
+
}
|
|
771
|
+
hideOtherMenus() {
|
|
772
|
+
// Cette méthode peut être étendue pour fermer d'autres menus si nécessaire
|
|
773
|
+
// Pour l'instant, elle sert de placeholder pour une future coordination entre menus
|
|
774
|
+
}
|
|
775
|
+
showTippy() {
|
|
776
|
+
if (!this.tippyInstance)
|
|
777
|
+
return;
|
|
778
|
+
// Mettre à jour la position
|
|
779
|
+
this.tippyInstance.setProps({
|
|
780
|
+
getReferenceClientRect: () => this.getSelectionRect(),
|
|
781
|
+
});
|
|
782
|
+
this.tippyInstance.show();
|
|
783
|
+
}
|
|
784
|
+
hideTippy() {
|
|
785
|
+
if (this.tippyInstance) {
|
|
786
|
+
this.tippyInstance.hide();
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
isActive(mark) {
|
|
790
|
+
const ed = this.editor();
|
|
791
|
+
return ed?.isActive(mark) || false;
|
|
792
|
+
}
|
|
793
|
+
onCommand(command, event) {
|
|
794
|
+
event.preventDefault();
|
|
795
|
+
const ed = this.editor();
|
|
796
|
+
if (!ed)
|
|
797
|
+
return;
|
|
798
|
+
switch (command) {
|
|
799
|
+
case "bold":
|
|
800
|
+
ed.chain().focus().toggleBold().run();
|
|
801
|
+
break;
|
|
802
|
+
case "italic":
|
|
803
|
+
ed.chain().focus().toggleItalic().run();
|
|
804
|
+
break;
|
|
805
|
+
case "underline":
|
|
806
|
+
ed.chain().focus().toggleUnderline().run();
|
|
807
|
+
break;
|
|
808
|
+
case "strike":
|
|
809
|
+
ed.chain().focus().toggleStrike().run();
|
|
810
|
+
break;
|
|
811
|
+
case "code":
|
|
812
|
+
ed.chain().focus().toggleCode().run();
|
|
813
|
+
break;
|
|
814
|
+
case "superscript":
|
|
815
|
+
ed.chain().focus().toggleSuperscript().run();
|
|
816
|
+
break;
|
|
817
|
+
case "subscript":
|
|
818
|
+
ed.chain().focus().toggleSubscript().run();
|
|
819
|
+
break;
|
|
820
|
+
case "highlight":
|
|
821
|
+
ed.chain().focus().toggleHighlight().run();
|
|
822
|
+
break;
|
|
823
|
+
case "link":
|
|
824
|
+
const href = window.prompt("URL du lien:");
|
|
825
|
+
if (href) {
|
|
826
|
+
ed.chain().focus().toggleLink({ href }).run();
|
|
827
|
+
}
|
|
828
|
+
break;
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: TiptapBubbleMenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
832
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.0", type: TiptapBubbleMenuComponent, isStandalone: true, selector: "tiptap-bubble-menu", inputs: { editor: { classPropertyName: "editor", publicName: "editor", isSignal: true, isRequired: true, transformFunction: null }, config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "menuRef", first: true, predicate: ["menuRef"], descendants: true }], ngImport: i0, template: `
|
|
833
|
+
<div #menuRef class="bubble-menu">
|
|
834
|
+
@if (bubbleMenuConfig().bold) {
|
|
835
|
+
<tiptap-button
|
|
836
|
+
icon="format_bold"
|
|
837
|
+
title="Gras"
|
|
838
|
+
[active]="isActive('bold')"
|
|
839
|
+
(click)="onCommand('bold', $event)"
|
|
840
|
+
></tiptap-button>
|
|
841
|
+
} @if (bubbleMenuConfig().italic) {
|
|
842
|
+
<tiptap-button
|
|
843
|
+
icon="format_italic"
|
|
844
|
+
title="Italique"
|
|
845
|
+
[active]="isActive('italic')"
|
|
846
|
+
(click)="onCommand('italic', $event)"
|
|
847
|
+
></tiptap-button>
|
|
848
|
+
} @if (bubbleMenuConfig().underline) {
|
|
849
|
+
<tiptap-button
|
|
850
|
+
icon="format_underlined"
|
|
851
|
+
title="Souligné"
|
|
852
|
+
[active]="isActive('underline')"
|
|
853
|
+
(click)="onCommand('underline', $event)"
|
|
854
|
+
></tiptap-button>
|
|
855
|
+
} @if (bubbleMenuConfig().strike) {
|
|
856
|
+
<tiptap-button
|
|
857
|
+
icon="strikethrough_s"
|
|
858
|
+
title="Barré"
|
|
859
|
+
[active]="isActive('strike')"
|
|
860
|
+
(click)="onCommand('strike', $event)"
|
|
861
|
+
></tiptap-button>
|
|
862
|
+
} @if (bubbleMenuConfig().superscript) {
|
|
863
|
+
<tiptap-button
|
|
864
|
+
icon="superscript"
|
|
865
|
+
title="Exposant"
|
|
866
|
+
[active]="isActive('superscript')"
|
|
867
|
+
(click)="onCommand('superscript', $event)"
|
|
868
|
+
></tiptap-button>
|
|
869
|
+
} @if (bubbleMenuConfig().subscript) {
|
|
870
|
+
<tiptap-button
|
|
871
|
+
icon="subscript"
|
|
872
|
+
title="Indice"
|
|
873
|
+
[active]="isActive('subscript')"
|
|
874
|
+
(click)="onCommand('subscript', $event)"
|
|
875
|
+
></tiptap-button>
|
|
876
|
+
} @if (bubbleMenuConfig().highlight) {
|
|
877
|
+
<tiptap-button
|
|
878
|
+
icon="highlight"
|
|
879
|
+
title="Surbrillance"
|
|
880
|
+
[active]="isActive('highlight')"
|
|
881
|
+
(click)="onCommand('highlight', $event)"
|
|
882
|
+
></tiptap-button>
|
|
883
|
+
} @if (bubbleMenuConfig().separator && (bubbleMenuConfig().code ||
|
|
884
|
+
bubbleMenuConfig().link)) {
|
|
885
|
+
<div class="tiptap-separator"></div>
|
|
886
|
+
} @if (bubbleMenuConfig().code) {
|
|
887
|
+
<tiptap-button
|
|
888
|
+
icon="code"
|
|
889
|
+
title="Code"
|
|
890
|
+
[active]="isActive('code')"
|
|
891
|
+
(click)="onCommand('code', $event)"
|
|
892
|
+
></tiptap-button>
|
|
893
|
+
} @if (bubbleMenuConfig().link) {
|
|
894
|
+
<tiptap-button
|
|
895
|
+
icon="link"
|
|
896
|
+
title="Lien"
|
|
897
|
+
[active]="isActive('link')"
|
|
898
|
+
(click)="onCommand('link', $event)"
|
|
899
|
+
></tiptap-button>
|
|
900
|
+
}
|
|
901
|
+
</div>
|
|
902
|
+
`, isInline: true, dependencies: [{ kind: "component", type: TiptapButtonComponent, selector: "tiptap-button", inputs: ["icon", "title", "active", "disabled", "variant", "size", "iconSize"], outputs: ["onClick"] }] }); }
|
|
903
|
+
}
|
|
904
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: TiptapBubbleMenuComponent, decorators: [{
|
|
905
|
+
type: Component,
|
|
906
|
+
args: [{ selector: "tiptap-bubble-menu", standalone: true, imports: [TiptapButtonComponent], template: `
|
|
907
|
+
<div #menuRef class="bubble-menu">
|
|
908
|
+
@if (bubbleMenuConfig().bold) {
|
|
909
|
+
<tiptap-button
|
|
910
|
+
icon="format_bold"
|
|
911
|
+
title="Gras"
|
|
912
|
+
[active]="isActive('bold')"
|
|
913
|
+
(click)="onCommand('bold', $event)"
|
|
914
|
+
></tiptap-button>
|
|
915
|
+
} @if (bubbleMenuConfig().italic) {
|
|
916
|
+
<tiptap-button
|
|
917
|
+
icon="format_italic"
|
|
918
|
+
title="Italique"
|
|
919
|
+
[active]="isActive('italic')"
|
|
920
|
+
(click)="onCommand('italic', $event)"
|
|
921
|
+
></tiptap-button>
|
|
922
|
+
} @if (bubbleMenuConfig().underline) {
|
|
923
|
+
<tiptap-button
|
|
924
|
+
icon="format_underlined"
|
|
925
|
+
title="Souligné"
|
|
926
|
+
[active]="isActive('underline')"
|
|
927
|
+
(click)="onCommand('underline', $event)"
|
|
928
|
+
></tiptap-button>
|
|
929
|
+
} @if (bubbleMenuConfig().strike) {
|
|
930
|
+
<tiptap-button
|
|
931
|
+
icon="strikethrough_s"
|
|
932
|
+
title="Barré"
|
|
933
|
+
[active]="isActive('strike')"
|
|
934
|
+
(click)="onCommand('strike', $event)"
|
|
935
|
+
></tiptap-button>
|
|
936
|
+
} @if (bubbleMenuConfig().superscript) {
|
|
937
|
+
<tiptap-button
|
|
938
|
+
icon="superscript"
|
|
939
|
+
title="Exposant"
|
|
940
|
+
[active]="isActive('superscript')"
|
|
941
|
+
(click)="onCommand('superscript', $event)"
|
|
942
|
+
></tiptap-button>
|
|
943
|
+
} @if (bubbleMenuConfig().subscript) {
|
|
944
|
+
<tiptap-button
|
|
945
|
+
icon="subscript"
|
|
946
|
+
title="Indice"
|
|
947
|
+
[active]="isActive('subscript')"
|
|
948
|
+
(click)="onCommand('subscript', $event)"
|
|
949
|
+
></tiptap-button>
|
|
950
|
+
} @if (bubbleMenuConfig().highlight) {
|
|
951
|
+
<tiptap-button
|
|
952
|
+
icon="highlight"
|
|
953
|
+
title="Surbrillance"
|
|
954
|
+
[active]="isActive('highlight')"
|
|
955
|
+
(click)="onCommand('highlight', $event)"
|
|
956
|
+
></tiptap-button>
|
|
957
|
+
} @if (bubbleMenuConfig().separator && (bubbleMenuConfig().code ||
|
|
958
|
+
bubbleMenuConfig().link)) {
|
|
959
|
+
<div class="tiptap-separator"></div>
|
|
960
|
+
} @if (bubbleMenuConfig().code) {
|
|
961
|
+
<tiptap-button
|
|
962
|
+
icon="code"
|
|
963
|
+
title="Code"
|
|
964
|
+
[active]="isActive('code')"
|
|
965
|
+
(click)="onCommand('code', $event)"
|
|
966
|
+
></tiptap-button>
|
|
967
|
+
} @if (bubbleMenuConfig().link) {
|
|
968
|
+
<tiptap-button
|
|
969
|
+
icon="link"
|
|
970
|
+
title="Lien"
|
|
971
|
+
[active]="isActive('link')"
|
|
972
|
+
(click)="onCommand('link', $event)"
|
|
973
|
+
></tiptap-button>
|
|
974
|
+
}
|
|
975
|
+
</div>
|
|
976
|
+
` }]
|
|
977
|
+
}], ctorParameters: () => [], propDecorators: { menuRef: [{
|
|
978
|
+
type: ViewChild,
|
|
979
|
+
args: ["menuRef", { static: false }]
|
|
980
|
+
}] } });
|
|
981
|
+
|
|
565
982
|
class TiptapSeparatorComponent {
|
|
566
983
|
constructor() {
|
|
567
984
|
this.orientation = input("vertical");
|
|
@@ -1011,31 +1428,396 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.0", ngImpor
|
|
|
1011
1428
|
}]
|
|
1012
1429
|
}] });
|
|
1013
1430
|
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1431
|
+
class TiptapImageBubbleMenuComponent {
|
|
1432
|
+
constructor() {
|
|
1433
|
+
this.editor = input.required();
|
|
1434
|
+
this.config = input({
|
|
1435
|
+
changeImage: true,
|
|
1436
|
+
resizeSmall: true,
|
|
1437
|
+
resizeMedium: true,
|
|
1438
|
+
resizeLarge: true,
|
|
1439
|
+
resizeOriginal: true,
|
|
1440
|
+
deleteImage: true,
|
|
1441
|
+
separator: true,
|
|
1442
|
+
});
|
|
1443
|
+
this.tippyInstance = null;
|
|
1444
|
+
this.imageService = inject(ImageService);
|
|
1445
|
+
this.updateTimeout = null;
|
|
1446
|
+
this.imageBubbleMenuConfig = computed(() => ({
|
|
1447
|
+
changeImage: true,
|
|
1448
|
+
resizeSmall: true,
|
|
1449
|
+
resizeMedium: true,
|
|
1450
|
+
resizeLarge: true,
|
|
1451
|
+
resizeOriginal: true,
|
|
1452
|
+
deleteImage: true,
|
|
1453
|
+
separator: true,
|
|
1454
|
+
...this.config(),
|
|
1455
|
+
}));
|
|
1456
|
+
this.hasResizeButtons = computed(() => {
|
|
1457
|
+
const config = this.imageBubbleMenuConfig();
|
|
1458
|
+
return (config.resizeSmall ||
|
|
1459
|
+
config.resizeMedium ||
|
|
1460
|
+
config.resizeLarge ||
|
|
1461
|
+
config.resizeOriginal);
|
|
1462
|
+
});
|
|
1463
|
+
this.updateMenu = () => {
|
|
1464
|
+
// Debounce pour éviter les appels trop fréquents
|
|
1465
|
+
if (this.updateTimeout) {
|
|
1466
|
+
clearTimeout(this.updateTimeout);
|
|
1467
|
+
}
|
|
1468
|
+
this.updateTimeout = setTimeout(() => {
|
|
1469
|
+
const ed = this.editor();
|
|
1470
|
+
if (!ed)
|
|
1471
|
+
return;
|
|
1472
|
+
const isImageSelected = ed.isActive("resizableImage") || ed.isActive("image");
|
|
1473
|
+
const { from, to } = ed.state.selection;
|
|
1474
|
+
const hasTextSelection = from !== to;
|
|
1475
|
+
// Ne montrer le menu image que si :
|
|
1476
|
+
// - Une image est sélectionnée
|
|
1477
|
+
// - L'éditeur est éditable
|
|
1478
|
+
const shouldShow = isImageSelected && ed.isEditable;
|
|
1479
|
+
if (shouldShow) {
|
|
1480
|
+
this.showTippy();
|
|
1481
|
+
}
|
|
1482
|
+
else {
|
|
1483
|
+
this.hideTippy();
|
|
1484
|
+
}
|
|
1485
|
+
}, 10);
|
|
1486
|
+
};
|
|
1487
|
+
this.handleBlur = () => {
|
|
1488
|
+
// Masquer le menu quand l'éditeur perd le focus
|
|
1489
|
+
setTimeout(() => {
|
|
1490
|
+
this.hideTippy();
|
|
1491
|
+
}, 100);
|
|
1492
|
+
};
|
|
1493
|
+
effect(() => {
|
|
1494
|
+
const ed = this.editor();
|
|
1495
|
+
if (!ed)
|
|
1496
|
+
return;
|
|
1497
|
+
// Nettoyer les anciens listeners
|
|
1498
|
+
ed.off("selectionUpdate", this.updateMenu);
|
|
1499
|
+
ed.off("transaction", this.updateMenu);
|
|
1500
|
+
ed.off("focus", this.updateMenu);
|
|
1501
|
+
ed.off("blur", this.handleBlur);
|
|
1502
|
+
// Ajouter les nouveaux listeners
|
|
1503
|
+
ed.on("selectionUpdate", this.updateMenu);
|
|
1504
|
+
ed.on("transaction", this.updateMenu);
|
|
1505
|
+
ed.on("focus", this.updateMenu);
|
|
1506
|
+
ed.on("blur", this.handleBlur);
|
|
1507
|
+
// Ne pas appeler updateMenu() ici pour éviter l'affichage prématuré
|
|
1508
|
+
// Il sera appelé automatiquement quand l'éditeur sera prêt
|
|
1509
|
+
});
|
|
1510
|
+
}
|
|
1511
|
+
ngOnInit() {
|
|
1512
|
+
// Initialiser Tippy de manière synchrone après que le component soit ready
|
|
1513
|
+
this.initTippy();
|
|
1514
|
+
}
|
|
1515
|
+
ngOnDestroy() {
|
|
1516
|
+
const ed = this.editor();
|
|
1517
|
+
if (ed) {
|
|
1518
|
+
ed.off("selectionUpdate", this.updateMenu);
|
|
1519
|
+
ed.off("transaction", this.updateMenu);
|
|
1520
|
+
ed.off("focus", this.updateMenu);
|
|
1521
|
+
ed.off("blur", this.handleBlur);
|
|
1522
|
+
}
|
|
1523
|
+
// Nettoyer les timeouts
|
|
1524
|
+
if (this.updateTimeout) {
|
|
1525
|
+
clearTimeout(this.updateTimeout);
|
|
1526
|
+
}
|
|
1527
|
+
// Nettoyer Tippy
|
|
1528
|
+
if (this.tippyInstance) {
|
|
1529
|
+
this.tippyInstance.destroy();
|
|
1530
|
+
this.tippyInstance = null;
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
initTippy() {
|
|
1534
|
+
// Attendre que l'élément soit disponible
|
|
1535
|
+
if (!this.menuRef?.nativeElement) {
|
|
1536
|
+
setTimeout(() => this.initTippy(), 50);
|
|
1537
|
+
return;
|
|
1538
|
+
}
|
|
1539
|
+
const menuElement = this.menuRef.nativeElement;
|
|
1540
|
+
// S'assurer qu'il n'y a pas déjà une instance
|
|
1541
|
+
if (this.tippyInstance) {
|
|
1542
|
+
this.tippyInstance.destroy();
|
|
1543
|
+
}
|
|
1544
|
+
// Créer l'instance Tippy
|
|
1545
|
+
this.tippyInstance = tippy(document.body, {
|
|
1546
|
+
content: menuElement,
|
|
1547
|
+
trigger: "manual",
|
|
1548
|
+
placement: "top-start",
|
|
1549
|
+
appendTo: () => document.body,
|
|
1550
|
+
interactive: true,
|
|
1551
|
+
arrow: false,
|
|
1552
|
+
offset: [0, 8],
|
|
1553
|
+
hideOnClick: false,
|
|
1554
|
+
onShow: (instance) => {
|
|
1555
|
+
// S'assurer que les autres menus sont fermés
|
|
1556
|
+
this.hideOtherMenus();
|
|
1557
|
+
},
|
|
1558
|
+
getReferenceClientRect: () => this.getImageRect(),
|
|
1559
|
+
// Améliorer le positionnement avec scroll
|
|
1560
|
+
popperOptions: {
|
|
1561
|
+
modifiers: [
|
|
1562
|
+
{
|
|
1563
|
+
name: "preventOverflow",
|
|
1564
|
+
options: {
|
|
1565
|
+
boundary: "viewport",
|
|
1566
|
+
padding: 8,
|
|
1567
|
+
},
|
|
1568
|
+
},
|
|
1569
|
+
{
|
|
1570
|
+
name: "flip",
|
|
1571
|
+
options: {
|
|
1572
|
+
fallbackPlacements: ["bottom-start", "top-end", "bottom-end"],
|
|
1573
|
+
},
|
|
1574
|
+
},
|
|
1575
|
+
],
|
|
1576
|
+
},
|
|
1577
|
+
});
|
|
1578
|
+
// Maintenant que Tippy est initialisé, faire un premier check
|
|
1579
|
+
this.updateMenu();
|
|
1580
|
+
}
|
|
1581
|
+
getImageRect() {
|
|
1582
|
+
const ed = this.editor();
|
|
1583
|
+
if (!ed)
|
|
1584
|
+
return new DOMRect(0, 0, 0, 0);
|
|
1585
|
+
// Trouver l'image sélectionnée dans le DOM
|
|
1586
|
+
const { from } = ed.state.selection;
|
|
1587
|
+
// Fonction pour trouver toutes les images dans l'éditeur
|
|
1588
|
+
const getAllImages = () => {
|
|
1589
|
+
const editorElement = ed.view.dom;
|
|
1590
|
+
return Array.from(editorElement.querySelectorAll("img"));
|
|
1591
|
+
};
|
|
1592
|
+
// Fonction pour trouver l'image à la position spécifique
|
|
1593
|
+
const findImageAtPosition = () => {
|
|
1594
|
+
const allImages = getAllImages();
|
|
1595
|
+
for (const img of allImages) {
|
|
1596
|
+
try {
|
|
1597
|
+
// Obtenir la position ProseMirror de cette image
|
|
1598
|
+
const imgPos = ed.view.posAtDOM(img, 0);
|
|
1599
|
+
// Vérifier si cette image correspond à la position sélectionnée
|
|
1600
|
+
if (Math.abs(imgPos - from) <= 1) {
|
|
1601
|
+
return img;
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
catch (error) {
|
|
1605
|
+
// Continuer si on ne peut pas obtenir la position de cette image
|
|
1606
|
+
continue;
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1609
|
+
return null;
|
|
1610
|
+
};
|
|
1611
|
+
// Chercher l'image à la position exacte
|
|
1612
|
+
const imageElement = findImageAtPosition();
|
|
1613
|
+
if (imageElement) {
|
|
1614
|
+
return imageElement.getBoundingClientRect();
|
|
1615
|
+
}
|
|
1616
|
+
return new DOMRect(0, 0, 0, 0);
|
|
1617
|
+
}
|
|
1618
|
+
hideOtherMenus() {
|
|
1619
|
+
// Cette méthode peut être étendue pour fermer d'autres menus si nécessaire
|
|
1620
|
+
// Pour l'instant, elle sert de placeholder pour une future coordination entre menus
|
|
1621
|
+
}
|
|
1622
|
+
showTippy() {
|
|
1623
|
+
if (!this.tippyInstance)
|
|
1624
|
+
return;
|
|
1625
|
+
// Mettre à jour la position
|
|
1626
|
+
this.tippyInstance.setProps({
|
|
1627
|
+
getReferenceClientRect: () => this.getImageRect(),
|
|
1628
|
+
});
|
|
1629
|
+
this.tippyInstance.show();
|
|
1630
|
+
}
|
|
1631
|
+
hideTippy() {
|
|
1632
|
+
if (this.tippyInstance) {
|
|
1633
|
+
this.tippyInstance.hide();
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
onCommand(command, event) {
|
|
1637
|
+
event.preventDefault();
|
|
1638
|
+
const ed = this.editor();
|
|
1639
|
+
if (!ed)
|
|
1640
|
+
return;
|
|
1641
|
+
switch (command) {
|
|
1642
|
+
case "changeImage":
|
|
1643
|
+
this.changeImage();
|
|
1644
|
+
break;
|
|
1645
|
+
case "resizeSmall":
|
|
1646
|
+
this.imageService.resizeImageToSmall(ed);
|
|
1647
|
+
break;
|
|
1648
|
+
case "resizeMedium":
|
|
1649
|
+
this.imageService.resizeImageToMedium(ed);
|
|
1650
|
+
break;
|
|
1651
|
+
case "resizeLarge":
|
|
1652
|
+
this.imageService.resizeImageToLarge(ed);
|
|
1653
|
+
break;
|
|
1654
|
+
case "resizeOriginal":
|
|
1655
|
+
this.imageService.resizeImageToOriginal(ed);
|
|
1656
|
+
break;
|
|
1657
|
+
case "deleteImage":
|
|
1658
|
+
this.deleteImage();
|
|
1659
|
+
break;
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
async changeImage() {
|
|
1663
|
+
const ed = this.editor();
|
|
1664
|
+
if (!ed)
|
|
1665
|
+
return;
|
|
1666
|
+
try {
|
|
1667
|
+
// Utiliser la méthode spécifique pour remplacer une image existante
|
|
1668
|
+
await this.imageService.selectAndReplaceImage(ed, {
|
|
1669
|
+
quality: 0.8,
|
|
1670
|
+
maxWidth: 1920,
|
|
1671
|
+
maxHeight: 1080,
|
|
1672
|
+
accept: "image/*",
|
|
1673
|
+
});
|
|
1674
|
+
}
|
|
1675
|
+
catch (error) {
|
|
1676
|
+
console.error("Erreur lors du changement d'image:", error);
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
deleteImage() {
|
|
1680
|
+
const ed = this.editor();
|
|
1681
|
+
if (ed) {
|
|
1682
|
+
ed.chain().focus().deleteSelection().run();
|
|
1683
|
+
}
|
|
1684
|
+
}
|
|
1685
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: TiptapImageBubbleMenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1686
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.0", type: TiptapImageBubbleMenuComponent, isStandalone: true, selector: "tiptap-image-bubble-menu", inputs: { editor: { classPropertyName: "editor", publicName: "editor", isSignal: true, isRequired: true, transformFunction: null }, config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "menuRef", first: true, predicate: ["menuRef"], descendants: true }], ngImport: i0, template: `
|
|
1687
|
+
<div #menuRef class="bubble-menu">
|
|
1688
|
+
@if (imageBubbleMenuConfig().changeImage) {
|
|
1689
|
+
<tiptap-button
|
|
1690
|
+
icon="drive_file_rename_outline"
|
|
1691
|
+
title="Changer l'image"
|
|
1692
|
+
(click)="onCommand('changeImage', $event)"
|
|
1693
|
+
></tiptap-button>
|
|
1694
|
+
} @if (imageBubbleMenuConfig().separator && hasResizeButtons()) {
|
|
1695
|
+
<tiptap-separator></tiptap-separator>
|
|
1696
|
+
} @if (imageBubbleMenuConfig().resizeSmall) {
|
|
1697
|
+
<tiptap-button
|
|
1698
|
+
icon="crop_square"
|
|
1699
|
+
iconSize="small"
|
|
1700
|
+
title="Petite (300×200)"
|
|
1701
|
+
(click)="onCommand('resizeSmall', $event)"
|
|
1702
|
+
></tiptap-button>
|
|
1703
|
+
} @if (imageBubbleMenuConfig().resizeMedium) {
|
|
1704
|
+
<tiptap-button
|
|
1705
|
+
icon="crop_square"
|
|
1706
|
+
iconSize="medium"
|
|
1707
|
+
title="Moyenne (500×350)"
|
|
1708
|
+
(click)="onCommand('resizeMedium', $event)"
|
|
1709
|
+
></tiptap-button>
|
|
1710
|
+
} @if (imageBubbleMenuConfig().resizeLarge) {
|
|
1711
|
+
<tiptap-button
|
|
1712
|
+
icon="crop_square"
|
|
1713
|
+
iconSize="large"
|
|
1714
|
+
title="Grande (800×600)"
|
|
1715
|
+
(click)="onCommand('resizeLarge', $event)"
|
|
1716
|
+
></tiptap-button>
|
|
1717
|
+
} @if (imageBubbleMenuConfig().resizeOriginal) {
|
|
1718
|
+
<tiptap-button
|
|
1719
|
+
icon="photo_size_select_actual"
|
|
1720
|
+
title="Taille originale"
|
|
1721
|
+
(click)="onCommand('resizeOriginal', $event)"
|
|
1722
|
+
></tiptap-button>
|
|
1723
|
+
} @if (imageBubbleMenuConfig().separator &&
|
|
1724
|
+
imageBubbleMenuConfig().deleteImage) {
|
|
1725
|
+
<tiptap-separator></tiptap-separator>
|
|
1726
|
+
} @if (imageBubbleMenuConfig().deleteImage) {
|
|
1727
|
+
<tiptap-button
|
|
1728
|
+
icon="delete"
|
|
1729
|
+
title="Supprimer l'image"
|
|
1730
|
+
variant="danger"
|
|
1731
|
+
(click)="onCommand('deleteImage', $event)"
|
|
1732
|
+
></tiptap-button>
|
|
1733
|
+
}
|
|
1734
|
+
</div>
|
|
1735
|
+
`, isInline: true, dependencies: [{ kind: "component", type: TiptapButtonComponent, selector: "tiptap-button", inputs: ["icon", "title", "active", "disabled", "variant", "size", "iconSize"], outputs: ["onClick"] }, { kind: "component", type: TiptapSeparatorComponent, selector: "tiptap-separator", inputs: ["orientation", "size"] }] }); }
|
|
1736
|
+
}
|
|
1737
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: TiptapImageBubbleMenuComponent, decorators: [{
|
|
1738
|
+
type: Component,
|
|
1739
|
+
args: [{ selector: "tiptap-image-bubble-menu", standalone: true, imports: [TiptapButtonComponent, TiptapSeparatorComponent], template: `
|
|
1740
|
+
<div #menuRef class="bubble-menu">
|
|
1741
|
+
@if (imageBubbleMenuConfig().changeImage) {
|
|
1742
|
+
<tiptap-button
|
|
1743
|
+
icon="drive_file_rename_outline"
|
|
1744
|
+
title="Changer l'image"
|
|
1745
|
+
(click)="onCommand('changeImage', $event)"
|
|
1746
|
+
></tiptap-button>
|
|
1747
|
+
} @if (imageBubbleMenuConfig().separator && hasResizeButtons()) {
|
|
1748
|
+
<tiptap-separator></tiptap-separator>
|
|
1749
|
+
} @if (imageBubbleMenuConfig().resizeSmall) {
|
|
1750
|
+
<tiptap-button
|
|
1751
|
+
icon="crop_square"
|
|
1752
|
+
iconSize="small"
|
|
1753
|
+
title="Petite (300×200)"
|
|
1754
|
+
(click)="onCommand('resizeSmall', $event)"
|
|
1755
|
+
></tiptap-button>
|
|
1756
|
+
} @if (imageBubbleMenuConfig().resizeMedium) {
|
|
1757
|
+
<tiptap-button
|
|
1758
|
+
icon="crop_square"
|
|
1759
|
+
iconSize="medium"
|
|
1760
|
+
title="Moyenne (500×350)"
|
|
1761
|
+
(click)="onCommand('resizeMedium', $event)"
|
|
1762
|
+
></tiptap-button>
|
|
1763
|
+
} @if (imageBubbleMenuConfig().resizeLarge) {
|
|
1764
|
+
<tiptap-button
|
|
1765
|
+
icon="crop_square"
|
|
1766
|
+
iconSize="large"
|
|
1767
|
+
title="Grande (800×600)"
|
|
1768
|
+
(click)="onCommand('resizeLarge', $event)"
|
|
1769
|
+
></tiptap-button>
|
|
1770
|
+
} @if (imageBubbleMenuConfig().resizeOriginal) {
|
|
1771
|
+
<tiptap-button
|
|
1772
|
+
icon="photo_size_select_actual"
|
|
1773
|
+
title="Taille originale"
|
|
1774
|
+
(click)="onCommand('resizeOriginal', $event)"
|
|
1775
|
+
></tiptap-button>
|
|
1776
|
+
} @if (imageBubbleMenuConfig().separator &&
|
|
1777
|
+
imageBubbleMenuConfig().deleteImage) {
|
|
1778
|
+
<tiptap-separator></tiptap-separator>
|
|
1779
|
+
} @if (imageBubbleMenuConfig().deleteImage) {
|
|
1780
|
+
<tiptap-button
|
|
1781
|
+
icon="delete"
|
|
1782
|
+
title="Supprimer l'image"
|
|
1783
|
+
variant="danger"
|
|
1784
|
+
(click)="onCommand('deleteImage', $event)"
|
|
1785
|
+
></tiptap-button>
|
|
1786
|
+
}
|
|
1787
|
+
</div>
|
|
1788
|
+
` }]
|
|
1789
|
+
}], ctorParameters: () => [], propDecorators: { menuRef: [{
|
|
1790
|
+
type: ViewChild,
|
|
1791
|
+
args: ["menuRef", { static: false }]
|
|
1792
|
+
}] } });
|
|
1793
|
+
|
|
1794
|
+
const ENGLISH_TRANSLATIONS = {
|
|
1795
|
+
toolbar: {
|
|
1796
|
+
bold: "Bold",
|
|
1797
|
+
italic: "Italic",
|
|
1798
|
+
underline: "Underline",
|
|
1799
|
+
strike: "Strikethrough",
|
|
1800
|
+
code: "Code",
|
|
1801
|
+
superscript: "Superscript",
|
|
1802
|
+
subscript: "Subscript",
|
|
1803
|
+
highlight: "Highlight",
|
|
1804
|
+
heading1: "Heading 1",
|
|
1805
|
+
heading2: "Heading 2",
|
|
1806
|
+
heading3: "Heading 3",
|
|
1807
|
+
bulletList: "Bullet List",
|
|
1808
|
+
orderedList: "Ordered List",
|
|
1809
|
+
blockquote: "Blockquote",
|
|
1810
|
+
alignLeft: "Align Left",
|
|
1811
|
+
alignCenter: "Align Center",
|
|
1032
1812
|
alignRight: "Align Right",
|
|
1033
1813
|
alignJustify: "Align Justify",
|
|
1034
1814
|
link: "Add Link",
|
|
1035
1815
|
image: "Add Image",
|
|
1036
1816
|
horizontalRule: "Horizontal Rule",
|
|
1817
|
+
table: "Insert Table",
|
|
1037
1818
|
undo: "Undo",
|
|
1038
1819
|
redo: "Redo",
|
|
1820
|
+
clear: "Clear",
|
|
1039
1821
|
},
|
|
1040
1822
|
bubbleMenu: {
|
|
1041
1823
|
bold: "Bold",
|
|
@@ -1100,6 +1882,24 @@ const ENGLISH_TRANSLATIONS = {
|
|
|
1100
1882
|
description: "Add a horizontal line",
|
|
1101
1883
|
keywords: ["hr", "horizontal", "rule", "line", "separator"],
|
|
1102
1884
|
},
|
|
1885
|
+
table: {
|
|
1886
|
+
title: "Table",
|
|
1887
|
+
description: "Insert a table",
|
|
1888
|
+
keywords: ["table", "grid", "data", "rows", "columns"],
|
|
1889
|
+
},
|
|
1890
|
+
},
|
|
1891
|
+
table: {
|
|
1892
|
+
addRowBefore: "Add row before",
|
|
1893
|
+
addRowAfter: "Add row after",
|
|
1894
|
+
deleteRow: "Delete row",
|
|
1895
|
+
addColumnBefore: "Add column before",
|
|
1896
|
+
addColumnAfter: "Add column after",
|
|
1897
|
+
deleteColumn: "Delete column",
|
|
1898
|
+
deleteTable: "Delete table",
|
|
1899
|
+
toggleHeaderRow: "Toggle header row",
|
|
1900
|
+
toggleHeaderColumn: "Toggle header column",
|
|
1901
|
+
mergeCells: "Merge cells",
|
|
1902
|
+
splitCell: "Split cell",
|
|
1103
1903
|
},
|
|
1104
1904
|
imageUpload: {
|
|
1105
1905
|
selectImage: "Select Image",
|
|
@@ -1119,8 +1919,8 @@ const ENGLISH_TRANSLATIONS = {
|
|
|
1119
1919
|
},
|
|
1120
1920
|
editor: {
|
|
1121
1921
|
placeholder: "Start typing...",
|
|
1122
|
-
|
|
1123
|
-
|
|
1922
|
+
character: "character",
|
|
1923
|
+
word: "word",
|
|
1124
1924
|
imageLoadError: "Error loading image",
|
|
1125
1925
|
linkPrompt: "Enter link URL",
|
|
1126
1926
|
linkUrlPrompt: "Enter URL",
|
|
@@ -1161,8 +1961,10 @@ const FRENCH_TRANSLATIONS = {
|
|
|
1161
1961
|
link: "Ajouter un lien",
|
|
1162
1962
|
image: "Ajouter une image",
|
|
1163
1963
|
horizontalRule: "Ligne horizontale",
|
|
1964
|
+
table: "Insérer un tableau",
|
|
1164
1965
|
undo: "Annuler",
|
|
1165
1966
|
redo: "Refaire",
|
|
1967
|
+
clear: "Vider",
|
|
1166
1968
|
},
|
|
1167
1969
|
bubbleMenu: {
|
|
1168
1970
|
bold: "Gras",
|
|
@@ -1227,6 +2029,33 @@ const FRENCH_TRANSLATIONS = {
|
|
|
1227
2029
|
description: "Ajouter une ligne de séparation",
|
|
1228
2030
|
keywords: ["hr", "horizontal", "rule", "ligne", "séparation"],
|
|
1229
2031
|
},
|
|
2032
|
+
table: {
|
|
2033
|
+
title: "Tableau",
|
|
2034
|
+
description: "Insérer un tableau",
|
|
2035
|
+
keywords: [
|
|
2036
|
+
"table",
|
|
2037
|
+
"tableau",
|
|
2038
|
+
"grid",
|
|
2039
|
+
"grille",
|
|
2040
|
+
"data",
|
|
2041
|
+
"données",
|
|
2042
|
+
"rows",
|
|
2043
|
+
"colonnes",
|
|
2044
|
+
],
|
|
2045
|
+
},
|
|
2046
|
+
},
|
|
2047
|
+
table: {
|
|
2048
|
+
addRowBefore: "Ajouter une ligne avant",
|
|
2049
|
+
addRowAfter: "Ajouter une ligne après",
|
|
2050
|
+
deleteRow: "Supprimer la ligne",
|
|
2051
|
+
addColumnBefore: "Ajouter une colonne avant",
|
|
2052
|
+
addColumnAfter: "Ajouter une colonne après",
|
|
2053
|
+
deleteColumn: "Supprimer la colonne",
|
|
2054
|
+
deleteTable: "Supprimer le tableau",
|
|
2055
|
+
toggleHeaderRow: "Basculer ligne d'en-tête",
|
|
2056
|
+
toggleHeaderColumn: "Basculer colonne d'en-tête",
|
|
2057
|
+
mergeCells: "Fusionner les cellules",
|
|
2058
|
+
splitCell: "Diviser la cellule",
|
|
1230
2059
|
},
|
|
1231
2060
|
imageUpload: {
|
|
1232
2061
|
selectImage: "Sélectionner une image",
|
|
@@ -1246,8 +2075,8 @@ const FRENCH_TRANSLATIONS = {
|
|
|
1246
2075
|
},
|
|
1247
2076
|
editor: {
|
|
1248
2077
|
placeholder: "Commencez à écrire...",
|
|
1249
|
-
|
|
1250
|
-
|
|
2078
|
+
character: "caractère",
|
|
2079
|
+
word: "mot",
|
|
1251
2080
|
imageLoadError: "Erreur de chargement de l'image",
|
|
1252
2081
|
linkPrompt: "Entrez l'URL du lien",
|
|
1253
2082
|
linkUrlPrompt: "Entrez l'URL",
|
|
@@ -1278,1027 +2107,262 @@ class TiptapI18nService {
|
|
|
1278
2107
|
// Méthodes de traduction rapides
|
|
1279
2108
|
this.t = computed(() => this.translations());
|
|
1280
2109
|
this.toolbar = computed(() => this.translations().toolbar);
|
|
1281
|
-
this.bubbleMenu = computed(() => this.translations().bubbleMenu);
|
|
1282
|
-
this.slashCommands = computed(() => this.translations().slashCommands);
|
|
1283
|
-
this.
|
|
1284
|
-
this.
|
|
1285
|
-
this.
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
setLocale(locale) {
|
|
1290
|
-
this._currentLocale.set(locale);
|
|
1291
|
-
}
|
|
1292
|
-
autoDetectLocale() {
|
|
1293
|
-
this.detectBrowserLanguage();
|
|
1294
|
-
}
|
|
1295
|
-
getSupportedLocales() {
|
|
1296
|
-
return Object.keys(this._translations());
|
|
1297
|
-
}
|
|
1298
|
-
addTranslations(locale, translations) {
|
|
1299
|
-
this._translations.update((current) => ({
|
|
1300
|
-
...current,
|
|
1301
|
-
[locale]: {
|
|
1302
|
-
...current[locale],
|
|
1303
|
-
...translations,
|
|
1304
|
-
},
|
|
1305
|
-
}));
|
|
1306
|
-
}
|
|
1307
|
-
detectBrowserLanguage() {
|
|
1308
|
-
const browserLang = navigator.language.toLowerCase();
|
|
1309
|
-
if (browserLang.startsWith("fr")) {
|
|
1310
|
-
this._currentLocale.set("fr");
|
|
1311
|
-
}
|
|
1312
|
-
else {
|
|
1313
|
-
this._currentLocale.set("en");
|
|
1314
|
-
}
|
|
1315
|
-
}
|
|
1316
|
-
// Méthodes utilitaires pour les composants
|
|
1317
|
-
getToolbarTitle(key) {
|
|
1318
|
-
return this.translations().toolbar[key];
|
|
1319
|
-
}
|
|
1320
|
-
getBubbleMenuTitle(key) {
|
|
1321
|
-
return this.translations().bubbleMenu[key];
|
|
1322
|
-
}
|
|
1323
|
-
getSlashCommand(key) {
|
|
1324
|
-
return this.translations().slashCommands[key];
|
|
1325
|
-
}
|
|
1326
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: TiptapI18nService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
1327
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: TiptapI18nService, providedIn: "root" }); }
|
|
1328
|
-
}
|
|
1329
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: TiptapI18nService, decorators: [{
|
|
1330
|
-
type: Injectable,
|
|
1331
|
-
args: [{
|
|
1332
|
-
providedIn: "root",
|
|
1333
|
-
}]
|
|
1334
|
-
}], ctorParameters: () => [] });
|
|
1335
|
-
|
|
1336
|
-
class EditorCommandsService {
|
|
1337
|
-
// Méthodes pour vérifier l'état actif
|
|
1338
|
-
isActive(editor, name, attributes) {
|
|
1339
|
-
return editor.isActive(name, attributes);
|
|
1340
|
-
}
|
|
1341
|
-
// Méthodes pour vérifier si une commande peut être exécutée
|
|
1342
|
-
canExecute(editor, command) {
|
|
1343
|
-
if (!editor)
|
|
1344
|
-
return false;
|
|
1345
|
-
switch (command) {
|
|
1346
|
-
case "toggleBold":
|
|
1347
|
-
return editor.can().chain().focus().toggleBold().run();
|
|
1348
|
-
case "toggleItalic":
|
|
1349
|
-
return editor.can().chain().focus().toggleItalic().run();
|
|
1350
|
-
case "toggleStrike":
|
|
1351
|
-
return editor.can().chain().focus().toggleStrike().run();
|
|
1352
|
-
case "toggleCode":
|
|
1353
|
-
return editor.can().chain().focus().toggleCode().run();
|
|
1354
|
-
case "toggleUnderline":
|
|
1355
|
-
return editor.can().chain().focus().toggleUnderline().run();
|
|
1356
|
-
case "toggleSuperscript":
|
|
1357
|
-
return editor.can().chain().focus().toggleSuperscript().run();
|
|
1358
|
-
case "toggleSubscript":
|
|
1359
|
-
return editor.can().chain().focus().toggleSubscript().run();
|
|
1360
|
-
case "setTextAlign":
|
|
1361
|
-
return editor.can().chain().focus().setTextAlign("left").run();
|
|
1362
|
-
case "toggleLink":
|
|
1363
|
-
return editor.can().chain().focus().toggleLink({ href: "" }).run();
|
|
1364
|
-
case "insertHorizontalRule":
|
|
1365
|
-
return editor.can().chain().focus().setHorizontalRule().run();
|
|
1366
|
-
case "toggleHighlight":
|
|
1367
|
-
return editor.can().chain().focus().toggleHighlight().run();
|
|
1368
|
-
case "undo":
|
|
1369
|
-
return editor.can().chain().focus().undo().run();
|
|
1370
|
-
case "redo":
|
|
1371
|
-
return editor.can().chain().focus().redo().run();
|
|
1372
|
-
default:
|
|
1373
|
-
return false;
|
|
1374
|
-
}
|
|
1375
|
-
}
|
|
1376
|
-
// Méthodes pour exécuter les commandes
|
|
1377
|
-
toggleBold(editor) {
|
|
1378
|
-
editor.chain().focus().toggleBold().run();
|
|
1379
|
-
}
|
|
1380
|
-
toggleItalic(editor) {
|
|
1381
|
-
editor.chain().focus().toggleItalic().run();
|
|
1382
|
-
}
|
|
1383
|
-
toggleStrike(editor) {
|
|
1384
|
-
editor.chain().focus().toggleStrike().run();
|
|
1385
|
-
}
|
|
1386
|
-
toggleCode(editor) {
|
|
1387
|
-
editor.chain().focus().toggleCode().run();
|
|
1388
|
-
}
|
|
1389
|
-
toggleHeading(editor, level) {
|
|
1390
|
-
editor.chain().focus().toggleHeading({ level }).run();
|
|
1391
|
-
}
|
|
1392
|
-
toggleBulletList(editor) {
|
|
1393
|
-
editor.chain().focus().toggleBulletList().run();
|
|
1394
|
-
}
|
|
1395
|
-
toggleOrderedList(editor) {
|
|
1396
|
-
editor.chain().focus().toggleOrderedList().run();
|
|
1397
|
-
}
|
|
1398
|
-
toggleBlockquote(editor) {
|
|
1399
|
-
editor.chain().focus().toggleBlockquote().run();
|
|
1400
|
-
}
|
|
1401
|
-
undo(editor) {
|
|
1402
|
-
editor.chain().focus().undo().run();
|
|
1403
|
-
}
|
|
1404
|
-
redo(editor) {
|
|
1405
|
-
editor.chain().focus().redo().run();
|
|
1406
|
-
}
|
|
1407
|
-
// Nouvelles méthodes pour les formatages supplémentaires
|
|
1408
|
-
toggleUnderline(editor) {
|
|
1409
|
-
editor.chain().focus().toggleUnderline().run();
|
|
1410
|
-
}
|
|
1411
|
-
toggleSuperscript(editor) {
|
|
1412
|
-
editor.chain().focus().toggleSuperscript().run();
|
|
1413
|
-
}
|
|
1414
|
-
toggleSubscript(editor) {
|
|
1415
|
-
editor.chain().focus().toggleSubscript().run();
|
|
1416
|
-
}
|
|
1417
|
-
setTextAlign(editor, alignment) {
|
|
1418
|
-
editor.chain().focus().setTextAlign(alignment).run();
|
|
1419
|
-
}
|
|
1420
|
-
toggleLink(editor, url) {
|
|
1421
|
-
if (url) {
|
|
1422
|
-
editor.chain().focus().toggleLink({ href: url }).run();
|
|
1423
|
-
}
|
|
1424
|
-
else {
|
|
1425
|
-
// Si pas d'URL fournie, on demande à l'utilisateur
|
|
1426
|
-
const href = window.prompt("URL du lien:");
|
|
1427
|
-
if (href) {
|
|
1428
|
-
editor.chain().focus().toggleLink({ href }).run();
|
|
1429
|
-
}
|
|
1430
|
-
}
|
|
1431
|
-
}
|
|
1432
|
-
insertHorizontalRule(editor) {
|
|
1433
|
-
editor.chain().focus().setHorizontalRule().run();
|
|
1434
|
-
}
|
|
1435
|
-
toggleHighlight(editor, color) {
|
|
1436
|
-
if (color) {
|
|
1437
|
-
editor.chain().focus().toggleHighlight({ color }).run();
|
|
1438
|
-
}
|
|
1439
|
-
else {
|
|
1440
|
-
editor.chain().focus().toggleHighlight().run();
|
|
1441
|
-
}
|
|
1442
|
-
}
|
|
1443
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: EditorCommandsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
1444
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: EditorCommandsService, providedIn: "root" }); }
|
|
1445
|
-
}
|
|
1446
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: EditorCommandsService, decorators: [{
|
|
1447
|
-
type: Injectable,
|
|
1448
|
-
args: [{
|
|
1449
|
-
providedIn: "root",
|
|
1450
|
-
}]
|
|
1451
|
-
}] });
|
|
1452
|
-
|
|
1453
|
-
class TiptapToolbarComponent {
|
|
1454
|
-
constructor(editorCommands) {
|
|
1455
|
-
this.editorCommands = editorCommands;
|
|
1456
|
-
this.editor = input.required();
|
|
1457
|
-
this.config = input.required();
|
|
1458
|
-
// Outputs pour les événements d'image
|
|
1459
|
-
this.imageUploaded = output();
|
|
1460
|
-
this.imageError = output();
|
|
1461
|
-
this.imageService = inject(ImageService);
|
|
1462
|
-
this.i18nService = inject(TiptapI18nService);
|
|
1463
|
-
// Computed values pour les traductions
|
|
1464
|
-
this.t = this.i18nService.toolbar;
|
|
1465
|
-
}
|
|
1466
|
-
isActive(name, attributes) {
|
|
1467
|
-
return this.editorCommands.isActive(this.editor(), name, attributes);
|
|
1468
|
-
}
|
|
1469
|
-
canExecute(command) {
|
|
1470
|
-
return this.editorCommands.canExecute(this.editor(), command);
|
|
1471
|
-
}
|
|
1472
|
-
toggleBold() {
|
|
1473
|
-
this.editorCommands.toggleBold(this.editor());
|
|
1474
|
-
}
|
|
1475
|
-
toggleItalic() {
|
|
1476
|
-
this.editorCommands.toggleItalic(this.editor());
|
|
1477
|
-
}
|
|
1478
|
-
toggleStrike() {
|
|
1479
|
-
this.editorCommands.toggleStrike(this.editor());
|
|
1480
|
-
}
|
|
1481
|
-
toggleCode() {
|
|
1482
|
-
this.editorCommands.toggleCode(this.editor());
|
|
1483
|
-
}
|
|
1484
|
-
toggleHeading(level) {
|
|
1485
|
-
this.editorCommands.toggleHeading(this.editor(), level);
|
|
1486
|
-
}
|
|
1487
|
-
toggleBulletList() {
|
|
1488
|
-
this.editorCommands.toggleBulletList(this.editor());
|
|
1489
|
-
}
|
|
1490
|
-
toggleOrderedList() {
|
|
1491
|
-
this.editorCommands.toggleOrderedList(this.editor());
|
|
1492
|
-
}
|
|
1493
|
-
toggleBlockquote() {
|
|
1494
|
-
this.editorCommands.toggleBlockquote(this.editor());
|
|
1495
|
-
}
|
|
1496
|
-
undo() {
|
|
1497
|
-
this.editorCommands.undo(this.editor());
|
|
1498
|
-
}
|
|
1499
|
-
redo() {
|
|
1500
|
-
this.editorCommands.redo(this.editor());
|
|
1501
|
-
}
|
|
1502
|
-
// Nouvelles méthodes pour les formatages supplémentaires
|
|
1503
|
-
toggleUnderline() {
|
|
1504
|
-
this.editorCommands.toggleUnderline(this.editor());
|
|
1505
|
-
}
|
|
1506
|
-
toggleSuperscript() {
|
|
1507
|
-
this.editorCommands.toggleSuperscript(this.editor());
|
|
1508
|
-
}
|
|
1509
|
-
toggleSubscript() {
|
|
1510
|
-
this.editorCommands.toggleSubscript(this.editor());
|
|
2110
|
+
this.bubbleMenu = computed(() => this.translations().bubbleMenu);
|
|
2111
|
+
this.slashCommands = computed(() => this.translations().slashCommands);
|
|
2112
|
+
this.table = computed(() => this.translations().table);
|
|
2113
|
+
this.imageUpload = computed(() => this.translations().imageUpload);
|
|
2114
|
+
this.editor = computed(() => this.translations().editor);
|
|
2115
|
+
this.common = computed(() => this.translations().common);
|
|
2116
|
+
// Détecter automatiquement la langue du navigateur
|
|
2117
|
+
this.detectBrowserLanguage();
|
|
1511
2118
|
}
|
|
1512
|
-
|
|
1513
|
-
this.
|
|
2119
|
+
setLocale(locale) {
|
|
2120
|
+
this._currentLocale.set(locale);
|
|
1514
2121
|
}
|
|
1515
|
-
|
|
1516
|
-
this.
|
|
2122
|
+
autoDetectLocale() {
|
|
2123
|
+
this.detectBrowserLanguage();
|
|
1517
2124
|
}
|
|
1518
|
-
|
|
1519
|
-
|
|
2125
|
+
getSupportedLocales() {
|
|
2126
|
+
return Object.keys(this._translations());
|
|
1520
2127
|
}
|
|
1521
|
-
|
|
1522
|
-
this.
|
|
2128
|
+
addTranslations(locale, translations) {
|
|
2129
|
+
this._translations.update((current) => ({
|
|
2130
|
+
...current,
|
|
2131
|
+
[locale]: {
|
|
2132
|
+
...current[locale],
|
|
2133
|
+
...translations,
|
|
2134
|
+
},
|
|
2135
|
+
}));
|
|
1523
2136
|
}
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
2137
|
+
detectBrowserLanguage() {
|
|
2138
|
+
const browserLang = navigator.language.toLowerCase();
|
|
2139
|
+
if (browserLang.startsWith("fr")) {
|
|
2140
|
+
this._currentLocale.set("fr");
|
|
1528
2141
|
}
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
this.imageError.emit("Erreur lors de l'upload d'image");
|
|
2142
|
+
else {
|
|
2143
|
+
this._currentLocale.set("en");
|
|
1532
2144
|
}
|
|
1533
2145
|
}
|
|
1534
|
-
// Méthodes pour les
|
|
1535
|
-
|
|
1536
|
-
this.
|
|
2146
|
+
// Méthodes utilitaires pour les composants
|
|
2147
|
+
getToolbarTitle(key) {
|
|
2148
|
+
return this.translations().toolbar[key];
|
|
1537
2149
|
}
|
|
1538
|
-
|
|
1539
|
-
this.
|
|
2150
|
+
getBubbleMenuTitle(key) {
|
|
2151
|
+
return this.translations().bubbleMenu[key];
|
|
1540
2152
|
}
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
icon="format_bold"
|
|
1547
|
-
[title]="t().bold"
|
|
1548
|
-
[active]="isActive('bold')"
|
|
1549
|
-
[disabled]="!canExecute('toggleBold')"
|
|
1550
|
-
(onClick)="toggleBold()"
|
|
1551
|
-
/>
|
|
1552
|
-
} @if (config().italic) {
|
|
1553
|
-
<tiptap-button
|
|
1554
|
-
icon="format_italic"
|
|
1555
|
-
[title]="t().italic"
|
|
1556
|
-
[active]="isActive('italic')"
|
|
1557
|
-
[disabled]="!canExecute('toggleItalic')"
|
|
1558
|
-
(onClick)="toggleItalic()"
|
|
1559
|
-
/>
|
|
1560
|
-
} @if (config().underline) {
|
|
1561
|
-
<tiptap-button
|
|
1562
|
-
icon="format_underlined"
|
|
1563
|
-
[title]="t().underline"
|
|
1564
|
-
[active]="isActive('underline')"
|
|
1565
|
-
[disabled]="!canExecute('toggleUnderline')"
|
|
1566
|
-
(onClick)="toggleUnderline()"
|
|
1567
|
-
/>
|
|
1568
|
-
} @if (config().strike) {
|
|
1569
|
-
<tiptap-button
|
|
1570
|
-
icon="strikethrough_s"
|
|
1571
|
-
[title]="t().strike"
|
|
1572
|
-
[active]="isActive('strike')"
|
|
1573
|
-
[disabled]="!canExecute('toggleStrike')"
|
|
1574
|
-
(onClick)="toggleStrike()"
|
|
1575
|
-
/>
|
|
1576
|
-
} @if (config().code) {
|
|
1577
|
-
<tiptap-button
|
|
1578
|
-
icon="code"
|
|
1579
|
-
[title]="t().code"
|
|
1580
|
-
[active]="isActive('code')"
|
|
1581
|
-
[disabled]="!canExecute('toggleCode')"
|
|
1582
|
-
(onClick)="toggleCode()"
|
|
1583
|
-
/>
|
|
1584
|
-
} @if (config().superscript) {
|
|
1585
|
-
<tiptap-button
|
|
1586
|
-
icon="superscript"
|
|
1587
|
-
[title]="t().superscript"
|
|
1588
|
-
[active]="isActive('superscript')"
|
|
1589
|
-
[disabled]="!canExecute('toggleSuperscript')"
|
|
1590
|
-
(onClick)="toggleSuperscript()"
|
|
1591
|
-
/>
|
|
1592
|
-
} @if (config().subscript) {
|
|
1593
|
-
<tiptap-button
|
|
1594
|
-
icon="subscript"
|
|
1595
|
-
[title]="t().subscript"
|
|
1596
|
-
[active]="isActive('subscript')"
|
|
1597
|
-
[disabled]="!canExecute('toggleSubscript')"
|
|
1598
|
-
(onClick)="toggleSubscript()"
|
|
1599
|
-
/>
|
|
1600
|
-
} @if (config().highlight) {
|
|
1601
|
-
<tiptap-button
|
|
1602
|
-
icon="highlight"
|
|
1603
|
-
[title]="t().highlight"
|
|
1604
|
-
[active]="isActive('highlight')"
|
|
1605
|
-
[disabled]="!canExecute('toggleHighlight')"
|
|
1606
|
-
(onClick)="toggleHighlight()"
|
|
1607
|
-
/>
|
|
1608
|
-
} @if (config().separator && (config().heading1 || config().heading2 ||
|
|
1609
|
-
config().heading3)) {
|
|
1610
|
-
<tiptap-separator />
|
|
1611
|
-
} @if (config().heading1) {
|
|
1612
|
-
<tiptap-button
|
|
1613
|
-
icon="format_h1"
|
|
1614
|
-
[title]="t().heading1"
|
|
1615
|
-
variant="text"
|
|
1616
|
-
[active]="isActive('heading', { level: 1 })"
|
|
1617
|
-
(onClick)="toggleHeading(1)"
|
|
1618
|
-
/>
|
|
1619
|
-
} @if (config().heading2) {
|
|
1620
|
-
<tiptap-button
|
|
1621
|
-
icon="format_h2"
|
|
1622
|
-
[title]="t().heading2"
|
|
1623
|
-
variant="text"
|
|
1624
|
-
[active]="isActive('heading', { level: 2 })"
|
|
1625
|
-
(onClick)="toggleHeading(2)"
|
|
1626
|
-
/>
|
|
1627
|
-
} @if (config().heading3) {
|
|
1628
|
-
<tiptap-button
|
|
1629
|
-
icon="format_h3"
|
|
1630
|
-
[title]="t().heading3"
|
|
1631
|
-
variant="text"
|
|
1632
|
-
[active]="isActive('heading', { level: 3 })"
|
|
1633
|
-
(onClick)="toggleHeading(3)"
|
|
1634
|
-
/>
|
|
1635
|
-
} @if (config().separator && (config().bulletList || config().orderedList
|
|
1636
|
-
|| config().blockquote)) {
|
|
1637
|
-
<tiptap-separator />
|
|
1638
|
-
} @if (config().bulletList) {
|
|
1639
|
-
<tiptap-button
|
|
1640
|
-
icon="format_list_bulleted"
|
|
1641
|
-
[title]="t().bulletList"
|
|
1642
|
-
[active]="isActive('bulletList')"
|
|
1643
|
-
(onClick)="toggleBulletList()"
|
|
1644
|
-
/>
|
|
1645
|
-
} @if (config().orderedList) {
|
|
1646
|
-
<tiptap-button
|
|
1647
|
-
icon="format_list_numbered"
|
|
1648
|
-
[title]="t().orderedList"
|
|
1649
|
-
[active]="isActive('orderedList')"
|
|
1650
|
-
(onClick)="toggleOrderedList()"
|
|
1651
|
-
/>
|
|
1652
|
-
} @if (config().blockquote) {
|
|
1653
|
-
<tiptap-button
|
|
1654
|
-
icon="format_quote"
|
|
1655
|
-
[title]="t().blockquote"
|
|
1656
|
-
[active]="isActive('blockquote')"
|
|
1657
|
-
(onClick)="toggleBlockquote()"
|
|
1658
|
-
/>
|
|
1659
|
-
} @if (config().separator && (config().alignLeft || config().alignCenter
|
|
1660
|
-
|| config().alignRight || config().alignJustify)) {
|
|
1661
|
-
<tiptap-separator />
|
|
1662
|
-
} @if (config().alignLeft) {
|
|
1663
|
-
<tiptap-button
|
|
1664
|
-
icon="format_align_left"
|
|
1665
|
-
[title]="t().alignLeft"
|
|
1666
|
-
[active]="isActive('textAlign', { textAlign: 'left' })"
|
|
1667
|
-
(onClick)="setTextAlign('left')"
|
|
1668
|
-
/>
|
|
1669
|
-
} @if (config().alignCenter) {
|
|
1670
|
-
<tiptap-button
|
|
1671
|
-
icon="format_align_center"
|
|
1672
|
-
[title]="t().alignCenter"
|
|
1673
|
-
[active]="isActive('textAlign', { textAlign: 'center' })"
|
|
1674
|
-
(onClick)="setTextAlign('center')"
|
|
1675
|
-
/>
|
|
1676
|
-
} @if (config().alignRight) {
|
|
1677
|
-
<tiptap-button
|
|
1678
|
-
icon="format_align_right"
|
|
1679
|
-
[title]="t().alignRight"
|
|
1680
|
-
[active]="isActive('textAlign', { textAlign: 'right' })"
|
|
1681
|
-
(onClick)="setTextAlign('right')"
|
|
1682
|
-
/>
|
|
1683
|
-
} @if (config().alignJustify) {
|
|
1684
|
-
<tiptap-button
|
|
1685
|
-
icon="format_align_justify"
|
|
1686
|
-
[title]="t().alignJustify"
|
|
1687
|
-
[active]="isActive('textAlign', { textAlign: 'justify' })"
|
|
1688
|
-
(onClick)="setTextAlign('justify')"
|
|
1689
|
-
/>
|
|
1690
|
-
} @if (config().separator && (config().link || config().horizontalRule)) {
|
|
1691
|
-
<tiptap-separator />
|
|
1692
|
-
} @if (config().link) {
|
|
1693
|
-
<tiptap-button
|
|
1694
|
-
icon="link"
|
|
1695
|
-
[title]="t().link"
|
|
1696
|
-
[active]="isActive('link')"
|
|
1697
|
-
(onClick)="toggleLink()"
|
|
1698
|
-
/>
|
|
1699
|
-
} @if (config().horizontalRule) {
|
|
1700
|
-
<tiptap-button
|
|
1701
|
-
icon="horizontal_rule"
|
|
1702
|
-
[title]="t().horizontalRule"
|
|
1703
|
-
(onClick)="insertHorizontalRule()"
|
|
1704
|
-
/>
|
|
1705
|
-
} @if (config().separator && config().image) {
|
|
1706
|
-
<tiptap-separator />
|
|
1707
|
-
} @if (config().image) {
|
|
1708
|
-
<tiptap-button
|
|
1709
|
-
icon="image"
|
|
1710
|
-
[title]="t().image"
|
|
1711
|
-
(onClick)="insertImage()"
|
|
1712
|
-
/>
|
|
1713
|
-
} @if (config().separator && (config().undo || config().redo)) {
|
|
1714
|
-
<tiptap-separator />
|
|
1715
|
-
} @if (config().undo) {
|
|
1716
|
-
<tiptap-button
|
|
1717
|
-
icon="undo"
|
|
1718
|
-
[title]="t().undo"
|
|
1719
|
-
[disabled]="!canExecute('undo')"
|
|
1720
|
-
(onClick)="undo()"
|
|
1721
|
-
/>
|
|
1722
|
-
} @if (config().redo) {
|
|
1723
|
-
<tiptap-button
|
|
1724
|
-
icon="redo"
|
|
1725
|
-
[title]="t().redo"
|
|
1726
|
-
[disabled]="!canExecute('redo')"
|
|
1727
|
-
(onClick)="redo()"
|
|
1728
|
-
/>
|
|
1729
|
-
}
|
|
1730
|
-
</div>
|
|
1731
|
-
`, isInline: true, styles: [".tiptap-toolbar{display:flex;align-items:center;gap:4px;padding:4px 8px;background:#f8f9fa;border-bottom:1px solid #e2e8f0;flex-wrap:wrap;min-height:32px;position:relative}.toolbar-group{display:flex;align-items:center;gap:2px;padding:0 4px}.toolbar-separator{width:1px;height:24px;background:#e2e8f0;margin:0 4px}@media (max-width: 768px){.tiptap-toolbar{padding:6px 8px;gap:2px}.toolbar-group{gap:1px}}@keyframes toolbarSlideIn{0%{opacity:0;transform:translateY(-10px)}to{opacity:1;transform:translateY(0)}}.tiptap-toolbar{animation:toolbarSlideIn .3s cubic-bezier(.4,0,.2,1)}\n"], dependencies: [{ kind: "component", type: TiptapButtonComponent, selector: "tiptap-button", inputs: ["icon", "title", "active", "disabled", "variant", "size", "iconSize"], outputs: ["onClick"] }, { kind: "component", type: TiptapSeparatorComponent, selector: "tiptap-separator", inputs: ["orientation", "size"] }] }); }
|
|
2153
|
+
getSlashCommand(key) {
|
|
2154
|
+
return this.translations().slashCommands[key];
|
|
2155
|
+
}
|
|
2156
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: TiptapI18nService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
2157
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: TiptapI18nService, providedIn: "root" }); }
|
|
1732
2158
|
}
|
|
1733
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type:
|
|
1734
|
-
type:
|
|
1735
|
-
args: [{
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
icon="format_bold"
|
|
1740
|
-
[title]="t().bold"
|
|
1741
|
-
[active]="isActive('bold')"
|
|
1742
|
-
[disabled]="!canExecute('toggleBold')"
|
|
1743
|
-
(onClick)="toggleBold()"
|
|
1744
|
-
/>
|
|
1745
|
-
} @if (config().italic) {
|
|
1746
|
-
<tiptap-button
|
|
1747
|
-
icon="format_italic"
|
|
1748
|
-
[title]="t().italic"
|
|
1749
|
-
[active]="isActive('italic')"
|
|
1750
|
-
[disabled]="!canExecute('toggleItalic')"
|
|
1751
|
-
(onClick)="toggleItalic()"
|
|
1752
|
-
/>
|
|
1753
|
-
} @if (config().underline) {
|
|
1754
|
-
<tiptap-button
|
|
1755
|
-
icon="format_underlined"
|
|
1756
|
-
[title]="t().underline"
|
|
1757
|
-
[active]="isActive('underline')"
|
|
1758
|
-
[disabled]="!canExecute('toggleUnderline')"
|
|
1759
|
-
(onClick)="toggleUnderline()"
|
|
1760
|
-
/>
|
|
1761
|
-
} @if (config().strike) {
|
|
1762
|
-
<tiptap-button
|
|
1763
|
-
icon="strikethrough_s"
|
|
1764
|
-
[title]="t().strike"
|
|
1765
|
-
[active]="isActive('strike')"
|
|
1766
|
-
[disabled]="!canExecute('toggleStrike')"
|
|
1767
|
-
(onClick)="toggleStrike()"
|
|
1768
|
-
/>
|
|
1769
|
-
} @if (config().code) {
|
|
1770
|
-
<tiptap-button
|
|
1771
|
-
icon="code"
|
|
1772
|
-
[title]="t().code"
|
|
1773
|
-
[active]="isActive('code')"
|
|
1774
|
-
[disabled]="!canExecute('toggleCode')"
|
|
1775
|
-
(onClick)="toggleCode()"
|
|
1776
|
-
/>
|
|
1777
|
-
} @if (config().superscript) {
|
|
1778
|
-
<tiptap-button
|
|
1779
|
-
icon="superscript"
|
|
1780
|
-
[title]="t().superscript"
|
|
1781
|
-
[active]="isActive('superscript')"
|
|
1782
|
-
[disabled]="!canExecute('toggleSuperscript')"
|
|
1783
|
-
(onClick)="toggleSuperscript()"
|
|
1784
|
-
/>
|
|
1785
|
-
} @if (config().subscript) {
|
|
1786
|
-
<tiptap-button
|
|
1787
|
-
icon="subscript"
|
|
1788
|
-
[title]="t().subscript"
|
|
1789
|
-
[active]="isActive('subscript')"
|
|
1790
|
-
[disabled]="!canExecute('toggleSubscript')"
|
|
1791
|
-
(onClick)="toggleSubscript()"
|
|
1792
|
-
/>
|
|
1793
|
-
} @if (config().highlight) {
|
|
1794
|
-
<tiptap-button
|
|
1795
|
-
icon="highlight"
|
|
1796
|
-
[title]="t().highlight"
|
|
1797
|
-
[active]="isActive('highlight')"
|
|
1798
|
-
[disabled]="!canExecute('toggleHighlight')"
|
|
1799
|
-
(onClick)="toggleHighlight()"
|
|
1800
|
-
/>
|
|
1801
|
-
} @if (config().separator && (config().heading1 || config().heading2 ||
|
|
1802
|
-
config().heading3)) {
|
|
1803
|
-
<tiptap-separator />
|
|
1804
|
-
} @if (config().heading1) {
|
|
1805
|
-
<tiptap-button
|
|
1806
|
-
icon="format_h1"
|
|
1807
|
-
[title]="t().heading1"
|
|
1808
|
-
variant="text"
|
|
1809
|
-
[active]="isActive('heading', { level: 1 })"
|
|
1810
|
-
(onClick)="toggleHeading(1)"
|
|
1811
|
-
/>
|
|
1812
|
-
} @if (config().heading2) {
|
|
1813
|
-
<tiptap-button
|
|
1814
|
-
icon="format_h2"
|
|
1815
|
-
[title]="t().heading2"
|
|
1816
|
-
variant="text"
|
|
1817
|
-
[active]="isActive('heading', { level: 2 })"
|
|
1818
|
-
(onClick)="toggleHeading(2)"
|
|
1819
|
-
/>
|
|
1820
|
-
} @if (config().heading3) {
|
|
1821
|
-
<tiptap-button
|
|
1822
|
-
icon="format_h3"
|
|
1823
|
-
[title]="t().heading3"
|
|
1824
|
-
variant="text"
|
|
1825
|
-
[active]="isActive('heading', { level: 3 })"
|
|
1826
|
-
(onClick)="toggleHeading(3)"
|
|
1827
|
-
/>
|
|
1828
|
-
} @if (config().separator && (config().bulletList || config().orderedList
|
|
1829
|
-
|| config().blockquote)) {
|
|
1830
|
-
<tiptap-separator />
|
|
1831
|
-
} @if (config().bulletList) {
|
|
1832
|
-
<tiptap-button
|
|
1833
|
-
icon="format_list_bulleted"
|
|
1834
|
-
[title]="t().bulletList"
|
|
1835
|
-
[active]="isActive('bulletList')"
|
|
1836
|
-
(onClick)="toggleBulletList()"
|
|
1837
|
-
/>
|
|
1838
|
-
} @if (config().orderedList) {
|
|
1839
|
-
<tiptap-button
|
|
1840
|
-
icon="format_list_numbered"
|
|
1841
|
-
[title]="t().orderedList"
|
|
1842
|
-
[active]="isActive('orderedList')"
|
|
1843
|
-
(onClick)="toggleOrderedList()"
|
|
1844
|
-
/>
|
|
1845
|
-
} @if (config().blockquote) {
|
|
1846
|
-
<tiptap-button
|
|
1847
|
-
icon="format_quote"
|
|
1848
|
-
[title]="t().blockquote"
|
|
1849
|
-
[active]="isActive('blockquote')"
|
|
1850
|
-
(onClick)="toggleBlockquote()"
|
|
1851
|
-
/>
|
|
1852
|
-
} @if (config().separator && (config().alignLeft || config().alignCenter
|
|
1853
|
-
|| config().alignRight || config().alignJustify)) {
|
|
1854
|
-
<tiptap-separator />
|
|
1855
|
-
} @if (config().alignLeft) {
|
|
1856
|
-
<tiptap-button
|
|
1857
|
-
icon="format_align_left"
|
|
1858
|
-
[title]="t().alignLeft"
|
|
1859
|
-
[active]="isActive('textAlign', { textAlign: 'left' })"
|
|
1860
|
-
(onClick)="setTextAlign('left')"
|
|
1861
|
-
/>
|
|
1862
|
-
} @if (config().alignCenter) {
|
|
1863
|
-
<tiptap-button
|
|
1864
|
-
icon="format_align_center"
|
|
1865
|
-
[title]="t().alignCenter"
|
|
1866
|
-
[active]="isActive('textAlign', { textAlign: 'center' })"
|
|
1867
|
-
(onClick)="setTextAlign('center')"
|
|
1868
|
-
/>
|
|
1869
|
-
} @if (config().alignRight) {
|
|
1870
|
-
<tiptap-button
|
|
1871
|
-
icon="format_align_right"
|
|
1872
|
-
[title]="t().alignRight"
|
|
1873
|
-
[active]="isActive('textAlign', { textAlign: 'right' })"
|
|
1874
|
-
(onClick)="setTextAlign('right')"
|
|
1875
|
-
/>
|
|
1876
|
-
} @if (config().alignJustify) {
|
|
1877
|
-
<tiptap-button
|
|
1878
|
-
icon="format_align_justify"
|
|
1879
|
-
[title]="t().alignJustify"
|
|
1880
|
-
[active]="isActive('textAlign', { textAlign: 'justify' })"
|
|
1881
|
-
(onClick)="setTextAlign('justify')"
|
|
1882
|
-
/>
|
|
1883
|
-
} @if (config().separator && (config().link || config().horizontalRule)) {
|
|
1884
|
-
<tiptap-separator />
|
|
1885
|
-
} @if (config().link) {
|
|
1886
|
-
<tiptap-button
|
|
1887
|
-
icon="link"
|
|
1888
|
-
[title]="t().link"
|
|
1889
|
-
[active]="isActive('link')"
|
|
1890
|
-
(onClick)="toggleLink()"
|
|
1891
|
-
/>
|
|
1892
|
-
} @if (config().horizontalRule) {
|
|
1893
|
-
<tiptap-button
|
|
1894
|
-
icon="horizontal_rule"
|
|
1895
|
-
[title]="t().horizontalRule"
|
|
1896
|
-
(onClick)="insertHorizontalRule()"
|
|
1897
|
-
/>
|
|
1898
|
-
} @if (config().separator && config().image) {
|
|
1899
|
-
<tiptap-separator />
|
|
1900
|
-
} @if (config().image) {
|
|
1901
|
-
<tiptap-button
|
|
1902
|
-
icon="image"
|
|
1903
|
-
[title]="t().image"
|
|
1904
|
-
(onClick)="insertImage()"
|
|
1905
|
-
/>
|
|
1906
|
-
} @if (config().separator && (config().undo || config().redo)) {
|
|
1907
|
-
<tiptap-separator />
|
|
1908
|
-
} @if (config().undo) {
|
|
1909
|
-
<tiptap-button
|
|
1910
|
-
icon="undo"
|
|
1911
|
-
[title]="t().undo"
|
|
1912
|
-
[disabled]="!canExecute('undo')"
|
|
1913
|
-
(onClick)="undo()"
|
|
1914
|
-
/>
|
|
1915
|
-
} @if (config().redo) {
|
|
1916
|
-
<tiptap-button
|
|
1917
|
-
icon="redo"
|
|
1918
|
-
[title]="t().redo"
|
|
1919
|
-
[disabled]="!canExecute('redo')"
|
|
1920
|
-
(onClick)="redo()"
|
|
1921
|
-
/>
|
|
1922
|
-
}
|
|
1923
|
-
</div>
|
|
1924
|
-
`, styles: [".tiptap-toolbar{display:flex;align-items:center;gap:4px;padding:4px 8px;background:#f8f9fa;border-bottom:1px solid #e2e8f0;flex-wrap:wrap;min-height:32px;position:relative}.toolbar-group{display:flex;align-items:center;gap:2px;padding:0 4px}.toolbar-separator{width:1px;height:24px;background:#e2e8f0;margin:0 4px}@media (max-width: 768px){.tiptap-toolbar{padding:6px 8px;gap:2px}.toolbar-group{gap:1px}}@keyframes toolbarSlideIn{0%{opacity:0;transform:translateY(-10px)}to{opacity:1;transform:translateY(0)}}.tiptap-toolbar{animation:toolbarSlideIn .3s cubic-bezier(.4,0,.2,1)}\n"] }]
|
|
1925
|
-
}], ctorParameters: () => [{ type: EditorCommandsService }] });
|
|
2159
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: TiptapI18nService, decorators: [{
|
|
2160
|
+
type: Injectable,
|
|
2161
|
+
args: [{
|
|
2162
|
+
providedIn: "root",
|
|
2163
|
+
}]
|
|
2164
|
+
}], ctorParameters: () => [] });
|
|
1926
2165
|
|
|
1927
|
-
class
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
maxSize: 5, // 5MB par défaut
|
|
1932
|
-
maxWidth: 1920, // largeur max par défaut
|
|
1933
|
-
maxHeight: 1080, // hauteur max par défaut
|
|
1934
|
-
allowedTypes: ["image/jpeg", "image/png", "image/gif", "image/webp"],
|
|
1935
|
-
enableDragDrop: true,
|
|
1936
|
-
showPreview: true,
|
|
1937
|
-
multiple: false,
|
|
1938
|
-
compressImages: true,
|
|
1939
|
-
quality: 0.8,
|
|
1940
|
-
});
|
|
1941
|
-
// Outputs
|
|
1942
|
-
this.imageSelected = output();
|
|
1943
|
-
this.error = output();
|
|
1944
|
-
// Signals internes
|
|
1945
|
-
this.isDragOver = signal(false);
|
|
1946
|
-
this.isUploading = signal(false);
|
|
1947
|
-
this.uploadProgress = signal(0);
|
|
1948
|
-
this.previewImage = signal(null);
|
|
1949
|
-
this.previewInfo = signal("");
|
|
1950
|
-
this.errorMessage = signal(null);
|
|
1951
|
-
// Computed
|
|
1952
|
-
this.acceptedTypes = computed(() => {
|
|
1953
|
-
const types = this.config().allowedTypes || ["image/*"];
|
|
1954
|
-
return types.join(",");
|
|
1955
|
-
});
|
|
2166
|
+
class EditorCommandsService {
|
|
2167
|
+
// Méthodes pour vérifier l'état actif
|
|
2168
|
+
isActive(editor, name, attributes) {
|
|
2169
|
+
return editor.isActive(name, attributes);
|
|
1956
2170
|
}
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
if (
|
|
1960
|
-
|
|
2171
|
+
// Méthodes pour vérifier si une commande peut être exécutée
|
|
2172
|
+
canExecute(editor, command) {
|
|
2173
|
+
if (!editor)
|
|
2174
|
+
return false;
|
|
2175
|
+
switch (command) {
|
|
2176
|
+
case "toggleBold":
|
|
2177
|
+
return editor.can().chain().focus().toggleBold().run();
|
|
2178
|
+
case "toggleItalic":
|
|
2179
|
+
return editor.can().chain().focus().toggleItalic().run();
|
|
2180
|
+
case "toggleStrike":
|
|
2181
|
+
return editor.can().chain().focus().toggleStrike().run();
|
|
2182
|
+
case "toggleCode":
|
|
2183
|
+
return editor.can().chain().focus().toggleCode().run();
|
|
2184
|
+
case "toggleUnderline":
|
|
2185
|
+
return editor.can().chain().focus().toggleUnderline().run();
|
|
2186
|
+
case "toggleSuperscript":
|
|
2187
|
+
return editor.can().chain().focus().toggleSuperscript().run();
|
|
2188
|
+
case "toggleSubscript":
|
|
2189
|
+
return editor.can().chain().focus().toggleSubscript().run();
|
|
2190
|
+
case "setTextAlign":
|
|
2191
|
+
return editor.can().chain().focus().setTextAlign("left").run();
|
|
2192
|
+
case "toggleLink":
|
|
2193
|
+
return editor.can().chain().focus().toggleLink({ href: "" }).run();
|
|
2194
|
+
case "insertHorizontalRule":
|
|
2195
|
+
return editor.can().chain().focus().setHorizontalRule().run();
|
|
2196
|
+
case "toggleHighlight":
|
|
2197
|
+
return editor.can().chain().focus().toggleHighlight().run();
|
|
2198
|
+
case "undo":
|
|
2199
|
+
return editor.can().chain().focus().undo().run();
|
|
2200
|
+
case "redo":
|
|
2201
|
+
return editor.can().chain().focus().redo().run();
|
|
2202
|
+
case "insertTable":
|
|
2203
|
+
return editor.can().chain().focus().insertTable().run();
|
|
2204
|
+
case "addColumnBefore":
|
|
2205
|
+
return editor.can().chain().focus().addColumnBefore().run();
|
|
2206
|
+
case "addColumnAfter":
|
|
2207
|
+
return editor.can().chain().focus().addColumnAfter().run();
|
|
2208
|
+
case "deleteColumn":
|
|
2209
|
+
return editor.can().chain().focus().deleteColumn().run();
|
|
2210
|
+
case "addRowBefore":
|
|
2211
|
+
return editor.can().chain().focus().addRowBefore().run();
|
|
2212
|
+
case "addRowAfter":
|
|
2213
|
+
return editor.can().chain().focus().addRowAfter().run();
|
|
2214
|
+
case "deleteRow":
|
|
2215
|
+
return editor.can().chain().focus().deleteRow().run();
|
|
2216
|
+
case "deleteTable":
|
|
2217
|
+
return editor.can().chain().focus().deleteTable().run();
|
|
2218
|
+
case "mergeCells":
|
|
2219
|
+
return editor.can().chain().focus().mergeCells().run();
|
|
2220
|
+
case "splitCell":
|
|
2221
|
+
return editor.can().chain().focus().splitCell().run();
|
|
2222
|
+
case "toggleHeaderColumn":
|
|
2223
|
+
return editor.can().chain().focus().toggleHeaderColumn().run();
|
|
2224
|
+
case "toggleHeaderRow":
|
|
2225
|
+
return editor.can().chain().focus().toggleHeaderRow().run();
|
|
2226
|
+
case "toggleHeaderCell":
|
|
2227
|
+
return editor.can().chain().focus().toggleHeaderCell().run();
|
|
2228
|
+
default:
|
|
2229
|
+
return false;
|
|
1961
2230
|
}
|
|
1962
2231
|
}
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
2232
|
+
// Méthodes pour exécuter les commandes
|
|
2233
|
+
toggleBold(editor) {
|
|
2234
|
+
editor.chain().focus().toggleBold().run();
|
|
2235
|
+
}
|
|
2236
|
+
toggleItalic(editor) {
|
|
2237
|
+
editor.chain().focus().toggleItalic().run();
|
|
2238
|
+
}
|
|
2239
|
+
toggleStrike(editor) {
|
|
2240
|
+
editor.chain().focus().toggleStrike().run();
|
|
2241
|
+
}
|
|
2242
|
+
toggleCode(editor) {
|
|
2243
|
+
editor.chain().focus().toggleCode().run();
|
|
1971
2244
|
}
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
event.stopPropagation();
|
|
1975
|
-
this.isDragOver.set(true);
|
|
2245
|
+
toggleHeading(editor, level) {
|
|
2246
|
+
editor.chain().focus().toggleHeading({ level }).run();
|
|
1976
2247
|
}
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
event.stopPropagation();
|
|
1980
|
-
this.isDragOver.set(false);
|
|
1981
|
-
const files = event.dataTransfer?.files;
|
|
1982
|
-
if (files && files.length > 0) {
|
|
1983
|
-
this.processFiles(Array.from(files));
|
|
1984
|
-
}
|
|
2248
|
+
toggleBulletList(editor) {
|
|
2249
|
+
editor.chain().focus().toggleBulletList().run();
|
|
1985
2250
|
}
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
event.stopPropagation();
|
|
1989
|
-
this.isDragOver.set(false);
|
|
2251
|
+
toggleOrderedList(editor) {
|
|
2252
|
+
editor.chain().focus().toggleOrderedList().run();
|
|
1990
2253
|
}
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
const maxSize = (config.maxSize || 5) * 1024 * 1024; // Convertir en bytes
|
|
1994
|
-
const allowedTypes = config.allowedTypes || ["image/*"];
|
|
1995
|
-
// Vérifier le nombre de fichiers
|
|
1996
|
-
if (!config.multiple && files.length > 1) {
|
|
1997
|
-
this.showError("Veuillez sélectionner une seule image");
|
|
1998
|
-
return;
|
|
1999
|
-
}
|
|
2000
|
-
// Traiter chaque fichier
|
|
2001
|
-
files.forEach((file) => {
|
|
2002
|
-
// Vérifier le type
|
|
2003
|
-
if (!this.isValidFileType(file, allowedTypes)) {
|
|
2004
|
-
this.showError(`Type de fichier non supporté: ${file.name}`);
|
|
2005
|
-
return;
|
|
2006
|
-
}
|
|
2007
|
-
// Vérifier la taille
|
|
2008
|
-
if (file.size > maxSize) {
|
|
2009
|
-
this.showError(`Fichier trop volumineux: ${file.name} (max ${config.maxSize}MB)`);
|
|
2010
|
-
return;
|
|
2011
|
-
}
|
|
2012
|
-
// Traiter l'image avec compression si nécessaire
|
|
2013
|
-
this.processImage(file);
|
|
2014
|
-
});
|
|
2254
|
+
toggleBlockquote(editor) {
|
|
2255
|
+
editor.chain().focus().toggleBlockquote().run();
|
|
2015
2256
|
}
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
return file.type.startsWith("image/");
|
|
2019
|
-
}
|
|
2020
|
-
return allowedTypes.includes(file.type);
|
|
2257
|
+
undo(editor) {
|
|
2258
|
+
editor.chain().focus().undo().run();
|
|
2021
2259
|
}
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
this.uploadProgress.set(10);
|
|
2025
|
-
const config = this.config();
|
|
2026
|
-
const originalSize = file.size;
|
|
2027
|
-
// Créer un canvas pour la compression
|
|
2028
|
-
const canvas = document.createElement("canvas");
|
|
2029
|
-
const ctx = canvas.getContext("2d");
|
|
2030
|
-
const img = new Image();
|
|
2031
|
-
img.onload = () => {
|
|
2032
|
-
this.uploadProgress.set(30);
|
|
2033
|
-
// Vérifier les dimensions
|
|
2034
|
-
const maxWidth = config.maxWidth || 1920;
|
|
2035
|
-
const maxHeight = config.maxHeight || 1080;
|
|
2036
|
-
let { width, height } = img;
|
|
2037
|
-
// Redimensionner si nécessaire
|
|
2038
|
-
if (width > maxWidth || height > maxHeight) {
|
|
2039
|
-
const ratio = Math.min(maxWidth / width, maxHeight / height);
|
|
2040
|
-
width *= ratio;
|
|
2041
|
-
height *= ratio;
|
|
2042
|
-
}
|
|
2043
|
-
canvas.width = width;
|
|
2044
|
-
canvas.height = height;
|
|
2045
|
-
// Dessiner l'image redimensionnée
|
|
2046
|
-
ctx?.drawImage(img, 0, 0, width, height);
|
|
2047
|
-
this.uploadProgress.set(70);
|
|
2048
|
-
// Convertir en base64 avec compression
|
|
2049
|
-
const quality = config.quality || 0.8;
|
|
2050
|
-
const mimeType = file.type;
|
|
2051
|
-
canvas.toBlob((blob) => {
|
|
2052
|
-
this.uploadProgress.set(90);
|
|
2053
|
-
if (blob) {
|
|
2054
|
-
const reader = new FileReader();
|
|
2055
|
-
reader.onload = (e) => {
|
|
2056
|
-
const base64 = e.target?.result;
|
|
2057
|
-
if (base64) {
|
|
2058
|
-
const result = {
|
|
2059
|
-
src: base64,
|
|
2060
|
-
name: file.name,
|
|
2061
|
-
size: blob.size,
|
|
2062
|
-
type: file.type,
|
|
2063
|
-
width: Math.round(width),
|
|
2064
|
-
height: Math.round(height),
|
|
2065
|
-
originalSize: originalSize,
|
|
2066
|
-
};
|
|
2067
|
-
// Afficher la prévisualisation si activée
|
|
2068
|
-
if (config.showPreview) {
|
|
2069
|
-
this.previewImage.set(base64);
|
|
2070
|
-
this.previewInfo.set(`${result.width}×${result.height} • ${this.formatFileSize(blob.size)}`);
|
|
2071
|
-
}
|
|
2072
|
-
// Émettre l'événement
|
|
2073
|
-
this.imageSelected.emit(result);
|
|
2074
|
-
this.clearError();
|
|
2075
|
-
}
|
|
2076
|
-
this.uploadProgress.set(100);
|
|
2077
|
-
setTimeout(() => {
|
|
2078
|
-
this.isUploading.set(false);
|
|
2079
|
-
this.uploadProgress.set(0);
|
|
2080
|
-
}, 500);
|
|
2081
|
-
};
|
|
2082
|
-
reader.readAsDataURL(blob);
|
|
2083
|
-
}
|
|
2084
|
-
else {
|
|
2085
|
-
this.showError("Erreur lors de la compression de l'image");
|
|
2086
|
-
this.isUploading.set(false);
|
|
2087
|
-
this.uploadProgress.set(0);
|
|
2088
|
-
}
|
|
2089
|
-
}, mimeType, quality);
|
|
2090
|
-
};
|
|
2091
|
-
img.onerror = () => {
|
|
2092
|
-
this.showError("Erreur lors du chargement de l'image");
|
|
2093
|
-
this.isUploading.set(false);
|
|
2094
|
-
this.uploadProgress.set(0);
|
|
2095
|
-
};
|
|
2096
|
-
img.src = URL.createObjectURL(file);
|
|
2260
|
+
redo(editor) {
|
|
2261
|
+
editor.chain().focus().redo().run();
|
|
2097
2262
|
}
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
const k = 1024;
|
|
2102
|
-
const sizes = ["B", "KB", "MB", "GB"];
|
|
2103
|
-
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
2104
|
-
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + " " + sizes[i];
|
|
2263
|
+
// Nouvelles méthodes pour les formatages supplémentaires
|
|
2264
|
+
toggleUnderline(editor) {
|
|
2265
|
+
editor.chain().focus().toggleUnderline().run();
|
|
2105
2266
|
}
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
this.error.emit(message);
|
|
2109
|
-
this.isUploading.set(false);
|
|
2110
|
-
this.uploadProgress.set(0);
|
|
2111
|
-
// Auto-clear après 5 secondes
|
|
2112
|
-
setTimeout(() => {
|
|
2113
|
-
this.clearError();
|
|
2114
|
-
}, 5000);
|
|
2267
|
+
toggleSuperscript(editor) {
|
|
2268
|
+
editor.chain().focus().toggleSuperscript().run();
|
|
2115
2269
|
}
|
|
2116
|
-
|
|
2117
|
-
|
|
2270
|
+
toggleSubscript(editor) {
|
|
2271
|
+
editor.chain().focus().toggleSubscript().run();
|
|
2118
2272
|
}
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
this.previewInfo.set("");
|
|
2273
|
+
setTextAlign(editor, alignment) {
|
|
2274
|
+
editor.chain().focus().setTextAlign(alignment).run();
|
|
2122
2275
|
}
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
(
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
}
|
|
2193
|
-
</div>
|
|
2194
|
-
`, isInline: true, styles: [".image-upload-container{position:relative;display:inline-block}.drag-overlay{position:fixed;inset:0;background:#3182ce1a;border:2px dashed #3182ce;border-radius:6px;display:flex;align-items:center;justify-content:center;z-index:1000;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px)}.drag-content{text-align:center;color:#3182ce;font-weight:600}.drag-content .material-symbols-outlined{font-size:48px;margin-bottom:16px}.drag-content p{margin:0;font-size:18px}.upload-progress{position:absolute;top:100%;left:0;background:#fff;border:1px solid #e2e8f0;border-radius:8px;padding:12px;margin-top:8px;z-index:100;min-width:200px;box-shadow:0 4px 12px #00000026}.progress-bar{width:100%;height:6px;background:#e2e8f0;border-radius:3px;overflow:hidden;margin-bottom:8px}.progress-fill{height:100%;background:#3182ce;border-radius:3px;transition:width .3s ease}.progress-text{font-size:12px;color:#4a5568;text-align:center}.image-preview{position:absolute;top:100%;left:0;background:#fff;border:1px solid #e2e8f0;border-radius:8px;box-shadow:0 4px 12px #00000026;padding:8px;margin-top:8px;z-index:100;min-width:200px}.image-preview img{max-width:200px;max-height:150px;border-radius:4px;display:block}.preview-info{margin-top:8px;font-size:11px;color:#718096;text-align:center}.preview-close{position:absolute;top:4px;right:4px;background:#000000b3;color:#fff;border:none;border-radius:50%;width:24px;height:24px;cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:14px}.preview-close:hover{background:#000000e6}.error-message{position:absolute;top:100%;left:0;background:#fed7d7;color:#c53030;border:1px solid #feb2b2;border-radius:6px;padding:8px 12px;margin-top:8px;font-size:12px;display:flex;align-items:center;gap:6px;z-index:100;min-width:200px}.error-message .material-symbols-outlined{font-size:16px}\n"], dependencies: [{ kind: "component", type: TiptapButtonComponent, selector: "tiptap-button", inputs: ["icon", "title", "active", "disabled", "variant", "size", "iconSize"], outputs: ["onClick"] }] }); }
|
|
2276
|
+
toggleLink(editor, url) {
|
|
2277
|
+
if (url) {
|
|
2278
|
+
editor.chain().focus().toggleLink({ href: url }).run();
|
|
2279
|
+
}
|
|
2280
|
+
else {
|
|
2281
|
+
// Si pas d'URL fournie, on demande à l'utilisateur
|
|
2282
|
+
const href = window.prompt("URL du lien:");
|
|
2283
|
+
if (href) {
|
|
2284
|
+
editor.chain().focus().toggleLink({ href }).run();
|
|
2285
|
+
}
|
|
2286
|
+
}
|
|
2287
|
+
}
|
|
2288
|
+
insertHorizontalRule(editor) {
|
|
2289
|
+
editor.chain().focus().setHorizontalRule().run();
|
|
2290
|
+
}
|
|
2291
|
+
toggleHighlight(editor, color) {
|
|
2292
|
+
if (color) {
|
|
2293
|
+
editor.chain().focus().toggleHighlight({ color }).run();
|
|
2294
|
+
}
|
|
2295
|
+
else {
|
|
2296
|
+
editor.chain().focus().toggleHighlight().run();
|
|
2297
|
+
}
|
|
2298
|
+
}
|
|
2299
|
+
// Table commands
|
|
2300
|
+
insertTable(editor, rows = 3, cols = 3) {
|
|
2301
|
+
editor.chain().focus().insertTable({ rows, cols }).run();
|
|
2302
|
+
}
|
|
2303
|
+
addColumnBefore(editor) {
|
|
2304
|
+
editor.chain().focus().addColumnBefore().run();
|
|
2305
|
+
}
|
|
2306
|
+
addColumnAfter(editor) {
|
|
2307
|
+
editor.chain().focus().addColumnAfter().run();
|
|
2308
|
+
}
|
|
2309
|
+
deleteColumn(editor) {
|
|
2310
|
+
editor.chain().focus().deleteColumn().run();
|
|
2311
|
+
}
|
|
2312
|
+
addRowBefore(editor) {
|
|
2313
|
+
editor.chain().focus().addRowBefore().run();
|
|
2314
|
+
}
|
|
2315
|
+
addRowAfter(editor) {
|
|
2316
|
+
editor.chain().focus().addRowAfter().run();
|
|
2317
|
+
}
|
|
2318
|
+
deleteRow(editor) {
|
|
2319
|
+
editor.chain().focus().deleteRow().run();
|
|
2320
|
+
}
|
|
2321
|
+
deleteTable(editor) {
|
|
2322
|
+
editor.chain().focus().deleteTable().run();
|
|
2323
|
+
}
|
|
2324
|
+
mergeCells(editor) {
|
|
2325
|
+
editor.chain().focus().mergeCells().run();
|
|
2326
|
+
}
|
|
2327
|
+
splitCell(editor) {
|
|
2328
|
+
editor.chain().focus().splitCell().run();
|
|
2329
|
+
}
|
|
2330
|
+
toggleHeaderColumn(editor) {
|
|
2331
|
+
editor.chain().focus().toggleHeaderColumn().run();
|
|
2332
|
+
}
|
|
2333
|
+
toggleHeaderRow(editor) {
|
|
2334
|
+
editor.chain().focus().toggleHeaderRow().run();
|
|
2335
|
+
}
|
|
2336
|
+
toggleHeaderCell(editor) {
|
|
2337
|
+
editor.chain().focus().toggleHeaderCell().run();
|
|
2338
|
+
}
|
|
2339
|
+
// Méthode pour vider le contenu
|
|
2340
|
+
clearContent(editor) {
|
|
2341
|
+
editor.chain().focus().clearContent().run();
|
|
2342
|
+
}
|
|
2343
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: EditorCommandsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
2344
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: EditorCommandsService, providedIn: "root" }); }
|
|
2195
2345
|
}
|
|
2196
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type:
|
|
2197
|
-
type:
|
|
2198
|
-
args: [{
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
<tiptap-button
|
|
2202
|
-
icon="image"
|
|
2203
|
-
title="Ajouter une image"
|
|
2204
|
-
[disabled]="isUploading()"
|
|
2205
|
-
(onClick)="triggerFileInput()"
|
|
2206
|
-
/>
|
|
2207
|
-
|
|
2208
|
-
<!-- Input file caché -->
|
|
2209
|
-
<input
|
|
2210
|
-
#fileInput
|
|
2211
|
-
type="file"
|
|
2212
|
-
[accept]="acceptedTypes()"
|
|
2213
|
-
[multiple]="config().multiple"
|
|
2214
|
-
(change)="onFileSelected($event)"
|
|
2215
|
-
style="display: none;"
|
|
2216
|
-
/>
|
|
2217
|
-
|
|
2218
|
-
<!-- Zone de drag & drop -->
|
|
2219
|
-
@if (config().enableDragDrop && isDragOver()) {
|
|
2220
|
-
<div
|
|
2221
|
-
class="drag-overlay"
|
|
2222
|
-
(dragover)="onDragOver($event)"
|
|
2223
|
-
(drop)="onDrop($event)"
|
|
2224
|
-
(dragleave)="onDragLeave($event)"
|
|
2225
|
-
>
|
|
2226
|
-
<div class="drag-content">
|
|
2227
|
-
<span class="material-symbols-outlined">cloud_upload</span>
|
|
2228
|
-
<p>Déposez votre image ici</p>
|
|
2229
|
-
</div>
|
|
2230
|
-
</div>
|
|
2231
|
-
}
|
|
2232
|
-
|
|
2233
|
-
<!-- Barre de progression -->
|
|
2234
|
-
@if (isUploading() && uploadProgress() > 0) {
|
|
2235
|
-
<div class="upload-progress">
|
|
2236
|
-
<div class="progress-bar">
|
|
2237
|
-
<div class="progress-fill" [style.width.%]="uploadProgress()"></div>
|
|
2238
|
-
</div>
|
|
2239
|
-
<div class="progress-text">{{ uploadProgress() }}%</div>
|
|
2240
|
-
</div>
|
|
2241
|
-
}
|
|
2242
|
-
|
|
2243
|
-
<!-- Prévisualisation -->
|
|
2244
|
-
@if (config().showPreview && previewImage()) {
|
|
2245
|
-
<div class="image-preview">
|
|
2246
|
-
<img [src]="previewImage()" alt="Prévisualisation" />
|
|
2247
|
-
<div class="preview-info">
|
|
2248
|
-
<span>{{ previewInfo() }}</span>
|
|
2249
|
-
</div>
|
|
2250
|
-
<button
|
|
2251
|
-
class="preview-close"
|
|
2252
|
-
(click)="clearPreview()"
|
|
2253
|
-
title="Fermer la prévisualisation"
|
|
2254
|
-
>
|
|
2255
|
-
<span class="material-symbols-outlined">close</span>
|
|
2256
|
-
</button>
|
|
2257
|
-
</div>
|
|
2258
|
-
}
|
|
2259
|
-
|
|
2260
|
-
<!-- Messages d'erreur -->
|
|
2261
|
-
@if (errorMessage()) {
|
|
2262
|
-
<div class="error-message">
|
|
2263
|
-
<span class="material-symbols-outlined">error</span>
|
|
2264
|
-
{{ errorMessage() }}
|
|
2265
|
-
</div>
|
|
2266
|
-
}
|
|
2267
|
-
</div>
|
|
2268
|
-
`, styles: [".image-upload-container{position:relative;display:inline-block}.drag-overlay{position:fixed;inset:0;background:#3182ce1a;border:2px dashed #3182ce;border-radius:6px;display:flex;align-items:center;justify-content:center;z-index:1000;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px)}.drag-content{text-align:center;color:#3182ce;font-weight:600}.drag-content .material-symbols-outlined{font-size:48px;margin-bottom:16px}.drag-content p{margin:0;font-size:18px}.upload-progress{position:absolute;top:100%;left:0;background:#fff;border:1px solid #e2e8f0;border-radius:8px;padding:12px;margin-top:8px;z-index:100;min-width:200px;box-shadow:0 4px 12px #00000026}.progress-bar{width:100%;height:6px;background:#e2e8f0;border-radius:3px;overflow:hidden;margin-bottom:8px}.progress-fill{height:100%;background:#3182ce;border-radius:3px;transition:width .3s ease}.progress-text{font-size:12px;color:#4a5568;text-align:center}.image-preview{position:absolute;top:100%;left:0;background:#fff;border:1px solid #e2e8f0;border-radius:8px;box-shadow:0 4px 12px #00000026;padding:8px;margin-top:8px;z-index:100;min-width:200px}.image-preview img{max-width:200px;max-height:150px;border-radius:4px;display:block}.preview-info{margin-top:8px;font-size:11px;color:#718096;text-align:center}.preview-close{position:absolute;top:4px;right:4px;background:#000000b3;color:#fff;border:none;border-radius:50%;width:24px;height:24px;cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:14px}.preview-close:hover{background:#000000e6}.error-message{position:absolute;top:100%;left:0;background:#fed7d7;color:#c53030;border:1px solid #feb2b2;border-radius:6px;padding:8px 12px;margin-top:8px;font-size:12px;display:flex;align-items:center;gap:6px;z-index:100;min-width:200px}.error-message .material-symbols-outlined{font-size:16px}\n"] }]
|
|
2346
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: EditorCommandsService, decorators: [{
|
|
2347
|
+
type: Injectable,
|
|
2348
|
+
args: [{
|
|
2349
|
+
providedIn: "root",
|
|
2350
|
+
}]
|
|
2269
2351
|
}] });
|
|
2270
2352
|
|
|
2271
|
-
class
|
|
2272
|
-
// Effect comme propriété de classe pour éviter l'erreur d'injection context
|
|
2353
|
+
class TiptapTableBubbleMenuComponent {
|
|
2273
2354
|
constructor() {
|
|
2355
|
+
// Inputs
|
|
2274
2356
|
this.editor = input.required();
|
|
2275
|
-
this.config = input({
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
code: true,
|
|
2281
|
-
superscript: false,
|
|
2282
|
-
subscript: false,
|
|
2283
|
-
highlight: true,
|
|
2284
|
-
link: true,
|
|
2285
|
-
separator: true,
|
|
2286
|
-
});
|
|
2357
|
+
this.config = input({});
|
|
2358
|
+
// Services
|
|
2359
|
+
this.i18nService = inject(TiptapI18nService);
|
|
2360
|
+
this.commandsService = inject(EditorCommandsService);
|
|
2361
|
+
// Tippy instance
|
|
2287
2362
|
this.tippyInstance = null;
|
|
2288
2363
|
this.updateTimeout = null;
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
italic: true,
|
|
2292
|
-
underline: true,
|
|
2293
|
-
strike: true,
|
|
2294
|
-
code: true,
|
|
2295
|
-
superscript: false,
|
|
2296
|
-
subscript: false,
|
|
2297
|
-
highlight: true,
|
|
2298
|
-
link: true,
|
|
2299
|
-
separator: true,
|
|
2300
|
-
...this.config(),
|
|
2301
|
-
}));
|
|
2364
|
+
// Signaux
|
|
2365
|
+
this.i18n = this.i18nService;
|
|
2302
2366
|
this.updateMenu = () => {
|
|
2303
2367
|
// Debounce pour éviter les appels trop fréquents
|
|
2304
2368
|
if (this.updateTimeout) {
|
|
@@ -2308,14 +2372,25 @@ class TiptapBubbleMenuComponent {
|
|
|
2308
2372
|
const ed = this.editor();
|
|
2309
2373
|
if (!ed)
|
|
2310
2374
|
return;
|
|
2375
|
+
const isTableSelected = ed.isActive("table") ||
|
|
2376
|
+
ed.isActive("tableCell") ||
|
|
2377
|
+
ed.isActive("tableHeader");
|
|
2378
|
+
// Vérifier s'il y a une sélection de cellules (priorité au menu de cellules)
|
|
2311
2379
|
const { from, to } = ed.state.selection;
|
|
2312
|
-
const
|
|
2313
|
-
const
|
|
2314
|
-
//
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
//
|
|
2318
|
-
|
|
2380
|
+
const hasCellSelection = from !== to;
|
|
2381
|
+
const isTableCell = ed.isActive("tableCell") || ed.isActive("tableHeader");
|
|
2382
|
+
// Vérifier si la sélection traverse plusieurs cellules
|
|
2383
|
+
const selectionSize = to - from;
|
|
2384
|
+
const hasMultiCellSelection = hasCellSelection && selectionSize > 1;
|
|
2385
|
+
// Ne montrer le menu de table que si :
|
|
2386
|
+
// 1. Une table est sélectionnée
|
|
2387
|
+
// 2. L'éditeur est éditable
|
|
2388
|
+
// 3. Il n'y a PAS de sélection de cellules (priorité au menu de cellules)
|
|
2389
|
+
// 4. Il n'y a PAS de sélection multi-cellules
|
|
2390
|
+
const shouldShow = isTableSelected &&
|
|
2391
|
+
ed.isEditable &&
|
|
2392
|
+
!(hasCellSelection && isTableCell) &&
|
|
2393
|
+
!hasMultiCellSelection;
|
|
2319
2394
|
if (shouldShow) {
|
|
2320
2395
|
this.showTippy();
|
|
2321
2396
|
}
|
|
@@ -2330,57 +2405,41 @@ class TiptapBubbleMenuComponent {
|
|
|
2330
2405
|
this.hideTippy();
|
|
2331
2406
|
}, 100);
|
|
2332
2407
|
};
|
|
2408
|
+
// Effet pour mettre à jour le menu quand l'éditeur change
|
|
2333
2409
|
effect(() => {
|
|
2334
|
-
const
|
|
2335
|
-
if (
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
ed.on("focus", this.updateMenu);
|
|
2346
|
-
ed.on("blur", this.handleBlur);
|
|
2347
|
-
// Ne pas appeler updateMenu() ici pour éviter l'affichage prématuré
|
|
2348
|
-
// Il sera appelé automatiquement quand l'éditeur sera prêt
|
|
2410
|
+
const editor = this.editor();
|
|
2411
|
+
if (editor) {
|
|
2412
|
+
// Nettoyer les anciens listeners
|
|
2413
|
+
editor.off("selectionUpdate", this.updateMenu);
|
|
2414
|
+
editor.off("focus", this.updateMenu);
|
|
2415
|
+
editor.off("blur", this.handleBlur);
|
|
2416
|
+
// Ajouter les nouveaux listeners
|
|
2417
|
+
editor.on("selectionUpdate", this.updateMenu);
|
|
2418
|
+
editor.on("focus", this.updateMenu);
|
|
2419
|
+
editor.on("blur", this.handleBlur);
|
|
2420
|
+
}
|
|
2349
2421
|
});
|
|
2350
2422
|
}
|
|
2351
2423
|
ngOnInit() {
|
|
2352
|
-
// Initialiser Tippy de manière synchrone après que le component soit ready
|
|
2353
2424
|
this.initTippy();
|
|
2354
2425
|
}
|
|
2355
2426
|
ngOnDestroy() {
|
|
2356
|
-
const
|
|
2357
|
-
if (
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
}
|
|
2363
|
-
// Nettoyer les timeouts
|
|
2364
|
-
if (this.updateTimeout) {
|
|
2365
|
-
clearTimeout(this.updateTimeout);
|
|
2427
|
+
const editor = this.editor();
|
|
2428
|
+
if (editor) {
|
|
2429
|
+
// Nettoyer les événements
|
|
2430
|
+
editor.off("selectionUpdate", this.updateMenu);
|
|
2431
|
+
editor.off("focus", this.updateMenu);
|
|
2432
|
+
editor.off("blur", this.handleBlur);
|
|
2366
2433
|
}
|
|
2367
|
-
// Nettoyer Tippy
|
|
2368
2434
|
if (this.tippyInstance) {
|
|
2369
2435
|
this.tippyInstance.destroy();
|
|
2370
|
-
|
|
2436
|
+
}
|
|
2437
|
+
if (this.updateTimeout) {
|
|
2438
|
+
clearTimeout(this.updateTimeout);
|
|
2371
2439
|
}
|
|
2372
2440
|
}
|
|
2373
2441
|
initTippy() {
|
|
2374
|
-
|
|
2375
|
-
if (!this.menuRef?.nativeElement) {
|
|
2376
|
-
setTimeout(() => this.initTippy(), 50);
|
|
2377
|
-
return;
|
|
2378
|
-
}
|
|
2379
|
-
const menuElement = this.menuRef.nativeElement;
|
|
2380
|
-
// S'assurer qu'il n'y a pas déjà une instance
|
|
2381
|
-
if (this.tippyInstance) {
|
|
2382
|
-
this.tippyInstance.destroy();
|
|
2383
|
-
}
|
|
2442
|
+
const menuElement = this.menuElement.nativeElement;
|
|
2384
2443
|
// Créer l'instance Tippy
|
|
2385
2444
|
this.tippyInstance = tippy(document.body, {
|
|
2386
2445
|
content: menuElement,
|
|
@@ -2390,13 +2449,13 @@ class TiptapBubbleMenuComponent {
|
|
|
2390
2449
|
interactive: true,
|
|
2391
2450
|
arrow: false,
|
|
2392
2451
|
offset: [0, 8],
|
|
2452
|
+
maxWidth: "none",
|
|
2393
2453
|
hideOnClick: false,
|
|
2394
2454
|
onShow: (instance) => {
|
|
2395
2455
|
// S'assurer que les autres menus sont fermés
|
|
2396
2456
|
this.hideOtherMenus();
|
|
2397
2457
|
},
|
|
2398
|
-
getReferenceClientRect: () => this.
|
|
2399
|
-
// Améliorer le positionnement avec scroll
|
|
2458
|
+
getReferenceClientRect: () => this.getTableRect(),
|
|
2400
2459
|
popperOptions: {
|
|
2401
2460
|
modifiers: [
|
|
2402
2461
|
{
|
|
@@ -2418,257 +2477,275 @@ class TiptapBubbleMenuComponent {
|
|
|
2418
2477
|
// Maintenant que Tippy est initialisé, faire un premier check
|
|
2419
2478
|
this.updateMenu();
|
|
2420
2479
|
}
|
|
2421
|
-
|
|
2422
|
-
const
|
|
2423
|
-
if (!
|
|
2480
|
+
getTableRect() {
|
|
2481
|
+
const ed = this.editor();
|
|
2482
|
+
if (!ed)
|
|
2424
2483
|
return new DOMRect(0, 0, 0, 0);
|
|
2484
|
+
// Méthode 1: Utiliser coordsAtPos (méthode native ProseMirror)
|
|
2485
|
+
const { from } = ed.state.selection;
|
|
2486
|
+
const coords = ed.view.coordsAtPos(from);
|
|
2487
|
+
// Trouver la table qui contient cette position
|
|
2488
|
+
const editorElement = ed.view.dom;
|
|
2489
|
+
const tables = Array.from(editorElement.querySelectorAll("table"));
|
|
2490
|
+
for (let i = 0; i < tables.length; i++) {
|
|
2491
|
+
const table = tables[i];
|
|
2492
|
+
try {
|
|
2493
|
+
const tableRect = table.getBoundingClientRect();
|
|
2494
|
+
// Vérifier si la position ProseMirror est dans cette table
|
|
2495
|
+
const isInside = coords.left >= tableRect.left &&
|
|
2496
|
+
coords.left <= tableRect.right &&
|
|
2497
|
+
coords.top >= tableRect.top &&
|
|
2498
|
+
coords.top <= tableRect.bottom;
|
|
2499
|
+
if (isInside) {
|
|
2500
|
+
return tableRect;
|
|
2501
|
+
}
|
|
2502
|
+
}
|
|
2503
|
+
catch (error) {
|
|
2504
|
+
continue;
|
|
2505
|
+
}
|
|
2425
2506
|
}
|
|
2426
|
-
|
|
2427
|
-
|
|
2507
|
+
// Fallback : utiliser la méthode DOM si ProseMirror échoue
|
|
2508
|
+
const selection = window.getSelection();
|
|
2509
|
+
if (selection && selection.rangeCount > 0) {
|
|
2510
|
+
const range = selection.getRangeAt(0);
|
|
2511
|
+
const rect = range.getBoundingClientRect();
|
|
2512
|
+
if (rect.width > 0 && rect.height > 0) {
|
|
2513
|
+
return rect;
|
|
2514
|
+
}
|
|
2515
|
+
}
|
|
2516
|
+
// Dernier fallback : première table
|
|
2517
|
+
if (tables.length > 0) {
|
|
2518
|
+
return tables[0].getBoundingClientRect();
|
|
2519
|
+
}
|
|
2520
|
+
return new DOMRect(0, 0, 0, 0);
|
|
2428
2521
|
}
|
|
2429
2522
|
hideOtherMenus() {
|
|
2430
2523
|
// Cette méthode peut être étendue pour fermer d'autres menus si nécessaire
|
|
2431
|
-
// Pour l'instant, elle sert de placeholder pour une future coordination entre menus
|
|
2432
2524
|
}
|
|
2433
2525
|
showTippy() {
|
|
2434
2526
|
if (!this.tippyInstance)
|
|
2435
2527
|
return;
|
|
2436
2528
|
// Mettre à jour la position
|
|
2437
2529
|
this.tippyInstance.setProps({
|
|
2438
|
-
getReferenceClientRect: () => this.
|
|
2530
|
+
getReferenceClientRect: () => this.getTableRect(),
|
|
2439
2531
|
});
|
|
2440
2532
|
this.tippyInstance.show();
|
|
2441
2533
|
}
|
|
2442
2534
|
hideTippy() {
|
|
2443
|
-
if (this.tippyInstance)
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
}
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
}
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
<tiptap-button
|
|
2508
|
-
icon="
|
|
2509
|
-
title="
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
></tiptap-
|
|
2527
|
-
}
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
<
|
|
2544
|
-
}
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
></tiptap-button>
|
|
2558
|
-
}
|
|
2559
|
-
</div>
|
|
2560
|
-
`, isInline: true, dependencies: [{ kind: "component", type: TiptapButtonComponent, selector: "tiptap-button", inputs: ["icon", "title", "active", "disabled", "variant", "size", "iconSize"], outputs: ["onClick"] }] }); }
|
|
2535
|
+
if (!this.tippyInstance)
|
|
2536
|
+
return;
|
|
2537
|
+
this.tippyInstance.hide();
|
|
2538
|
+
}
|
|
2539
|
+
// Actions de lignes
|
|
2540
|
+
addRowBefore() {
|
|
2541
|
+
this.commandsService.addRowBefore(this.editor());
|
|
2542
|
+
}
|
|
2543
|
+
addRowAfter() {
|
|
2544
|
+
this.commandsService.addRowAfter(this.editor());
|
|
2545
|
+
}
|
|
2546
|
+
deleteRow() {
|
|
2547
|
+
this.commandsService.deleteRow(this.editor());
|
|
2548
|
+
}
|
|
2549
|
+
// Actions de colonnes
|
|
2550
|
+
addColumnBefore() {
|
|
2551
|
+
this.commandsService.addColumnBefore(this.editor());
|
|
2552
|
+
}
|
|
2553
|
+
addColumnAfter() {
|
|
2554
|
+
this.commandsService.addColumnAfter(this.editor());
|
|
2555
|
+
}
|
|
2556
|
+
deleteColumn() {
|
|
2557
|
+
this.commandsService.deleteColumn(this.editor());
|
|
2558
|
+
}
|
|
2559
|
+
// Actions de headers
|
|
2560
|
+
toggleHeaderRow() {
|
|
2561
|
+
this.commandsService.toggleHeaderRow(this.editor());
|
|
2562
|
+
}
|
|
2563
|
+
toggleHeaderColumn() {
|
|
2564
|
+
this.commandsService.toggleHeaderColumn(this.editor());
|
|
2565
|
+
}
|
|
2566
|
+
// Actions de table
|
|
2567
|
+
deleteTable() {
|
|
2568
|
+
this.commandsService.deleteTable(this.editor());
|
|
2569
|
+
}
|
|
2570
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: TiptapTableBubbleMenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
2571
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.0", type: TiptapTableBubbleMenuComponent, isStandalone: true, selector: "tiptap-table-bubble-menu", inputs: { editor: { classPropertyName: "editor", publicName: "editor", isSignal: true, isRequired: true, transformFunction: null }, config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "menuElement", first: true, predicate: ["menuElement"], descendants: true, static: true }], ngImport: i0, template: `
|
|
2572
|
+
<div #menuElement class="bubble-menu">
|
|
2573
|
+
<!-- Actions de lignes -->
|
|
2574
|
+
@if (config().addRowBefore !== false) {
|
|
2575
|
+
<tiptap-button
|
|
2576
|
+
icon="add_row_above"
|
|
2577
|
+
title="{{ i18n.table().addRowBefore }}"
|
|
2578
|
+
(click)="addRowBefore()"
|
|
2579
|
+
></tiptap-button>
|
|
2580
|
+
} @if (config().addRowAfter !== false) {
|
|
2581
|
+
<tiptap-button
|
|
2582
|
+
icon="add_row_below"
|
|
2583
|
+
title="{{ i18n.table().addRowAfter }}"
|
|
2584
|
+
(click)="addRowAfter()"
|
|
2585
|
+
></tiptap-button>
|
|
2586
|
+
} @if (config().deleteRow !== false) {
|
|
2587
|
+
<tiptap-button
|
|
2588
|
+
icon="delete"
|
|
2589
|
+
title="{{ i18n.table().deleteRow }}"
|
|
2590
|
+
variant="danger"
|
|
2591
|
+
(click)="deleteRow()"
|
|
2592
|
+
></tiptap-button>
|
|
2593
|
+
} @if (config().separator !== false) {
|
|
2594
|
+
<tiptap-separator></tiptap-separator>
|
|
2595
|
+
}
|
|
2596
|
+
|
|
2597
|
+
<!-- Actions de colonnes -->
|
|
2598
|
+
@if (config().addColumnBefore !== false) {
|
|
2599
|
+
<tiptap-button
|
|
2600
|
+
icon="add_column_left"
|
|
2601
|
+
title="{{ i18n.table().addColumnBefore }}"
|
|
2602
|
+
(click)="addColumnBefore()"
|
|
2603
|
+
></tiptap-button>
|
|
2604
|
+
} @if (config().addColumnAfter !== false) {
|
|
2605
|
+
<tiptap-button
|
|
2606
|
+
icon="add_column_right"
|
|
2607
|
+
title="{{ i18n.table().addColumnAfter }}"
|
|
2608
|
+
(click)="addColumnAfter()"
|
|
2609
|
+
></tiptap-button>
|
|
2610
|
+
} @if (config().deleteColumn !== false) {
|
|
2611
|
+
<tiptap-button
|
|
2612
|
+
icon="delete"
|
|
2613
|
+
title="{{ i18n.table().deleteColumn }}"
|
|
2614
|
+
variant="danger"
|
|
2615
|
+
(click)="deleteColumn()"
|
|
2616
|
+
></tiptap-button>
|
|
2617
|
+
} @if (config().separator !== false) {
|
|
2618
|
+
<tiptap-separator></tiptap-separator>
|
|
2619
|
+
}
|
|
2620
|
+
|
|
2621
|
+
<!-- Actions de cellules -->
|
|
2622
|
+
@if (config().toggleHeaderRow !== false) {
|
|
2623
|
+
<tiptap-button
|
|
2624
|
+
icon="toolbar"
|
|
2625
|
+
title="{{ i18n.table().toggleHeaderRow }}"
|
|
2626
|
+
(click)="toggleHeaderRow()"
|
|
2627
|
+
></tiptap-button>
|
|
2628
|
+
} @if (config().toggleHeaderColumn !== false) {
|
|
2629
|
+
<tiptap-button
|
|
2630
|
+
icon="dock_to_right"
|
|
2631
|
+
title="{{ i18n.table().toggleHeaderColumn }}"
|
|
2632
|
+
(click)="toggleHeaderColumn()"
|
|
2633
|
+
></tiptap-button>
|
|
2634
|
+
} @if (config().separator !== false && config().deleteTable !== false) {
|
|
2635
|
+
<tiptap-separator></tiptap-separator>
|
|
2636
|
+
}
|
|
2637
|
+
|
|
2638
|
+
<!-- Actions de table -->
|
|
2639
|
+
@if (config().deleteTable !== false) {
|
|
2640
|
+
<tiptap-button
|
|
2641
|
+
icon="delete_forever"
|
|
2642
|
+
title="{{ i18n.table().deleteTable }}"
|
|
2643
|
+
variant="danger"
|
|
2644
|
+
(click)="deleteTable()"
|
|
2645
|
+
></tiptap-button>
|
|
2646
|
+
}
|
|
2647
|
+
</div>
|
|
2648
|
+
`, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: TiptapButtonComponent, selector: "tiptap-button", inputs: ["icon", "title", "active", "disabled", "variant", "size", "iconSize"], outputs: ["onClick"] }, { kind: "component", type: TiptapSeparatorComponent, selector: "tiptap-separator", inputs: ["orientation", "size"] }] }); }
|
|
2561
2649
|
}
|
|
2562
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type:
|
|
2650
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: TiptapTableBubbleMenuComponent, decorators: [{
|
|
2563
2651
|
type: Component,
|
|
2564
|
-
args: [{ selector: "tiptap-bubble-menu", standalone: true, imports: [TiptapButtonComponent], template: `
|
|
2565
|
-
<div #
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
(click)="
|
|
2572
|
-
></tiptap-button>
|
|
2573
|
-
} @if (
|
|
2574
|
-
<tiptap-button
|
|
2575
|
-
icon="
|
|
2576
|
-
title="
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2652
|
+
args: [{ selector: "tiptap-table-bubble-menu", standalone: true, imports: [CommonModule, TiptapButtonComponent, TiptapSeparatorComponent], template: `
|
|
2653
|
+
<div #menuElement class="bubble-menu">
|
|
2654
|
+
<!-- Actions de lignes -->
|
|
2655
|
+
@if (config().addRowBefore !== false) {
|
|
2656
|
+
<tiptap-button
|
|
2657
|
+
icon="add_row_above"
|
|
2658
|
+
title="{{ i18n.table().addRowBefore }}"
|
|
2659
|
+
(click)="addRowBefore()"
|
|
2660
|
+
></tiptap-button>
|
|
2661
|
+
} @if (config().addRowAfter !== false) {
|
|
2662
|
+
<tiptap-button
|
|
2663
|
+
icon="add_row_below"
|
|
2664
|
+
title="{{ i18n.table().addRowAfter }}"
|
|
2665
|
+
(click)="addRowAfter()"
|
|
2666
|
+
></tiptap-button>
|
|
2667
|
+
} @if (config().deleteRow !== false) {
|
|
2668
|
+
<tiptap-button
|
|
2669
|
+
icon="delete"
|
|
2670
|
+
title="{{ i18n.table().deleteRow }}"
|
|
2671
|
+
variant="danger"
|
|
2672
|
+
(click)="deleteRow()"
|
|
2673
|
+
></tiptap-button>
|
|
2674
|
+
} @if (config().separator !== false) {
|
|
2675
|
+
<tiptap-separator></tiptap-separator>
|
|
2676
|
+
}
|
|
2677
|
+
|
|
2678
|
+
<!-- Actions de colonnes -->
|
|
2679
|
+
@if (config().addColumnBefore !== false) {
|
|
2680
|
+
<tiptap-button
|
|
2681
|
+
icon="add_column_left"
|
|
2682
|
+
title="{{ i18n.table().addColumnBefore }}"
|
|
2683
|
+
(click)="addColumnBefore()"
|
|
2684
|
+
></tiptap-button>
|
|
2685
|
+
} @if (config().addColumnAfter !== false) {
|
|
2686
|
+
<tiptap-button
|
|
2687
|
+
icon="add_column_right"
|
|
2688
|
+
title="{{ i18n.table().addColumnAfter }}"
|
|
2689
|
+
(click)="addColumnAfter()"
|
|
2690
|
+
></tiptap-button>
|
|
2691
|
+
} @if (config().deleteColumn !== false) {
|
|
2692
|
+
<tiptap-button
|
|
2693
|
+
icon="delete"
|
|
2694
|
+
title="{{ i18n.table().deleteColumn }}"
|
|
2695
|
+
variant="danger"
|
|
2696
|
+
(click)="deleteColumn()"
|
|
2697
|
+
></tiptap-button>
|
|
2698
|
+
} @if (config().separator !== false) {
|
|
2699
|
+
<tiptap-separator></tiptap-separator>
|
|
2700
|
+
}
|
|
2701
|
+
|
|
2702
|
+
<!-- Actions de cellules -->
|
|
2703
|
+
@if (config().toggleHeaderRow !== false) {
|
|
2704
|
+
<tiptap-button
|
|
2705
|
+
icon="toolbar"
|
|
2706
|
+
title="{{ i18n.table().toggleHeaderRow }}"
|
|
2707
|
+
(click)="toggleHeaderRow()"
|
|
2708
|
+
></tiptap-button>
|
|
2709
|
+
} @if (config().toggleHeaderColumn !== false) {
|
|
2710
|
+
<tiptap-button
|
|
2711
|
+
icon="dock_to_right"
|
|
2712
|
+
title="{{ i18n.table().toggleHeaderColumn }}"
|
|
2713
|
+
(click)="toggleHeaderColumn()"
|
|
2714
|
+
></tiptap-button>
|
|
2715
|
+
} @if (config().separator !== false && config().deleteTable !== false) {
|
|
2716
|
+
<tiptap-separator></tiptap-separator>
|
|
2717
|
+
}
|
|
2718
|
+
|
|
2719
|
+
<!-- Actions de table -->
|
|
2720
|
+
@if (config().deleteTable !== false) {
|
|
2721
|
+
<tiptap-button
|
|
2722
|
+
icon="delete_forever"
|
|
2723
|
+
title="{{ i18n.table().deleteTable }}"
|
|
2724
|
+
variant="danger"
|
|
2725
|
+
(click)="deleteTable()"
|
|
2726
|
+
></tiptap-button>
|
|
2727
|
+
}
|
|
2728
|
+
</div>
|
|
2634
2729
|
` }]
|
|
2635
|
-
}], ctorParameters: () => [], propDecorators: {
|
|
2730
|
+
}], ctorParameters: () => [], propDecorators: { menuElement: [{
|
|
2636
2731
|
type: ViewChild,
|
|
2637
|
-
args: ["
|
|
2732
|
+
args: ["menuElement", { static: true }]
|
|
2638
2733
|
}] } });
|
|
2639
2734
|
|
|
2640
|
-
class
|
|
2735
|
+
class TiptapCellBubbleMenuComponent {
|
|
2641
2736
|
constructor() {
|
|
2737
|
+
// Inputs
|
|
2642
2738
|
this.editor = input.required();
|
|
2643
|
-
this.config = input({
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
resizeOriginal: true,
|
|
2649
|
-
deleteImage: true,
|
|
2650
|
-
separator: true,
|
|
2651
|
-
});
|
|
2739
|
+
this.config = input({});
|
|
2740
|
+
// Services
|
|
2741
|
+
this.i18nService = inject(TiptapI18nService);
|
|
2742
|
+
this.commandsService = inject(EditorCommandsService);
|
|
2743
|
+
// Tippy instance
|
|
2652
2744
|
this.tippyInstance = null;
|
|
2653
|
-
this.imageService = inject(ImageService);
|
|
2654
2745
|
this.updateTimeout = null;
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
resizeMedium: true,
|
|
2659
|
-
resizeLarge: true,
|
|
2660
|
-
resizeOriginal: true,
|
|
2661
|
-
deleteImage: true,
|
|
2662
|
-
separator: true,
|
|
2663
|
-
...this.config(),
|
|
2664
|
-
}));
|
|
2665
|
-
this.hasResizeButtons = computed(() => {
|
|
2666
|
-
const config = this.imageBubbleMenuConfig();
|
|
2667
|
-
return (config.resizeSmall ||
|
|
2668
|
-
config.resizeMedium ||
|
|
2669
|
-
config.resizeLarge ||
|
|
2670
|
-
config.resizeOriginal);
|
|
2671
|
-
});
|
|
2746
|
+
// Signaux
|
|
2747
|
+
this.i18n = this.i18nService;
|
|
2748
|
+
this.isSingleCellSelected = false;
|
|
2672
2749
|
this.updateMenu = () => {
|
|
2673
2750
|
// Debounce pour éviter les appels trop fréquents
|
|
2674
2751
|
if (this.updateTimeout) {
|
|
@@ -2678,14 +2755,33 @@ class TiptapImageBubbleMenuComponent {
|
|
|
2678
2755
|
const ed = this.editor();
|
|
2679
2756
|
if (!ed)
|
|
2680
2757
|
return;
|
|
2681
|
-
const
|
|
2682
|
-
const { from, to } =
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
//
|
|
2686
|
-
|
|
2687
|
-
|
|
2758
|
+
const { selection } = ed.state;
|
|
2759
|
+
const { from, to } = selection;
|
|
2760
|
+
// Détecter spécifiquement la sélection de CELLULES (pas de texte)
|
|
2761
|
+
const hasCellSelection = selection instanceof CellSelection;
|
|
2762
|
+
// Une seule cellule si ancre et tête pointent vers la même cellule
|
|
2763
|
+
this.isSingleCellSelected =
|
|
2764
|
+
hasCellSelection &&
|
|
2765
|
+
selection.$anchorCell.pos ===
|
|
2766
|
+
selection.$headCell.pos;
|
|
2767
|
+
const hasTextSelection = !selection.empty && !(selection instanceof CellSelection);
|
|
2768
|
+
const isTableCell = ed.isActive("tableCell") || ed.isActive("tableHeader");
|
|
2769
|
+
console.log("CellBubbleMenu - updateMenu:", {
|
|
2770
|
+
hasCellSelection,
|
|
2771
|
+
isSingleCellSelected: this.isSingleCellSelected,
|
|
2772
|
+
hasTextSelection,
|
|
2773
|
+
isTableCell,
|
|
2774
|
+
selectionEmpty: selection.empty,
|
|
2775
|
+
selectionType: selection.constructor.name,
|
|
2776
|
+
from,
|
|
2777
|
+
to,
|
|
2778
|
+
isEditable: ed.isEditable,
|
|
2779
|
+
});
|
|
2780
|
+
// Le menu de cellule ne s'affiche QUE pour les sélections de cellules multiples
|
|
2781
|
+
// (pas pour la sélection de texte dans une cellule)
|
|
2782
|
+
const shouldShow = hasCellSelection && isTableCell && ed.isEditable;
|
|
2688
2783
|
if (shouldShow) {
|
|
2784
|
+
console.log("CellBubbleMenu - Affichage du menu de cellules");
|
|
2689
2785
|
this.showTippy();
|
|
2690
2786
|
}
|
|
2691
2787
|
else {
|
|
@@ -2699,57 +2795,41 @@ class TiptapImageBubbleMenuComponent {
|
|
|
2699
2795
|
this.hideTippy();
|
|
2700
2796
|
}, 100);
|
|
2701
2797
|
};
|
|
2798
|
+
// Effet pour mettre à jour le menu quand l'éditeur change
|
|
2702
2799
|
effect(() => {
|
|
2703
|
-
const
|
|
2704
|
-
if (
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
ed.on("focus", this.updateMenu);
|
|
2715
|
-
ed.on("blur", this.handleBlur);
|
|
2716
|
-
// Ne pas appeler updateMenu() ici pour éviter l'affichage prématuré
|
|
2717
|
-
// Il sera appelé automatiquement quand l'éditeur sera prêt
|
|
2800
|
+
const editor = this.editor();
|
|
2801
|
+
if (editor) {
|
|
2802
|
+
// Nettoyer les anciens listeners
|
|
2803
|
+
editor.off("selectionUpdate", this.updateMenu);
|
|
2804
|
+
editor.off("focus", this.updateMenu);
|
|
2805
|
+
editor.off("blur", this.handleBlur);
|
|
2806
|
+
// Ajouter les nouveaux listeners
|
|
2807
|
+
editor.on("selectionUpdate", this.updateMenu);
|
|
2808
|
+
editor.on("focus", this.updateMenu);
|
|
2809
|
+
editor.on("blur", this.handleBlur);
|
|
2810
|
+
}
|
|
2718
2811
|
});
|
|
2719
2812
|
}
|
|
2720
2813
|
ngOnInit() {
|
|
2721
|
-
// Initialiser Tippy de manière synchrone après que le component soit ready
|
|
2722
2814
|
this.initTippy();
|
|
2723
2815
|
}
|
|
2724
2816
|
ngOnDestroy() {
|
|
2725
|
-
const
|
|
2726
|
-
if (
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
}
|
|
2732
|
-
// Nettoyer les timeouts
|
|
2733
|
-
if (this.updateTimeout) {
|
|
2734
|
-
clearTimeout(this.updateTimeout);
|
|
2817
|
+
const editor = this.editor();
|
|
2818
|
+
if (editor) {
|
|
2819
|
+
// Nettoyer les événements
|
|
2820
|
+
editor.off("selectionUpdate", this.updateMenu);
|
|
2821
|
+
editor.off("focus", this.updateMenu);
|
|
2822
|
+
editor.off("blur", this.handleBlur);
|
|
2735
2823
|
}
|
|
2736
|
-
// Nettoyer Tippy
|
|
2737
2824
|
if (this.tippyInstance) {
|
|
2738
2825
|
this.tippyInstance.destroy();
|
|
2739
|
-
|
|
2826
|
+
}
|
|
2827
|
+
if (this.updateTimeout) {
|
|
2828
|
+
clearTimeout(this.updateTimeout);
|
|
2740
2829
|
}
|
|
2741
2830
|
}
|
|
2742
2831
|
initTippy() {
|
|
2743
|
-
|
|
2744
|
-
if (!this.menuRef?.nativeElement) {
|
|
2745
|
-
setTimeout(() => this.initTippy(), 50);
|
|
2746
|
-
return;
|
|
2747
|
-
}
|
|
2748
|
-
const menuElement = this.menuRef.nativeElement;
|
|
2749
|
-
// S'assurer qu'il n'y a pas déjà une instance
|
|
2750
|
-
if (this.tippyInstance) {
|
|
2751
|
-
this.tippyInstance.destroy();
|
|
2752
|
-
}
|
|
2832
|
+
const menuElement = this.menuElement.nativeElement;
|
|
2753
2833
|
// Créer l'instance Tippy
|
|
2754
2834
|
this.tippyInstance = tippy(document.body, {
|
|
2755
2835
|
content: menuElement,
|
|
@@ -2764,8 +2844,7 @@ class TiptapImageBubbleMenuComponent {
|
|
|
2764
2844
|
// S'assurer que les autres menus sont fermés
|
|
2765
2845
|
this.hideOtherMenus();
|
|
2766
2846
|
},
|
|
2767
|
-
getReferenceClientRect: () => this.
|
|
2768
|
-
// Améliorer le positionnement avec scroll
|
|
2847
|
+
getReferenceClientRect: () => this.getCellRect(),
|
|
2769
2848
|
popperOptions: {
|
|
2770
2849
|
modifiers: [
|
|
2771
2850
|
{
|
|
@@ -2787,218 +2866,636 @@ class TiptapImageBubbleMenuComponent {
|
|
|
2787
2866
|
// Maintenant que Tippy est initialisé, faire un premier check
|
|
2788
2867
|
this.updateMenu();
|
|
2789
2868
|
}
|
|
2790
|
-
|
|
2869
|
+
getCellRect() {
|
|
2791
2870
|
const ed = this.editor();
|
|
2792
2871
|
if (!ed)
|
|
2793
2872
|
return new DOMRect(0, 0, 0, 0);
|
|
2794
|
-
//
|
|
2795
|
-
const { from } = ed.state.selection;
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
return Array.from(editorElement.querySelectorAll("img"));
|
|
2800
|
-
};
|
|
2801
|
-
// Fonction pour trouver l'image à la position spécifique
|
|
2802
|
-
const findImageAtPosition = () => {
|
|
2803
|
-
const allImages = getAllImages();
|
|
2804
|
-
for (const img of allImages) {
|
|
2805
|
-
try {
|
|
2806
|
-
// Obtenir la position ProseMirror de cette image
|
|
2807
|
-
const imgPos = ed.view.posAtDOM(img, 0);
|
|
2808
|
-
// Vérifier si cette image correspond à la position sélectionnée
|
|
2809
|
-
if (Math.abs(imgPos - from) <= 1) {
|
|
2810
|
-
return img;
|
|
2811
|
-
}
|
|
2812
|
-
}
|
|
2813
|
-
catch (error) {
|
|
2814
|
-
// Continuer si on ne peut pas obtenir la position de cette image
|
|
2815
|
-
continue;
|
|
2816
|
-
}
|
|
2817
|
-
}
|
|
2818
|
-
return null;
|
|
2819
|
-
};
|
|
2820
|
-
// Chercher l'image à la position exacte
|
|
2821
|
-
const imageElement = findImageAtPosition();
|
|
2822
|
-
if (imageElement) {
|
|
2823
|
-
return imageElement.getBoundingClientRect();
|
|
2873
|
+
// Détecter la sélection de cellules
|
|
2874
|
+
const { from, to } = ed.state.selection;
|
|
2875
|
+
const hasCellSelection = from !== to;
|
|
2876
|
+
if (!hasCellSelection) {
|
|
2877
|
+
return new DOMRect(0, 0, 0, 0);
|
|
2824
2878
|
}
|
|
2825
|
-
|
|
2879
|
+
// Obtenir les coordonnées de la sélection
|
|
2880
|
+
const coords = ed.view.coordsAtPos(from);
|
|
2881
|
+
const endCoords = ed.view.coordsAtPos(to);
|
|
2882
|
+
// Créer un rectangle englobant la sélection
|
|
2883
|
+
const rect = new DOMRect(Math.min(coords.left, endCoords.left), Math.min(coords.top, endCoords.top), Math.abs(endCoords.left - coords.left), Math.abs(endCoords.top - coords.top));
|
|
2884
|
+
return rect;
|
|
2826
2885
|
}
|
|
2827
2886
|
hideOtherMenus() {
|
|
2828
|
-
//
|
|
2829
|
-
|
|
2887
|
+
// Masquer tous les autres menus quand le menu de cellules est actif
|
|
2888
|
+
this.hideTableMenu();
|
|
2889
|
+
this.hideTextBubbleMenu();
|
|
2830
2890
|
}
|
|
2831
2891
|
showTippy() {
|
|
2832
2892
|
if (!this.tippyInstance)
|
|
2833
2893
|
return;
|
|
2894
|
+
// Masquer les autres menus avant d'afficher le menu de cellules
|
|
2895
|
+
this.hideTableMenu();
|
|
2896
|
+
this.hideTextBubbleMenu();
|
|
2834
2897
|
// Mettre à jour la position
|
|
2835
2898
|
this.tippyInstance.setProps({
|
|
2836
|
-
getReferenceClientRect: () => this.
|
|
2899
|
+
getReferenceClientRect: () => this.getCellRect(),
|
|
2837
2900
|
});
|
|
2838
2901
|
this.tippyInstance.show();
|
|
2839
2902
|
}
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2903
|
+
hideTableMenu() {
|
|
2904
|
+
// Masquer le menu de table quand le menu de cellules est actif
|
|
2905
|
+
const tableMenu = document.querySelector("tiptap-table-bubble-menu");
|
|
2906
|
+
if (tableMenu) {
|
|
2907
|
+
const tippyInstance = tableMenu._tippy;
|
|
2908
|
+
if (tippyInstance) {
|
|
2909
|
+
tippyInstance.hide();
|
|
2910
|
+
}
|
|
2911
|
+
}
|
|
2912
|
+
// Alternative : masquer via l'élément Angular
|
|
2913
|
+
const tableMenuComponent = document.querySelector("tiptap-table-bubble-menu");
|
|
2914
|
+
if (tableMenuComponent && tableMenuComponent.hideTippy) {
|
|
2915
|
+
tableMenuComponent.hideTippy();
|
|
2843
2916
|
}
|
|
2844
2917
|
}
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
const
|
|
2848
|
-
if (
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
this.imageService.resizeImageToMedium(ed);
|
|
2859
|
-
break;
|
|
2860
|
-
case "resizeLarge":
|
|
2861
|
-
this.imageService.resizeImageToLarge(ed);
|
|
2862
|
-
break;
|
|
2863
|
-
case "resizeOriginal":
|
|
2864
|
-
this.imageService.resizeImageToOriginal(ed);
|
|
2865
|
-
break;
|
|
2866
|
-
case "deleteImage":
|
|
2867
|
-
this.deleteImage();
|
|
2868
|
-
break;
|
|
2918
|
+
hideTextBubbleMenu() {
|
|
2919
|
+
// Masquer le menu de texte (bubble menu général) quand le menu de cellules est actif
|
|
2920
|
+
const textMenu = document.querySelector("tiptap-bubble-menu");
|
|
2921
|
+
if (textMenu) {
|
|
2922
|
+
const tippyInstance = textMenu._tippy;
|
|
2923
|
+
if (tippyInstance) {
|
|
2924
|
+
tippyInstance.hide();
|
|
2925
|
+
}
|
|
2926
|
+
}
|
|
2927
|
+
// Alternative : masquer via l'élément Angular
|
|
2928
|
+
const textMenuComponent = document.querySelector("tiptap-bubble-menu");
|
|
2929
|
+
if (textMenuComponent && textMenuComponent.hideTippy) {
|
|
2930
|
+
textMenuComponent.hideTippy();
|
|
2869
2931
|
}
|
|
2870
2932
|
}
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
if (!ed)
|
|
2933
|
+
hideTippy() {
|
|
2934
|
+
if (!this.tippyInstance)
|
|
2874
2935
|
return;
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2936
|
+
this.tippyInstance.hide();
|
|
2937
|
+
}
|
|
2938
|
+
// Actions spécifiques aux cellules
|
|
2939
|
+
mergeCells() {
|
|
2940
|
+
this.commandsService.mergeCells(this.editor());
|
|
2941
|
+
}
|
|
2942
|
+
splitCell() {
|
|
2943
|
+
this.commandsService.splitCell(this.editor());
|
|
2944
|
+
}
|
|
2945
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: TiptapCellBubbleMenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
2946
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.0", type: TiptapCellBubbleMenuComponent, isStandalone: true, selector: "tiptap-cell-bubble-menu", inputs: { editor: { classPropertyName: "editor", publicName: "editor", isSignal: true, isRequired: true, transformFunction: null }, config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "menuElement", first: true, predicate: ["menuElement"], descendants: true, static: true }], ngImport: i0, template: `
|
|
2947
|
+
<div #menuElement class="bubble-menu">
|
|
2948
|
+
<!-- Actions spécifiques aux cellules -->
|
|
2949
|
+
@if (config().mergeCells !== false && !isSingleCellSelected) {
|
|
2950
|
+
<tiptap-button
|
|
2951
|
+
icon="cell_merge"
|
|
2952
|
+
title="{{ i18n.table().mergeCells }}"
|
|
2953
|
+
(click)="mergeCells()"
|
|
2954
|
+
></tiptap-button>
|
|
2955
|
+
} @if (config().splitCell !== false && isSingleCellSelected) {
|
|
2956
|
+
<tiptap-button
|
|
2957
|
+
icon="split_scene"
|
|
2958
|
+
title="{{ i18n.table().splitCell }}"
|
|
2959
|
+
(click)="splitCell()"
|
|
2960
|
+
></tiptap-button>
|
|
2961
|
+
}
|
|
2962
|
+
</div>
|
|
2963
|
+
`, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: TiptapButtonComponent, selector: "tiptap-button", inputs: ["icon", "title", "active", "disabled", "variant", "size", "iconSize"], outputs: ["onClick"] }] }); }
|
|
2964
|
+
}
|
|
2965
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: TiptapCellBubbleMenuComponent, decorators: [{
|
|
2966
|
+
type: Component,
|
|
2967
|
+
args: [{ selector: "tiptap-cell-bubble-menu", standalone: true, imports: [CommonModule, TiptapButtonComponent], template: `
|
|
2968
|
+
<div #menuElement class="bubble-menu">
|
|
2969
|
+
<!-- Actions spécifiques aux cellules -->
|
|
2970
|
+
@if (config().mergeCells !== false && !isSingleCellSelected) {
|
|
2971
|
+
<tiptap-button
|
|
2972
|
+
icon="cell_merge"
|
|
2973
|
+
title="{{ i18n.table().mergeCells }}"
|
|
2974
|
+
(click)="mergeCells()"
|
|
2975
|
+
></tiptap-button>
|
|
2976
|
+
} @if (config().splitCell !== false && isSingleCellSelected) {
|
|
2977
|
+
<tiptap-button
|
|
2978
|
+
icon="split_scene"
|
|
2979
|
+
title="{{ i18n.table().splitCell }}"
|
|
2980
|
+
(click)="splitCell()"
|
|
2981
|
+
></tiptap-button>
|
|
2982
|
+
}
|
|
2983
|
+
</div>
|
|
2984
|
+
` }]
|
|
2985
|
+
}], ctorParameters: () => [], propDecorators: { menuElement: [{
|
|
2986
|
+
type: ViewChild,
|
|
2987
|
+
args: ["menuElement", { static: true }]
|
|
2988
|
+
}] } });
|
|
2989
|
+
|
|
2990
|
+
class TiptapToolbarComponent {
|
|
2991
|
+
constructor(editorCommands) {
|
|
2992
|
+
this.editorCommands = editorCommands;
|
|
2993
|
+
this.editor = input.required();
|
|
2994
|
+
this.config = input.required();
|
|
2995
|
+
// Outputs pour les événements d'image
|
|
2996
|
+
this.imageUploaded = output();
|
|
2997
|
+
this.imageError = output();
|
|
2998
|
+
this.imageService = inject(ImageService);
|
|
2999
|
+
this.i18nService = inject(TiptapI18nService);
|
|
3000
|
+
// Computed values pour les traductions
|
|
3001
|
+
this.t = this.i18nService.toolbar;
|
|
3002
|
+
}
|
|
3003
|
+
isActive(name, attributes) {
|
|
3004
|
+
return this.editorCommands.isActive(this.editor(), name, attributes);
|
|
3005
|
+
}
|
|
3006
|
+
canExecute(command) {
|
|
3007
|
+
return this.editorCommands.canExecute(this.editor(), command);
|
|
3008
|
+
}
|
|
3009
|
+
toggleBold() {
|
|
3010
|
+
this.editorCommands.toggleBold(this.editor());
|
|
3011
|
+
}
|
|
3012
|
+
toggleItalic() {
|
|
3013
|
+
this.editorCommands.toggleItalic(this.editor());
|
|
3014
|
+
}
|
|
3015
|
+
toggleStrike() {
|
|
3016
|
+
this.editorCommands.toggleStrike(this.editor());
|
|
3017
|
+
}
|
|
3018
|
+
toggleCode() {
|
|
3019
|
+
this.editorCommands.toggleCode(this.editor());
|
|
3020
|
+
}
|
|
3021
|
+
toggleHeading(level) {
|
|
3022
|
+
this.editorCommands.toggleHeading(this.editor(), level);
|
|
3023
|
+
}
|
|
3024
|
+
toggleBulletList() {
|
|
3025
|
+
this.editorCommands.toggleBulletList(this.editor());
|
|
3026
|
+
}
|
|
3027
|
+
toggleOrderedList() {
|
|
3028
|
+
this.editorCommands.toggleOrderedList(this.editor());
|
|
3029
|
+
}
|
|
3030
|
+
toggleBlockquote() {
|
|
3031
|
+
this.editorCommands.toggleBlockquote(this.editor());
|
|
3032
|
+
}
|
|
3033
|
+
undo() {
|
|
3034
|
+
this.editorCommands.undo(this.editor());
|
|
3035
|
+
}
|
|
3036
|
+
redo() {
|
|
3037
|
+
this.editorCommands.redo(this.editor());
|
|
3038
|
+
}
|
|
3039
|
+
// Nouvelles méthodes pour les formatages supplémentaires
|
|
3040
|
+
toggleUnderline() {
|
|
3041
|
+
this.editorCommands.toggleUnderline(this.editor());
|
|
3042
|
+
}
|
|
3043
|
+
toggleSuperscript() {
|
|
3044
|
+
this.editorCommands.toggleSuperscript(this.editor());
|
|
3045
|
+
}
|
|
3046
|
+
toggleSubscript() {
|
|
3047
|
+
this.editorCommands.toggleSubscript(this.editor());
|
|
3048
|
+
}
|
|
3049
|
+
setTextAlign(alignment) {
|
|
3050
|
+
this.editorCommands.setTextAlign(this.editor(), alignment);
|
|
3051
|
+
}
|
|
3052
|
+
toggleLink() {
|
|
3053
|
+
this.editorCommands.toggleLink(this.editor());
|
|
3054
|
+
}
|
|
3055
|
+
insertHorizontalRule() {
|
|
3056
|
+
this.editorCommands.insertHorizontalRule(this.editor());
|
|
3057
|
+
}
|
|
3058
|
+
toggleHighlight() {
|
|
3059
|
+
this.editorCommands.toggleHighlight(this.editor());
|
|
3060
|
+
}
|
|
3061
|
+
// Méthode pour insérer un tableau
|
|
3062
|
+
insertTable() {
|
|
3063
|
+
this.editorCommands.insertTable(this.editor());
|
|
3064
|
+
}
|
|
3065
|
+
// Méthode pour insérer une image
|
|
3066
|
+
async insertImage() {
|
|
3067
|
+
try {
|
|
3068
|
+
await this.imageService.selectAndUploadImage(this.editor());
|
|
2883
3069
|
}
|
|
2884
3070
|
catch (error) {
|
|
2885
|
-
console.error("Erreur lors
|
|
3071
|
+
console.error("Erreur lors de l'upload d'image:", error);
|
|
3072
|
+
this.imageError.emit("Erreur lors de l'upload d'image");
|
|
2886
3073
|
}
|
|
2887
3074
|
}
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
ed.chain().focus().deleteSelection().run();
|
|
2892
|
-
}
|
|
3075
|
+
// Méthode pour vider le contenu
|
|
3076
|
+
clearContent() {
|
|
3077
|
+
this.editorCommands.clearContent(this.editor());
|
|
2893
3078
|
}
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
(
|
|
2911
|
-
|
|
2912
|
-
} @if (
|
|
2913
|
-
<tiptap-button
|
|
2914
|
-
icon="
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
title="
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
3079
|
+
// Méthodes pour les événements d'image (conservées pour compatibilité)
|
|
3080
|
+
onImageSelected(result) {
|
|
3081
|
+
this.imageUploaded.emit(result);
|
|
3082
|
+
}
|
|
3083
|
+
onImageError(error) {
|
|
3084
|
+
this.imageError.emit(error);
|
|
3085
|
+
}
|
|
3086
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: TiptapToolbarComponent, deps: [{ token: EditorCommandsService }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
3087
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.0", type: TiptapToolbarComponent, isStandalone: true, selector: "tiptap-toolbar", inputs: { editor: { classPropertyName: "editor", publicName: "editor", isSignal: true, isRequired: true, transformFunction: null }, config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { imageUploaded: "imageUploaded", imageError: "imageError" }, ngImport: i0, template: `
|
|
3088
|
+
<div class="tiptap-toolbar">
|
|
3089
|
+
@if (config().bold) {
|
|
3090
|
+
<tiptap-button
|
|
3091
|
+
icon="format_bold"
|
|
3092
|
+
[title]="t().bold"
|
|
3093
|
+
[active]="isActive('bold')"
|
|
3094
|
+
[disabled]="!canExecute('toggleBold')"
|
|
3095
|
+
(onClick)="toggleBold()"
|
|
3096
|
+
/>
|
|
3097
|
+
} @if (config().italic) {
|
|
3098
|
+
<tiptap-button
|
|
3099
|
+
icon="format_italic"
|
|
3100
|
+
[title]="t().italic"
|
|
3101
|
+
[active]="isActive('italic')"
|
|
3102
|
+
[disabled]="!canExecute('toggleItalic')"
|
|
3103
|
+
(onClick)="toggleItalic()"
|
|
3104
|
+
/>
|
|
3105
|
+
} @if (config().underline) {
|
|
3106
|
+
<tiptap-button
|
|
3107
|
+
icon="format_underlined"
|
|
3108
|
+
[title]="t().underline"
|
|
3109
|
+
[active]="isActive('underline')"
|
|
3110
|
+
[disabled]="!canExecute('toggleUnderline')"
|
|
3111
|
+
(onClick)="toggleUnderline()"
|
|
3112
|
+
/>
|
|
3113
|
+
} @if (config().strike) {
|
|
3114
|
+
<tiptap-button
|
|
3115
|
+
icon="strikethrough_s"
|
|
3116
|
+
[title]="t().strike"
|
|
3117
|
+
[active]="isActive('strike')"
|
|
3118
|
+
[disabled]="!canExecute('toggleStrike')"
|
|
3119
|
+
(onClick)="toggleStrike()"
|
|
3120
|
+
/>
|
|
3121
|
+
} @if (config().code) {
|
|
3122
|
+
<tiptap-button
|
|
3123
|
+
icon="code"
|
|
3124
|
+
[title]="t().code"
|
|
3125
|
+
[active]="isActive('code')"
|
|
3126
|
+
[disabled]="!canExecute('toggleCode')"
|
|
3127
|
+
(onClick)="toggleCode()"
|
|
3128
|
+
/>
|
|
3129
|
+
} @if (config().superscript) {
|
|
3130
|
+
<tiptap-button
|
|
3131
|
+
icon="superscript"
|
|
3132
|
+
[title]="t().superscript"
|
|
3133
|
+
[active]="isActive('superscript')"
|
|
3134
|
+
[disabled]="!canExecute('toggleSuperscript')"
|
|
3135
|
+
(onClick)="toggleSuperscript()"
|
|
3136
|
+
/>
|
|
3137
|
+
} @if (config().subscript) {
|
|
3138
|
+
<tiptap-button
|
|
3139
|
+
icon="subscript"
|
|
3140
|
+
[title]="t().subscript"
|
|
3141
|
+
[active]="isActive('subscript')"
|
|
3142
|
+
[disabled]="!canExecute('toggleSubscript')"
|
|
3143
|
+
(onClick)="toggleSubscript()"
|
|
3144
|
+
/>
|
|
3145
|
+
} @if (config().highlight) {
|
|
3146
|
+
<tiptap-button
|
|
3147
|
+
icon="highlight"
|
|
3148
|
+
[title]="t().highlight"
|
|
3149
|
+
[active]="isActive('highlight')"
|
|
3150
|
+
[disabled]="!canExecute('toggleHighlight')"
|
|
3151
|
+
(onClick)="toggleHighlight()"
|
|
3152
|
+
/>
|
|
3153
|
+
} @if (config().separator && (config().heading1 || config().heading2 ||
|
|
3154
|
+
config().heading3)) {
|
|
3155
|
+
<tiptap-separator />
|
|
3156
|
+
} @if (config().heading1) {
|
|
3157
|
+
<tiptap-button
|
|
3158
|
+
icon="format_h1"
|
|
3159
|
+
[title]="t().heading1"
|
|
3160
|
+
variant="text"
|
|
3161
|
+
[active]="isActive('heading', { level: 1 })"
|
|
3162
|
+
(onClick)="toggleHeading(1)"
|
|
3163
|
+
/>
|
|
3164
|
+
} @if (config().heading2) {
|
|
3165
|
+
<tiptap-button
|
|
3166
|
+
icon="format_h2"
|
|
3167
|
+
[title]="t().heading2"
|
|
3168
|
+
variant="text"
|
|
3169
|
+
[active]="isActive('heading', { level: 2 })"
|
|
3170
|
+
(onClick)="toggleHeading(2)"
|
|
3171
|
+
/>
|
|
3172
|
+
} @if (config().heading3) {
|
|
3173
|
+
<tiptap-button
|
|
3174
|
+
icon="format_h3"
|
|
3175
|
+
[title]="t().heading3"
|
|
3176
|
+
variant="text"
|
|
3177
|
+
[active]="isActive('heading', { level: 3 })"
|
|
3178
|
+
(onClick)="toggleHeading(3)"
|
|
3179
|
+
/>
|
|
3180
|
+
} @if (config().separator && (config().bulletList || config().orderedList
|
|
3181
|
+
|| config().blockquote)) {
|
|
3182
|
+
<tiptap-separator />
|
|
3183
|
+
} @if (config().bulletList) {
|
|
3184
|
+
<tiptap-button
|
|
3185
|
+
icon="format_list_bulleted"
|
|
3186
|
+
[title]="t().bulletList"
|
|
3187
|
+
[active]="isActive('bulletList')"
|
|
3188
|
+
(onClick)="toggleBulletList()"
|
|
3189
|
+
/>
|
|
3190
|
+
} @if (config().orderedList) {
|
|
3191
|
+
<tiptap-button
|
|
3192
|
+
icon="format_list_numbered"
|
|
3193
|
+
[title]="t().orderedList"
|
|
3194
|
+
[active]="isActive('orderedList')"
|
|
3195
|
+
(onClick)="toggleOrderedList()"
|
|
3196
|
+
/>
|
|
3197
|
+
} @if (config().blockquote) {
|
|
3198
|
+
<tiptap-button
|
|
3199
|
+
icon="format_quote"
|
|
3200
|
+
[title]="t().blockquote"
|
|
3201
|
+
[active]="isActive('blockquote')"
|
|
3202
|
+
(onClick)="toggleBlockquote()"
|
|
3203
|
+
/>
|
|
3204
|
+
} @if (config().separator && (config().alignLeft || config().alignCenter
|
|
3205
|
+
|| config().alignRight || config().alignJustify)) {
|
|
3206
|
+
<tiptap-separator />
|
|
3207
|
+
} @if (config().alignLeft) {
|
|
3208
|
+
<tiptap-button
|
|
3209
|
+
icon="format_align_left"
|
|
3210
|
+
[title]="t().alignLeft"
|
|
3211
|
+
[active]="isActive('textAlign', { textAlign: 'left' })"
|
|
3212
|
+
(onClick)="setTextAlign('left')"
|
|
3213
|
+
/>
|
|
3214
|
+
} @if (config().alignCenter) {
|
|
3215
|
+
<tiptap-button
|
|
3216
|
+
icon="format_align_center"
|
|
3217
|
+
[title]="t().alignCenter"
|
|
3218
|
+
[active]="isActive('textAlign', { textAlign: 'center' })"
|
|
3219
|
+
(onClick)="setTextAlign('center')"
|
|
3220
|
+
/>
|
|
3221
|
+
} @if (config().alignRight) {
|
|
3222
|
+
<tiptap-button
|
|
3223
|
+
icon="format_align_right"
|
|
3224
|
+
[title]="t().alignRight"
|
|
3225
|
+
[active]="isActive('textAlign', { textAlign: 'right' })"
|
|
3226
|
+
(onClick)="setTextAlign('right')"
|
|
3227
|
+
/>
|
|
3228
|
+
} @if (config().alignJustify) {
|
|
3229
|
+
<tiptap-button
|
|
3230
|
+
icon="format_align_justify"
|
|
3231
|
+
[title]="t().alignJustify"
|
|
3232
|
+
[active]="isActive('textAlign', { textAlign: 'justify' })"
|
|
3233
|
+
(onClick)="setTextAlign('justify')"
|
|
3234
|
+
/>
|
|
3235
|
+
} @if (config().separator && (config().link || config().horizontalRule)) {
|
|
3236
|
+
<tiptap-separator />
|
|
3237
|
+
} @if (config().link) {
|
|
3238
|
+
<tiptap-button
|
|
3239
|
+
icon="link"
|
|
3240
|
+
[title]="t().link"
|
|
3241
|
+
[active]="isActive('link')"
|
|
3242
|
+
(onClick)="toggleLink()"
|
|
3243
|
+
/>
|
|
3244
|
+
} @if (config().horizontalRule) {
|
|
3245
|
+
<tiptap-button
|
|
3246
|
+
icon="horizontal_rule"
|
|
3247
|
+
[title]="t().horizontalRule"
|
|
3248
|
+
(onClick)="insertHorizontalRule()"
|
|
3249
|
+
/>
|
|
3250
|
+
} @if (config().table) {
|
|
3251
|
+
<tiptap-button
|
|
3252
|
+
icon="table_view"
|
|
3253
|
+
[title]="t().table"
|
|
3254
|
+
(onClick)="insertTable()"
|
|
3255
|
+
/>
|
|
3256
|
+
} @if (config().separator && config().image) {
|
|
3257
|
+
<tiptap-separator />
|
|
3258
|
+
} @if (config().image) {
|
|
3259
|
+
<tiptap-button
|
|
3260
|
+
icon="image"
|
|
3261
|
+
[title]="t().image"
|
|
3262
|
+
(onClick)="insertImage()"
|
|
3263
|
+
/>
|
|
3264
|
+
} @if (config().separator && (config().undo || config().redo)) {
|
|
3265
|
+
<tiptap-separator />
|
|
3266
|
+
} @if (config().undo) {
|
|
3267
|
+
<tiptap-button
|
|
3268
|
+
icon="undo"
|
|
3269
|
+
[title]="t().undo"
|
|
3270
|
+
[disabled]="!canExecute('undo')"
|
|
3271
|
+
(onClick)="undo()"
|
|
3272
|
+
/>
|
|
3273
|
+
} @if (config().redo) {
|
|
3274
|
+
<tiptap-button
|
|
3275
|
+
icon="redo"
|
|
3276
|
+
[title]="t().redo"
|
|
3277
|
+
[disabled]="!canExecute('redo')"
|
|
3278
|
+
(onClick)="redo()"
|
|
3279
|
+
/>
|
|
3280
|
+
} @if (config().separator && config().clear) {
|
|
3281
|
+
<tiptap-separator />
|
|
3282
|
+
} @if (config().clear) {
|
|
3283
|
+
<tiptap-button
|
|
3284
|
+
icon="delete"
|
|
3285
|
+
[title]="t().clear"
|
|
3286
|
+
(onClick)="clearContent()"
|
|
3287
|
+
/>
|
|
3288
|
+
}
|
|
3289
|
+
</div>
|
|
3290
|
+
`, isInline: true, styles: [".tiptap-toolbar{display:flex;align-items:center;gap:4px;padding:4px 8px;background:#f8f9fa;border-bottom:1px solid #e2e8f0;flex-wrap:wrap;min-height:32px;position:relative}.toolbar-group{display:flex;align-items:center;gap:2px;padding:0 4px}.toolbar-separator{width:1px;height:24px;background:#e2e8f0;margin:0 4px}@media (max-width: 768px){.tiptap-toolbar{padding:6px 8px;gap:2px}.toolbar-group{gap:1px}}@keyframes toolbarSlideIn{0%{opacity:0;transform:translateY(-10px)}to{opacity:1;transform:translateY(0)}}.tiptap-toolbar{animation:toolbarSlideIn .3s cubic-bezier(.4,0,.2,1)}\n"], dependencies: [{ kind: "component", type: TiptapButtonComponent, selector: "tiptap-button", inputs: ["icon", "title", "active", "disabled", "variant", "size", "iconSize"], outputs: ["onClick"] }, { kind: "component", type: TiptapSeparatorComponent, selector: "tiptap-separator", inputs: ["orientation", "size"] }] }); }
|
|
2945
3291
|
}
|
|
2946
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type:
|
|
3292
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: TiptapToolbarComponent, decorators: [{
|
|
2947
3293
|
type: Component,
|
|
2948
|
-
args: [{ selector: "tiptap-
|
|
2949
|
-
<div
|
|
2950
|
-
@if (
|
|
2951
|
-
<tiptap-button
|
|
2952
|
-
icon="
|
|
2953
|
-
title="
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
} @if (
|
|
2959
|
-
<tiptap-button
|
|
2960
|
-
icon="
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
title="
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3294
|
+
args: [{ selector: "tiptap-toolbar", standalone: true, imports: [TiptapButtonComponent, TiptapSeparatorComponent], template: `
|
|
3295
|
+
<div class="tiptap-toolbar">
|
|
3296
|
+
@if (config().bold) {
|
|
3297
|
+
<tiptap-button
|
|
3298
|
+
icon="format_bold"
|
|
3299
|
+
[title]="t().bold"
|
|
3300
|
+
[active]="isActive('bold')"
|
|
3301
|
+
[disabled]="!canExecute('toggleBold')"
|
|
3302
|
+
(onClick)="toggleBold()"
|
|
3303
|
+
/>
|
|
3304
|
+
} @if (config().italic) {
|
|
3305
|
+
<tiptap-button
|
|
3306
|
+
icon="format_italic"
|
|
3307
|
+
[title]="t().italic"
|
|
3308
|
+
[active]="isActive('italic')"
|
|
3309
|
+
[disabled]="!canExecute('toggleItalic')"
|
|
3310
|
+
(onClick)="toggleItalic()"
|
|
3311
|
+
/>
|
|
3312
|
+
} @if (config().underline) {
|
|
3313
|
+
<tiptap-button
|
|
3314
|
+
icon="format_underlined"
|
|
3315
|
+
[title]="t().underline"
|
|
3316
|
+
[active]="isActive('underline')"
|
|
3317
|
+
[disabled]="!canExecute('toggleUnderline')"
|
|
3318
|
+
(onClick)="toggleUnderline()"
|
|
3319
|
+
/>
|
|
3320
|
+
} @if (config().strike) {
|
|
3321
|
+
<tiptap-button
|
|
3322
|
+
icon="strikethrough_s"
|
|
3323
|
+
[title]="t().strike"
|
|
3324
|
+
[active]="isActive('strike')"
|
|
3325
|
+
[disabled]="!canExecute('toggleStrike')"
|
|
3326
|
+
(onClick)="toggleStrike()"
|
|
3327
|
+
/>
|
|
3328
|
+
} @if (config().code) {
|
|
3329
|
+
<tiptap-button
|
|
3330
|
+
icon="code"
|
|
3331
|
+
[title]="t().code"
|
|
3332
|
+
[active]="isActive('code')"
|
|
3333
|
+
[disabled]="!canExecute('toggleCode')"
|
|
3334
|
+
(onClick)="toggleCode()"
|
|
3335
|
+
/>
|
|
3336
|
+
} @if (config().superscript) {
|
|
3337
|
+
<tiptap-button
|
|
3338
|
+
icon="superscript"
|
|
3339
|
+
[title]="t().superscript"
|
|
3340
|
+
[active]="isActive('superscript')"
|
|
3341
|
+
[disabled]="!canExecute('toggleSuperscript')"
|
|
3342
|
+
(onClick)="toggleSuperscript()"
|
|
3343
|
+
/>
|
|
3344
|
+
} @if (config().subscript) {
|
|
3345
|
+
<tiptap-button
|
|
3346
|
+
icon="subscript"
|
|
3347
|
+
[title]="t().subscript"
|
|
3348
|
+
[active]="isActive('subscript')"
|
|
3349
|
+
[disabled]="!canExecute('toggleSubscript')"
|
|
3350
|
+
(onClick)="toggleSubscript()"
|
|
3351
|
+
/>
|
|
3352
|
+
} @if (config().highlight) {
|
|
3353
|
+
<tiptap-button
|
|
3354
|
+
icon="highlight"
|
|
3355
|
+
[title]="t().highlight"
|
|
3356
|
+
[active]="isActive('highlight')"
|
|
3357
|
+
[disabled]="!canExecute('toggleHighlight')"
|
|
3358
|
+
(onClick)="toggleHighlight()"
|
|
3359
|
+
/>
|
|
3360
|
+
} @if (config().separator && (config().heading1 || config().heading2 ||
|
|
3361
|
+
config().heading3)) {
|
|
3362
|
+
<tiptap-separator />
|
|
3363
|
+
} @if (config().heading1) {
|
|
3364
|
+
<tiptap-button
|
|
3365
|
+
icon="format_h1"
|
|
3366
|
+
[title]="t().heading1"
|
|
3367
|
+
variant="text"
|
|
3368
|
+
[active]="isActive('heading', { level: 1 })"
|
|
3369
|
+
(onClick)="toggleHeading(1)"
|
|
3370
|
+
/>
|
|
3371
|
+
} @if (config().heading2) {
|
|
3372
|
+
<tiptap-button
|
|
3373
|
+
icon="format_h2"
|
|
3374
|
+
[title]="t().heading2"
|
|
3375
|
+
variant="text"
|
|
3376
|
+
[active]="isActive('heading', { level: 2 })"
|
|
3377
|
+
(onClick)="toggleHeading(2)"
|
|
3378
|
+
/>
|
|
3379
|
+
} @if (config().heading3) {
|
|
3380
|
+
<tiptap-button
|
|
3381
|
+
icon="format_h3"
|
|
3382
|
+
[title]="t().heading3"
|
|
3383
|
+
variant="text"
|
|
3384
|
+
[active]="isActive('heading', { level: 3 })"
|
|
3385
|
+
(onClick)="toggleHeading(3)"
|
|
3386
|
+
/>
|
|
3387
|
+
} @if (config().separator && (config().bulletList || config().orderedList
|
|
3388
|
+
|| config().blockquote)) {
|
|
3389
|
+
<tiptap-separator />
|
|
3390
|
+
} @if (config().bulletList) {
|
|
3391
|
+
<tiptap-button
|
|
3392
|
+
icon="format_list_bulleted"
|
|
3393
|
+
[title]="t().bulletList"
|
|
3394
|
+
[active]="isActive('bulletList')"
|
|
3395
|
+
(onClick)="toggleBulletList()"
|
|
3396
|
+
/>
|
|
3397
|
+
} @if (config().orderedList) {
|
|
3398
|
+
<tiptap-button
|
|
3399
|
+
icon="format_list_numbered"
|
|
3400
|
+
[title]="t().orderedList"
|
|
3401
|
+
[active]="isActive('orderedList')"
|
|
3402
|
+
(onClick)="toggleOrderedList()"
|
|
3403
|
+
/>
|
|
3404
|
+
} @if (config().blockquote) {
|
|
3405
|
+
<tiptap-button
|
|
3406
|
+
icon="format_quote"
|
|
3407
|
+
[title]="t().blockquote"
|
|
3408
|
+
[active]="isActive('blockquote')"
|
|
3409
|
+
(onClick)="toggleBlockquote()"
|
|
3410
|
+
/>
|
|
3411
|
+
} @if (config().separator && (config().alignLeft || config().alignCenter
|
|
3412
|
+
|| config().alignRight || config().alignJustify)) {
|
|
3413
|
+
<tiptap-separator />
|
|
3414
|
+
} @if (config().alignLeft) {
|
|
3415
|
+
<tiptap-button
|
|
3416
|
+
icon="format_align_left"
|
|
3417
|
+
[title]="t().alignLeft"
|
|
3418
|
+
[active]="isActive('textAlign', { textAlign: 'left' })"
|
|
3419
|
+
(onClick)="setTextAlign('left')"
|
|
3420
|
+
/>
|
|
3421
|
+
} @if (config().alignCenter) {
|
|
3422
|
+
<tiptap-button
|
|
3423
|
+
icon="format_align_center"
|
|
3424
|
+
[title]="t().alignCenter"
|
|
3425
|
+
[active]="isActive('textAlign', { textAlign: 'center' })"
|
|
3426
|
+
(onClick)="setTextAlign('center')"
|
|
3427
|
+
/>
|
|
3428
|
+
} @if (config().alignRight) {
|
|
3429
|
+
<tiptap-button
|
|
3430
|
+
icon="format_align_right"
|
|
3431
|
+
[title]="t().alignRight"
|
|
3432
|
+
[active]="isActive('textAlign', { textAlign: 'right' })"
|
|
3433
|
+
(onClick)="setTextAlign('right')"
|
|
3434
|
+
/>
|
|
3435
|
+
} @if (config().alignJustify) {
|
|
3436
|
+
<tiptap-button
|
|
3437
|
+
icon="format_align_justify"
|
|
3438
|
+
[title]="t().alignJustify"
|
|
3439
|
+
[active]="isActive('textAlign', { textAlign: 'justify' })"
|
|
3440
|
+
(onClick)="setTextAlign('justify')"
|
|
3441
|
+
/>
|
|
3442
|
+
} @if (config().separator && (config().link || config().horizontalRule)) {
|
|
3443
|
+
<tiptap-separator />
|
|
3444
|
+
} @if (config().link) {
|
|
3445
|
+
<tiptap-button
|
|
3446
|
+
icon="link"
|
|
3447
|
+
[title]="t().link"
|
|
3448
|
+
[active]="isActive('link')"
|
|
3449
|
+
(onClick)="toggleLink()"
|
|
3450
|
+
/>
|
|
3451
|
+
} @if (config().horizontalRule) {
|
|
3452
|
+
<tiptap-button
|
|
3453
|
+
icon="horizontal_rule"
|
|
3454
|
+
[title]="t().horizontalRule"
|
|
3455
|
+
(onClick)="insertHorizontalRule()"
|
|
3456
|
+
/>
|
|
3457
|
+
} @if (config().table) {
|
|
3458
|
+
<tiptap-button
|
|
3459
|
+
icon="table_view"
|
|
3460
|
+
[title]="t().table"
|
|
3461
|
+
(onClick)="insertTable()"
|
|
3462
|
+
/>
|
|
3463
|
+
} @if (config().separator && config().image) {
|
|
3464
|
+
<tiptap-separator />
|
|
3465
|
+
} @if (config().image) {
|
|
3466
|
+
<tiptap-button
|
|
3467
|
+
icon="image"
|
|
3468
|
+
[title]="t().image"
|
|
3469
|
+
(onClick)="insertImage()"
|
|
3470
|
+
/>
|
|
3471
|
+
} @if (config().separator && (config().undo || config().redo)) {
|
|
3472
|
+
<tiptap-separator />
|
|
3473
|
+
} @if (config().undo) {
|
|
3474
|
+
<tiptap-button
|
|
3475
|
+
icon="undo"
|
|
3476
|
+
[title]="t().undo"
|
|
3477
|
+
[disabled]="!canExecute('undo')"
|
|
3478
|
+
(onClick)="undo()"
|
|
3479
|
+
/>
|
|
3480
|
+
} @if (config().redo) {
|
|
3481
|
+
<tiptap-button
|
|
3482
|
+
icon="redo"
|
|
3483
|
+
[title]="t().redo"
|
|
3484
|
+
[disabled]="!canExecute('redo')"
|
|
3485
|
+
(onClick)="redo()"
|
|
3486
|
+
/>
|
|
3487
|
+
} @if (config().separator && config().clear) {
|
|
3488
|
+
<tiptap-separator />
|
|
3489
|
+
} @if (config().clear) {
|
|
3490
|
+
<tiptap-button
|
|
3491
|
+
icon="delete"
|
|
3492
|
+
[title]="t().clear"
|
|
3493
|
+
(onClick)="clearContent()"
|
|
3494
|
+
/>
|
|
3495
|
+
}
|
|
3496
|
+
</div>
|
|
3497
|
+
`, styles: [".tiptap-toolbar{display:flex;align-items:center;gap:4px;padding:4px 8px;background:#f8f9fa;border-bottom:1px solid #e2e8f0;flex-wrap:wrap;min-height:32px;position:relative}.toolbar-group{display:flex;align-items:center;gap:2px;padding:0 4px}.toolbar-separator{width:1px;height:24px;background:#e2e8f0;margin:0 4px}@media (max-width: 768px){.tiptap-toolbar{padding:6px 8px;gap:2px}.toolbar-group{gap:1px}}@keyframes toolbarSlideIn{0%{opacity:0;transform:translateY(-10px)}to{opacity:1;transform:translateY(0)}}.tiptap-toolbar{animation:toolbarSlideIn .3s cubic-bezier(.4,0,.2,1)}\n"] }]
|
|
3498
|
+
}], ctorParameters: () => [{ type: EditorCommandsService }] });
|
|
3002
3499
|
|
|
3003
3500
|
const DEFAULT_SLASH_COMMANDS = [
|
|
3004
3501
|
{
|
|
@@ -3437,65 +3934,411 @@ class TiptapSlashCommandsComponent {
|
|
|
3437
3934
|
tr.delete(this.slashRange.from, this.slashRange.to);
|
|
3438
3935
|
view.dispatch(tr);
|
|
3439
3936
|
}
|
|
3440
|
-
return true;
|
|
3441
|
-
}
|
|
3442
|
-
return false;
|
|
3443
|
-
},
|
|
3444
|
-
},
|
|
3445
|
-
});
|
|
3446
|
-
// Ajouter le plugin à l'éditeur
|
|
3447
|
-
ed.view.updateState(ed.view.state.reconfigure({
|
|
3448
|
-
plugins: [keyboardPlugin, ...ed.view.state.plugins],
|
|
3449
|
-
}));
|
|
3937
|
+
return true;
|
|
3938
|
+
}
|
|
3939
|
+
return false;
|
|
3940
|
+
},
|
|
3941
|
+
},
|
|
3942
|
+
});
|
|
3943
|
+
// Ajouter le plugin à l'éditeur
|
|
3944
|
+
ed.view.updateState(ed.view.state.reconfigure({
|
|
3945
|
+
plugins: [keyboardPlugin, ...ed.view.state.plugins],
|
|
3946
|
+
}));
|
|
3947
|
+
}
|
|
3948
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: TiptapSlashCommandsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
3949
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.0", type: TiptapSlashCommandsComponent, isStandalone: true, selector: "tiptap-slash-commands", inputs: { editor: { classPropertyName: "editor", publicName: "editor", isSignal: true, isRequired: true, transformFunction: null }, config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { imageUploadRequested: "imageUploadRequested" }, viewQueries: [{ propertyName: "menuRef", first: true, predicate: ["menuRef"], descendants: true }], ngImport: i0, template: `
|
|
3950
|
+
<div #menuRef class="slash-commands-menu">
|
|
3951
|
+
@for (command of filteredCommands(); track command.title) {
|
|
3952
|
+
<div
|
|
3953
|
+
class="slash-command-item"
|
|
3954
|
+
[class.selected]="$index === selectedIndex()"
|
|
3955
|
+
(click)="executeCommand(command)"
|
|
3956
|
+
(mouseenter)="selectedIndex.set($index)"
|
|
3957
|
+
>
|
|
3958
|
+
<div class="slash-command-icon">
|
|
3959
|
+
<span class="material-symbols-outlined">{{ command.icon }}</span>
|
|
3960
|
+
</div>
|
|
3961
|
+
<div class="slash-command-content">
|
|
3962
|
+
<div class="slash-command-title">{{ command.title }}</div>
|
|
3963
|
+
<div class="slash-command-description">{{ command.description }}</div>
|
|
3964
|
+
</div>
|
|
3965
|
+
</div>
|
|
3966
|
+
}
|
|
3967
|
+
</div>
|
|
3968
|
+
`, isInline: true, styles: [".slash-commands-menu{background:#fff;border:1px solid #e2e8f0;border-radius:8px;box-shadow:0 4px 12px #00000026;padding:8px;max-height:300px;overflow-y:auto;min-width:280px;outline:none}.slash-command-item{display:flex;align-items:center;gap:12px;padding:8px 12px;border-radius:6px;cursor:pointer;transition:all .2s ease;border:2px solid transparent;outline:none}.slash-command-item:hover{background:#f1f5f9;border-color:#e2e8f0}.slash-command-item.selected{background:#e6f3ff;border-color:#3182ce;box-shadow:0 0 0 1px #3182ce}.slash-command-item:focus{outline:2px solid #3182ce;outline-offset:2px}.slash-command-icon{display:flex;align-items:center;justify-content:center;width:32px;height:32px;background:#f8f9fa;border-radius:6px;color:#3182ce;flex-shrink:0}.slash-command-icon .material-symbols-outlined{font-size:18px}.slash-command-content{flex:1;min-width:0}.slash-command-title{font-weight:600;color:#2d3748;font-size:14px;margin-bottom:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.slash-command-description{color:#718096;font-size:12px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n"] }); }
|
|
3969
|
+
}
|
|
3970
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: TiptapSlashCommandsComponent, decorators: [{
|
|
3971
|
+
type: Component,
|
|
3972
|
+
args: [{ selector: "tiptap-slash-commands", standalone: true, template: `
|
|
3973
|
+
<div #menuRef class="slash-commands-menu">
|
|
3974
|
+
@for (command of filteredCommands(); track command.title) {
|
|
3975
|
+
<div
|
|
3976
|
+
class="slash-command-item"
|
|
3977
|
+
[class.selected]="$index === selectedIndex()"
|
|
3978
|
+
(click)="executeCommand(command)"
|
|
3979
|
+
(mouseenter)="selectedIndex.set($index)"
|
|
3980
|
+
>
|
|
3981
|
+
<div class="slash-command-icon">
|
|
3982
|
+
<span class="material-symbols-outlined">{{ command.icon }}</span>
|
|
3983
|
+
</div>
|
|
3984
|
+
<div class="slash-command-content">
|
|
3985
|
+
<div class="slash-command-title">{{ command.title }}</div>
|
|
3986
|
+
<div class="slash-command-description">{{ command.description }}</div>
|
|
3987
|
+
</div>
|
|
3988
|
+
</div>
|
|
3989
|
+
}
|
|
3990
|
+
</div>
|
|
3991
|
+
`, styles: [".slash-commands-menu{background:#fff;border:1px solid #e2e8f0;border-radius:8px;box-shadow:0 4px 12px #00000026;padding:8px;max-height:300px;overflow-y:auto;min-width:280px;outline:none}.slash-command-item{display:flex;align-items:center;gap:12px;padding:8px 12px;border-radius:6px;cursor:pointer;transition:all .2s ease;border:2px solid transparent;outline:none}.slash-command-item:hover{background:#f1f5f9;border-color:#e2e8f0}.slash-command-item.selected{background:#e6f3ff;border-color:#3182ce;box-shadow:0 0 0 1px #3182ce}.slash-command-item:focus{outline:2px solid #3182ce;outline-offset:2px}.slash-command-icon{display:flex;align-items:center;justify-content:center;width:32px;height:32px;background:#f8f9fa;border-radius:6px;color:#3182ce;flex-shrink:0}.slash-command-icon .material-symbols-outlined{font-size:18px}.slash-command-content{flex:1;min-width:0}.slash-command-title{font-weight:600;color:#2d3748;font-size:14px;margin-bottom:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.slash-command-description{color:#718096;font-size:12px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n"] }]
|
|
3992
|
+
}], ctorParameters: () => [], propDecorators: { menuRef: [{
|
|
3993
|
+
type: ViewChild,
|
|
3994
|
+
args: ["menuRef", { static: false }]
|
|
3995
|
+
}] } });
|
|
3996
|
+
|
|
3997
|
+
// Main component
|
|
3998
|
+
|
|
3999
|
+
class TiptapImageUploadComponent {
|
|
4000
|
+
constructor() {
|
|
4001
|
+
// Inputs
|
|
4002
|
+
this.config = input({
|
|
4003
|
+
maxSize: 5, // 5MB par défaut
|
|
4004
|
+
maxWidth: 1920, // largeur max par défaut
|
|
4005
|
+
maxHeight: 1080, // hauteur max par défaut
|
|
4006
|
+
allowedTypes: ["image/jpeg", "image/png", "image/gif", "image/webp"],
|
|
4007
|
+
enableDragDrop: true,
|
|
4008
|
+
showPreview: true,
|
|
4009
|
+
multiple: false,
|
|
4010
|
+
compressImages: true,
|
|
4011
|
+
quality: 0.8,
|
|
4012
|
+
});
|
|
4013
|
+
// Outputs
|
|
4014
|
+
this.imageSelected = output();
|
|
4015
|
+
this.error = output();
|
|
4016
|
+
// Signals internes
|
|
4017
|
+
this.isDragOver = signal(false);
|
|
4018
|
+
this.isUploading = signal(false);
|
|
4019
|
+
this.uploadProgress = signal(0);
|
|
4020
|
+
this.previewImage = signal(null);
|
|
4021
|
+
this.previewInfo = signal("");
|
|
4022
|
+
this.errorMessage = signal(null);
|
|
4023
|
+
// Computed
|
|
4024
|
+
this.acceptedTypes = computed(() => {
|
|
4025
|
+
const types = this.config().allowedTypes || ["image/*"];
|
|
4026
|
+
return types.join(",");
|
|
4027
|
+
});
|
|
4028
|
+
}
|
|
4029
|
+
triggerFileInput() {
|
|
4030
|
+
const input = document.querySelector('input[type="file"]');
|
|
4031
|
+
if (input) {
|
|
4032
|
+
input.click();
|
|
4033
|
+
}
|
|
4034
|
+
}
|
|
4035
|
+
onFileSelected(event) {
|
|
4036
|
+
const input = event.target;
|
|
4037
|
+
const files = input.files;
|
|
4038
|
+
if (files && files.length > 0) {
|
|
4039
|
+
this.processFiles(Array.from(files));
|
|
4040
|
+
}
|
|
4041
|
+
// Reset input
|
|
4042
|
+
input.value = "";
|
|
4043
|
+
}
|
|
4044
|
+
onDragOver(event) {
|
|
4045
|
+
event.preventDefault();
|
|
4046
|
+
event.stopPropagation();
|
|
4047
|
+
this.isDragOver.set(true);
|
|
4048
|
+
}
|
|
4049
|
+
onDrop(event) {
|
|
4050
|
+
event.preventDefault();
|
|
4051
|
+
event.stopPropagation();
|
|
4052
|
+
this.isDragOver.set(false);
|
|
4053
|
+
const files = event.dataTransfer?.files;
|
|
4054
|
+
if (files && files.length > 0) {
|
|
4055
|
+
this.processFiles(Array.from(files));
|
|
4056
|
+
}
|
|
4057
|
+
}
|
|
4058
|
+
onDragLeave(event) {
|
|
4059
|
+
event.preventDefault();
|
|
4060
|
+
event.stopPropagation();
|
|
4061
|
+
this.isDragOver.set(false);
|
|
4062
|
+
}
|
|
4063
|
+
processFiles(files) {
|
|
4064
|
+
const config = this.config();
|
|
4065
|
+
const maxSize = (config.maxSize || 5) * 1024 * 1024; // Convertir en bytes
|
|
4066
|
+
const allowedTypes = config.allowedTypes || ["image/*"];
|
|
4067
|
+
// Vérifier le nombre de fichiers
|
|
4068
|
+
if (!config.multiple && files.length > 1) {
|
|
4069
|
+
this.showError("Veuillez sélectionner une seule image");
|
|
4070
|
+
return;
|
|
4071
|
+
}
|
|
4072
|
+
// Traiter chaque fichier
|
|
4073
|
+
files.forEach((file) => {
|
|
4074
|
+
// Vérifier le type
|
|
4075
|
+
if (!this.isValidFileType(file, allowedTypes)) {
|
|
4076
|
+
this.showError(`Type de fichier non supporté: ${file.name}`);
|
|
4077
|
+
return;
|
|
4078
|
+
}
|
|
4079
|
+
// Vérifier la taille
|
|
4080
|
+
if (file.size > maxSize) {
|
|
4081
|
+
this.showError(`Fichier trop volumineux: ${file.name} (max ${config.maxSize}MB)`);
|
|
4082
|
+
return;
|
|
4083
|
+
}
|
|
4084
|
+
// Traiter l'image avec compression si nécessaire
|
|
4085
|
+
this.processImage(file);
|
|
4086
|
+
});
|
|
4087
|
+
}
|
|
4088
|
+
isValidFileType(file, allowedTypes) {
|
|
4089
|
+
if (allowedTypes.includes("image/*")) {
|
|
4090
|
+
return file.type.startsWith("image/");
|
|
4091
|
+
}
|
|
4092
|
+
return allowedTypes.includes(file.type);
|
|
4093
|
+
}
|
|
4094
|
+
processImage(file) {
|
|
4095
|
+
this.isUploading.set(true);
|
|
4096
|
+
this.uploadProgress.set(10);
|
|
4097
|
+
const config = this.config();
|
|
4098
|
+
const originalSize = file.size;
|
|
4099
|
+
// Créer un canvas pour la compression
|
|
4100
|
+
const canvas = document.createElement("canvas");
|
|
4101
|
+
const ctx = canvas.getContext("2d");
|
|
4102
|
+
const img = new Image();
|
|
4103
|
+
img.onload = () => {
|
|
4104
|
+
this.uploadProgress.set(30);
|
|
4105
|
+
// Vérifier les dimensions
|
|
4106
|
+
const maxWidth = config.maxWidth || 1920;
|
|
4107
|
+
const maxHeight = config.maxHeight || 1080;
|
|
4108
|
+
let { width, height } = img;
|
|
4109
|
+
// Redimensionner si nécessaire
|
|
4110
|
+
if (width > maxWidth || height > maxHeight) {
|
|
4111
|
+
const ratio = Math.min(maxWidth / width, maxHeight / height);
|
|
4112
|
+
width *= ratio;
|
|
4113
|
+
height *= ratio;
|
|
4114
|
+
}
|
|
4115
|
+
canvas.width = width;
|
|
4116
|
+
canvas.height = height;
|
|
4117
|
+
// Dessiner l'image redimensionnée
|
|
4118
|
+
ctx?.drawImage(img, 0, 0, width, height);
|
|
4119
|
+
this.uploadProgress.set(70);
|
|
4120
|
+
// Convertir en base64 avec compression
|
|
4121
|
+
const quality = config.quality || 0.8;
|
|
4122
|
+
const mimeType = file.type;
|
|
4123
|
+
canvas.toBlob((blob) => {
|
|
4124
|
+
this.uploadProgress.set(90);
|
|
4125
|
+
if (blob) {
|
|
4126
|
+
const reader = new FileReader();
|
|
4127
|
+
reader.onload = (e) => {
|
|
4128
|
+
const base64 = e.target?.result;
|
|
4129
|
+
if (base64) {
|
|
4130
|
+
const result = {
|
|
4131
|
+
src: base64,
|
|
4132
|
+
name: file.name,
|
|
4133
|
+
size: blob.size,
|
|
4134
|
+
type: file.type,
|
|
4135
|
+
width: Math.round(width),
|
|
4136
|
+
height: Math.round(height),
|
|
4137
|
+
originalSize: originalSize,
|
|
4138
|
+
};
|
|
4139
|
+
// Afficher la prévisualisation si activée
|
|
4140
|
+
if (config.showPreview) {
|
|
4141
|
+
this.previewImage.set(base64);
|
|
4142
|
+
this.previewInfo.set(`${result.width}×${result.height} • ${this.formatFileSize(blob.size)}`);
|
|
4143
|
+
}
|
|
4144
|
+
// Émettre l'événement
|
|
4145
|
+
this.imageSelected.emit(result);
|
|
4146
|
+
this.clearError();
|
|
4147
|
+
}
|
|
4148
|
+
this.uploadProgress.set(100);
|
|
4149
|
+
setTimeout(() => {
|
|
4150
|
+
this.isUploading.set(false);
|
|
4151
|
+
this.uploadProgress.set(0);
|
|
4152
|
+
}, 500);
|
|
4153
|
+
};
|
|
4154
|
+
reader.readAsDataURL(blob);
|
|
4155
|
+
}
|
|
4156
|
+
else {
|
|
4157
|
+
this.showError("Erreur lors de la compression de l'image");
|
|
4158
|
+
this.isUploading.set(false);
|
|
4159
|
+
this.uploadProgress.set(0);
|
|
4160
|
+
}
|
|
4161
|
+
}, mimeType, quality);
|
|
4162
|
+
};
|
|
4163
|
+
img.onerror = () => {
|
|
4164
|
+
this.showError("Erreur lors du chargement de l'image");
|
|
4165
|
+
this.isUploading.set(false);
|
|
4166
|
+
this.uploadProgress.set(0);
|
|
4167
|
+
};
|
|
4168
|
+
img.src = URL.createObjectURL(file);
|
|
3450
4169
|
}
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
4170
|
+
formatFileSize(bytes) {
|
|
4171
|
+
if (bytes === 0)
|
|
4172
|
+
return "0 B";
|
|
4173
|
+
const k = 1024;
|
|
4174
|
+
const sizes = ["B", "KB", "MB", "GB"];
|
|
4175
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
4176
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + " " + sizes[i];
|
|
4177
|
+
}
|
|
4178
|
+
showError(message) {
|
|
4179
|
+
this.errorMessage.set(message);
|
|
4180
|
+
this.error.emit(message);
|
|
4181
|
+
this.isUploading.set(false);
|
|
4182
|
+
this.uploadProgress.set(0);
|
|
4183
|
+
// Auto-clear après 5 secondes
|
|
4184
|
+
setTimeout(() => {
|
|
4185
|
+
this.clearError();
|
|
4186
|
+
}, 5000);
|
|
4187
|
+
}
|
|
4188
|
+
clearError() {
|
|
4189
|
+
this.errorMessage.set(null);
|
|
4190
|
+
}
|
|
4191
|
+
clearPreview() {
|
|
4192
|
+
this.previewImage.set(null);
|
|
4193
|
+
this.previewInfo.set("");
|
|
4194
|
+
}
|
|
4195
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: TiptapImageUploadComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
4196
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.0", type: TiptapImageUploadComponent, isStandalone: true, selector: "tiptap-image-upload", inputs: { config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { imageSelected: "imageSelected", error: "error" }, ngImport: i0, template: `
|
|
4197
|
+
<div class="image-upload-container">
|
|
4198
|
+
<!-- Bouton d'upload -->
|
|
4199
|
+
<tiptap-button
|
|
4200
|
+
icon="image"
|
|
4201
|
+
title="Ajouter une image"
|
|
4202
|
+
[disabled]="isUploading()"
|
|
4203
|
+
(onClick)="triggerFileInput()"
|
|
4204
|
+
/>
|
|
4205
|
+
|
|
4206
|
+
<!-- Input file caché -->
|
|
4207
|
+
<input
|
|
4208
|
+
#fileInput
|
|
4209
|
+
type="file"
|
|
4210
|
+
[accept]="acceptedTypes()"
|
|
4211
|
+
[multiple]="config().multiple"
|
|
4212
|
+
(change)="onFileSelected($event)"
|
|
4213
|
+
style="display: none;"
|
|
4214
|
+
/>
|
|
4215
|
+
|
|
4216
|
+
<!-- Zone de drag & drop -->
|
|
4217
|
+
@if (config().enableDragDrop && isDragOver()) {
|
|
3455
4218
|
<div
|
|
3456
|
-
class="
|
|
3457
|
-
|
|
3458
|
-
(
|
|
3459
|
-
(
|
|
4219
|
+
class="drag-overlay"
|
|
4220
|
+
(dragover)="onDragOver($event)"
|
|
4221
|
+
(drop)="onDrop($event)"
|
|
4222
|
+
(dragleave)="onDragLeave($event)"
|
|
3460
4223
|
>
|
|
3461
|
-
<div class="
|
|
3462
|
-
<span class="material-symbols-outlined">
|
|
4224
|
+
<div class="drag-content">
|
|
4225
|
+
<span class="material-symbols-outlined">cloud_upload</span>
|
|
4226
|
+
<p>Déposez votre image ici</p>
|
|
3463
4227
|
</div>
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
4228
|
+
</div>
|
|
4229
|
+
}
|
|
4230
|
+
|
|
4231
|
+
<!-- Barre de progression -->
|
|
4232
|
+
@if (isUploading() && uploadProgress() > 0) {
|
|
4233
|
+
<div class="upload-progress">
|
|
4234
|
+
<div class="progress-bar">
|
|
4235
|
+
<div class="progress-fill" [style.width.%]="uploadProgress()"></div>
|
|
3467
4236
|
</div>
|
|
4237
|
+
<div class="progress-text">{{ uploadProgress() }}%</div>
|
|
4238
|
+
</div>
|
|
4239
|
+
}
|
|
4240
|
+
|
|
4241
|
+
<!-- Prévisualisation -->
|
|
4242
|
+
@if (config().showPreview && previewImage()) {
|
|
4243
|
+
<div class="image-preview">
|
|
4244
|
+
<img [src]="previewImage()" alt="Prévisualisation" />
|
|
4245
|
+
<div class="preview-info">
|
|
4246
|
+
<span>{{ previewInfo() }}</span>
|
|
4247
|
+
</div>
|
|
4248
|
+
<button
|
|
4249
|
+
class="preview-close"
|
|
4250
|
+
(click)="clearPreview()"
|
|
4251
|
+
title="Fermer la prévisualisation"
|
|
4252
|
+
>
|
|
4253
|
+
<span class="material-symbols-outlined">close</span>
|
|
4254
|
+
</button>
|
|
4255
|
+
</div>
|
|
4256
|
+
}
|
|
4257
|
+
|
|
4258
|
+
<!-- Messages d'erreur -->
|
|
4259
|
+
@if (errorMessage()) {
|
|
4260
|
+
<div class="error-message">
|
|
4261
|
+
<span class="material-symbols-outlined">error</span>
|
|
4262
|
+
{{ errorMessage() }}
|
|
3468
4263
|
</div>
|
|
3469
4264
|
}
|
|
3470
4265
|
</div>
|
|
3471
|
-
`, isInline: true, styles: [".
|
|
4266
|
+
`, isInline: true, styles: [".image-upload-container{position:relative;display:inline-block}.drag-overlay{position:fixed;inset:0;background:#3182ce1a;border:2px dashed #3182ce;border-radius:6px;display:flex;align-items:center;justify-content:center;z-index:1000;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px)}.drag-content{text-align:center;color:#3182ce;font-weight:600}.drag-content .material-symbols-outlined{font-size:48px;margin-bottom:16px}.drag-content p{margin:0;font-size:18px}.upload-progress{position:absolute;top:100%;left:0;background:#fff;border:1px solid #e2e8f0;border-radius:8px;padding:12px;margin-top:8px;z-index:100;min-width:200px;box-shadow:0 4px 12px #00000026}.progress-bar{width:100%;height:6px;background:#e2e8f0;border-radius:3px;overflow:hidden;margin-bottom:8px}.progress-fill{height:100%;background:#3182ce;border-radius:3px;transition:width .3s ease}.progress-text{font-size:12px;color:#4a5568;text-align:center}.image-preview{position:absolute;top:100%;left:0;background:#fff;border:1px solid #e2e8f0;border-radius:8px;box-shadow:0 4px 12px #00000026;padding:8px;margin-top:8px;z-index:100;min-width:200px}.image-preview img{max-width:200px;max-height:150px;border-radius:4px;display:block}.preview-info{margin-top:8px;font-size:11px;color:#718096;text-align:center}.preview-close{position:absolute;top:4px;right:4px;background:#000000b3;color:#fff;border:none;border-radius:50%;width:24px;height:24px;cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:14px}.preview-close:hover{background:#000000e6}.error-message{position:absolute;top:100%;left:0;background:#fed7d7;color:#c53030;border:1px solid #feb2b2;border-radius:6px;padding:8px 12px;margin-top:8px;font-size:12px;display:flex;align-items:center;gap:6px;z-index:100;min-width:200px}.error-message .material-symbols-outlined{font-size:16px}\n"], dependencies: [{ kind: "component", type: TiptapButtonComponent, selector: "tiptap-button", inputs: ["icon", "title", "active", "disabled", "variant", "size", "iconSize"], outputs: ["onClick"] }] }); }
|
|
3472
4267
|
}
|
|
3473
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type:
|
|
4268
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: TiptapImageUploadComponent, decorators: [{
|
|
3474
4269
|
type: Component,
|
|
3475
|
-
args: [{ selector: "tiptap-
|
|
3476
|
-
<div
|
|
3477
|
-
|
|
4270
|
+
args: [{ selector: "tiptap-image-upload", standalone: true, imports: [TiptapButtonComponent], template: `
|
|
4271
|
+
<div class="image-upload-container">
|
|
4272
|
+
<!-- Bouton d'upload -->
|
|
4273
|
+
<tiptap-button
|
|
4274
|
+
icon="image"
|
|
4275
|
+
title="Ajouter une image"
|
|
4276
|
+
[disabled]="isUploading()"
|
|
4277
|
+
(onClick)="triggerFileInput()"
|
|
4278
|
+
/>
|
|
4279
|
+
|
|
4280
|
+
<!-- Input file caché -->
|
|
4281
|
+
<input
|
|
4282
|
+
#fileInput
|
|
4283
|
+
type="file"
|
|
4284
|
+
[accept]="acceptedTypes()"
|
|
4285
|
+
[multiple]="config().multiple"
|
|
4286
|
+
(change)="onFileSelected($event)"
|
|
4287
|
+
style="display: none;"
|
|
4288
|
+
/>
|
|
4289
|
+
|
|
4290
|
+
<!-- Zone de drag & drop -->
|
|
4291
|
+
@if (config().enableDragDrop && isDragOver()) {
|
|
3478
4292
|
<div
|
|
3479
|
-
class="
|
|
3480
|
-
|
|
3481
|
-
(
|
|
3482
|
-
(
|
|
4293
|
+
class="drag-overlay"
|
|
4294
|
+
(dragover)="onDragOver($event)"
|
|
4295
|
+
(drop)="onDrop($event)"
|
|
4296
|
+
(dragleave)="onDragLeave($event)"
|
|
3483
4297
|
>
|
|
3484
|
-
<div class="
|
|
3485
|
-
<span class="material-symbols-outlined">
|
|
4298
|
+
<div class="drag-content">
|
|
4299
|
+
<span class="material-symbols-outlined">cloud_upload</span>
|
|
4300
|
+
<p>Déposez votre image ici</p>
|
|
3486
4301
|
</div>
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
4302
|
+
</div>
|
|
4303
|
+
}
|
|
4304
|
+
|
|
4305
|
+
<!-- Barre de progression -->
|
|
4306
|
+
@if (isUploading() && uploadProgress() > 0) {
|
|
4307
|
+
<div class="upload-progress">
|
|
4308
|
+
<div class="progress-bar">
|
|
4309
|
+
<div class="progress-fill" [style.width.%]="uploadProgress()"></div>
|
|
4310
|
+
</div>
|
|
4311
|
+
<div class="progress-text">{{ uploadProgress() }}%</div>
|
|
4312
|
+
</div>
|
|
4313
|
+
}
|
|
4314
|
+
|
|
4315
|
+
<!-- Prévisualisation -->
|
|
4316
|
+
@if (config().showPreview && previewImage()) {
|
|
4317
|
+
<div class="image-preview">
|
|
4318
|
+
<img [src]="previewImage()" alt="Prévisualisation" />
|
|
4319
|
+
<div class="preview-info">
|
|
4320
|
+
<span>{{ previewInfo() }}</span>
|
|
3490
4321
|
</div>
|
|
4322
|
+
<button
|
|
4323
|
+
class="preview-close"
|
|
4324
|
+
(click)="clearPreview()"
|
|
4325
|
+
title="Fermer la prévisualisation"
|
|
4326
|
+
>
|
|
4327
|
+
<span class="material-symbols-outlined">close</span>
|
|
4328
|
+
</button>
|
|
4329
|
+
</div>
|
|
4330
|
+
}
|
|
4331
|
+
|
|
4332
|
+
<!-- Messages d'erreur -->
|
|
4333
|
+
@if (errorMessage()) {
|
|
4334
|
+
<div class="error-message">
|
|
4335
|
+
<span class="material-symbols-outlined">error</span>
|
|
4336
|
+
{{ errorMessage() }}
|
|
3491
4337
|
</div>
|
|
3492
4338
|
}
|
|
3493
4339
|
</div>
|
|
3494
|
-
`, styles: [".
|
|
3495
|
-
}]
|
|
3496
|
-
type: ViewChild,
|
|
3497
|
-
args: ["menuRef", { static: false }]
|
|
3498
|
-
}] } });
|
|
4340
|
+
`, styles: [".image-upload-container{position:relative;display:inline-block}.drag-overlay{position:fixed;inset:0;background:#3182ce1a;border:2px dashed #3182ce;border-radius:6px;display:flex;align-items:center;justify-content:center;z-index:1000;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px)}.drag-content{text-align:center;color:#3182ce;font-weight:600}.drag-content .material-symbols-outlined{font-size:48px;margin-bottom:16px}.drag-content p{margin:0;font-size:18px}.upload-progress{position:absolute;top:100%;left:0;background:#fff;border:1px solid #e2e8f0;border-radius:8px;padding:12px;margin-top:8px;z-index:100;min-width:200px;box-shadow:0 4px 12px #00000026}.progress-bar{width:100%;height:6px;background:#e2e8f0;border-radius:3px;overflow:hidden;margin-bottom:8px}.progress-fill{height:100%;background:#3182ce;border-radius:3px;transition:width .3s ease}.progress-text{font-size:12px;color:#4a5568;text-align:center}.image-preview{position:absolute;top:100%;left:0;background:#fff;border:1px solid #e2e8f0;border-radius:8px;box-shadow:0 4px 12px #00000026;padding:8px;margin-top:8px;z-index:100;min-width:200px}.image-preview img{max-width:200px;max-height:150px;border-radius:4px;display:block}.preview-info{margin-top:8px;font-size:11px;color:#718096;text-align:center}.preview-close{position:absolute;top:4px;right:4px;background:#000000b3;color:#fff;border:none;border-radius:50%;width:24px;height:24px;cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:14px}.preview-close:hover{background:#000000e6}.error-message{position:absolute;top:100%;left:0;background:#fed7d7;color:#c53030;border:1px solid #feb2b2;border-radius:6px;padding:8px 12px;margin-top:8px;font-size:12px;display:flex;align-items:center;gap:6px;z-index:100;min-width:200px}.error-message .material-symbols-outlined{font-size:16px}\n"] }]
|
|
4341
|
+
}] });
|
|
3499
4342
|
|
|
3500
4343
|
class NoopValueAccessorDirective {
|
|
3501
4344
|
writeValue(obj) { }
|
|
@@ -3547,8 +4390,10 @@ const DEFAULT_TOOLBAR_CONFIG = {
|
|
|
3547
4390
|
link: true,
|
|
3548
4391
|
image: true,
|
|
3549
4392
|
horizontalRule: true,
|
|
4393
|
+
table: true,
|
|
3550
4394
|
undo: true,
|
|
3551
4395
|
redo: true,
|
|
4396
|
+
clear: false, // Désactivé par défaut (opt-in)
|
|
3552
4397
|
separator: true,
|
|
3553
4398
|
};
|
|
3554
4399
|
// Configuration par défaut du bubble menu
|
|
@@ -3574,6 +4419,23 @@ const DEFAULT_IMAGE_BUBBLE_MENU_CONFIG = {
|
|
|
3574
4419
|
deleteImage: true,
|
|
3575
4420
|
separator: true,
|
|
3576
4421
|
};
|
|
4422
|
+
// Configuration par défaut du menu de table
|
|
4423
|
+
const DEFAULT_TABLE_MENU_CONFIG = {
|
|
4424
|
+
addRowBefore: true,
|
|
4425
|
+
addRowAfter: true,
|
|
4426
|
+
deleteRow: true,
|
|
4427
|
+
addColumnBefore: true,
|
|
4428
|
+
addColumnAfter: true,
|
|
4429
|
+
deleteColumn: true,
|
|
4430
|
+
toggleHeaderRow: true,
|
|
4431
|
+
toggleHeaderColumn: true,
|
|
4432
|
+
deleteTable: true,
|
|
4433
|
+
separator: true,
|
|
4434
|
+
};
|
|
4435
|
+
const DEFAULT_CELL_MENU_CONFIG = {
|
|
4436
|
+
mergeCells: true,
|
|
4437
|
+
splitCell: true,
|
|
4438
|
+
};
|
|
3577
4439
|
class AngularTiptapEditorComponent {
|
|
3578
4440
|
constructor() {
|
|
3579
4441
|
// Nouveaux inputs avec signal
|
|
@@ -3609,7 +4471,8 @@ class AngularTiptapEditorComponent {
|
|
|
3609
4471
|
this.editorElement = viewChild.required("editorElement");
|
|
3610
4472
|
// Signals pour l'état interne
|
|
3611
4473
|
this.editor = signal(null);
|
|
3612
|
-
this.
|
|
4474
|
+
this.characterCount = signal(0);
|
|
4475
|
+
this.wordCount = signal(0);
|
|
3613
4476
|
this.isDragOver = signal(false);
|
|
3614
4477
|
this.editorFullyInitialized = signal(false);
|
|
3615
4478
|
// Computed pour les états de l'éditeur
|
|
@@ -3626,6 +4489,23 @@ class AngularTiptapEditorComponent {
|
|
|
3626
4489
|
this.imageBubbleMenuConfig = computed(() => Object.keys(this.imageBubbleMenu()).length === 0
|
|
3627
4490
|
? DEFAULT_IMAGE_BUBBLE_MENU_CONFIG
|
|
3628
4491
|
: { ...DEFAULT_IMAGE_BUBBLE_MENU_CONFIG, ...this.imageBubbleMenu() });
|
|
4492
|
+
// Computed pour la configuration du bubble menu table
|
|
4493
|
+
this.tableBubbleMenuConfig = computed(() => ({
|
|
4494
|
+
addRowBefore: true,
|
|
4495
|
+
addRowAfter: true,
|
|
4496
|
+
deleteRow: true,
|
|
4497
|
+
addColumnBefore: true,
|
|
4498
|
+
addColumnAfter: true,
|
|
4499
|
+
deleteColumn: true,
|
|
4500
|
+
deleteTable: true,
|
|
4501
|
+
toggleHeaderRow: true,
|
|
4502
|
+
toggleHeaderColumn: true,
|
|
4503
|
+
}));
|
|
4504
|
+
// Computed pour la configuration du menu de cellules
|
|
4505
|
+
this.cellBubbleMenuConfig = computed(() => ({
|
|
4506
|
+
mergeCells: true,
|
|
4507
|
+
splitCell: true,
|
|
4508
|
+
}));
|
|
3629
4509
|
// Computed pour la configuration de l'upload d'images
|
|
3630
4510
|
this.imageUploadConfig = computed(() => ({
|
|
3631
4511
|
maxSize: 5,
|
|
@@ -3689,6 +4569,13 @@ class AngularTiptapEditorComponent {
|
|
|
3689
4569
|
currentEditor.setEditable(isEditable);
|
|
3690
4570
|
}
|
|
3691
4571
|
});
|
|
4572
|
+
// Effect pour la détection du survol des tables
|
|
4573
|
+
effect(() => {
|
|
4574
|
+
const currentEditor = this.editor();
|
|
4575
|
+
if (!currentEditor)
|
|
4576
|
+
return;
|
|
4577
|
+
// Table hover detection supprimée car remplacée par le menu bubble
|
|
4578
|
+
});
|
|
3692
4579
|
}
|
|
3693
4580
|
ngAfterViewInit() {
|
|
3694
4581
|
// La vue est déjà complètement initialisée dans ngAfterViewInit
|
|
@@ -3739,6 +4626,7 @@ class AngularTiptapEditorComponent {
|
|
|
3739
4626
|
uploadProgress: () => this.imageService.uploadProgress(),
|
|
3740
4627
|
uploadMessage: () => this.imageService.uploadMessage(),
|
|
3741
4628
|
}),
|
|
4629
|
+
TableExtension,
|
|
3742
4630
|
];
|
|
3743
4631
|
// Ajouter l'extension Office Paste si activée
|
|
3744
4632
|
if (this.enableOfficePaste()) {
|
|
@@ -3798,10 +4686,8 @@ class AngularTiptapEditorComponent {
|
|
|
3798
4686
|
updateCharacterCount(editor) {
|
|
3799
4687
|
if (this.showCharacterCount() && editor.storage["characterCount"]) {
|
|
3800
4688
|
const storage = editor.storage["characterCount"];
|
|
3801
|
-
this.
|
|
3802
|
-
|
|
3803
|
-
words: storage.words(),
|
|
3804
|
-
});
|
|
4689
|
+
this.characterCount.set(storage.characters());
|
|
4690
|
+
this.wordCount.set(storage.words());
|
|
3805
4691
|
}
|
|
3806
4692
|
}
|
|
3807
4693
|
// Méthodes pour l'upload d'images
|
|
@@ -3880,7 +4766,12 @@ class AngularTiptapEditorComponent {
|
|
|
3880
4766
|
this.editor()?.commands.blur();
|
|
3881
4767
|
}
|
|
3882
4768
|
clearContent() {
|
|
3883
|
-
this.editor()
|
|
4769
|
+
const editor = this.editor();
|
|
4770
|
+
if (editor) {
|
|
4771
|
+
editor.commands.clearContent();
|
|
4772
|
+
// Mettre à jour les compteurs après avoir vidé le contenu
|
|
4773
|
+
this.updateCharacterCount(editor);
|
|
4774
|
+
}
|
|
3884
4775
|
}
|
|
3885
4776
|
setupFormControlSubscription() {
|
|
3886
4777
|
const control = this.ngControl?.control;
|
|
@@ -3975,19 +4866,40 @@ class AngularTiptapEditorComponent {
|
|
|
3975
4866
|
></tiptap-slash-commands>
|
|
3976
4867
|
}
|
|
3977
4868
|
|
|
4869
|
+
<!-- Table Menu -->
|
|
4870
|
+
@if (editor()) {
|
|
4871
|
+
<tiptap-table-bubble-menu
|
|
4872
|
+
[editor]="editor()!"
|
|
4873
|
+
[config]="tableBubbleMenuConfig()"
|
|
4874
|
+
[style.display]="editorFullyInitialized() ? 'block' : 'none'"
|
|
4875
|
+
></tiptap-table-bubble-menu>
|
|
4876
|
+
}
|
|
4877
|
+
|
|
4878
|
+
<!-- Cell Menu -->
|
|
4879
|
+
@if (editor()) {
|
|
4880
|
+
<tiptap-cell-bubble-menu
|
|
4881
|
+
[editor]="editor()!"
|
|
4882
|
+
[config]="cellBubbleMenuConfig()"
|
|
4883
|
+
[style.display]="editorFullyInitialized() ? 'block' : 'none'"
|
|
4884
|
+
></tiptap-cell-bubble-menu>
|
|
4885
|
+
}
|
|
4886
|
+
|
|
4887
|
+
<!-- Table Edit Button - Supprimé car remplacé par le menu bubble -->
|
|
4888
|
+
|
|
3978
4889
|
<!-- Compteur de caractères -->
|
|
3979
|
-
@if (showCharacterCount()
|
|
4890
|
+
@if (showCharacterCount()) {
|
|
3980
4891
|
<div class="character-count">
|
|
3981
|
-
{{
|
|
3982
|
-
{{ i18nService.editor().
|
|
3983
|
-
{{
|
|
3984
|
-
(
|
|
4892
|
+
{{ characterCount() }}
|
|
4893
|
+
{{ i18nService.editor().character
|
|
4894
|
+
}}{{ characterCount() > 1 ? "s" : "" }}, {{ wordCount() }}
|
|
4895
|
+
{{ i18nService.editor().word }}{{ wordCount() > 1 ? "s" : "" }}
|
|
4896
|
+
@if (maxCharacters()) { /
|
|
3985
4897
|
{{ maxCharacters() }}
|
|
3986
4898
|
}
|
|
3987
4899
|
</div>
|
|
3988
4900
|
}
|
|
3989
4901
|
</div>
|
|
3990
|
-
`, isInline: true, styles: [".tiptap-editor{border:2px solid #e2e8f0;border-radius:8px;background:#fff;
|
|
4902
|
+
`, isInline: true, styles: [".tiptap-editor{border:2px solid #e2e8f0;border-radius:8px;background:#fff;overflow:hidden;transition:border-color .2s ease}.tiptap-editor:focus-within{border-color:#3182ce}.tiptap-content{padding:16px;min-height:var(--editor-min-height, 200px);height:var(--editor-height, auto);max-height:var(--editor-max-height, none);overflow-y:var(--editor-overflow, visible);outline:none;position:relative}.tiptap-content.drag-over{background:#f0f8ff;border:2px dashed #3182ce}.character-count{padding:8px 16px;font-size:12px;color:#718096;text-align:right;border-top:1px solid #e2e8f0;background:#f8f9fa}.image-upload-container{position:relative;display:inline-block}:host ::ng-deep .ProseMirror{outline:none;line-height:1.6;color:#2d3748;min-height:100%;height:100%;word-wrap:break-word;overflow-wrap:break-word}:host ::ng-deep .ProseMirror h1{font-size:2em;font-weight:700;margin-top:0;margin-bottom:.5em}:host ::ng-deep .ProseMirror h2{font-size:1.5em;font-weight:700;margin-top:1em;margin-bottom:.5em}:host ::ng-deep .ProseMirror h3{font-size:1.25em;font-weight:700;margin-top:1em;margin-bottom:.5em}:host ::ng-deep .ProseMirror p{margin:.5em 0}:host ::ng-deep .ProseMirror ul,:host ::ng-deep .ProseMirror ol{padding-left:2em;margin:.5em 0}:host ::ng-deep .ProseMirror blockquote{border-left:4px solid #e2e8f0;margin:1em 0;font-style:italic;background:#f8f9fa;padding:.5em 1em;border-radius:0 4px 4px 0}:host ::ng-deep .ProseMirror code{background:#f1f5f9;padding:.2em .4em;border-radius:3px;font-family:Monaco,Consolas,monospace;font-size:.9em}:host ::ng-deep .ProseMirror pre{background:#1a202c;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;margin:1em 0}:host ::ng-deep .ProseMirror pre code{background:none;color:inherit;padding:0}:host ::ng-deep .ProseMirror p.is-editor-empty:first-child:before{content:attr(data-placeholder);color:#a0aec0;pointer-events:none;float:left;height:0}:host ::ng-deep .ProseMirror[contenteditable=false]{pointer-events:none}:host ::ng-deep .ProseMirror[contenteditable=false] img{cursor:default;pointer-events:none}:host ::ng-deep .ProseMirror[contenteditable=false] img:hover{transform:none;box-shadow:0 2px 8px #0000001a}:host ::ng-deep .ProseMirror[contenteditable=false] img.ProseMirror-selectednode{outline:none}:host ::ng-deep .ProseMirror img{position:relative;display:inline-block;max-width:100%;height:auto;cursor:pointer;transition:all .2s ease;border:2px solid transparent;border-radius:8px}:host ::ng-deep .ProseMirror img:hover{border-color:#e2e8f0;box-shadow:0 2px 4px #0000001a}:host ::ng-deep .ProseMirror img.ProseMirror-selectednode{border-color:#3182ce;box-shadow:0 0 0 3px #3182ce1a;transition:all .2s ease}:host ::ng-deep .ProseMirror .tiptap-image{max-width:100%;height:auto;border-radius:16px;box-shadow:0 4px 20px #00000014;margin:.5em 0;cursor:pointer;transition:all .3s cubic-bezier(.4,0,.2,1);display:block;filter:brightness(1) contrast(1)}:host ::ng-deep .ProseMirror .tiptap-image:hover{box-shadow:0 8px 30px #0000001f;filter:brightness(1.02) contrast(1.02)}:host ::ng-deep .ProseMirror .tiptap-image.ProseMirror-selectednode{outline:2px solid #6366f1;outline-offset:2px;border-radius:16px;box-shadow:0 0 0 4px #6366f11a}:host ::ng-deep .image-container{margin:.5em 0;text-align:center;border-radius:16px;overflow:hidden;transition:all .3s cubic-bezier(.4,0,.2,1)}:host ::ng-deep .image-container.image-align-left{text-align:left}:host ::ng-deep .image-container.image-align-center{text-align:center}:host ::ng-deep .image-container.image-align-right{text-align:right}:host ::ng-deep .image-container img{display:inline-block;max-width:100%;height:auto;border-radius:16px}:host ::ng-deep .resizable-image-container{position:relative;display:inline-block;margin:.5em 0}:host ::ng-deep .resize-controls{position:absolute;inset:0;pointer-events:none;z-index:1000}:host ::ng-deep .resize-handle{position:absolute;width:12px;height:12px;background:#3b82f6;border:2px solid white;border-radius:50%;pointer-events:all;cursor:pointer;z-index:1001;transition:all .15s ease;box-shadow:0 2px 6px #0003}:host ::ng-deep .resize-handle:hover{background:#2563eb;box-shadow:0 3px 8px #0000004d}:host ::ng-deep .resize-handle:active{background:#1d4ed8}:host ::ng-deep .resize-handle-n:hover,:host ::ng-deep .resize-handle-s:hover{transform:translate(-50%) scale(1.2)}:host ::ng-deep .resize-handle-w:hover,:host ::ng-deep .resize-handle-e:hover{transform:translateY(-50%) scale(1.2)}:host ::ng-deep .resize-handle-n:active,:host ::ng-deep .resize-handle-s:active{transform:translate(-50%) scale(.9)}:host ::ng-deep .resize-handle-w:active,:host ::ng-deep .resize-handle-e:active{transform:translateY(-50%) scale(.9)}:host ::ng-deep .resize-handle-nw:hover,:host ::ng-deep .resize-handle-ne:hover,:host ::ng-deep .resize-handle-sw:hover,:host ::ng-deep .resize-handle-se:hover{transform:scale(1.2)}:host ::ng-deep .resize-handle-nw:active,:host ::ng-deep .resize-handle-ne:active,:host ::ng-deep .resize-handle-sw:active,:host ::ng-deep .resize-handle-se:active{transform:scale(.9)}:host ::ng-deep .resize-handle-nw{top:0;left:-6px;cursor:nw-resize}:host ::ng-deep .resize-handle-n{top:0;left:50%;transform:translate(-50%);cursor:n-resize}:host ::ng-deep .resize-handle-ne{top:0;right:-6px;cursor:ne-resize}:host ::ng-deep .resize-handle-w{top:50%;left:-6px;transform:translateY(-50%);cursor:w-resize}:host ::ng-deep .resize-handle-e{top:50%;right:-6px;transform:translateY(-50%);cursor:e-resize}:host ::ng-deep .resize-handle-sw{bottom:0;left:-6px;cursor:sw-resize}:host ::ng-deep .resize-handle-s{bottom:0;left:50%;transform:translate(-50%);cursor:s-resize}:host ::ng-deep .resize-handle-se{bottom:0;right:-6px;cursor:se-resize}:host ::ng-deep body.resizing{-webkit-user-select:none;user-select:none;cursor:crosshair}:host ::ng-deep body.resizing .ProseMirror{pointer-events:none}:host ::ng-deep body.resizing .ProseMirror .tiptap-image{pointer-events:none}:host ::ng-deep .image-size-info{position:absolute;bottom:-20px;left:50%;transform:translate(-50%);background:#000c;color:#fff;padding:2px 6px;border-radius:3px;font-size:11px;white-space:nowrap;opacity:0;transition:opacity .2s ease}:host ::ng-deep .image-container:hover .image-size-info{opacity:1}:host ::ng-deep .ProseMirror table{border-collapse:separate;border-spacing:0;margin:0;table-layout:fixed;width:100%;border-radius:8px;overflow:hidden}:host ::ng-deep .ProseMirror table td,:host ::ng-deep .ProseMirror table th{border:none;border-right:1px solid #e2e8f0;border-bottom:1px solid #e2e8f0;box-sizing:border-box;min-width:1em;padding:8px 12px;position:relative;vertical-align:top;background:#fff}:host ::ng-deep .ProseMirror table td:first-child,:host ::ng-deep .ProseMirror table th:first-child{border-left:1px solid #e2e8f0}:host ::ng-deep .ProseMirror table tr:first-child td,:host ::ng-deep .ProseMirror table tr:first-child th{border-top:1px solid #e2e8f0}:host ::ng-deep .ProseMirror table tr:first-child th:first-child{border-top-left-radius:8px}:host ::ng-deep .ProseMirror table tr:first-child th:last-child{border-top-right-radius:8px}:host ::ng-deep .ProseMirror table tr:last-child td:first-child{border-bottom-left-radius:8px}:host ::ng-deep .ProseMirror table tr:last-child td:last-child{border-bottom-right-radius:8px}:host ::ng-deep .ProseMirror table th{background:#f8f9fa;font-weight:600;color:#374151}:host ::ng-deep .ProseMirror table .selectedCell:after{background:#c8c8ff66;content:\"\";inset:0;pointer-events:none;position:absolute;z-index:2}:host ::ng-deep .ProseMirror table .column-resize-handle{position:absolute;right:-2px;top:0;bottom:0;width:4px;background-color:#6366f1;opacity:0;transition:opacity .2s ease}:host ::ng-deep .ProseMirror table:hover .column-resize-handle{opacity:1}:host ::ng-deep .ProseMirror table .column-resize-handle:hover{background-color:#4f46e5}:host ::ng-deep .ProseMirror .tableWrapper{overflow-x:auto;margin:1em 0;border-radius:8px}:host ::ng-deep .ProseMirror .tableWrapper table{margin:0;border-radius:8px;min-width:600px;overflow:hidden}:host ::ng-deep .ProseMirror table p{margin:0}:host ::ng-deep .ProseMirror table .selectedCell{background-color:#6366f11a}:host ::ng-deep .ProseMirror table th{background-color:#f8f9fa;font-weight:600;color:#374151;text-align:left}:host ::ng-deep .ProseMirror table tbody tr:nth-child(2n){background-color:#fafbfc}:host ::ng-deep .ProseMirror table tbody tr:hover{background-color:#f1f5f9}\n"], dependencies: [{ kind: "component", type: TiptapToolbarComponent, selector: "tiptap-toolbar", inputs: ["editor", "config"], outputs: ["imageUploaded", "imageError"] }, { kind: "component", type: TiptapImageUploadComponent, selector: "tiptap-image-upload", inputs: ["config"], outputs: ["imageSelected", "error"] }, { kind: "component", type: TiptapBubbleMenuComponent, selector: "tiptap-bubble-menu", inputs: ["editor", "config"] }, { kind: "component", type: TiptapImageBubbleMenuComponent, selector: "tiptap-image-bubble-menu", inputs: ["editor", "config"] }, { kind: "component", type: TiptapTableBubbleMenuComponent, selector: "tiptap-table-bubble-menu", inputs: ["editor", "config"] }, { kind: "component", type: TiptapCellBubbleMenuComponent, selector: "tiptap-cell-bubble-menu", inputs: ["editor", "config"] }, { kind: "component", type: TiptapSlashCommandsComponent, selector: "tiptap-slash-commands", inputs: ["editor", "config"], outputs: ["imageUploadRequested"] }] }); }
|
|
3991
4903
|
}
|
|
3992
4904
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: AngularTiptapEditorComponent, decorators: [{
|
|
3993
4905
|
type: Component,
|
|
@@ -3996,6 +4908,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.0", ngImpor
|
|
|
3996
4908
|
TiptapImageUploadComponent,
|
|
3997
4909
|
TiptapBubbleMenuComponent,
|
|
3998
4910
|
TiptapImageBubbleMenuComponent,
|
|
4911
|
+
TiptapTableBubbleMenuComponent,
|
|
4912
|
+
TiptapCellBubbleMenuComponent,
|
|
3999
4913
|
TiptapSlashCommandsComponent,
|
|
4000
4914
|
], template: `
|
|
4001
4915
|
<div class="tiptap-editor">
|
|
@@ -4050,19 +4964,40 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.0", ngImpor
|
|
|
4050
4964
|
></tiptap-slash-commands>
|
|
4051
4965
|
}
|
|
4052
4966
|
|
|
4967
|
+
<!-- Table Menu -->
|
|
4968
|
+
@if (editor()) {
|
|
4969
|
+
<tiptap-table-bubble-menu
|
|
4970
|
+
[editor]="editor()!"
|
|
4971
|
+
[config]="tableBubbleMenuConfig()"
|
|
4972
|
+
[style.display]="editorFullyInitialized() ? 'block' : 'none'"
|
|
4973
|
+
></tiptap-table-bubble-menu>
|
|
4974
|
+
}
|
|
4975
|
+
|
|
4976
|
+
<!-- Cell Menu -->
|
|
4977
|
+
@if (editor()) {
|
|
4978
|
+
<tiptap-cell-bubble-menu
|
|
4979
|
+
[editor]="editor()!"
|
|
4980
|
+
[config]="cellBubbleMenuConfig()"
|
|
4981
|
+
[style.display]="editorFullyInitialized() ? 'block' : 'none'"
|
|
4982
|
+
></tiptap-cell-bubble-menu>
|
|
4983
|
+
}
|
|
4984
|
+
|
|
4985
|
+
<!-- Table Edit Button - Supprimé car remplacé par le menu bubble -->
|
|
4986
|
+
|
|
4053
4987
|
<!-- Compteur de caractères -->
|
|
4054
|
-
@if (showCharacterCount()
|
|
4988
|
+
@if (showCharacterCount()) {
|
|
4055
4989
|
<div class="character-count">
|
|
4056
|
-
{{
|
|
4057
|
-
{{ i18nService.editor().
|
|
4058
|
-
{{
|
|
4059
|
-
(
|
|
4990
|
+
{{ characterCount() }}
|
|
4991
|
+
{{ i18nService.editor().character
|
|
4992
|
+
}}{{ characterCount() > 1 ? "s" : "" }}, {{ wordCount() }}
|
|
4993
|
+
{{ i18nService.editor().word }}{{ wordCount() > 1 ? "s" : "" }}
|
|
4994
|
+
@if (maxCharacters()) { /
|
|
4060
4995
|
{{ maxCharacters() }}
|
|
4061
4996
|
}
|
|
4062
4997
|
</div>
|
|
4063
4998
|
}
|
|
4064
4999
|
</div>
|
|
4065
|
-
`, styles: [".tiptap-editor{border:2px solid #e2e8f0;border-radius:8px;background:#fff;
|
|
5000
|
+
`, styles: [".tiptap-editor{border:2px solid #e2e8f0;border-radius:8px;background:#fff;overflow:hidden;transition:border-color .2s ease}.tiptap-editor:focus-within{border-color:#3182ce}.tiptap-content{padding:16px;min-height:var(--editor-min-height, 200px);height:var(--editor-height, auto);max-height:var(--editor-max-height, none);overflow-y:var(--editor-overflow, visible);outline:none;position:relative}.tiptap-content.drag-over{background:#f0f8ff;border:2px dashed #3182ce}.character-count{padding:8px 16px;font-size:12px;color:#718096;text-align:right;border-top:1px solid #e2e8f0;background:#f8f9fa}.image-upload-container{position:relative;display:inline-block}:host ::ng-deep .ProseMirror{outline:none;line-height:1.6;color:#2d3748;min-height:100%;height:100%;word-wrap:break-word;overflow-wrap:break-word}:host ::ng-deep .ProseMirror h1{font-size:2em;font-weight:700;margin-top:0;margin-bottom:.5em}:host ::ng-deep .ProseMirror h2{font-size:1.5em;font-weight:700;margin-top:1em;margin-bottom:.5em}:host ::ng-deep .ProseMirror h3{font-size:1.25em;font-weight:700;margin-top:1em;margin-bottom:.5em}:host ::ng-deep .ProseMirror p{margin:.5em 0}:host ::ng-deep .ProseMirror ul,:host ::ng-deep .ProseMirror ol{padding-left:2em;margin:.5em 0}:host ::ng-deep .ProseMirror blockquote{border-left:4px solid #e2e8f0;margin:1em 0;font-style:italic;background:#f8f9fa;padding:.5em 1em;border-radius:0 4px 4px 0}:host ::ng-deep .ProseMirror code{background:#f1f5f9;padding:.2em .4em;border-radius:3px;font-family:Monaco,Consolas,monospace;font-size:.9em}:host ::ng-deep .ProseMirror pre{background:#1a202c;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;margin:1em 0}:host ::ng-deep .ProseMirror pre code{background:none;color:inherit;padding:0}:host ::ng-deep .ProseMirror p.is-editor-empty:first-child:before{content:attr(data-placeholder);color:#a0aec0;pointer-events:none;float:left;height:0}:host ::ng-deep .ProseMirror[contenteditable=false]{pointer-events:none}:host ::ng-deep .ProseMirror[contenteditable=false] img{cursor:default;pointer-events:none}:host ::ng-deep .ProseMirror[contenteditable=false] img:hover{transform:none;box-shadow:0 2px 8px #0000001a}:host ::ng-deep .ProseMirror[contenteditable=false] img.ProseMirror-selectednode{outline:none}:host ::ng-deep .ProseMirror img{position:relative;display:inline-block;max-width:100%;height:auto;cursor:pointer;transition:all .2s ease;border:2px solid transparent;border-radius:8px}:host ::ng-deep .ProseMirror img:hover{border-color:#e2e8f0;box-shadow:0 2px 4px #0000001a}:host ::ng-deep .ProseMirror img.ProseMirror-selectednode{border-color:#3182ce;box-shadow:0 0 0 3px #3182ce1a;transition:all .2s ease}:host ::ng-deep .ProseMirror .tiptap-image{max-width:100%;height:auto;border-radius:16px;box-shadow:0 4px 20px #00000014;margin:.5em 0;cursor:pointer;transition:all .3s cubic-bezier(.4,0,.2,1);display:block;filter:brightness(1) contrast(1)}:host ::ng-deep .ProseMirror .tiptap-image:hover{box-shadow:0 8px 30px #0000001f;filter:brightness(1.02) contrast(1.02)}:host ::ng-deep .ProseMirror .tiptap-image.ProseMirror-selectednode{outline:2px solid #6366f1;outline-offset:2px;border-radius:16px;box-shadow:0 0 0 4px #6366f11a}:host ::ng-deep .image-container{margin:.5em 0;text-align:center;border-radius:16px;overflow:hidden;transition:all .3s cubic-bezier(.4,0,.2,1)}:host ::ng-deep .image-container.image-align-left{text-align:left}:host ::ng-deep .image-container.image-align-center{text-align:center}:host ::ng-deep .image-container.image-align-right{text-align:right}:host ::ng-deep .image-container img{display:inline-block;max-width:100%;height:auto;border-radius:16px}:host ::ng-deep .resizable-image-container{position:relative;display:inline-block;margin:.5em 0}:host ::ng-deep .resize-controls{position:absolute;inset:0;pointer-events:none;z-index:1000}:host ::ng-deep .resize-handle{position:absolute;width:12px;height:12px;background:#3b82f6;border:2px solid white;border-radius:50%;pointer-events:all;cursor:pointer;z-index:1001;transition:all .15s ease;box-shadow:0 2px 6px #0003}:host ::ng-deep .resize-handle:hover{background:#2563eb;box-shadow:0 3px 8px #0000004d}:host ::ng-deep .resize-handle:active{background:#1d4ed8}:host ::ng-deep .resize-handle-n:hover,:host ::ng-deep .resize-handle-s:hover{transform:translate(-50%) scale(1.2)}:host ::ng-deep .resize-handle-w:hover,:host ::ng-deep .resize-handle-e:hover{transform:translateY(-50%) scale(1.2)}:host ::ng-deep .resize-handle-n:active,:host ::ng-deep .resize-handle-s:active{transform:translate(-50%) scale(.9)}:host ::ng-deep .resize-handle-w:active,:host ::ng-deep .resize-handle-e:active{transform:translateY(-50%) scale(.9)}:host ::ng-deep .resize-handle-nw:hover,:host ::ng-deep .resize-handle-ne:hover,:host ::ng-deep .resize-handle-sw:hover,:host ::ng-deep .resize-handle-se:hover{transform:scale(1.2)}:host ::ng-deep .resize-handle-nw:active,:host ::ng-deep .resize-handle-ne:active,:host ::ng-deep .resize-handle-sw:active,:host ::ng-deep .resize-handle-se:active{transform:scale(.9)}:host ::ng-deep .resize-handle-nw{top:0;left:-6px;cursor:nw-resize}:host ::ng-deep .resize-handle-n{top:0;left:50%;transform:translate(-50%);cursor:n-resize}:host ::ng-deep .resize-handle-ne{top:0;right:-6px;cursor:ne-resize}:host ::ng-deep .resize-handle-w{top:50%;left:-6px;transform:translateY(-50%);cursor:w-resize}:host ::ng-deep .resize-handle-e{top:50%;right:-6px;transform:translateY(-50%);cursor:e-resize}:host ::ng-deep .resize-handle-sw{bottom:0;left:-6px;cursor:sw-resize}:host ::ng-deep .resize-handle-s{bottom:0;left:50%;transform:translate(-50%);cursor:s-resize}:host ::ng-deep .resize-handle-se{bottom:0;right:-6px;cursor:se-resize}:host ::ng-deep body.resizing{-webkit-user-select:none;user-select:none;cursor:crosshair}:host ::ng-deep body.resizing .ProseMirror{pointer-events:none}:host ::ng-deep body.resizing .ProseMirror .tiptap-image{pointer-events:none}:host ::ng-deep .image-size-info{position:absolute;bottom:-20px;left:50%;transform:translate(-50%);background:#000c;color:#fff;padding:2px 6px;border-radius:3px;font-size:11px;white-space:nowrap;opacity:0;transition:opacity .2s ease}:host ::ng-deep .image-container:hover .image-size-info{opacity:1}:host ::ng-deep .ProseMirror table{border-collapse:separate;border-spacing:0;margin:0;table-layout:fixed;width:100%;border-radius:8px;overflow:hidden}:host ::ng-deep .ProseMirror table td,:host ::ng-deep .ProseMirror table th{border:none;border-right:1px solid #e2e8f0;border-bottom:1px solid #e2e8f0;box-sizing:border-box;min-width:1em;padding:8px 12px;position:relative;vertical-align:top;background:#fff}:host ::ng-deep .ProseMirror table td:first-child,:host ::ng-deep .ProseMirror table th:first-child{border-left:1px solid #e2e8f0}:host ::ng-deep .ProseMirror table tr:first-child td,:host ::ng-deep .ProseMirror table tr:first-child th{border-top:1px solid #e2e8f0}:host ::ng-deep .ProseMirror table tr:first-child th:first-child{border-top-left-radius:8px}:host ::ng-deep .ProseMirror table tr:first-child th:last-child{border-top-right-radius:8px}:host ::ng-deep .ProseMirror table tr:last-child td:first-child{border-bottom-left-radius:8px}:host ::ng-deep .ProseMirror table tr:last-child td:last-child{border-bottom-right-radius:8px}:host ::ng-deep .ProseMirror table th{background:#f8f9fa;font-weight:600;color:#374151}:host ::ng-deep .ProseMirror table .selectedCell:after{background:#c8c8ff66;content:\"\";inset:0;pointer-events:none;position:absolute;z-index:2}:host ::ng-deep .ProseMirror table .column-resize-handle{position:absolute;right:-2px;top:0;bottom:0;width:4px;background-color:#6366f1;opacity:0;transition:opacity .2s ease}:host ::ng-deep .ProseMirror table:hover .column-resize-handle{opacity:1}:host ::ng-deep .ProseMirror table .column-resize-handle:hover{background-color:#4f46e5}:host ::ng-deep .ProseMirror .tableWrapper{overflow-x:auto;margin:1em 0;border-radius:8px}:host ::ng-deep .ProseMirror .tableWrapper table{margin:0;border-radius:8px;min-width:600px;overflow:hidden}:host ::ng-deep .ProseMirror table p{margin:0}:host ::ng-deep .ProseMirror table .selectedCell{background-color:#6366f11a}:host ::ng-deep .ProseMirror table th{background-color:#f8f9fa;font-weight:600;color:#374151;text-align:left}:host ::ng-deep .ProseMirror table tbody tr:nth-child(2n){background-color:#fafbfc}:host ::ng-deep .ProseMirror table tbody tr:hover{background-color:#f1f5f9}\n"] }]
|
|
4066
5001
|
}], ctorParameters: () => [] });
|
|
4067
5002
|
|
|
4068
5003
|
/**
|
|
@@ -4202,6 +5137,13 @@ function createI18nSlashCommands(i18nService) {
|
|
|
4202
5137
|
keywords: slashCommands.horizontalRule.keywords,
|
|
4203
5138
|
command: (editor) => editor.chain().focus().setHorizontalRule().run(),
|
|
4204
5139
|
},
|
|
5140
|
+
{
|
|
5141
|
+
title: slashCommands.table.title,
|
|
5142
|
+
description: slashCommands.table.description,
|
|
5143
|
+
icon: "table_view",
|
|
5144
|
+
keywords: slashCommands.table.keywords,
|
|
5145
|
+
command: (editor) => editor.chain().focus().insertTable({ rows: 3, cols: 3 }).run(),
|
|
5146
|
+
},
|
|
4205
5147
|
];
|
|
4206
5148
|
}
|
|
4207
5149
|
/**
|
|
@@ -4217,6 +5159,7 @@ const SLASH_COMMAND_KEYS = {
|
|
|
4217
5159
|
code: "code",
|
|
4218
5160
|
image: "image",
|
|
4219
5161
|
horizontalRule: "horizontalRule",
|
|
5162
|
+
table: "table",
|
|
4220
5163
|
};
|
|
4221
5164
|
|
|
4222
5165
|
/*
|
|
@@ -4228,5 +5171,5 @@ const SLASH_COMMAND_KEYS = {
|
|
|
4228
5171
|
* Generated bundle index. Do not edit.
|
|
4229
5172
|
*/
|
|
4230
5173
|
|
|
4231
|
-
export { AngularTiptapEditorComponent, DEFAULT_BUBBLE_MENU_CONFIG, DEFAULT_IMAGE_BUBBLE_MENU_CONFIG, DEFAULT_SLASH_COMMANDS, DEFAULT_TOOLBAR_CONFIG, NoopValueAccessorDirective, TiptapI18nService, createI18nSlashCommands };
|
|
5174
|
+
export { AngularTiptapEditorComponent, DEFAULT_BUBBLE_MENU_CONFIG, DEFAULT_CELL_MENU_CONFIG, DEFAULT_IMAGE_BUBBLE_MENU_CONFIG, DEFAULT_SLASH_COMMANDS, DEFAULT_TABLE_MENU_CONFIG, DEFAULT_TOOLBAR_CONFIG, NoopValueAccessorDirective, TiptapI18nService, createI18nSlashCommands };
|
|
4232
5175
|
//# sourceMappingURL=flogeez-angular-tiptap-editor.mjs.map
|