@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, signal, computed, Injectable, inject, effect, ViewChild, Directive, viewChild, DestroyRef } from '@angular/core';
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
- const ENGLISH_TRANSLATIONS = {
1015
- toolbar: {
1016
- bold: "Bold",
1017
- italic: "Italic",
1018
- underline: "Underline",
1019
- strike: "Strikethrough",
1020
- code: "Code",
1021
- superscript: "Superscript",
1022
- subscript: "Subscript",
1023
- highlight: "Highlight",
1024
- heading1: "Heading 1",
1025
- heading2: "Heading 2",
1026
- heading3: "Heading 3",
1027
- bulletList: "Bullet List",
1028
- orderedList: "Ordered List",
1029
- blockquote: "Blockquote",
1030
- alignLeft: "Align Left",
1031
- alignCenter: "Align Center",
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
- characters: "characters",
1123
- words: "words",
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
- characters: "caractères",
1250
- words: "mots",
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.imageUpload = computed(() => this.translations().imageUpload);
1284
- this.editor = computed(() => this.translations().editor);
1285
- this.common = computed(() => this.translations().common);
1286
- // Détecter automatiquement la langue du navigateur
1287
- this.detectBrowserLanguage();
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
- setTextAlign(alignment) {
1513
- this.editorCommands.setTextAlign(this.editor(), alignment);
2119
+ setLocale(locale) {
2120
+ this._currentLocale.set(locale);
1514
2121
  }
1515
- toggleLink() {
1516
- this.editorCommands.toggleLink(this.editor());
2122
+ autoDetectLocale() {
2123
+ this.detectBrowserLanguage();
1517
2124
  }
1518
- insertHorizontalRule() {
1519
- this.editorCommands.insertHorizontalRule(this.editor());
2125
+ getSupportedLocales() {
2126
+ return Object.keys(this._translations());
1520
2127
  }
1521
- toggleHighlight() {
1522
- this.editorCommands.toggleHighlight(this.editor());
2128
+ addTranslations(locale, translations) {
2129
+ this._translations.update((current) => ({
2130
+ ...current,
2131
+ [locale]: {
2132
+ ...current[locale],
2133
+ ...translations,
2134
+ },
2135
+ }));
1523
2136
  }
1524
- // Méthode pour insérer une image
1525
- async insertImage() {
1526
- try {
1527
- await this.imageService.selectAndUploadImage(this.editor());
2137
+ detectBrowserLanguage() {
2138
+ const browserLang = navigator.language.toLowerCase();
2139
+ if (browserLang.startsWith("fr")) {
2140
+ this._currentLocale.set("fr");
1528
2141
  }
1529
- catch (error) {
1530
- console.error("Erreur lors de l'upload d'image:", error);
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 événements d'image (conservées pour compatibilité)
1535
- onImageSelected(result) {
1536
- this.imageUploaded.emit(result);
2146
+ // Méthodes utilitaires pour les composants
2147
+ getToolbarTitle(key) {
2148
+ return this.translations().toolbar[key];
1537
2149
  }
1538
- onImageError(error) {
1539
- this.imageError.emit(error);
2150
+ getBubbleMenuTitle(key) {
2151
+ return this.translations().bubbleMenu[key];
1540
2152
  }
1541
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: TiptapToolbarComponent, deps: [{ token: EditorCommandsService }], target: i0.ɵɵFactoryTarget.Component }); }
1542
- 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: `
1543
- <div class="tiptap-toolbar">
1544
- @if (config().bold) {
1545
- <tiptap-button
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: TiptapToolbarComponent, decorators: [{
1734
- type: Component,
1735
- args: [{ selector: "tiptap-toolbar", standalone: true, imports: [TiptapButtonComponent, TiptapSeparatorComponent], template: `
1736
- <div class="tiptap-toolbar">
1737
- @if (config().bold) {
1738
- <tiptap-button
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 TiptapImageUploadComponent {
1928
- constructor() {
1929
- // Inputs
1930
- this.config = input({
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
- triggerFileInput() {
1958
- const input = document.querySelector('input[type="file"]');
1959
- if (input) {
1960
- input.click();
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
- onFileSelected(event) {
1964
- const input = event.target;
1965
- const files = input.files;
1966
- if (files && files.length > 0) {
1967
- this.processFiles(Array.from(files));
1968
- }
1969
- // Reset input
1970
- input.value = "";
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
- onDragOver(event) {
1973
- event.preventDefault();
1974
- event.stopPropagation();
1975
- this.isDragOver.set(true);
2245
+ toggleHeading(editor, level) {
2246
+ editor.chain().focus().toggleHeading({ level }).run();
1976
2247
  }
1977
- onDrop(event) {
1978
- event.preventDefault();
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
- onDragLeave(event) {
1987
- event.preventDefault();
1988
- event.stopPropagation();
1989
- this.isDragOver.set(false);
2251
+ toggleOrderedList(editor) {
2252
+ editor.chain().focus().toggleOrderedList().run();
1990
2253
  }
1991
- processFiles(files) {
1992
- const config = this.config();
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
- isValidFileType(file, allowedTypes) {
2017
- if (allowedTypes.includes("image/*")) {
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
- processImage(file) {
2023
- this.isUploading.set(true);
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
- formatFileSize(bytes) {
2099
- if (bytes === 0)
2100
- return "0 B";
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
- showError(message) {
2107
- this.errorMessage.set(message);
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
- clearError() {
2117
- this.errorMessage.set(null);
2270
+ toggleSubscript(editor) {
2271
+ editor.chain().focus().toggleSubscript().run();
2118
2272
  }
2119
- clearPreview() {
2120
- this.previewImage.set(null);
2121
- this.previewInfo.set("");
2273
+ setTextAlign(editor, alignment) {
2274
+ editor.chain().focus().setTextAlign(alignment).run();
2122
2275
  }
2123
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: TiptapImageUploadComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2124
- 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: `
2125
- <div class="image-upload-container">
2126
- <!-- Bouton d'upload -->
2127
- <tiptap-button
2128
- icon="image"
2129
- title="Ajouter une image"
2130
- [disabled]="isUploading()"
2131
- (onClick)="triggerFileInput()"
2132
- />
2133
-
2134
- <!-- Input file caché -->
2135
- <input
2136
- #fileInput
2137
- type="file"
2138
- [accept]="acceptedTypes()"
2139
- [multiple]="config().multiple"
2140
- (change)="onFileSelected($event)"
2141
- style="display: none;"
2142
- />
2143
-
2144
- <!-- Zone de drag & drop -->
2145
- @if (config().enableDragDrop && isDragOver()) {
2146
- <div
2147
- class="drag-overlay"
2148
- (dragover)="onDragOver($event)"
2149
- (drop)="onDrop($event)"
2150
- (dragleave)="onDragLeave($event)"
2151
- >
2152
- <div class="drag-content">
2153
- <span class="material-symbols-outlined">cloud_upload</span>
2154
- <p>Déposez votre image ici</p>
2155
- </div>
2156
- </div>
2157
- }
2158
-
2159
- <!-- Barre de progression -->
2160
- @if (isUploading() && uploadProgress() > 0) {
2161
- <div class="upload-progress">
2162
- <div class="progress-bar">
2163
- <div class="progress-fill" [style.width.%]="uploadProgress()"></div>
2164
- </div>
2165
- <div class="progress-text">{{ uploadProgress() }}%</div>
2166
- </div>
2167
- }
2168
-
2169
- <!-- Prévisualisation -->
2170
- @if (config().showPreview && previewImage()) {
2171
- <div class="image-preview">
2172
- <img [src]="previewImage()" alt="Prévisualisation" />
2173
- <div class="preview-info">
2174
- <span>{{ previewInfo() }}</span>
2175
- </div>
2176
- <button
2177
- class="preview-close"
2178
- (click)="clearPreview()"
2179
- title="Fermer la prévisualisation"
2180
- >
2181
- <span class="material-symbols-outlined">close</span>
2182
- </button>
2183
- </div>
2184
- }
2185
-
2186
- <!-- Messages d'erreur -->
2187
- @if (errorMessage()) {
2188
- <div class="error-message">
2189
- <span class="material-symbols-outlined">error</span>
2190
- {{ errorMessage() }}
2191
- </div>
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: TiptapImageUploadComponent, decorators: [{
2197
- type: Component,
2198
- args: [{ selector: "tiptap-image-upload", standalone: true, imports: [TiptapButtonComponent], template: `
2199
- <div class="image-upload-container">
2200
- <!-- Bouton d'upload -->
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 TiptapBubbleMenuComponent {
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
- bold: true,
2277
- italic: true,
2278
- underline: true,
2279
- strike: true,
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
- this.bubbleMenuConfig = computed(() => ({
2290
- bold: true,
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 hasTextSelection = from !== to;
2313
- const isImageSelected = ed.isActive("image") || ed.isActive("resizableImage");
2314
- // Ne montrer le menu texte que si :
2315
- // - Il y a une sélection de texte
2316
- // - Aucune image n'est sélectionnée (priorité aux images)
2317
- // - L'éditeur est éditable
2318
- const shouldShow = hasTextSelection && !isImageSelected && ed.isEditable;
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 ed = this.editor();
2335
- if (!ed)
2336
- return;
2337
- // Nettoyer les anciens listeners
2338
- ed.off("selectionUpdate", this.updateMenu);
2339
- ed.off("transaction", this.updateMenu);
2340
- ed.off("focus", this.updateMenu);
2341
- ed.off("blur", this.handleBlur);
2342
- // Ajouter les nouveaux listeners
2343
- ed.on("selectionUpdate", this.updateMenu);
2344
- ed.on("transaction", this.updateMenu);
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 ed = this.editor();
2357
- if (ed) {
2358
- ed.off("selectionUpdate", this.updateMenu);
2359
- ed.off("transaction", this.updateMenu);
2360
- ed.off("focus", this.updateMenu);
2361
- ed.off("blur", this.handleBlur);
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
- this.tippyInstance = null;
2436
+ }
2437
+ if (this.updateTimeout) {
2438
+ clearTimeout(this.updateTimeout);
2371
2439
  }
2372
2440
  }
2373
2441
  initTippy() {
2374
- // Attendre que l'élément soit disponible
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.getSelectionRect(),
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
- getSelectionRect() {
2422
- const selection = window.getSelection();
2423
- if (!selection || selection.rangeCount === 0) {
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
- const range = selection.getRangeAt(0);
2427
- return range.getBoundingClientRect();
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.getSelectionRect(),
2530
+ getReferenceClientRect: () => this.getTableRect(),
2439
2531
  });
2440
2532
  this.tippyInstance.show();
2441
2533
  }
2442
2534
  hideTippy() {
2443
- if (this.tippyInstance) {
2444
- this.tippyInstance.hide();
2445
- }
2446
- }
2447
- isActive(mark) {
2448
- const ed = this.editor();
2449
- return ed?.isActive(mark) || false;
2450
- }
2451
- onCommand(command, event) {
2452
- event.preventDefault();
2453
- const ed = this.editor();
2454
- if (!ed)
2455
- return;
2456
- switch (command) {
2457
- case "bold":
2458
- ed.chain().focus().toggleBold().run();
2459
- break;
2460
- case "italic":
2461
- ed.chain().focus().toggleItalic().run();
2462
- break;
2463
- case "underline":
2464
- ed.chain().focus().toggleUnderline().run();
2465
- break;
2466
- case "strike":
2467
- ed.chain().focus().toggleStrike().run();
2468
- break;
2469
- case "code":
2470
- ed.chain().focus().toggleCode().run();
2471
- break;
2472
- case "superscript":
2473
- ed.chain().focus().toggleSuperscript().run();
2474
- break;
2475
- case "subscript":
2476
- ed.chain().focus().toggleSubscript().run();
2477
- break;
2478
- case "highlight":
2479
- ed.chain().focus().toggleHighlight().run();
2480
- break;
2481
- case "link":
2482
- const href = window.prompt("URL du lien:");
2483
- if (href) {
2484
- ed.chain().focus().toggleLink({ href }).run();
2485
- }
2486
- break;
2487
- }
2488
- }
2489
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: TiptapBubbleMenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2490
- 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: `
2491
- <div #menuRef class="bubble-menu">
2492
- @if (bubbleMenuConfig().bold) {
2493
- <tiptap-button
2494
- icon="format_bold"
2495
- title="Gras"
2496
- [active]="isActive('bold')"
2497
- (click)="onCommand('bold', $event)"
2498
- ></tiptap-button>
2499
- } @if (bubbleMenuConfig().italic) {
2500
- <tiptap-button
2501
- icon="format_italic"
2502
- title="Italique"
2503
- [active]="isActive('italic')"
2504
- (click)="onCommand('italic', $event)"
2505
- ></tiptap-button>
2506
- } @if (bubbleMenuConfig().underline) {
2507
- <tiptap-button
2508
- icon="format_underlined"
2509
- title="Souligné"
2510
- [active]="isActive('underline')"
2511
- (click)="onCommand('underline', $event)"
2512
- ></tiptap-button>
2513
- } @if (bubbleMenuConfig().strike) {
2514
- <tiptap-button
2515
- icon="strikethrough_s"
2516
- title="Barré"
2517
- [active]="isActive('strike')"
2518
- (click)="onCommand('strike', $event)"
2519
- ></tiptap-button>
2520
- } @if (bubbleMenuConfig().superscript) {
2521
- <tiptap-button
2522
- icon="superscript"
2523
- title="Exposant"
2524
- [active]="isActive('superscript')"
2525
- (click)="onCommand('superscript', $event)"
2526
- ></tiptap-button>
2527
- } @if (bubbleMenuConfig().subscript) {
2528
- <tiptap-button
2529
- icon="subscript"
2530
- title="Indice"
2531
- [active]="isActive('subscript')"
2532
- (click)="onCommand('subscript', $event)"
2533
- ></tiptap-button>
2534
- } @if (bubbleMenuConfig().highlight) {
2535
- <tiptap-button
2536
- icon="highlight"
2537
- title="Surbrillance"
2538
- [active]="isActive('highlight')"
2539
- (click)="onCommand('highlight', $event)"
2540
- ></tiptap-button>
2541
- } @if (bubbleMenuConfig().separator && (bubbleMenuConfig().code ||
2542
- bubbleMenuConfig().link)) {
2543
- <div class="tiptap-separator"></div>
2544
- } @if (bubbleMenuConfig().code) {
2545
- <tiptap-button
2546
- icon="code"
2547
- title="Code"
2548
- [active]="isActive('code')"
2549
- (click)="onCommand('code', $event)"
2550
- ></tiptap-button>
2551
- } @if (bubbleMenuConfig().link) {
2552
- <tiptap-button
2553
- icon="link"
2554
- title="Lien"
2555
- [active]="isActive('link')"
2556
- (click)="onCommand('link', $event)"
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: TiptapBubbleMenuComponent, decorators: [{
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 #menuRef class="bubble-menu">
2566
- @if (bubbleMenuConfig().bold) {
2567
- <tiptap-button
2568
- icon="format_bold"
2569
- title="Gras"
2570
- [active]="isActive('bold')"
2571
- (click)="onCommand('bold', $event)"
2572
- ></tiptap-button>
2573
- } @if (bubbleMenuConfig().italic) {
2574
- <tiptap-button
2575
- icon="format_italic"
2576
- title="Italique"
2577
- [active]="isActive('italic')"
2578
- (click)="onCommand('italic', $event)"
2579
- ></tiptap-button>
2580
- } @if (bubbleMenuConfig().underline) {
2581
- <tiptap-button
2582
- icon="format_underlined"
2583
- title="Souligné"
2584
- [active]="isActive('underline')"
2585
- (click)="onCommand('underline', $event)"
2586
- ></tiptap-button>
2587
- } @if (bubbleMenuConfig().strike) {
2588
- <tiptap-button
2589
- icon="strikethrough_s"
2590
- title="Barré"
2591
- [active]="isActive('strike')"
2592
- (click)="onCommand('strike', $event)"
2593
- ></tiptap-button>
2594
- } @if (bubbleMenuConfig().superscript) {
2595
- <tiptap-button
2596
- icon="superscript"
2597
- title="Exposant"
2598
- [active]="isActive('superscript')"
2599
- (click)="onCommand('superscript', $event)"
2600
- ></tiptap-button>
2601
- } @if (bubbleMenuConfig().subscript) {
2602
- <tiptap-button
2603
- icon="subscript"
2604
- title="Indice"
2605
- [active]="isActive('subscript')"
2606
- (click)="onCommand('subscript', $event)"
2607
- ></tiptap-button>
2608
- } @if (bubbleMenuConfig().highlight) {
2609
- <tiptap-button
2610
- icon="highlight"
2611
- title="Surbrillance"
2612
- [active]="isActive('highlight')"
2613
- (click)="onCommand('highlight', $event)"
2614
- ></tiptap-button>
2615
- } @if (bubbleMenuConfig().separator && (bubbleMenuConfig().code ||
2616
- bubbleMenuConfig().link)) {
2617
- <div class="tiptap-separator"></div>
2618
- } @if (bubbleMenuConfig().code) {
2619
- <tiptap-button
2620
- icon="code"
2621
- title="Code"
2622
- [active]="isActive('code')"
2623
- (click)="onCommand('code', $event)"
2624
- ></tiptap-button>
2625
- } @if (bubbleMenuConfig().link) {
2626
- <tiptap-button
2627
- icon="link"
2628
- title="Lien"
2629
- [active]="isActive('link')"
2630
- (click)="onCommand('link', $event)"
2631
- ></tiptap-button>
2632
- }
2633
- </div>
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: { menuRef: [{
2730
+ }], ctorParameters: () => [], propDecorators: { menuElement: [{
2636
2731
  type: ViewChild,
2637
- args: ["menuRef", { static: false }]
2732
+ args: ["menuElement", { static: true }]
2638
2733
  }] } });
2639
2734
 
2640
- class TiptapImageBubbleMenuComponent {
2735
+ class TiptapCellBubbleMenuComponent {
2641
2736
  constructor() {
2737
+ // Inputs
2642
2738
  this.editor = input.required();
2643
- this.config = input({
2644
- changeImage: true,
2645
- resizeSmall: true,
2646
- resizeMedium: true,
2647
- resizeLarge: true,
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
- this.imageBubbleMenuConfig = computed(() => ({
2656
- changeImage: true,
2657
- resizeSmall: true,
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 isImageSelected = ed.isActive("resizableImage") || ed.isActive("image");
2682
- const { from, to } = ed.state.selection;
2683
- const hasTextSelection = from !== to;
2684
- // Ne montrer le menu image que si :
2685
- // - Une image est sélectionnée
2686
- // - L'éditeur est éditable
2687
- const shouldShow = isImageSelected && ed.isEditable;
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 ed = this.editor();
2704
- if (!ed)
2705
- return;
2706
- // Nettoyer les anciens listeners
2707
- ed.off("selectionUpdate", this.updateMenu);
2708
- ed.off("transaction", this.updateMenu);
2709
- ed.off("focus", this.updateMenu);
2710
- ed.off("blur", this.handleBlur);
2711
- // Ajouter les nouveaux listeners
2712
- ed.on("selectionUpdate", this.updateMenu);
2713
- ed.on("transaction", this.updateMenu);
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 ed = this.editor();
2726
- if (ed) {
2727
- ed.off("selectionUpdate", this.updateMenu);
2728
- ed.off("transaction", this.updateMenu);
2729
- ed.off("focus", this.updateMenu);
2730
- ed.off("blur", this.handleBlur);
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
- this.tippyInstance = null;
2826
+ }
2827
+ if (this.updateTimeout) {
2828
+ clearTimeout(this.updateTimeout);
2740
2829
  }
2741
2830
  }
2742
2831
  initTippy() {
2743
- // Attendre que l'élément soit disponible
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.getImageRect(),
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
- getImageRect() {
2869
+ getCellRect() {
2791
2870
  const ed = this.editor();
2792
2871
  if (!ed)
2793
2872
  return new DOMRect(0, 0, 0, 0);
2794
- // Trouver l'image sélectionnée dans le DOM
2795
- const { from } = ed.state.selection;
2796
- // Fonction pour trouver toutes les images dans l'éditeur
2797
- const getAllImages = () => {
2798
- const editorElement = ed.view.dom;
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
- return new DOMRect(0, 0, 0, 0);
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
- // Cette méthode peut être étendue pour fermer d'autres menus si nécessaire
2829
- // Pour l'instant, elle sert de placeholder pour une future coordination entre menus
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.getImageRect(),
2899
+ getReferenceClientRect: () => this.getCellRect(),
2837
2900
  });
2838
2901
  this.tippyInstance.show();
2839
2902
  }
2840
- hideTippy() {
2841
- if (this.tippyInstance) {
2842
- this.tippyInstance.hide();
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
- onCommand(command, event) {
2846
- event.preventDefault();
2847
- const ed = this.editor();
2848
- if (!ed)
2849
- return;
2850
- switch (command) {
2851
- case "changeImage":
2852
- this.changeImage();
2853
- break;
2854
- case "resizeSmall":
2855
- this.imageService.resizeImageToSmall(ed);
2856
- break;
2857
- case "resizeMedium":
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
- async changeImage() {
2872
- const ed = this.editor();
2873
- if (!ed)
2933
+ hideTippy() {
2934
+ if (!this.tippyInstance)
2874
2935
  return;
2875
- try {
2876
- // Utiliser la méthode spécifique pour remplacer une image existante
2877
- await this.imageService.selectAndReplaceImage(ed, {
2878
- quality: 0.8,
2879
- maxWidth: 1920,
2880
- maxHeight: 1080,
2881
- accept: "image/*",
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 du changement d'image:", error);
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
- deleteImage() {
2889
- const ed = this.editor();
2890
- if (ed) {
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
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: TiptapImageBubbleMenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2895
- 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: `
2896
- <div #menuRef class="bubble-menu">
2897
- @if (imageBubbleMenuConfig().changeImage) {
2898
- <tiptap-button
2899
- icon="drive_file_rename_outline"
2900
- title="Changer l'image"
2901
- (click)="onCommand('changeImage', $event)"
2902
- ></tiptap-button>
2903
- } @if (imageBubbleMenuConfig().separator && hasResizeButtons()) {
2904
- <tiptap-separator></tiptap-separator>
2905
- } @if (imageBubbleMenuConfig().resizeSmall) {
2906
- <tiptap-button
2907
- icon="crop_square"
2908
- iconSize="small"
2909
- title="Petite (300×200)"
2910
- (click)="onCommand('resizeSmall', $event)"
2911
- ></tiptap-button>
2912
- } @if (imageBubbleMenuConfig().resizeMedium) {
2913
- <tiptap-button
2914
- icon="crop_square"
2915
- iconSize="medium"
2916
- title="Moyenne (500×350)"
2917
- (click)="onCommand('resizeMedium', $event)"
2918
- ></tiptap-button>
2919
- } @if (imageBubbleMenuConfig().resizeLarge) {
2920
- <tiptap-button
2921
- icon="crop_square"
2922
- iconSize="large"
2923
- title="Grande (800×600)"
2924
- (click)="onCommand('resizeLarge', $event)"
2925
- ></tiptap-button>
2926
- } @if (imageBubbleMenuConfig().resizeOriginal) {
2927
- <tiptap-button
2928
- icon="photo_size_select_actual"
2929
- title="Taille originale"
2930
- (click)="onCommand('resizeOriginal', $event)"
2931
- ></tiptap-button>
2932
- } @if (imageBubbleMenuConfig().separator &&
2933
- imageBubbleMenuConfig().deleteImage) {
2934
- <tiptap-separator></tiptap-separator>
2935
- } @if (imageBubbleMenuConfig().deleteImage) {
2936
- <tiptap-button
2937
- icon="delete"
2938
- title="Supprimer l'image"
2939
- variant="danger"
2940
- (click)="onCommand('deleteImage', $event)"
2941
- ></tiptap-button>
2942
- }
2943
- </div>
2944
- `, 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"] }] }); }
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: TiptapImageBubbleMenuComponent, decorators: [{
3292
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: TiptapToolbarComponent, decorators: [{
2947
3293
  type: Component,
2948
- args: [{ selector: "tiptap-image-bubble-menu", standalone: true, imports: [TiptapButtonComponent, TiptapSeparatorComponent], template: `
2949
- <div #menuRef class="bubble-menu">
2950
- @if (imageBubbleMenuConfig().changeImage) {
2951
- <tiptap-button
2952
- icon="drive_file_rename_outline"
2953
- title="Changer l'image"
2954
- (click)="onCommand('changeImage', $event)"
2955
- ></tiptap-button>
2956
- } @if (imageBubbleMenuConfig().separator && hasResizeButtons()) {
2957
- <tiptap-separator></tiptap-separator>
2958
- } @if (imageBubbleMenuConfig().resizeSmall) {
2959
- <tiptap-button
2960
- icon="crop_square"
2961
- iconSize="small"
2962
- title="Petite (300×200)"
2963
- (click)="onCommand('resizeSmall', $event)"
2964
- ></tiptap-button>
2965
- } @if (imageBubbleMenuConfig().resizeMedium) {
2966
- <tiptap-button
2967
- icon="crop_square"
2968
- iconSize="medium"
2969
- title="Moyenne (500×350)"
2970
- (click)="onCommand('resizeMedium', $event)"
2971
- ></tiptap-button>
2972
- } @if (imageBubbleMenuConfig().resizeLarge) {
2973
- <tiptap-button
2974
- icon="crop_square"
2975
- iconSize="large"
2976
- title="Grande (800×600)"
2977
- (click)="onCommand('resizeLarge', $event)"
2978
- ></tiptap-button>
2979
- } @if (imageBubbleMenuConfig().resizeOriginal) {
2980
- <tiptap-button
2981
- icon="photo_size_select_actual"
2982
- title="Taille originale"
2983
- (click)="onCommand('resizeOriginal', $event)"
2984
- ></tiptap-button>
2985
- } @if (imageBubbleMenuConfig().separator &&
2986
- imageBubbleMenuConfig().deleteImage) {
2987
- <tiptap-separator></tiptap-separator>
2988
- } @if (imageBubbleMenuConfig().deleteImage) {
2989
- <tiptap-button
2990
- icon="delete"
2991
- title="Supprimer l'image"
2992
- variant="danger"
2993
- (click)="onCommand('deleteImage', $event)"
2994
- ></tiptap-button>
2995
- }
2996
- </div>
2997
- ` }]
2998
- }], ctorParameters: () => [], propDecorators: { menuRef: [{
2999
- type: ViewChild,
3000
- args: ["menuRef", { static: false }]
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
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: TiptapSlashCommandsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3452
- 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: `
3453
- <div #menuRef class="slash-commands-menu">
3454
- @for (command of filteredCommands(); track command.title) {
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="slash-command-item"
3457
- [class.selected]="$index === selectedIndex()"
3458
- (click)="executeCommand(command)"
3459
- (mouseenter)="selectedIndex.set($index)"
4219
+ class="drag-overlay"
4220
+ (dragover)="onDragOver($event)"
4221
+ (drop)="onDrop($event)"
4222
+ (dragleave)="onDragLeave($event)"
3460
4223
  >
3461
- <div class="slash-command-icon">
3462
- <span class="material-symbols-outlined">{{ command.icon }}</span>
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
- <div class="slash-command-content">
3465
- <div class="slash-command-title">{{ command.title }}</div>
3466
- <div class="slash-command-description">{{ command.description }}</div>
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: [".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"] }); }
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: TiptapSlashCommandsComponent, decorators: [{
4268
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: TiptapImageUploadComponent, decorators: [{
3474
4269
  type: Component,
3475
- args: [{ selector: "tiptap-slash-commands", standalone: true, template: `
3476
- <div #menuRef class="slash-commands-menu">
3477
- @for (command of filteredCommands(); track command.title) {
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="slash-command-item"
3480
- [class.selected]="$index === selectedIndex()"
3481
- (click)="executeCommand(command)"
3482
- (mouseenter)="selectedIndex.set($index)"
4293
+ class="drag-overlay"
4294
+ (dragover)="onDragOver($event)"
4295
+ (drop)="onDrop($event)"
4296
+ (dragleave)="onDragLeave($event)"
3483
4297
  >
3484
- <div class="slash-command-icon">
3485
- <span class="material-symbols-outlined">{{ command.icon }}</span>
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
- <div class="slash-command-content">
3488
- <div class="slash-command-title">{{ command.title }}</div>
3489
- <div class="slash-command-description">{{ command.description }}</div>
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: [".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"] }]
3495
- }], ctorParameters: () => [], propDecorators: { menuRef: [{
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.characterCountData = signal(null);
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.characterCountData.set({
3802
- characters: storage.characters(),
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()?.commands.clearContent();
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() && characterCountData()) {
4890
+ @if (showCharacterCount()) {
3980
4891
  <div class="character-count">
3981
- {{ characterCountData()?.characters }}
3982
- {{ i18nService.editor().characters }},
3983
- {{ characterCountData()?.words }} {{ i18nService.editor().words }} @if
3984
- (maxCharacters()) { /
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;box-shadow:0 2px 4px #0000001a;overflow:hidden;transition:border-color .2s ease}.tiptap-editor:focus-within{border-color:#3182ce;box-shadow:0 0 0 3px #3182ce1a}.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}\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: TiptapSlashCommandsComponent, selector: "tiptap-slash-commands", inputs: ["editor", "config"], outputs: ["imageUploadRequested"] }] }); }
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() && characterCountData()) {
4988
+ @if (showCharacterCount()) {
4055
4989
  <div class="character-count">
4056
- {{ characterCountData()?.characters }}
4057
- {{ i18nService.editor().characters }},
4058
- {{ characterCountData()?.words }} {{ i18nService.editor().words }} @if
4059
- (maxCharacters()) { /
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;box-shadow:0 2px 4px #0000001a;overflow:hidden;transition:border-color .2s ease}.tiptap-editor:focus-within{border-color:#3182ce;box-shadow:0 0 0 3px #3182ce1a}.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}\n"] }]
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