@flogeez/angular-tiptap-editor 0.2.7 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { input, output, Component,
|
|
2
|
+
import { input, output, Component, computed, effect, ViewChild, signal, Injectable, inject, Directive, viewChild, DestroyRef } from '@angular/core';
|
|
3
3
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
4
4
|
import { Node, nodeInputRule, mergeAttributes, Extension, Editor } from '@tiptap/core';
|
|
5
5
|
import StarterKit from '@tiptap/starter-kit';
|
|
@@ -14,7 +14,13 @@ import Highlight from '@tiptap/extension-highlight';
|
|
|
14
14
|
import OfficePaste from '@intevation/tiptap-extension-office-paste';
|
|
15
15
|
import { Plugin, PluginKey } from '@tiptap/pm/state';
|
|
16
16
|
import { DecorationSet, Decoration } from '@tiptap/pm/view';
|
|
17
|
+
import Table from '@tiptap/extension-table';
|
|
18
|
+
import TableRow from '@tiptap/extension-table-row';
|
|
19
|
+
import TableCell from '@tiptap/extension-table-cell';
|
|
20
|
+
import TableHeader from '@tiptap/extension-table-header';
|
|
17
21
|
import tippy from 'tippy.js';
|
|
22
|
+
import { CellSelection } from '@tiptap/pm/tables';
|
|
23
|
+
import { CommonModule } from '@angular/common';
|
|
18
24
|
import { Plugin as Plugin$1, PluginKey as PluginKey$1 } from 'prosemirror-state';
|
|
19
25
|
import { NG_VALUE_ACCESSOR, NgControl } from '@angular/forms';
|
|
20
26
|
import { concat, defer, of, tap } from 'rxjs';
|
|
@@ -488,6 +494,30 @@ const UploadProgress = Extension.create({
|
|
|
488
494
|
},
|
|
489
495
|
});
|
|
490
496
|
|
|
497
|
+
const TableExtension = Extension.create({
|
|
498
|
+
name: "tableExtension",
|
|
499
|
+
addExtensions() {
|
|
500
|
+
return [
|
|
501
|
+
Table.configure({
|
|
502
|
+
resizable: true,
|
|
503
|
+
handleWidth: 5,
|
|
504
|
+
cellMinWidth: 100,
|
|
505
|
+
}),
|
|
506
|
+
TableRow,
|
|
507
|
+
TableHeader.configure({
|
|
508
|
+
HTMLAttributes: {
|
|
509
|
+
class: "table-header",
|
|
510
|
+
},
|
|
511
|
+
}),
|
|
512
|
+
TableCell.configure({
|
|
513
|
+
HTMLAttributes: {
|
|
514
|
+
class: "table-cell",
|
|
515
|
+
},
|
|
516
|
+
}),
|
|
517
|
+
];
|
|
518
|
+
},
|
|
519
|
+
});
|
|
520
|
+
|
|
491
521
|
class TiptapButtonComponent {
|
|
492
522
|
constructor() {
|
|
493
523
|
// Inputs
|
|
@@ -562,6 +592,393 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.0", ngImpor
|
|
|
562
592
|
`, styles: [".tiptap-button{display:flex;align-items:center;justify-content:center;width:32px;height:32px;border:none;background:transparent;border-radius:8px;cursor:pointer;transition:all .2s cubic-bezier(.4,0,.2,1);color:#64748b;position:relative;overflow:hidden}.tiptap-button:before{content:\"\";position:absolute;inset:0;background:linear-gradient(135deg,#6366f1,#8b5cf6);opacity:0;transition:opacity .2s ease;border-radius:8px}.tiptap-button:hover{color:#6366f1;transform:translateY(-1px)}.tiptap-button:hover:before{opacity:.1}.tiptap-button:active{transform:translateY(0)}.tiptap-button.is-active{color:#6366f1;background:#6366f11a}.tiptap-button.is-active:before{opacity:.15}.tiptap-button.is-active:hover{background:#6366f126}.tiptap-button:disabled{opacity:.5;cursor:not-allowed;pointer-events:none}.tiptap-button:disabled:hover{transform:none;color:#64748b}.tiptap-button:disabled:before{opacity:0}.tiptap-button .material-symbols-outlined{font-size:20px;position:relative;z-index:1}.tiptap-button .material-symbols-outlined.icon-small{font-size:16px}.tiptap-button .material-symbols-outlined.icon-medium{font-size:20px}.tiptap-button .material-symbols-outlined.icon-large{font-size:24px}.tiptap-button.text-button{width:auto;padding:0 12px;font-size:14px;font-weight:500}.tiptap-button.color-button{width:28px;height:28px;border-radius:50%;border:2px solid transparent;transition:all .2s ease}.tiptap-button.color-button:hover{border-color:#e2e8f0;transform:scale(1.1)}.tiptap-button.color-button.is-active{border-color:#6366f1;box-shadow:0 0 0 2px #6366f133}.tiptap-button.primary{background:linear-gradient(135deg,#6366f1,#8b5cf6);color:#fff}.tiptap-button.primary:hover{background:linear-gradient(135deg,#5b21b6,#7c3aed);color:#fff}.tiptap-button.secondary{background:#f1f5f9;color:#64748b}.tiptap-button.secondary:hover{background:#e2e8f0;color:#475569}.tiptap-button.danger{color:#ef4444}.tiptap-button.danger:hover{color:#dc2626;background:#ef44441a}.tiptap-button.danger:before{background:linear-gradient(135deg,#ef4444,#dc2626)}.tiptap-button.small{width:24px;height:24px}.tiptap-button.medium{width:32px;height:32px}.tiptap-button.large{width:40px;height:40px}.tiptap-button.has-badge{position:relative}.tiptap-button .badge{position:absolute;top:-4px;right:-4px;background:#ef4444;color:#fff;font-size:10px;padding:2px 4px;border-radius:8px;min-width:16px;text-align:center;line-height:1}.tiptap-button.has-tooltip{position:relative}.tiptap-button .tooltip{position:absolute;bottom:-30px;left:50%;transform:translate(-50%);background:#000c;color:#fff;padding:4px 8px;border-radius:4px;font-size:12px;white-space:nowrap;opacity:0;visibility:hidden;transition:all .2s ease;z-index:1000}.tiptap-button:hover .tooltip{opacity:1;visibility:visible}@keyframes pulse{0%,to{box-shadow:0 0 #6366f166}50%{box-shadow:0 0 0 4px #6366f100}}.tiptap-button.is-active.pulse{animation:pulse 2s infinite}@media (max-width: 768px){.tiptap-button{width:32px;height:32px}.tiptap-button .material-symbols-outlined{font-size:18px}.tiptap-button.text-button{padding:0 8px;font-size:13px}}\n"] }]
|
|
563
593
|
}] });
|
|
564
594
|
|
|
595
|
+
class TiptapBubbleMenuComponent {
|
|
596
|
+
// Effect comme propriété de classe pour éviter l'erreur d'injection context
|
|
597
|
+
constructor() {
|
|
598
|
+
this.editor = input.required();
|
|
599
|
+
this.config = input({
|
|
600
|
+
bold: true,
|
|
601
|
+
italic: true,
|
|
602
|
+
underline: true,
|
|
603
|
+
strike: true,
|
|
604
|
+
code: true,
|
|
605
|
+
superscript: false,
|
|
606
|
+
subscript: false,
|
|
607
|
+
highlight: true,
|
|
608
|
+
link: true,
|
|
609
|
+
separator: true,
|
|
610
|
+
});
|
|
611
|
+
this.tippyInstance = null;
|
|
612
|
+
this.updateTimeout = null;
|
|
613
|
+
this.bubbleMenuConfig = computed(() => ({
|
|
614
|
+
bold: true,
|
|
615
|
+
italic: true,
|
|
616
|
+
underline: true,
|
|
617
|
+
strike: true,
|
|
618
|
+
code: true,
|
|
619
|
+
superscript: false,
|
|
620
|
+
subscript: false,
|
|
621
|
+
highlight: true,
|
|
622
|
+
link: true,
|
|
623
|
+
separator: true,
|
|
624
|
+
...this.config(),
|
|
625
|
+
}));
|
|
626
|
+
this.updateMenu = () => {
|
|
627
|
+
// Debounce pour éviter les appels trop fréquents
|
|
628
|
+
if (this.updateTimeout) {
|
|
629
|
+
clearTimeout(this.updateTimeout);
|
|
630
|
+
}
|
|
631
|
+
this.updateTimeout = setTimeout(() => {
|
|
632
|
+
const ed = this.editor();
|
|
633
|
+
if (!ed)
|
|
634
|
+
return;
|
|
635
|
+
const { selection } = ed.state;
|
|
636
|
+
const { from, to } = selection;
|
|
637
|
+
const hasTextSelection = from !== to && !(selection instanceof CellSelection);
|
|
638
|
+
const isImageSelected = ed.isActive("image") || ed.isActive("resizableImage");
|
|
639
|
+
const isTableCellSelected = ed.isActive("tableCell") || ed.isActive("tableHeader");
|
|
640
|
+
const hasCellSelection = selection instanceof CellSelection;
|
|
641
|
+
console.log("TextBubbleMenu - updateMenu:", {
|
|
642
|
+
hasTextSelection,
|
|
643
|
+
isImageSelected,
|
|
644
|
+
isTableCellSelected,
|
|
645
|
+
hasCellSelection,
|
|
646
|
+
selectionType: selection.constructor.name,
|
|
647
|
+
selectionEmpty: selection.empty,
|
|
648
|
+
from,
|
|
649
|
+
to,
|
|
650
|
+
});
|
|
651
|
+
// Ne montrer le menu texte que si :
|
|
652
|
+
// - Il y a une sélection de texte (pas une sélection de cellules multiples)
|
|
653
|
+
// - Aucune image n'est sélectionnée (priorité aux images)
|
|
654
|
+
// - Ce n'est pas une sélection de cellules multiples (CellSelection)
|
|
655
|
+
// - L'éditeur est éditable
|
|
656
|
+
// Note: Le texte dans une cellule est autorisé (isTableCellSelected peut être true)
|
|
657
|
+
const shouldShow = hasTextSelection &&
|
|
658
|
+
!isImageSelected &&
|
|
659
|
+
!hasCellSelection &&
|
|
660
|
+
ed.isEditable;
|
|
661
|
+
if (shouldShow) {
|
|
662
|
+
this.showTippy();
|
|
663
|
+
}
|
|
664
|
+
else {
|
|
665
|
+
this.hideTippy();
|
|
666
|
+
}
|
|
667
|
+
}, 10);
|
|
668
|
+
};
|
|
669
|
+
this.handleBlur = () => {
|
|
670
|
+
// Masquer le menu quand l'éditeur perd le focus
|
|
671
|
+
setTimeout(() => {
|
|
672
|
+
this.hideTippy();
|
|
673
|
+
}, 100);
|
|
674
|
+
};
|
|
675
|
+
effect(() => {
|
|
676
|
+
const ed = this.editor();
|
|
677
|
+
if (!ed)
|
|
678
|
+
return;
|
|
679
|
+
// Nettoyer les anciens listeners
|
|
680
|
+
ed.off("selectionUpdate", this.updateMenu);
|
|
681
|
+
ed.off("transaction", this.updateMenu);
|
|
682
|
+
ed.off("focus", this.updateMenu);
|
|
683
|
+
ed.off("blur", this.handleBlur);
|
|
684
|
+
// Ajouter les nouveaux listeners
|
|
685
|
+
ed.on("selectionUpdate", this.updateMenu);
|
|
686
|
+
ed.on("transaction", this.updateMenu);
|
|
687
|
+
ed.on("focus", this.updateMenu);
|
|
688
|
+
ed.on("blur", this.handleBlur);
|
|
689
|
+
// Ne pas appeler updateMenu() ici pour éviter l'affichage prématuré
|
|
690
|
+
// Il sera appelé automatiquement quand l'éditeur sera prêt
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
ngOnInit() {
|
|
694
|
+
// Initialiser Tippy de manière synchrone après que le component soit ready
|
|
695
|
+
this.initTippy();
|
|
696
|
+
}
|
|
697
|
+
ngOnDestroy() {
|
|
698
|
+
const ed = this.editor();
|
|
699
|
+
if (ed) {
|
|
700
|
+
ed.off("selectionUpdate", this.updateMenu);
|
|
701
|
+
ed.off("transaction", this.updateMenu);
|
|
702
|
+
ed.off("focus", this.updateMenu);
|
|
703
|
+
ed.off("blur", this.handleBlur);
|
|
704
|
+
}
|
|
705
|
+
// Nettoyer les timeouts
|
|
706
|
+
if (this.updateTimeout) {
|
|
707
|
+
clearTimeout(this.updateTimeout);
|
|
708
|
+
}
|
|
709
|
+
// Nettoyer Tippy
|
|
710
|
+
if (this.tippyInstance) {
|
|
711
|
+
this.tippyInstance.destroy();
|
|
712
|
+
this.tippyInstance = null;
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
initTippy() {
|
|
716
|
+
// Attendre que l'élément soit disponible
|
|
717
|
+
if (!this.menuRef?.nativeElement) {
|
|
718
|
+
setTimeout(() => this.initTippy(), 50);
|
|
719
|
+
return;
|
|
720
|
+
}
|
|
721
|
+
const menuElement = this.menuRef.nativeElement;
|
|
722
|
+
// S'assurer qu'il n'y a pas déjà une instance
|
|
723
|
+
if (this.tippyInstance) {
|
|
724
|
+
this.tippyInstance.destroy();
|
|
725
|
+
}
|
|
726
|
+
// Créer l'instance Tippy
|
|
727
|
+
this.tippyInstance = tippy(document.body, {
|
|
728
|
+
content: menuElement,
|
|
729
|
+
trigger: "manual",
|
|
730
|
+
placement: "top-start",
|
|
731
|
+
appendTo: () => document.body,
|
|
732
|
+
interactive: true,
|
|
733
|
+
arrow: false,
|
|
734
|
+
offset: [0, 8],
|
|
735
|
+
hideOnClick: false,
|
|
736
|
+
onShow: (instance) => {
|
|
737
|
+
// S'assurer que les autres menus sont fermés
|
|
738
|
+
this.hideOtherMenus();
|
|
739
|
+
},
|
|
740
|
+
getReferenceClientRect: () => this.getSelectionRect(),
|
|
741
|
+
// Améliorer le positionnement avec scroll
|
|
742
|
+
popperOptions: {
|
|
743
|
+
modifiers: [
|
|
744
|
+
{
|
|
745
|
+
name: "preventOverflow",
|
|
746
|
+
options: {
|
|
747
|
+
boundary: "viewport",
|
|
748
|
+
padding: 8,
|
|
749
|
+
},
|
|
750
|
+
},
|
|
751
|
+
{
|
|
752
|
+
name: "flip",
|
|
753
|
+
options: {
|
|
754
|
+
fallbackPlacements: ["bottom-start", "top-end", "bottom-end"],
|
|
755
|
+
},
|
|
756
|
+
},
|
|
757
|
+
],
|
|
758
|
+
},
|
|
759
|
+
});
|
|
760
|
+
// Maintenant que Tippy est initialisé, faire un premier check
|
|
761
|
+
this.updateMenu();
|
|
762
|
+
}
|
|
763
|
+
getSelectionRect() {
|
|
764
|
+
const selection = window.getSelection();
|
|
765
|
+
if (!selection || selection.rangeCount === 0) {
|
|
766
|
+
return new DOMRect(0, 0, 0, 0);
|
|
767
|
+
}
|
|
768
|
+
const range = selection.getRangeAt(0);
|
|
769
|
+
return range.getBoundingClientRect();
|
|
770
|
+
}
|
|
771
|
+
hideOtherMenus() {
|
|
772
|
+
// Cette méthode peut être étendue pour fermer d'autres menus si nécessaire
|
|
773
|
+
// Pour l'instant, elle sert de placeholder pour une future coordination entre menus
|
|
774
|
+
}
|
|
775
|
+
showTippy() {
|
|
776
|
+
if (!this.tippyInstance)
|
|
777
|
+
return;
|
|
778
|
+
// Mettre à jour la position
|
|
779
|
+
this.tippyInstance.setProps({
|
|
780
|
+
getReferenceClientRect: () => this.getSelectionRect(),
|
|
781
|
+
});
|
|
782
|
+
this.tippyInstance.show();
|
|
783
|
+
}
|
|
784
|
+
hideTippy() {
|
|
785
|
+
if (this.tippyInstance) {
|
|
786
|
+
this.tippyInstance.hide();
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
isActive(mark) {
|
|
790
|
+
const ed = this.editor();
|
|
791
|
+
return ed?.isActive(mark) || false;
|
|
792
|
+
}
|
|
793
|
+
onCommand(command, event) {
|
|
794
|
+
event.preventDefault();
|
|
795
|
+
const ed = this.editor();
|
|
796
|
+
if (!ed)
|
|
797
|
+
return;
|
|
798
|
+
switch (command) {
|
|
799
|
+
case "bold":
|
|
800
|
+
ed.chain().focus().toggleBold().run();
|
|
801
|
+
break;
|
|
802
|
+
case "italic":
|
|
803
|
+
ed.chain().focus().toggleItalic().run();
|
|
804
|
+
break;
|
|
805
|
+
case "underline":
|
|
806
|
+
ed.chain().focus().toggleUnderline().run();
|
|
807
|
+
break;
|
|
808
|
+
case "strike":
|
|
809
|
+
ed.chain().focus().toggleStrike().run();
|
|
810
|
+
break;
|
|
811
|
+
case "code":
|
|
812
|
+
ed.chain().focus().toggleCode().run();
|
|
813
|
+
break;
|
|
814
|
+
case "superscript":
|
|
815
|
+
ed.chain().focus().toggleSuperscript().run();
|
|
816
|
+
break;
|
|
817
|
+
case "subscript":
|
|
818
|
+
ed.chain().focus().toggleSubscript().run();
|
|
819
|
+
break;
|
|
820
|
+
case "highlight":
|
|
821
|
+
ed.chain().focus().toggleHighlight().run();
|
|
822
|
+
break;
|
|
823
|
+
case "link":
|
|
824
|
+
const href = window.prompt("URL du lien:");
|
|
825
|
+
if (href) {
|
|
826
|
+
ed.chain().focus().toggleLink({ href }).run();
|
|
827
|
+
}
|
|
828
|
+
break;
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: TiptapBubbleMenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
832
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.0", type: TiptapBubbleMenuComponent, isStandalone: true, selector: "tiptap-bubble-menu", inputs: { editor: { classPropertyName: "editor", publicName: "editor", isSignal: true, isRequired: true, transformFunction: null }, config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "menuRef", first: true, predicate: ["menuRef"], descendants: true }], ngImport: i0, template: `
|
|
833
|
+
<div #menuRef class="bubble-menu">
|
|
834
|
+
@if (bubbleMenuConfig().bold) {
|
|
835
|
+
<tiptap-button
|
|
836
|
+
icon="format_bold"
|
|
837
|
+
title="Gras"
|
|
838
|
+
[active]="isActive('bold')"
|
|
839
|
+
(click)="onCommand('bold', $event)"
|
|
840
|
+
></tiptap-button>
|
|
841
|
+
} @if (bubbleMenuConfig().italic) {
|
|
842
|
+
<tiptap-button
|
|
843
|
+
icon="format_italic"
|
|
844
|
+
title="Italique"
|
|
845
|
+
[active]="isActive('italic')"
|
|
846
|
+
(click)="onCommand('italic', $event)"
|
|
847
|
+
></tiptap-button>
|
|
848
|
+
} @if (bubbleMenuConfig().underline) {
|
|
849
|
+
<tiptap-button
|
|
850
|
+
icon="format_underlined"
|
|
851
|
+
title="Souligné"
|
|
852
|
+
[active]="isActive('underline')"
|
|
853
|
+
(click)="onCommand('underline', $event)"
|
|
854
|
+
></tiptap-button>
|
|
855
|
+
} @if (bubbleMenuConfig().strike) {
|
|
856
|
+
<tiptap-button
|
|
857
|
+
icon="strikethrough_s"
|
|
858
|
+
title="Barré"
|
|
859
|
+
[active]="isActive('strike')"
|
|
860
|
+
(click)="onCommand('strike', $event)"
|
|
861
|
+
></tiptap-button>
|
|
862
|
+
} @if (bubbleMenuConfig().superscript) {
|
|
863
|
+
<tiptap-button
|
|
864
|
+
icon="superscript"
|
|
865
|
+
title="Exposant"
|
|
866
|
+
[active]="isActive('superscript')"
|
|
867
|
+
(click)="onCommand('superscript', $event)"
|
|
868
|
+
></tiptap-button>
|
|
869
|
+
} @if (bubbleMenuConfig().subscript) {
|
|
870
|
+
<tiptap-button
|
|
871
|
+
icon="subscript"
|
|
872
|
+
title="Indice"
|
|
873
|
+
[active]="isActive('subscript')"
|
|
874
|
+
(click)="onCommand('subscript', $event)"
|
|
875
|
+
></tiptap-button>
|
|
876
|
+
} @if (bubbleMenuConfig().highlight) {
|
|
877
|
+
<tiptap-button
|
|
878
|
+
icon="highlight"
|
|
879
|
+
title="Surbrillance"
|
|
880
|
+
[active]="isActive('highlight')"
|
|
881
|
+
(click)="onCommand('highlight', $event)"
|
|
882
|
+
></tiptap-button>
|
|
883
|
+
} @if (bubbleMenuConfig().separator && (bubbleMenuConfig().code ||
|
|
884
|
+
bubbleMenuConfig().link)) {
|
|
885
|
+
<div class="tiptap-separator"></div>
|
|
886
|
+
} @if (bubbleMenuConfig().code) {
|
|
887
|
+
<tiptap-button
|
|
888
|
+
icon="code"
|
|
889
|
+
title="Code"
|
|
890
|
+
[active]="isActive('code')"
|
|
891
|
+
(click)="onCommand('code', $event)"
|
|
892
|
+
></tiptap-button>
|
|
893
|
+
} @if (bubbleMenuConfig().link) {
|
|
894
|
+
<tiptap-button
|
|
895
|
+
icon="link"
|
|
896
|
+
title="Lien"
|
|
897
|
+
[active]="isActive('link')"
|
|
898
|
+
(click)="onCommand('link', $event)"
|
|
899
|
+
></tiptap-button>
|
|
900
|
+
}
|
|
901
|
+
</div>
|
|
902
|
+
`, isInline: true, dependencies: [{ kind: "component", type: TiptapButtonComponent, selector: "tiptap-button", inputs: ["icon", "title", "active", "disabled", "variant", "size", "iconSize"], outputs: ["onClick"] }] }); }
|
|
903
|
+
}
|
|
904
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: TiptapBubbleMenuComponent, decorators: [{
|
|
905
|
+
type: Component,
|
|
906
|
+
args: [{ selector: "tiptap-bubble-menu", standalone: true, imports: [TiptapButtonComponent], template: `
|
|
907
|
+
<div #menuRef class="bubble-menu">
|
|
908
|
+
@if (bubbleMenuConfig().bold) {
|
|
909
|
+
<tiptap-button
|
|
910
|
+
icon="format_bold"
|
|
911
|
+
title="Gras"
|
|
912
|
+
[active]="isActive('bold')"
|
|
913
|
+
(click)="onCommand('bold', $event)"
|
|
914
|
+
></tiptap-button>
|
|
915
|
+
} @if (bubbleMenuConfig().italic) {
|
|
916
|
+
<tiptap-button
|
|
917
|
+
icon="format_italic"
|
|
918
|
+
title="Italique"
|
|
919
|
+
[active]="isActive('italic')"
|
|
920
|
+
(click)="onCommand('italic', $event)"
|
|
921
|
+
></tiptap-button>
|
|
922
|
+
} @if (bubbleMenuConfig().underline) {
|
|
923
|
+
<tiptap-button
|
|
924
|
+
icon="format_underlined"
|
|
925
|
+
title="Souligné"
|
|
926
|
+
[active]="isActive('underline')"
|
|
927
|
+
(click)="onCommand('underline', $event)"
|
|
928
|
+
></tiptap-button>
|
|
929
|
+
} @if (bubbleMenuConfig().strike) {
|
|
930
|
+
<tiptap-button
|
|
931
|
+
icon="strikethrough_s"
|
|
932
|
+
title="Barré"
|
|
933
|
+
[active]="isActive('strike')"
|
|
934
|
+
(click)="onCommand('strike', $event)"
|
|
935
|
+
></tiptap-button>
|
|
936
|
+
} @if (bubbleMenuConfig().superscript) {
|
|
937
|
+
<tiptap-button
|
|
938
|
+
icon="superscript"
|
|
939
|
+
title="Exposant"
|
|
940
|
+
[active]="isActive('superscript')"
|
|
941
|
+
(click)="onCommand('superscript', $event)"
|
|
942
|
+
></tiptap-button>
|
|
943
|
+
} @if (bubbleMenuConfig().subscript) {
|
|
944
|
+
<tiptap-button
|
|
945
|
+
icon="subscript"
|
|
946
|
+
title="Indice"
|
|
947
|
+
[active]="isActive('subscript')"
|
|
948
|
+
(click)="onCommand('subscript', $event)"
|
|
949
|
+
></tiptap-button>
|
|
950
|
+
} @if (bubbleMenuConfig().highlight) {
|
|
951
|
+
<tiptap-button
|
|
952
|
+
icon="highlight"
|
|
953
|
+
title="Surbrillance"
|
|
954
|
+
[active]="isActive('highlight')"
|
|
955
|
+
(click)="onCommand('highlight', $event)"
|
|
956
|
+
></tiptap-button>
|
|
957
|
+
} @if (bubbleMenuConfig().separator && (bubbleMenuConfig().code ||
|
|
958
|
+
bubbleMenuConfig().link)) {
|
|
959
|
+
<div class="tiptap-separator"></div>
|
|
960
|
+
} @if (bubbleMenuConfig().code) {
|
|
961
|
+
<tiptap-button
|
|
962
|
+
icon="code"
|
|
963
|
+
title="Code"
|
|
964
|
+
[active]="isActive('code')"
|
|
965
|
+
(click)="onCommand('code', $event)"
|
|
966
|
+
></tiptap-button>
|
|
967
|
+
} @if (bubbleMenuConfig().link) {
|
|
968
|
+
<tiptap-button
|
|
969
|
+
icon="link"
|
|
970
|
+
title="Lien"
|
|
971
|
+
[active]="isActive('link')"
|
|
972
|
+
(click)="onCommand('link', $event)"
|
|
973
|
+
></tiptap-button>
|
|
974
|
+
}
|
|
975
|
+
</div>
|
|
976
|
+
` }]
|
|
977
|
+
}], ctorParameters: () => [], propDecorators: { menuRef: [{
|
|
978
|
+
type: ViewChild,
|
|
979
|
+
args: ["menuRef", { static: false }]
|
|
980
|
+
}] } });
|
|
981
|
+
|
|
565
982
|
class TiptapSeparatorComponent {
|
|
566
983
|
constructor() {
|
|
567
984
|
this.orientation = input("vertical");
|
|
@@ -1011,31 +1428,396 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.0", ngImpor
|
|
|
1011
1428
|
}]
|
|
1012
1429
|
}] });
|
|
1013
1430
|
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1431
|
+
class TiptapImageBubbleMenuComponent {
|
|
1432
|
+
constructor() {
|
|
1433
|
+
this.editor = input.required();
|
|
1434
|
+
this.config = input({
|
|
1435
|
+
changeImage: true,
|
|
1436
|
+
resizeSmall: true,
|
|
1437
|
+
resizeMedium: true,
|
|
1438
|
+
resizeLarge: true,
|
|
1439
|
+
resizeOriginal: true,
|
|
1440
|
+
deleteImage: true,
|
|
1441
|
+
separator: true,
|
|
1442
|
+
});
|
|
1443
|
+
this.tippyInstance = null;
|
|
1444
|
+
this.imageService = inject(ImageService);
|
|
1445
|
+
this.updateTimeout = null;
|
|
1446
|
+
this.imageBubbleMenuConfig = computed(() => ({
|
|
1447
|
+
changeImage: true,
|
|
1448
|
+
resizeSmall: true,
|
|
1449
|
+
resizeMedium: true,
|
|
1450
|
+
resizeLarge: true,
|
|
1451
|
+
resizeOriginal: true,
|
|
1452
|
+
deleteImage: true,
|
|
1453
|
+
separator: true,
|
|
1454
|
+
...this.config(),
|
|
1455
|
+
}));
|
|
1456
|
+
this.hasResizeButtons = computed(() => {
|
|
1457
|
+
const config = this.imageBubbleMenuConfig();
|
|
1458
|
+
return (config.resizeSmall ||
|
|
1459
|
+
config.resizeMedium ||
|
|
1460
|
+
config.resizeLarge ||
|
|
1461
|
+
config.resizeOriginal);
|
|
1462
|
+
});
|
|
1463
|
+
this.updateMenu = () => {
|
|
1464
|
+
// Debounce pour éviter les appels trop fréquents
|
|
1465
|
+
if (this.updateTimeout) {
|
|
1466
|
+
clearTimeout(this.updateTimeout);
|
|
1467
|
+
}
|
|
1468
|
+
this.updateTimeout = setTimeout(() => {
|
|
1469
|
+
const ed = this.editor();
|
|
1470
|
+
if (!ed)
|
|
1471
|
+
return;
|
|
1472
|
+
const isImageSelected = ed.isActive("resizableImage") || ed.isActive("image");
|
|
1473
|
+
const { from, to } = ed.state.selection;
|
|
1474
|
+
const hasTextSelection = from !== to;
|
|
1475
|
+
// Ne montrer le menu image que si :
|
|
1476
|
+
// - Une image est sélectionnée
|
|
1477
|
+
// - L'éditeur est éditable
|
|
1478
|
+
const shouldShow = isImageSelected && ed.isEditable;
|
|
1479
|
+
if (shouldShow) {
|
|
1480
|
+
this.showTippy();
|
|
1481
|
+
}
|
|
1482
|
+
else {
|
|
1483
|
+
this.hideTippy();
|
|
1484
|
+
}
|
|
1485
|
+
}, 10);
|
|
1486
|
+
};
|
|
1487
|
+
this.handleBlur = () => {
|
|
1488
|
+
// Masquer le menu quand l'éditeur perd le focus
|
|
1489
|
+
setTimeout(() => {
|
|
1490
|
+
this.hideTippy();
|
|
1491
|
+
}, 100);
|
|
1492
|
+
};
|
|
1493
|
+
effect(() => {
|
|
1494
|
+
const ed = this.editor();
|
|
1495
|
+
if (!ed)
|
|
1496
|
+
return;
|
|
1497
|
+
// Nettoyer les anciens listeners
|
|
1498
|
+
ed.off("selectionUpdate", this.updateMenu);
|
|
1499
|
+
ed.off("transaction", this.updateMenu);
|
|
1500
|
+
ed.off("focus", this.updateMenu);
|
|
1501
|
+
ed.off("blur", this.handleBlur);
|
|
1502
|
+
// Ajouter les nouveaux listeners
|
|
1503
|
+
ed.on("selectionUpdate", this.updateMenu);
|
|
1504
|
+
ed.on("transaction", this.updateMenu);
|
|
1505
|
+
ed.on("focus", this.updateMenu);
|
|
1506
|
+
ed.on("blur", this.handleBlur);
|
|
1507
|
+
// Ne pas appeler updateMenu() ici pour éviter l'affichage prématuré
|
|
1508
|
+
// Il sera appelé automatiquement quand l'éditeur sera prêt
|
|
1509
|
+
});
|
|
1510
|
+
}
|
|
1511
|
+
ngOnInit() {
|
|
1512
|
+
// Initialiser Tippy de manière synchrone après que le component soit ready
|
|
1513
|
+
this.initTippy();
|
|
1514
|
+
}
|
|
1515
|
+
ngOnDestroy() {
|
|
1516
|
+
const ed = this.editor();
|
|
1517
|
+
if (ed) {
|
|
1518
|
+
ed.off("selectionUpdate", this.updateMenu);
|
|
1519
|
+
ed.off("transaction", this.updateMenu);
|
|
1520
|
+
ed.off("focus", this.updateMenu);
|
|
1521
|
+
ed.off("blur", this.handleBlur);
|
|
1522
|
+
}
|
|
1523
|
+
// Nettoyer les timeouts
|
|
1524
|
+
if (this.updateTimeout) {
|
|
1525
|
+
clearTimeout(this.updateTimeout);
|
|
1526
|
+
}
|
|
1527
|
+
// Nettoyer Tippy
|
|
1528
|
+
if (this.tippyInstance) {
|
|
1529
|
+
this.tippyInstance.destroy();
|
|
1530
|
+
this.tippyInstance = null;
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
initTippy() {
|
|
1534
|
+
// Attendre que l'élément soit disponible
|
|
1535
|
+
if (!this.menuRef?.nativeElement) {
|
|
1536
|
+
setTimeout(() => this.initTippy(), 50);
|
|
1537
|
+
return;
|
|
1538
|
+
}
|
|
1539
|
+
const menuElement = this.menuRef.nativeElement;
|
|
1540
|
+
// S'assurer qu'il n'y a pas déjà une instance
|
|
1541
|
+
if (this.tippyInstance) {
|
|
1542
|
+
this.tippyInstance.destroy();
|
|
1543
|
+
}
|
|
1544
|
+
// Créer l'instance Tippy
|
|
1545
|
+
this.tippyInstance = tippy(document.body, {
|
|
1546
|
+
content: menuElement,
|
|
1547
|
+
trigger: "manual",
|
|
1548
|
+
placement: "top-start",
|
|
1549
|
+
appendTo: () => document.body,
|
|
1550
|
+
interactive: true,
|
|
1551
|
+
arrow: false,
|
|
1552
|
+
offset: [0, 8],
|
|
1553
|
+
hideOnClick: false,
|
|
1554
|
+
onShow: (instance) => {
|
|
1555
|
+
// S'assurer que les autres menus sont fermés
|
|
1556
|
+
this.hideOtherMenus();
|
|
1557
|
+
},
|
|
1558
|
+
getReferenceClientRect: () => this.getImageRect(),
|
|
1559
|
+
// Améliorer le positionnement avec scroll
|
|
1560
|
+
popperOptions: {
|
|
1561
|
+
modifiers: [
|
|
1562
|
+
{
|
|
1563
|
+
name: "preventOverflow",
|
|
1564
|
+
options: {
|
|
1565
|
+
boundary: "viewport",
|
|
1566
|
+
padding: 8,
|
|
1567
|
+
},
|
|
1568
|
+
},
|
|
1569
|
+
{
|
|
1570
|
+
name: "flip",
|
|
1571
|
+
options: {
|
|
1572
|
+
fallbackPlacements: ["bottom-start", "top-end", "bottom-end"],
|
|
1573
|
+
},
|
|
1574
|
+
},
|
|
1575
|
+
],
|
|
1576
|
+
},
|
|
1577
|
+
});
|
|
1578
|
+
// Maintenant que Tippy est initialisé, faire un premier check
|
|
1579
|
+
this.updateMenu();
|
|
1580
|
+
}
|
|
1581
|
+
getImageRect() {
|
|
1582
|
+
const ed = this.editor();
|
|
1583
|
+
if (!ed)
|
|
1584
|
+
return new DOMRect(0, 0, 0, 0);
|
|
1585
|
+
// Trouver l'image sélectionnée dans le DOM
|
|
1586
|
+
const { from } = ed.state.selection;
|
|
1587
|
+
// Fonction pour trouver toutes les images dans l'éditeur
|
|
1588
|
+
const getAllImages = () => {
|
|
1589
|
+
const editorElement = ed.view.dom;
|
|
1590
|
+
return Array.from(editorElement.querySelectorAll("img"));
|
|
1591
|
+
};
|
|
1592
|
+
// Fonction pour trouver l'image à la position spécifique
|
|
1593
|
+
const findImageAtPosition = () => {
|
|
1594
|
+
const allImages = getAllImages();
|
|
1595
|
+
for (const img of allImages) {
|
|
1596
|
+
try {
|
|
1597
|
+
// Obtenir la position ProseMirror de cette image
|
|
1598
|
+
const imgPos = ed.view.posAtDOM(img, 0);
|
|
1599
|
+
// Vérifier si cette image correspond à la position sélectionnée
|
|
1600
|
+
if (Math.abs(imgPos - from) <= 1) {
|
|
1601
|
+
return img;
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
catch (error) {
|
|
1605
|
+
// Continuer si on ne peut pas obtenir la position de cette image
|
|
1606
|
+
continue;
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1609
|
+
return null;
|
|
1610
|
+
};
|
|
1611
|
+
// Chercher l'image à la position exacte
|
|
1612
|
+
const imageElement = findImageAtPosition();
|
|
1613
|
+
if (imageElement) {
|
|
1614
|
+
return imageElement.getBoundingClientRect();
|
|
1615
|
+
}
|
|
1616
|
+
return new DOMRect(0, 0, 0, 0);
|
|
1617
|
+
}
|
|
1618
|
+
hideOtherMenus() {
|
|
1619
|
+
// Cette méthode peut être étendue pour fermer d'autres menus si nécessaire
|
|
1620
|
+
// Pour l'instant, elle sert de placeholder pour une future coordination entre menus
|
|
1621
|
+
}
|
|
1622
|
+
showTippy() {
|
|
1623
|
+
if (!this.tippyInstance)
|
|
1624
|
+
return;
|
|
1625
|
+
// Mettre à jour la position
|
|
1626
|
+
this.tippyInstance.setProps({
|
|
1627
|
+
getReferenceClientRect: () => this.getImageRect(),
|
|
1628
|
+
});
|
|
1629
|
+
this.tippyInstance.show();
|
|
1630
|
+
}
|
|
1631
|
+
hideTippy() {
|
|
1632
|
+
if (this.tippyInstance) {
|
|
1633
|
+
this.tippyInstance.hide();
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
onCommand(command, event) {
|
|
1637
|
+
event.preventDefault();
|
|
1638
|
+
const ed = this.editor();
|
|
1639
|
+
if (!ed)
|
|
1640
|
+
return;
|
|
1641
|
+
switch (command) {
|
|
1642
|
+
case "changeImage":
|
|
1643
|
+
this.changeImage();
|
|
1644
|
+
break;
|
|
1645
|
+
case "resizeSmall":
|
|
1646
|
+
this.imageService.resizeImageToSmall(ed);
|
|
1647
|
+
break;
|
|
1648
|
+
case "resizeMedium":
|
|
1649
|
+
this.imageService.resizeImageToMedium(ed);
|
|
1650
|
+
break;
|
|
1651
|
+
case "resizeLarge":
|
|
1652
|
+
this.imageService.resizeImageToLarge(ed);
|
|
1653
|
+
break;
|
|
1654
|
+
case "resizeOriginal":
|
|
1655
|
+
this.imageService.resizeImageToOriginal(ed);
|
|
1656
|
+
break;
|
|
1657
|
+
case "deleteImage":
|
|
1658
|
+
this.deleteImage();
|
|
1659
|
+
break;
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
async changeImage() {
|
|
1663
|
+
const ed = this.editor();
|
|
1664
|
+
if (!ed)
|
|
1665
|
+
return;
|
|
1666
|
+
try {
|
|
1667
|
+
// Utiliser la méthode spécifique pour remplacer une image existante
|
|
1668
|
+
await this.imageService.selectAndReplaceImage(ed, {
|
|
1669
|
+
quality: 0.8,
|
|
1670
|
+
maxWidth: 1920,
|
|
1671
|
+
maxHeight: 1080,
|
|
1672
|
+
accept: "image/*",
|
|
1673
|
+
});
|
|
1674
|
+
}
|
|
1675
|
+
catch (error) {
|
|
1676
|
+
console.error("Erreur lors du changement d'image:", error);
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
deleteImage() {
|
|
1680
|
+
const ed = this.editor();
|
|
1681
|
+
if (ed) {
|
|
1682
|
+
ed.chain().focus().deleteSelection().run();
|
|
1683
|
+
}
|
|
1684
|
+
}
|
|
1685
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: TiptapImageBubbleMenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1686
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.0", type: TiptapImageBubbleMenuComponent, isStandalone: true, selector: "tiptap-image-bubble-menu", inputs: { editor: { classPropertyName: "editor", publicName: "editor", isSignal: true, isRequired: true, transformFunction: null }, config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "menuRef", first: true, predicate: ["menuRef"], descendants: true }], ngImport: i0, template: `
|
|
1687
|
+
<div #menuRef class="bubble-menu">
|
|
1688
|
+
@if (imageBubbleMenuConfig().changeImage) {
|
|
1689
|
+
<tiptap-button
|
|
1690
|
+
icon="drive_file_rename_outline"
|
|
1691
|
+
title="Changer l'image"
|
|
1692
|
+
(click)="onCommand('changeImage', $event)"
|
|
1693
|
+
></tiptap-button>
|
|
1694
|
+
} @if (imageBubbleMenuConfig().separator && hasResizeButtons()) {
|
|
1695
|
+
<tiptap-separator></tiptap-separator>
|
|
1696
|
+
} @if (imageBubbleMenuConfig().resizeSmall) {
|
|
1697
|
+
<tiptap-button
|
|
1698
|
+
icon="crop_square"
|
|
1699
|
+
iconSize="small"
|
|
1700
|
+
title="Petite (300×200)"
|
|
1701
|
+
(click)="onCommand('resizeSmall', $event)"
|
|
1702
|
+
></tiptap-button>
|
|
1703
|
+
} @if (imageBubbleMenuConfig().resizeMedium) {
|
|
1704
|
+
<tiptap-button
|
|
1705
|
+
icon="crop_square"
|
|
1706
|
+
iconSize="medium"
|
|
1707
|
+
title="Moyenne (500×350)"
|
|
1708
|
+
(click)="onCommand('resizeMedium', $event)"
|
|
1709
|
+
></tiptap-button>
|
|
1710
|
+
} @if (imageBubbleMenuConfig().resizeLarge) {
|
|
1711
|
+
<tiptap-button
|
|
1712
|
+
icon="crop_square"
|
|
1713
|
+
iconSize="large"
|
|
1714
|
+
title="Grande (800×600)"
|
|
1715
|
+
(click)="onCommand('resizeLarge', $event)"
|
|
1716
|
+
></tiptap-button>
|
|
1717
|
+
} @if (imageBubbleMenuConfig().resizeOriginal) {
|
|
1718
|
+
<tiptap-button
|
|
1719
|
+
icon="photo_size_select_actual"
|
|
1720
|
+
title="Taille originale"
|
|
1721
|
+
(click)="onCommand('resizeOriginal', $event)"
|
|
1722
|
+
></tiptap-button>
|
|
1723
|
+
} @if (imageBubbleMenuConfig().separator &&
|
|
1724
|
+
imageBubbleMenuConfig().deleteImage) {
|
|
1725
|
+
<tiptap-separator></tiptap-separator>
|
|
1726
|
+
} @if (imageBubbleMenuConfig().deleteImage) {
|
|
1727
|
+
<tiptap-button
|
|
1728
|
+
icon="delete"
|
|
1729
|
+
title="Supprimer l'image"
|
|
1730
|
+
variant="danger"
|
|
1731
|
+
(click)="onCommand('deleteImage', $event)"
|
|
1732
|
+
></tiptap-button>
|
|
1733
|
+
}
|
|
1734
|
+
</div>
|
|
1735
|
+
`, isInline: true, dependencies: [{ kind: "component", type: TiptapButtonComponent, selector: "tiptap-button", inputs: ["icon", "title", "active", "disabled", "variant", "size", "iconSize"], outputs: ["onClick"] }, { kind: "component", type: TiptapSeparatorComponent, selector: "tiptap-separator", inputs: ["orientation", "size"] }] }); }
|
|
1736
|
+
}
|
|
1737
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: TiptapImageBubbleMenuComponent, decorators: [{
|
|
1738
|
+
type: Component,
|
|
1739
|
+
args: [{ selector: "tiptap-image-bubble-menu", standalone: true, imports: [TiptapButtonComponent, TiptapSeparatorComponent], template: `
|
|
1740
|
+
<div #menuRef class="bubble-menu">
|
|
1741
|
+
@if (imageBubbleMenuConfig().changeImage) {
|
|
1742
|
+
<tiptap-button
|
|
1743
|
+
icon="drive_file_rename_outline"
|
|
1744
|
+
title="Changer l'image"
|
|
1745
|
+
(click)="onCommand('changeImage', $event)"
|
|
1746
|
+
></tiptap-button>
|
|
1747
|
+
} @if (imageBubbleMenuConfig().separator && hasResizeButtons()) {
|
|
1748
|
+
<tiptap-separator></tiptap-separator>
|
|
1749
|
+
} @if (imageBubbleMenuConfig().resizeSmall) {
|
|
1750
|
+
<tiptap-button
|
|
1751
|
+
icon="crop_square"
|
|
1752
|
+
iconSize="small"
|
|
1753
|
+
title="Petite (300×200)"
|
|
1754
|
+
(click)="onCommand('resizeSmall', $event)"
|
|
1755
|
+
></tiptap-button>
|
|
1756
|
+
} @if (imageBubbleMenuConfig().resizeMedium) {
|
|
1757
|
+
<tiptap-button
|
|
1758
|
+
icon="crop_square"
|
|
1759
|
+
iconSize="medium"
|
|
1760
|
+
title="Moyenne (500×350)"
|
|
1761
|
+
(click)="onCommand('resizeMedium', $event)"
|
|
1762
|
+
></tiptap-button>
|
|
1763
|
+
} @if (imageBubbleMenuConfig().resizeLarge) {
|
|
1764
|
+
<tiptap-button
|
|
1765
|
+
icon="crop_square"
|
|
1766
|
+
iconSize="large"
|
|
1767
|
+
title="Grande (800×600)"
|
|
1768
|
+
(click)="onCommand('resizeLarge', $event)"
|
|
1769
|
+
></tiptap-button>
|
|
1770
|
+
} @if (imageBubbleMenuConfig().resizeOriginal) {
|
|
1771
|
+
<tiptap-button
|
|
1772
|
+
icon="photo_size_select_actual"
|
|
1773
|
+
title="Taille originale"
|
|
1774
|
+
(click)="onCommand('resizeOriginal', $event)"
|
|
1775
|
+
></tiptap-button>
|
|
1776
|
+
} @if (imageBubbleMenuConfig().separator &&
|
|
1777
|
+
imageBubbleMenuConfig().deleteImage) {
|
|
1778
|
+
<tiptap-separator></tiptap-separator>
|
|
1779
|
+
} @if (imageBubbleMenuConfig().deleteImage) {
|
|
1780
|
+
<tiptap-button
|
|
1781
|
+
icon="delete"
|
|
1782
|
+
title="Supprimer l'image"
|
|
1783
|
+
variant="danger"
|
|
1784
|
+
(click)="onCommand('deleteImage', $event)"
|
|
1785
|
+
></tiptap-button>
|
|
1786
|
+
}
|
|
1787
|
+
</div>
|
|
1788
|
+
` }]
|
|
1789
|
+
}], ctorParameters: () => [], propDecorators: { menuRef: [{
|
|
1790
|
+
type: ViewChild,
|
|
1791
|
+
args: ["menuRef", { static: false }]
|
|
1792
|
+
}] } });
|
|
1793
|
+
|
|
1794
|
+
const ENGLISH_TRANSLATIONS = {
|
|
1795
|
+
toolbar: {
|
|
1796
|
+
bold: "Bold",
|
|
1797
|
+
italic: "Italic",
|
|
1798
|
+
underline: "Underline",
|
|
1799
|
+
strike: "Strikethrough",
|
|
1800
|
+
code: "Code",
|
|
1801
|
+
superscript: "Superscript",
|
|
1802
|
+
subscript: "Subscript",
|
|
1803
|
+
highlight: "Highlight",
|
|
1804
|
+
heading1: "Heading 1",
|
|
1805
|
+
heading2: "Heading 2",
|
|
1806
|
+
heading3: "Heading 3",
|
|
1807
|
+
bulletList: "Bullet List",
|
|
1808
|
+
orderedList: "Ordered List",
|
|
1809
|
+
blockquote: "Blockquote",
|
|
1810
|
+
alignLeft: "Align Left",
|
|
1811
|
+
alignCenter: "Align Center",
|
|
1032
1812
|
alignRight: "Align Right",
|
|
1033
1813
|
alignJustify: "Align Justify",
|
|
1034
1814
|
link: "Add Link",
|
|
1035
1815
|
image: "Add Image",
|
|
1036
1816
|
horizontalRule: "Horizontal Rule",
|
|
1817
|
+
table: "Insert Table",
|
|
1037
1818
|
undo: "Undo",
|
|
1038
1819
|
redo: "Redo",
|
|
1820
|
+
clear: "Clear",
|
|
1039
1821
|
},
|
|
1040
1822
|
bubbleMenu: {
|
|
1041
1823
|
bold: "Bold",
|
|
@@ -1100,6 +1882,24 @@ const ENGLISH_TRANSLATIONS = {
|
|
|
1100
1882
|
description: "Add a horizontal line",
|
|
1101
1883
|
keywords: ["hr", "horizontal", "rule", "line", "separator"],
|
|
1102
1884
|
},
|
|
1885
|
+
table: {
|
|
1886
|
+
title: "Table",
|
|
1887
|
+
description: "Insert a table",
|
|
1888
|
+
keywords: ["table", "grid", "data", "rows", "columns"],
|
|
1889
|
+
},
|
|
1890
|
+
},
|
|
1891
|
+
table: {
|
|
1892
|
+
addRowBefore: "Add row before",
|
|
1893
|
+
addRowAfter: "Add row after",
|
|
1894
|
+
deleteRow: "Delete row",
|
|
1895
|
+
addColumnBefore: "Add column before",
|
|
1896
|
+
addColumnAfter: "Add column after",
|
|
1897
|
+
deleteColumn: "Delete column",
|
|
1898
|
+
deleteTable: "Delete table",
|
|
1899
|
+
toggleHeaderRow: "Toggle header row",
|
|
1900
|
+
toggleHeaderColumn: "Toggle header column",
|
|
1901
|
+
mergeCells: "Merge cells",
|
|
1902
|
+
splitCell: "Split cell",
|
|
1103
1903
|
},
|
|
1104
1904
|
imageUpload: {
|
|
1105
1905
|
selectImage: "Select Image",
|
|
@@ -1119,8 +1919,8 @@ const ENGLISH_TRANSLATIONS = {
|
|
|
1119
1919
|
},
|
|
1120
1920
|
editor: {
|
|
1121
1921
|
placeholder: "Start typing...",
|
|
1122
|
-
|
|
1123
|
-
|
|
1922
|
+
character: "character",
|
|
1923
|
+
word: "word",
|
|
1124
1924
|
imageLoadError: "Error loading image",
|
|
1125
1925
|
linkPrompt: "Enter link URL",
|
|
1126
1926
|
linkUrlPrompt: "Enter URL",
|
|
@@ -1161,8 +1961,10 @@ const FRENCH_TRANSLATIONS = {
|
|
|
1161
1961
|
link: "Ajouter un lien",
|
|
1162
1962
|
image: "Ajouter une image",
|
|
1163
1963
|
horizontalRule: "Ligne horizontale",
|
|
1964
|
+
table: "Insérer un tableau",
|
|
1164
1965
|
undo: "Annuler",
|
|
1165
1966
|
redo: "Refaire",
|
|
1967
|
+
clear: "Vider",
|
|
1166
1968
|
},
|
|
1167
1969
|
bubbleMenu: {
|
|
1168
1970
|
bold: "Gras",
|
|
@@ -1227,6 +2029,33 @@ const FRENCH_TRANSLATIONS = {
|
|
|
1227
2029
|
description: "Ajouter une ligne de séparation",
|
|
1228
2030
|
keywords: ["hr", "horizontal", "rule", "ligne", "séparation"],
|
|
1229
2031
|
},
|
|
2032
|
+
table: {
|
|
2033
|
+
title: "Tableau",
|
|
2034
|
+
description: "Insérer un tableau",
|
|
2035
|
+
keywords: [
|
|
2036
|
+
"table",
|
|
2037
|
+
"tableau",
|
|
2038
|
+
"grid",
|
|
2039
|
+
"grille",
|
|
2040
|
+
"data",
|
|
2041
|
+
"données",
|
|
2042
|
+
"rows",
|
|
2043
|
+
"colonnes",
|
|
2044
|
+
],
|
|
2045
|
+
},
|
|
2046
|
+
},
|
|
2047
|
+
table: {
|
|
2048
|
+
addRowBefore: "Ajouter une ligne avant",
|
|
2049
|
+
addRowAfter: "Ajouter une ligne après",
|
|
2050
|
+
deleteRow: "Supprimer la ligne",
|
|
2051
|
+
addColumnBefore: "Ajouter une colonne avant",
|
|
2052
|
+
addColumnAfter: "Ajouter une colonne après",
|
|
2053
|
+
deleteColumn: "Supprimer la colonne",
|
|
2054
|
+
deleteTable: "Supprimer le tableau",
|
|
2055
|
+
toggleHeaderRow: "Basculer ligne d'en-tête",
|
|
2056
|
+
toggleHeaderColumn: "Basculer colonne d'en-tête",
|
|
2057
|
+
mergeCells: "Fusionner les cellules",
|
|
2058
|
+
splitCell: "Diviser la cellule",
|
|
1230
2059
|
},
|
|
1231
2060
|
imageUpload: {
|
|
1232
2061
|
selectImage: "Sélectionner une image",
|
|
@@ -1246,8 +2075,8 @@ const FRENCH_TRANSLATIONS = {
|
|
|
1246
2075
|
},
|
|
1247
2076
|
editor: {
|
|
1248
2077
|
placeholder: "Commencez à écrire...",
|
|
1249
|
-
|
|
1250
|
-
|
|
2078
|
+
character: "caractère",
|
|
2079
|
+
word: "mot",
|
|
1251
2080
|
imageLoadError: "Erreur de chargement de l'image",
|
|
1252
2081
|
linkPrompt: "Entrez l'URL du lien",
|
|
1253
2082
|
linkUrlPrompt: "Entrez l'URL",
|
|
@@ -1280,6 +2109,7 @@ class TiptapI18nService {
|
|
|
1280
2109
|
this.toolbar = computed(() => this.translations().toolbar);
|
|
1281
2110
|
this.bubbleMenu = computed(() => this.translations().bubbleMenu);
|
|
1282
2111
|
this.slashCommands = computed(() => this.translations().slashCommands);
|
|
2112
|
+
this.table = computed(() => this.translations().table);
|
|
1283
2113
|
this.imageUpload = computed(() => this.translations().imageUpload);
|
|
1284
2114
|
this.editor = computed(() => this.translations().editor);
|
|
1285
2115
|
this.common = computed(() => this.translations().common);
|
|
@@ -1301,1004 +2131,251 @@ class TiptapI18nService {
|
|
|
1301
2131
|
[locale]: {
|
|
1302
2132
|
...current[locale],
|
|
1303
2133
|
...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());
|
|
2134
|
+
},
|
|
2135
|
+
}));
|
|
1489
2136
|
}
|
|
1490
|
-
|
|
1491
|
-
|
|
2137
|
+
detectBrowserLanguage() {
|
|
2138
|
+
const browserLang = navigator.language.toLowerCase();
|
|
2139
|
+
if (browserLang.startsWith("fr")) {
|
|
2140
|
+
this._currentLocale.set("fr");
|
|
2141
|
+
}
|
|
2142
|
+
else {
|
|
2143
|
+
this._currentLocale.set("en");
|
|
2144
|
+
}
|
|
1492
2145
|
}
|
|
1493
|
-
|
|
1494
|
-
|
|
2146
|
+
// Méthodes utilitaires pour les composants
|
|
2147
|
+
getToolbarTitle(key) {
|
|
2148
|
+
return this.translations().toolbar[key];
|
|
1495
2149
|
}
|
|
1496
|
-
|
|
1497
|
-
this.
|
|
2150
|
+
getBubbleMenuTitle(key) {
|
|
2151
|
+
return this.translations().bubbleMenu[key];
|
|
1498
2152
|
}
|
|
1499
|
-
|
|
1500
|
-
this.
|
|
2153
|
+
getSlashCommand(key) {
|
|
2154
|
+
return this.translations().slashCommands[key];
|
|
1501
2155
|
}
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
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" }); }
|
|
2158
|
+
}
|
|
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: () => [] });
|
|
2165
|
+
|
|
2166
|
+
class EditorCommandsService {
|
|
2167
|
+
// Méthodes pour vérifier l'état actif
|
|
2168
|
+
isActive(editor, name, attributes) {
|
|
2169
|
+
return editor.isActive(name, attributes);
|
|
1505
2170
|
}
|
|
1506
|
-
|
|
1507
|
-
|
|
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;
|
|
2230
|
+
}
|
|
1508
2231
|
}
|
|
1509
|
-
|
|
1510
|
-
|
|
2232
|
+
// Méthodes pour exécuter les commandes
|
|
2233
|
+
toggleBold(editor) {
|
|
2234
|
+
editor.chain().focus().toggleBold().run();
|
|
1511
2235
|
}
|
|
1512
|
-
|
|
1513
|
-
|
|
2236
|
+
toggleItalic(editor) {
|
|
2237
|
+
editor.chain().focus().toggleItalic().run();
|
|
1514
2238
|
}
|
|
1515
|
-
|
|
1516
|
-
|
|
2239
|
+
toggleStrike(editor) {
|
|
2240
|
+
editor.chain().focus().toggleStrike().run();
|
|
1517
2241
|
}
|
|
1518
|
-
|
|
1519
|
-
|
|
2242
|
+
toggleCode(editor) {
|
|
2243
|
+
editor.chain().focus().toggleCode().run();
|
|
1520
2244
|
}
|
|
1521
|
-
|
|
1522
|
-
|
|
2245
|
+
toggleHeading(editor, level) {
|
|
2246
|
+
editor.chain().focus().toggleHeading({ level }).run();
|
|
1523
2247
|
}
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
try {
|
|
1527
|
-
await this.imageService.selectAndUploadImage(this.editor());
|
|
1528
|
-
}
|
|
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");
|
|
1532
|
-
}
|
|
2248
|
+
toggleBulletList(editor) {
|
|
2249
|
+
editor.chain().focus().toggleBulletList().run();
|
|
1533
2250
|
}
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
this.imageUploaded.emit(result);
|
|
2251
|
+
toggleOrderedList(editor) {
|
|
2252
|
+
editor.chain().focus().toggleOrderedList().run();
|
|
1537
2253
|
}
|
|
1538
|
-
|
|
1539
|
-
|
|
2254
|
+
toggleBlockquote(editor) {
|
|
2255
|
+
editor.chain().focus().toggleBlockquote().run();
|
|
1540
2256
|
}
|
|
1541
|
-
|
|
1542
|
-
|
|
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"] }] }); }
|
|
1732
|
-
}
|
|
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 }] });
|
|
1926
|
-
|
|
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
|
-
});
|
|
2257
|
+
undo(editor) {
|
|
2258
|
+
editor.chain().focus().undo().run();
|
|
1956
2259
|
}
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
if (input) {
|
|
1960
|
-
input.click();
|
|
1961
|
-
}
|
|
2260
|
+
redo(editor) {
|
|
2261
|
+
editor.chain().focus().redo().run();
|
|
1962
2262
|
}
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
if (files && files.length > 0) {
|
|
1967
|
-
this.processFiles(Array.from(files));
|
|
1968
|
-
}
|
|
1969
|
-
// Reset input
|
|
1970
|
-
input.value = "";
|
|
2263
|
+
// Nouvelles méthodes pour les formatages supplémentaires
|
|
2264
|
+
toggleUnderline(editor) {
|
|
2265
|
+
editor.chain().focus().toggleUnderline().run();
|
|
1971
2266
|
}
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
event.stopPropagation();
|
|
1975
|
-
this.isDragOver.set(true);
|
|
2267
|
+
toggleSuperscript(editor) {
|
|
2268
|
+
editor.chain().focus().toggleSuperscript().run();
|
|
1976
2269
|
}
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
event.stopPropagation();
|
|
1980
|
-
this.isDragOver.set(false);
|
|
1981
|
-
const files = event.dataTransfer?.files;
|
|
1982
|
-
if (files && files.length > 0) {
|
|
1983
|
-
this.processFiles(Array.from(files));
|
|
1984
|
-
}
|
|
2270
|
+
toggleSubscript(editor) {
|
|
2271
|
+
editor.chain().focus().toggleSubscript().run();
|
|
1985
2272
|
}
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
event.stopPropagation();
|
|
1989
|
-
this.isDragOver.set(false);
|
|
2273
|
+
setTextAlign(editor, alignment) {
|
|
2274
|
+
editor.chain().focus().setTextAlign(alignment).run();
|
|
1990
2275
|
}
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
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;
|
|
2276
|
+
toggleLink(editor, url) {
|
|
2277
|
+
if (url) {
|
|
2278
|
+
editor.chain().focus().toggleLink({ href: url }).run();
|
|
1999
2279
|
}
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
if (
|
|
2004
|
-
|
|
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;
|
|
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();
|
|
2011
2285
|
}
|
|
2012
|
-
|
|
2013
|
-
this.processImage(file);
|
|
2014
|
-
});
|
|
2286
|
+
}
|
|
2015
2287
|
}
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
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();
|
|
2019
2297
|
}
|
|
2020
|
-
return allowedTypes.includes(file.type);
|
|
2021
2298
|
}
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
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);
|
|
2299
|
+
// Table commands
|
|
2300
|
+
insertTable(editor, rows = 3, cols = 3) {
|
|
2301
|
+
editor.chain().focus().insertTable({ rows, cols }).run();
|
|
2097
2302
|
}
|
|
2098
|
-
|
|
2099
|
-
|
|
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];
|
|
2303
|
+
addColumnBefore(editor) {
|
|
2304
|
+
editor.chain().focus().addColumnBefore().run();
|
|
2105
2305
|
}
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
this.error.emit(message);
|
|
2109
|
-
this.isUploading.set(false);
|
|
2110
|
-
this.uploadProgress.set(0);
|
|
2111
|
-
// Auto-clear après 5 secondes
|
|
2112
|
-
setTimeout(() => {
|
|
2113
|
-
this.clearError();
|
|
2114
|
-
}, 5000);
|
|
2306
|
+
addColumnAfter(editor) {
|
|
2307
|
+
editor.chain().focus().addColumnAfter().run();
|
|
2115
2308
|
}
|
|
2116
|
-
|
|
2117
|
-
|
|
2309
|
+
deleteColumn(editor) {
|
|
2310
|
+
editor.chain().focus().deleteColumn().run();
|
|
2118
2311
|
}
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
this.previewInfo.set("");
|
|
2312
|
+
addRowBefore(editor) {
|
|
2313
|
+
editor.chain().focus().addRowBefore().run();
|
|
2122
2314
|
}
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
(
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
</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"] }] }); }
|
|
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().setContent("", true).run(); // ✅ Forcer l'émission de l'événement
|
|
2342
|
+
}
|
|
2343
|
+
// Méthodes de base de l'éditeur
|
|
2344
|
+
focus(editor) {
|
|
2345
|
+
editor.chain().focus().run();
|
|
2346
|
+
}
|
|
2347
|
+
blur(editor) {
|
|
2348
|
+
editor.chain().blur().run();
|
|
2349
|
+
}
|
|
2350
|
+
setContent(editor, content, emitUpdate = true) {
|
|
2351
|
+
editor.chain().focus().setContent(content, emitUpdate).run();
|
|
2352
|
+
}
|
|
2353
|
+
setEditable(editor, editable) {
|
|
2354
|
+
editor.setEditable(editable);
|
|
2355
|
+
}
|
|
2356
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: EditorCommandsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
2357
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: EditorCommandsService, providedIn: "root" }); }
|
|
2195
2358
|
}
|
|
2196
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type:
|
|
2197
|
-
type:
|
|
2198
|
-
args: [{
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
<tiptap-button
|
|
2202
|
-
icon="image"
|
|
2203
|
-
title="Ajouter une image"
|
|
2204
|
-
[disabled]="isUploading()"
|
|
2205
|
-
(onClick)="triggerFileInput()"
|
|
2206
|
-
/>
|
|
2207
|
-
|
|
2208
|
-
<!-- Input file caché -->
|
|
2209
|
-
<input
|
|
2210
|
-
#fileInput
|
|
2211
|
-
type="file"
|
|
2212
|
-
[accept]="acceptedTypes()"
|
|
2213
|
-
[multiple]="config().multiple"
|
|
2214
|
-
(change)="onFileSelected($event)"
|
|
2215
|
-
style="display: none;"
|
|
2216
|
-
/>
|
|
2217
|
-
|
|
2218
|
-
<!-- Zone de drag & drop -->
|
|
2219
|
-
@if (config().enableDragDrop && isDragOver()) {
|
|
2220
|
-
<div
|
|
2221
|
-
class="drag-overlay"
|
|
2222
|
-
(dragover)="onDragOver($event)"
|
|
2223
|
-
(drop)="onDrop($event)"
|
|
2224
|
-
(dragleave)="onDragLeave($event)"
|
|
2225
|
-
>
|
|
2226
|
-
<div class="drag-content">
|
|
2227
|
-
<span class="material-symbols-outlined">cloud_upload</span>
|
|
2228
|
-
<p>Déposez votre image ici</p>
|
|
2229
|
-
</div>
|
|
2230
|
-
</div>
|
|
2231
|
-
}
|
|
2232
|
-
|
|
2233
|
-
<!-- Barre de progression -->
|
|
2234
|
-
@if (isUploading() && uploadProgress() > 0) {
|
|
2235
|
-
<div class="upload-progress">
|
|
2236
|
-
<div class="progress-bar">
|
|
2237
|
-
<div class="progress-fill" [style.width.%]="uploadProgress()"></div>
|
|
2238
|
-
</div>
|
|
2239
|
-
<div class="progress-text">{{ uploadProgress() }}%</div>
|
|
2240
|
-
</div>
|
|
2241
|
-
}
|
|
2242
|
-
|
|
2243
|
-
<!-- Prévisualisation -->
|
|
2244
|
-
@if (config().showPreview && previewImage()) {
|
|
2245
|
-
<div class="image-preview">
|
|
2246
|
-
<img [src]="previewImage()" alt="Prévisualisation" />
|
|
2247
|
-
<div class="preview-info">
|
|
2248
|
-
<span>{{ previewInfo() }}</span>
|
|
2249
|
-
</div>
|
|
2250
|
-
<button
|
|
2251
|
-
class="preview-close"
|
|
2252
|
-
(click)="clearPreview()"
|
|
2253
|
-
title="Fermer la prévisualisation"
|
|
2254
|
-
>
|
|
2255
|
-
<span class="material-symbols-outlined">close</span>
|
|
2256
|
-
</button>
|
|
2257
|
-
</div>
|
|
2258
|
-
}
|
|
2259
|
-
|
|
2260
|
-
<!-- Messages d'erreur -->
|
|
2261
|
-
@if (errorMessage()) {
|
|
2262
|
-
<div class="error-message">
|
|
2263
|
-
<span class="material-symbols-outlined">error</span>
|
|
2264
|
-
{{ errorMessage() }}
|
|
2265
|
-
</div>
|
|
2266
|
-
}
|
|
2267
|
-
</div>
|
|
2268
|
-
`, styles: [".image-upload-container{position:relative;display:inline-block}.drag-overlay{position:fixed;inset:0;background:#3182ce1a;border:2px dashed #3182ce;border-radius:6px;display:flex;align-items:center;justify-content:center;z-index:1000;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px)}.drag-content{text-align:center;color:#3182ce;font-weight:600}.drag-content .material-symbols-outlined{font-size:48px;margin-bottom:16px}.drag-content p{margin:0;font-size:18px}.upload-progress{position:absolute;top:100%;left:0;background:#fff;border:1px solid #e2e8f0;border-radius:8px;padding:12px;margin-top:8px;z-index:100;min-width:200px;box-shadow:0 4px 12px #00000026}.progress-bar{width:100%;height:6px;background:#e2e8f0;border-radius:3px;overflow:hidden;margin-bottom:8px}.progress-fill{height:100%;background:#3182ce;border-radius:3px;transition:width .3s ease}.progress-text{font-size:12px;color:#4a5568;text-align:center}.image-preview{position:absolute;top:100%;left:0;background:#fff;border:1px solid #e2e8f0;border-radius:8px;box-shadow:0 4px 12px #00000026;padding:8px;margin-top:8px;z-index:100;min-width:200px}.image-preview img{max-width:200px;max-height:150px;border-radius:4px;display:block}.preview-info{margin-top:8px;font-size:11px;color:#718096;text-align:center}.preview-close{position:absolute;top:4px;right:4px;background:#000000b3;color:#fff;border:none;border-radius:50%;width:24px;height:24px;cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:14px}.preview-close:hover{background:#000000e6}.error-message{position:absolute;top:100%;left:0;background:#fed7d7;color:#c53030;border:1px solid #feb2b2;border-radius:6px;padding:8px 12px;margin-top:8px;font-size:12px;display:flex;align-items:center;gap:6px;z-index:100;min-width:200px}.error-message .material-symbols-outlined{font-size:16px}\n"] }]
|
|
2359
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: EditorCommandsService, decorators: [{
|
|
2360
|
+
type: Injectable,
|
|
2361
|
+
args: [{
|
|
2362
|
+
providedIn: "root",
|
|
2363
|
+
}]
|
|
2269
2364
|
}] });
|
|
2270
2365
|
|
|
2271
|
-
class
|
|
2272
|
-
// Effect comme propriété de classe pour éviter l'erreur d'injection context
|
|
2366
|
+
class TiptapTableBubbleMenuComponent {
|
|
2273
2367
|
constructor() {
|
|
2368
|
+
// Inputs
|
|
2274
2369
|
this.editor = input.required();
|
|
2275
|
-
this.config = input({
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
code: true,
|
|
2281
|
-
superscript: false,
|
|
2282
|
-
subscript: false,
|
|
2283
|
-
highlight: true,
|
|
2284
|
-
link: true,
|
|
2285
|
-
separator: true,
|
|
2286
|
-
});
|
|
2370
|
+
this.config = input({});
|
|
2371
|
+
// Services
|
|
2372
|
+
this.i18nService = inject(TiptapI18nService);
|
|
2373
|
+
this.commandsService = inject(EditorCommandsService);
|
|
2374
|
+
// Tippy instance
|
|
2287
2375
|
this.tippyInstance = null;
|
|
2288
2376
|
this.updateTimeout = null;
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
italic: true,
|
|
2292
|
-
underline: true,
|
|
2293
|
-
strike: true,
|
|
2294
|
-
code: true,
|
|
2295
|
-
superscript: false,
|
|
2296
|
-
subscript: false,
|
|
2297
|
-
highlight: true,
|
|
2298
|
-
link: true,
|
|
2299
|
-
separator: true,
|
|
2300
|
-
...this.config(),
|
|
2301
|
-
}));
|
|
2377
|
+
// Signaux
|
|
2378
|
+
this.i18n = this.i18nService;
|
|
2302
2379
|
this.updateMenu = () => {
|
|
2303
2380
|
// Debounce pour éviter les appels trop fréquents
|
|
2304
2381
|
if (this.updateTimeout) {
|
|
@@ -2308,14 +2385,25 @@ class TiptapBubbleMenuComponent {
|
|
|
2308
2385
|
const ed = this.editor();
|
|
2309
2386
|
if (!ed)
|
|
2310
2387
|
return;
|
|
2388
|
+
const isTableSelected = ed.isActive("table") ||
|
|
2389
|
+
ed.isActive("tableCell") ||
|
|
2390
|
+
ed.isActive("tableHeader");
|
|
2391
|
+
// Vérifier s'il y a une sélection de cellules (priorité au menu de cellules)
|
|
2311
2392
|
const { from, to } = ed.state.selection;
|
|
2312
|
-
const
|
|
2313
|
-
const
|
|
2314
|
-
//
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
//
|
|
2318
|
-
|
|
2393
|
+
const hasCellSelection = from !== to;
|
|
2394
|
+
const isTableCell = ed.isActive("tableCell") || ed.isActive("tableHeader");
|
|
2395
|
+
// Vérifier si la sélection traverse plusieurs cellules
|
|
2396
|
+
const selectionSize = to - from;
|
|
2397
|
+
const hasMultiCellSelection = hasCellSelection && selectionSize > 1;
|
|
2398
|
+
// Ne montrer le menu de table que si :
|
|
2399
|
+
// 1. Une table est sélectionnée
|
|
2400
|
+
// 2. L'éditeur est éditable
|
|
2401
|
+
// 3. Il n'y a PAS de sélection de cellules (priorité au menu de cellules)
|
|
2402
|
+
// 4. Il n'y a PAS de sélection multi-cellules
|
|
2403
|
+
const shouldShow = isTableSelected &&
|
|
2404
|
+
ed.isEditable &&
|
|
2405
|
+
!(hasCellSelection && isTableCell) &&
|
|
2406
|
+
!hasMultiCellSelection;
|
|
2319
2407
|
if (shouldShow) {
|
|
2320
2408
|
this.showTippy();
|
|
2321
2409
|
}
|
|
@@ -2330,57 +2418,41 @@ class TiptapBubbleMenuComponent {
|
|
|
2330
2418
|
this.hideTippy();
|
|
2331
2419
|
}, 100);
|
|
2332
2420
|
};
|
|
2421
|
+
// Effet pour mettre à jour le menu quand l'éditeur change
|
|
2333
2422
|
effect(() => {
|
|
2334
|
-
const
|
|
2335
|
-
if (
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
ed.on("focus", this.updateMenu);
|
|
2346
|
-
ed.on("blur", this.handleBlur);
|
|
2347
|
-
// Ne pas appeler updateMenu() ici pour éviter l'affichage prématuré
|
|
2348
|
-
// Il sera appelé automatiquement quand l'éditeur sera prêt
|
|
2423
|
+
const editor = this.editor();
|
|
2424
|
+
if (editor) {
|
|
2425
|
+
// Nettoyer les anciens listeners
|
|
2426
|
+
editor.off("selectionUpdate", this.updateMenu);
|
|
2427
|
+
editor.off("focus", this.updateMenu);
|
|
2428
|
+
editor.off("blur", this.handleBlur);
|
|
2429
|
+
// Ajouter les nouveaux listeners
|
|
2430
|
+
editor.on("selectionUpdate", this.updateMenu);
|
|
2431
|
+
editor.on("focus", this.updateMenu);
|
|
2432
|
+
editor.on("blur", this.handleBlur);
|
|
2433
|
+
}
|
|
2349
2434
|
});
|
|
2350
2435
|
}
|
|
2351
2436
|
ngOnInit() {
|
|
2352
|
-
// Initialiser Tippy de manière synchrone après que le component soit ready
|
|
2353
2437
|
this.initTippy();
|
|
2354
2438
|
}
|
|
2355
2439
|
ngOnDestroy() {
|
|
2356
|
-
const
|
|
2357
|
-
if (
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
}
|
|
2363
|
-
// Nettoyer les timeouts
|
|
2364
|
-
if (this.updateTimeout) {
|
|
2365
|
-
clearTimeout(this.updateTimeout);
|
|
2440
|
+
const editor = this.editor();
|
|
2441
|
+
if (editor) {
|
|
2442
|
+
// Nettoyer les événements
|
|
2443
|
+
editor.off("selectionUpdate", this.updateMenu);
|
|
2444
|
+
editor.off("focus", this.updateMenu);
|
|
2445
|
+
editor.off("blur", this.handleBlur);
|
|
2366
2446
|
}
|
|
2367
|
-
// Nettoyer Tippy
|
|
2368
2447
|
if (this.tippyInstance) {
|
|
2369
2448
|
this.tippyInstance.destroy();
|
|
2370
|
-
|
|
2449
|
+
}
|
|
2450
|
+
if (this.updateTimeout) {
|
|
2451
|
+
clearTimeout(this.updateTimeout);
|
|
2371
2452
|
}
|
|
2372
2453
|
}
|
|
2373
2454
|
initTippy() {
|
|
2374
|
-
|
|
2375
|
-
if (!this.menuRef?.nativeElement) {
|
|
2376
|
-
setTimeout(() => this.initTippy(), 50);
|
|
2377
|
-
return;
|
|
2378
|
-
}
|
|
2379
|
-
const menuElement = this.menuRef.nativeElement;
|
|
2380
|
-
// S'assurer qu'il n'y a pas déjà une instance
|
|
2381
|
-
if (this.tippyInstance) {
|
|
2382
|
-
this.tippyInstance.destroy();
|
|
2383
|
-
}
|
|
2455
|
+
const menuElement = this.menuElement.nativeElement;
|
|
2384
2456
|
// Créer l'instance Tippy
|
|
2385
2457
|
this.tippyInstance = tippy(document.body, {
|
|
2386
2458
|
content: menuElement,
|
|
@@ -2390,13 +2462,13 @@ class TiptapBubbleMenuComponent {
|
|
|
2390
2462
|
interactive: true,
|
|
2391
2463
|
arrow: false,
|
|
2392
2464
|
offset: [0, 8],
|
|
2465
|
+
maxWidth: "none",
|
|
2393
2466
|
hideOnClick: false,
|
|
2394
2467
|
onShow: (instance) => {
|
|
2395
2468
|
// S'assurer que les autres menus sont fermés
|
|
2396
2469
|
this.hideOtherMenus();
|
|
2397
2470
|
},
|
|
2398
|
-
getReferenceClientRect: () => this.
|
|
2399
|
-
// Améliorer le positionnement avec scroll
|
|
2471
|
+
getReferenceClientRect: () => this.getTableRect(),
|
|
2400
2472
|
popperOptions: {
|
|
2401
2473
|
modifiers: [
|
|
2402
2474
|
{
|
|
@@ -2418,257 +2490,275 @@ class TiptapBubbleMenuComponent {
|
|
|
2418
2490
|
// Maintenant que Tippy est initialisé, faire un premier check
|
|
2419
2491
|
this.updateMenu();
|
|
2420
2492
|
}
|
|
2421
|
-
|
|
2422
|
-
const
|
|
2423
|
-
if (!
|
|
2493
|
+
getTableRect() {
|
|
2494
|
+
const ed = this.editor();
|
|
2495
|
+
if (!ed)
|
|
2424
2496
|
return new DOMRect(0, 0, 0, 0);
|
|
2497
|
+
// Méthode 1: Utiliser coordsAtPos (méthode native ProseMirror)
|
|
2498
|
+
const { from } = ed.state.selection;
|
|
2499
|
+
const coords = ed.view.coordsAtPos(from);
|
|
2500
|
+
// Trouver la table qui contient cette position
|
|
2501
|
+
const editorElement = ed.view.dom;
|
|
2502
|
+
const tables = Array.from(editorElement.querySelectorAll("table"));
|
|
2503
|
+
for (let i = 0; i < tables.length; i++) {
|
|
2504
|
+
const table = tables[i];
|
|
2505
|
+
try {
|
|
2506
|
+
const tableRect = table.getBoundingClientRect();
|
|
2507
|
+
// Vérifier si la position ProseMirror est dans cette table
|
|
2508
|
+
const isInside = coords.left >= tableRect.left &&
|
|
2509
|
+
coords.left <= tableRect.right &&
|
|
2510
|
+
coords.top >= tableRect.top &&
|
|
2511
|
+
coords.top <= tableRect.bottom;
|
|
2512
|
+
if (isInside) {
|
|
2513
|
+
return tableRect;
|
|
2514
|
+
}
|
|
2515
|
+
}
|
|
2516
|
+
catch (error) {
|
|
2517
|
+
continue;
|
|
2518
|
+
}
|
|
2425
2519
|
}
|
|
2426
|
-
|
|
2427
|
-
|
|
2520
|
+
// Fallback : utiliser la méthode DOM si ProseMirror échoue
|
|
2521
|
+
const selection = window.getSelection();
|
|
2522
|
+
if (selection && selection.rangeCount > 0) {
|
|
2523
|
+
const range = selection.getRangeAt(0);
|
|
2524
|
+
const rect = range.getBoundingClientRect();
|
|
2525
|
+
if (rect.width > 0 && rect.height > 0) {
|
|
2526
|
+
return rect;
|
|
2527
|
+
}
|
|
2528
|
+
}
|
|
2529
|
+
// Dernier fallback : première table
|
|
2530
|
+
if (tables.length > 0) {
|
|
2531
|
+
return tables[0].getBoundingClientRect();
|
|
2532
|
+
}
|
|
2533
|
+
return new DOMRect(0, 0, 0, 0);
|
|
2428
2534
|
}
|
|
2429
2535
|
hideOtherMenus() {
|
|
2430
2536
|
// 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
2537
|
}
|
|
2433
2538
|
showTippy() {
|
|
2434
2539
|
if (!this.tippyInstance)
|
|
2435
|
-
return;
|
|
2436
|
-
// Mettre à jour la position
|
|
2437
|
-
this.tippyInstance.setProps({
|
|
2438
|
-
getReferenceClientRect: () => this.
|
|
2439
|
-
});
|
|
2440
|
-
this.tippyInstance.show();
|
|
2441
|
-
}
|
|
2442
|
-
hideTippy() {
|
|
2443
|
-
if (this.tippyInstance)
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
}
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
}
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
<tiptap-button
|
|
2508
|
-
icon="
|
|
2509
|
-
title="
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
></tiptap-
|
|
2527
|
-
}
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
<
|
|
2544
|
-
}
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
></tiptap-button>
|
|
2558
|
-
}
|
|
2559
|
-
</div>
|
|
2560
|
-
`, isInline: true, dependencies: [{ kind: "component", type: TiptapButtonComponent, selector: "tiptap-button", inputs: ["icon", "title", "active", "disabled", "variant", "size", "iconSize"], outputs: ["onClick"] }] }); }
|
|
2540
|
+
return;
|
|
2541
|
+
// Mettre à jour la position
|
|
2542
|
+
this.tippyInstance.setProps({
|
|
2543
|
+
getReferenceClientRect: () => this.getTableRect(),
|
|
2544
|
+
});
|
|
2545
|
+
this.tippyInstance.show();
|
|
2546
|
+
}
|
|
2547
|
+
hideTippy() {
|
|
2548
|
+
if (!this.tippyInstance)
|
|
2549
|
+
return;
|
|
2550
|
+
this.tippyInstance.hide();
|
|
2551
|
+
}
|
|
2552
|
+
// Actions de lignes
|
|
2553
|
+
addRowBefore() {
|
|
2554
|
+
this.commandsService.addRowBefore(this.editor());
|
|
2555
|
+
}
|
|
2556
|
+
addRowAfter() {
|
|
2557
|
+
this.commandsService.addRowAfter(this.editor());
|
|
2558
|
+
}
|
|
2559
|
+
deleteRow() {
|
|
2560
|
+
this.commandsService.deleteRow(this.editor());
|
|
2561
|
+
}
|
|
2562
|
+
// Actions de colonnes
|
|
2563
|
+
addColumnBefore() {
|
|
2564
|
+
this.commandsService.addColumnBefore(this.editor());
|
|
2565
|
+
}
|
|
2566
|
+
addColumnAfter() {
|
|
2567
|
+
this.commandsService.addColumnAfter(this.editor());
|
|
2568
|
+
}
|
|
2569
|
+
deleteColumn() {
|
|
2570
|
+
this.commandsService.deleteColumn(this.editor());
|
|
2571
|
+
}
|
|
2572
|
+
// Actions de headers
|
|
2573
|
+
toggleHeaderRow() {
|
|
2574
|
+
this.commandsService.toggleHeaderRow(this.editor());
|
|
2575
|
+
}
|
|
2576
|
+
toggleHeaderColumn() {
|
|
2577
|
+
this.commandsService.toggleHeaderColumn(this.editor());
|
|
2578
|
+
}
|
|
2579
|
+
// Actions de table
|
|
2580
|
+
deleteTable() {
|
|
2581
|
+
this.commandsService.deleteTable(this.editor());
|
|
2582
|
+
}
|
|
2583
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: TiptapTableBubbleMenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
2584
|
+
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: `
|
|
2585
|
+
<div #menuElement class="bubble-menu">
|
|
2586
|
+
<!-- Actions de lignes -->
|
|
2587
|
+
@if (config().addRowBefore !== false) {
|
|
2588
|
+
<tiptap-button
|
|
2589
|
+
icon="add_row_above"
|
|
2590
|
+
title="{{ i18n.table().addRowBefore }}"
|
|
2591
|
+
(click)="addRowBefore()"
|
|
2592
|
+
></tiptap-button>
|
|
2593
|
+
} @if (config().addRowAfter !== false) {
|
|
2594
|
+
<tiptap-button
|
|
2595
|
+
icon="add_row_below"
|
|
2596
|
+
title="{{ i18n.table().addRowAfter }}"
|
|
2597
|
+
(click)="addRowAfter()"
|
|
2598
|
+
></tiptap-button>
|
|
2599
|
+
} @if (config().deleteRow !== false) {
|
|
2600
|
+
<tiptap-button
|
|
2601
|
+
icon="delete"
|
|
2602
|
+
title="{{ i18n.table().deleteRow }}"
|
|
2603
|
+
variant="danger"
|
|
2604
|
+
(click)="deleteRow()"
|
|
2605
|
+
></tiptap-button>
|
|
2606
|
+
} @if (config().separator !== false) {
|
|
2607
|
+
<tiptap-separator></tiptap-separator>
|
|
2608
|
+
}
|
|
2609
|
+
|
|
2610
|
+
<!-- Actions de colonnes -->
|
|
2611
|
+
@if (config().addColumnBefore !== false) {
|
|
2612
|
+
<tiptap-button
|
|
2613
|
+
icon="add_column_left"
|
|
2614
|
+
title="{{ i18n.table().addColumnBefore }}"
|
|
2615
|
+
(click)="addColumnBefore()"
|
|
2616
|
+
></tiptap-button>
|
|
2617
|
+
} @if (config().addColumnAfter !== false) {
|
|
2618
|
+
<tiptap-button
|
|
2619
|
+
icon="add_column_right"
|
|
2620
|
+
title="{{ i18n.table().addColumnAfter }}"
|
|
2621
|
+
(click)="addColumnAfter()"
|
|
2622
|
+
></tiptap-button>
|
|
2623
|
+
} @if (config().deleteColumn !== false) {
|
|
2624
|
+
<tiptap-button
|
|
2625
|
+
icon="delete"
|
|
2626
|
+
title="{{ i18n.table().deleteColumn }}"
|
|
2627
|
+
variant="danger"
|
|
2628
|
+
(click)="deleteColumn()"
|
|
2629
|
+
></tiptap-button>
|
|
2630
|
+
} @if (config().separator !== false) {
|
|
2631
|
+
<tiptap-separator></tiptap-separator>
|
|
2632
|
+
}
|
|
2633
|
+
|
|
2634
|
+
<!-- Actions de cellules -->
|
|
2635
|
+
@if (config().toggleHeaderRow !== false) {
|
|
2636
|
+
<tiptap-button
|
|
2637
|
+
icon="toolbar"
|
|
2638
|
+
title="{{ i18n.table().toggleHeaderRow }}"
|
|
2639
|
+
(click)="toggleHeaderRow()"
|
|
2640
|
+
></tiptap-button>
|
|
2641
|
+
} @if (config().toggleHeaderColumn !== false) {
|
|
2642
|
+
<tiptap-button
|
|
2643
|
+
icon="dock_to_right"
|
|
2644
|
+
title="{{ i18n.table().toggleHeaderColumn }}"
|
|
2645
|
+
(click)="toggleHeaderColumn()"
|
|
2646
|
+
></tiptap-button>
|
|
2647
|
+
} @if (config().separator !== false && config().deleteTable !== false) {
|
|
2648
|
+
<tiptap-separator></tiptap-separator>
|
|
2649
|
+
}
|
|
2650
|
+
|
|
2651
|
+
<!-- Actions de table -->
|
|
2652
|
+
@if (config().deleteTable !== false) {
|
|
2653
|
+
<tiptap-button
|
|
2654
|
+
icon="delete_forever"
|
|
2655
|
+
title="{{ i18n.table().deleteTable }}"
|
|
2656
|
+
variant="danger"
|
|
2657
|
+
(click)="deleteTable()"
|
|
2658
|
+
></tiptap-button>
|
|
2659
|
+
}
|
|
2660
|
+
</div>
|
|
2661
|
+
`, 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
2662
|
}
|
|
2562
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type:
|
|
2663
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: TiptapTableBubbleMenuComponent, decorators: [{
|
|
2563
2664
|
type: Component,
|
|
2564
|
-
args: [{ selector: "tiptap-bubble-menu", standalone: true, imports: [TiptapButtonComponent], template: `
|
|
2565
|
-
<div #
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
(click)="
|
|
2572
|
-
></tiptap-button>
|
|
2573
|
-
} @if (
|
|
2574
|
-
<tiptap-button
|
|
2575
|
-
icon="
|
|
2576
|
-
title="
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2665
|
+
args: [{ selector: "tiptap-table-bubble-menu", standalone: true, imports: [CommonModule, TiptapButtonComponent, TiptapSeparatorComponent], template: `
|
|
2666
|
+
<div #menuElement class="bubble-menu">
|
|
2667
|
+
<!-- Actions de lignes -->
|
|
2668
|
+
@if (config().addRowBefore !== false) {
|
|
2669
|
+
<tiptap-button
|
|
2670
|
+
icon="add_row_above"
|
|
2671
|
+
title="{{ i18n.table().addRowBefore }}"
|
|
2672
|
+
(click)="addRowBefore()"
|
|
2673
|
+
></tiptap-button>
|
|
2674
|
+
} @if (config().addRowAfter !== false) {
|
|
2675
|
+
<tiptap-button
|
|
2676
|
+
icon="add_row_below"
|
|
2677
|
+
title="{{ i18n.table().addRowAfter }}"
|
|
2678
|
+
(click)="addRowAfter()"
|
|
2679
|
+
></tiptap-button>
|
|
2680
|
+
} @if (config().deleteRow !== false) {
|
|
2681
|
+
<tiptap-button
|
|
2682
|
+
icon="delete"
|
|
2683
|
+
title="{{ i18n.table().deleteRow }}"
|
|
2684
|
+
variant="danger"
|
|
2685
|
+
(click)="deleteRow()"
|
|
2686
|
+
></tiptap-button>
|
|
2687
|
+
} @if (config().separator !== false) {
|
|
2688
|
+
<tiptap-separator></tiptap-separator>
|
|
2689
|
+
}
|
|
2690
|
+
|
|
2691
|
+
<!-- Actions de colonnes -->
|
|
2692
|
+
@if (config().addColumnBefore !== false) {
|
|
2693
|
+
<tiptap-button
|
|
2694
|
+
icon="add_column_left"
|
|
2695
|
+
title="{{ i18n.table().addColumnBefore }}"
|
|
2696
|
+
(click)="addColumnBefore()"
|
|
2697
|
+
></tiptap-button>
|
|
2698
|
+
} @if (config().addColumnAfter !== false) {
|
|
2699
|
+
<tiptap-button
|
|
2700
|
+
icon="add_column_right"
|
|
2701
|
+
title="{{ i18n.table().addColumnAfter }}"
|
|
2702
|
+
(click)="addColumnAfter()"
|
|
2703
|
+
></tiptap-button>
|
|
2704
|
+
} @if (config().deleteColumn !== false) {
|
|
2705
|
+
<tiptap-button
|
|
2706
|
+
icon="delete"
|
|
2707
|
+
title="{{ i18n.table().deleteColumn }}"
|
|
2708
|
+
variant="danger"
|
|
2709
|
+
(click)="deleteColumn()"
|
|
2710
|
+
></tiptap-button>
|
|
2711
|
+
} @if (config().separator !== false) {
|
|
2712
|
+
<tiptap-separator></tiptap-separator>
|
|
2713
|
+
}
|
|
2714
|
+
|
|
2715
|
+
<!-- Actions de cellules -->
|
|
2716
|
+
@if (config().toggleHeaderRow !== false) {
|
|
2717
|
+
<tiptap-button
|
|
2718
|
+
icon="toolbar"
|
|
2719
|
+
title="{{ i18n.table().toggleHeaderRow }}"
|
|
2720
|
+
(click)="toggleHeaderRow()"
|
|
2721
|
+
></tiptap-button>
|
|
2722
|
+
} @if (config().toggleHeaderColumn !== false) {
|
|
2723
|
+
<tiptap-button
|
|
2724
|
+
icon="dock_to_right"
|
|
2725
|
+
title="{{ i18n.table().toggleHeaderColumn }}"
|
|
2726
|
+
(click)="toggleHeaderColumn()"
|
|
2727
|
+
></tiptap-button>
|
|
2728
|
+
} @if (config().separator !== false && config().deleteTable !== false) {
|
|
2729
|
+
<tiptap-separator></tiptap-separator>
|
|
2730
|
+
}
|
|
2731
|
+
|
|
2732
|
+
<!-- Actions de table -->
|
|
2733
|
+
@if (config().deleteTable !== false) {
|
|
2734
|
+
<tiptap-button
|
|
2735
|
+
icon="delete_forever"
|
|
2736
|
+
title="{{ i18n.table().deleteTable }}"
|
|
2737
|
+
variant="danger"
|
|
2738
|
+
(click)="deleteTable()"
|
|
2739
|
+
></tiptap-button>
|
|
2740
|
+
}
|
|
2741
|
+
</div>
|
|
2634
2742
|
` }]
|
|
2635
|
-
}], ctorParameters: () => [], propDecorators: {
|
|
2743
|
+
}], ctorParameters: () => [], propDecorators: { menuElement: [{
|
|
2636
2744
|
type: ViewChild,
|
|
2637
|
-
args: ["
|
|
2745
|
+
args: ["menuElement", { static: true }]
|
|
2638
2746
|
}] } });
|
|
2639
2747
|
|
|
2640
|
-
class
|
|
2748
|
+
class TiptapCellBubbleMenuComponent {
|
|
2641
2749
|
constructor() {
|
|
2750
|
+
// Inputs
|
|
2642
2751
|
this.editor = input.required();
|
|
2643
|
-
this.config = input({
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
resizeOriginal: true,
|
|
2649
|
-
deleteImage: true,
|
|
2650
|
-
separator: true,
|
|
2651
|
-
});
|
|
2752
|
+
this.config = input({});
|
|
2753
|
+
// Services
|
|
2754
|
+
this.i18nService = inject(TiptapI18nService);
|
|
2755
|
+
this.commandsService = inject(EditorCommandsService);
|
|
2756
|
+
// Tippy instance
|
|
2652
2757
|
this.tippyInstance = null;
|
|
2653
|
-
this.imageService = inject(ImageService);
|
|
2654
2758
|
this.updateTimeout = null;
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
resizeMedium: true,
|
|
2659
|
-
resizeLarge: true,
|
|
2660
|
-
resizeOriginal: true,
|
|
2661
|
-
deleteImage: true,
|
|
2662
|
-
separator: true,
|
|
2663
|
-
...this.config(),
|
|
2664
|
-
}));
|
|
2665
|
-
this.hasResizeButtons = computed(() => {
|
|
2666
|
-
const config = this.imageBubbleMenuConfig();
|
|
2667
|
-
return (config.resizeSmall ||
|
|
2668
|
-
config.resizeMedium ||
|
|
2669
|
-
config.resizeLarge ||
|
|
2670
|
-
config.resizeOriginal);
|
|
2671
|
-
});
|
|
2759
|
+
// Signaux
|
|
2760
|
+
this.i18n = this.i18nService;
|
|
2761
|
+
this.isSingleCellSelected = false;
|
|
2672
2762
|
this.updateMenu = () => {
|
|
2673
2763
|
// Debounce pour éviter les appels trop fréquents
|
|
2674
2764
|
if (this.updateTimeout) {
|
|
@@ -2678,14 +2768,33 @@ class TiptapImageBubbleMenuComponent {
|
|
|
2678
2768
|
const ed = this.editor();
|
|
2679
2769
|
if (!ed)
|
|
2680
2770
|
return;
|
|
2681
|
-
const
|
|
2682
|
-
const { from, to } =
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
//
|
|
2686
|
-
|
|
2687
|
-
|
|
2771
|
+
const { selection } = ed.state;
|
|
2772
|
+
const { from, to } = selection;
|
|
2773
|
+
// Détecter spécifiquement la sélection de CELLULES (pas de texte)
|
|
2774
|
+
const hasCellSelection = selection instanceof CellSelection;
|
|
2775
|
+
// Une seule cellule si ancre et tête pointent vers la même cellule
|
|
2776
|
+
this.isSingleCellSelected =
|
|
2777
|
+
hasCellSelection &&
|
|
2778
|
+
selection.$anchorCell.pos ===
|
|
2779
|
+
selection.$headCell.pos;
|
|
2780
|
+
const hasTextSelection = !selection.empty && !(selection instanceof CellSelection);
|
|
2781
|
+
const isTableCell = ed.isActive("tableCell") || ed.isActive("tableHeader");
|
|
2782
|
+
console.log("CellBubbleMenu - updateMenu:", {
|
|
2783
|
+
hasCellSelection,
|
|
2784
|
+
isSingleCellSelected: this.isSingleCellSelected,
|
|
2785
|
+
hasTextSelection,
|
|
2786
|
+
isTableCell,
|
|
2787
|
+
selectionEmpty: selection.empty,
|
|
2788
|
+
selectionType: selection.constructor.name,
|
|
2789
|
+
from,
|
|
2790
|
+
to,
|
|
2791
|
+
isEditable: ed.isEditable,
|
|
2792
|
+
});
|
|
2793
|
+
// Le menu de cellule ne s'affiche QUE pour les sélections de cellules multiples
|
|
2794
|
+
// (pas pour la sélection de texte dans une cellule)
|
|
2795
|
+
const shouldShow = hasCellSelection && isTableCell && ed.isEditable;
|
|
2688
2796
|
if (shouldShow) {
|
|
2797
|
+
console.log("CellBubbleMenu - Affichage du menu de cellules");
|
|
2689
2798
|
this.showTippy();
|
|
2690
2799
|
}
|
|
2691
2800
|
else {
|
|
@@ -2699,57 +2808,41 @@ class TiptapImageBubbleMenuComponent {
|
|
|
2699
2808
|
this.hideTippy();
|
|
2700
2809
|
}, 100);
|
|
2701
2810
|
};
|
|
2811
|
+
// Effet pour mettre à jour le menu quand l'éditeur change
|
|
2702
2812
|
effect(() => {
|
|
2703
|
-
const
|
|
2704
|
-
if (
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
ed.on("focus", this.updateMenu);
|
|
2715
|
-
ed.on("blur", this.handleBlur);
|
|
2716
|
-
// Ne pas appeler updateMenu() ici pour éviter l'affichage prématuré
|
|
2717
|
-
// Il sera appelé automatiquement quand l'éditeur sera prêt
|
|
2813
|
+
const editor = this.editor();
|
|
2814
|
+
if (editor) {
|
|
2815
|
+
// Nettoyer les anciens listeners
|
|
2816
|
+
editor.off("selectionUpdate", this.updateMenu);
|
|
2817
|
+
editor.off("focus", this.updateMenu);
|
|
2818
|
+
editor.off("blur", this.handleBlur);
|
|
2819
|
+
// Ajouter les nouveaux listeners
|
|
2820
|
+
editor.on("selectionUpdate", this.updateMenu);
|
|
2821
|
+
editor.on("focus", this.updateMenu);
|
|
2822
|
+
editor.on("blur", this.handleBlur);
|
|
2823
|
+
}
|
|
2718
2824
|
});
|
|
2719
2825
|
}
|
|
2720
2826
|
ngOnInit() {
|
|
2721
|
-
// Initialiser Tippy de manière synchrone après que le component soit ready
|
|
2722
2827
|
this.initTippy();
|
|
2723
2828
|
}
|
|
2724
2829
|
ngOnDestroy() {
|
|
2725
|
-
const
|
|
2726
|
-
if (
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
}
|
|
2732
|
-
// Nettoyer les timeouts
|
|
2733
|
-
if (this.updateTimeout) {
|
|
2734
|
-
clearTimeout(this.updateTimeout);
|
|
2830
|
+
const editor = this.editor();
|
|
2831
|
+
if (editor) {
|
|
2832
|
+
// Nettoyer les événements
|
|
2833
|
+
editor.off("selectionUpdate", this.updateMenu);
|
|
2834
|
+
editor.off("focus", this.updateMenu);
|
|
2835
|
+
editor.off("blur", this.handleBlur);
|
|
2735
2836
|
}
|
|
2736
|
-
// Nettoyer Tippy
|
|
2737
2837
|
if (this.tippyInstance) {
|
|
2738
2838
|
this.tippyInstance.destroy();
|
|
2739
|
-
|
|
2839
|
+
}
|
|
2840
|
+
if (this.updateTimeout) {
|
|
2841
|
+
clearTimeout(this.updateTimeout);
|
|
2740
2842
|
}
|
|
2741
2843
|
}
|
|
2742
2844
|
initTippy() {
|
|
2743
|
-
|
|
2744
|
-
if (!this.menuRef?.nativeElement) {
|
|
2745
|
-
setTimeout(() => this.initTippy(), 50);
|
|
2746
|
-
return;
|
|
2747
|
-
}
|
|
2748
|
-
const menuElement = this.menuRef.nativeElement;
|
|
2749
|
-
// S'assurer qu'il n'y a pas déjà une instance
|
|
2750
|
-
if (this.tippyInstance) {
|
|
2751
|
-
this.tippyInstance.destroy();
|
|
2752
|
-
}
|
|
2845
|
+
const menuElement = this.menuElement.nativeElement;
|
|
2753
2846
|
// Créer l'instance Tippy
|
|
2754
2847
|
this.tippyInstance = tippy(document.body, {
|
|
2755
2848
|
content: menuElement,
|
|
@@ -2764,8 +2857,7 @@ class TiptapImageBubbleMenuComponent {
|
|
|
2764
2857
|
// S'assurer que les autres menus sont fermés
|
|
2765
2858
|
this.hideOtherMenus();
|
|
2766
2859
|
},
|
|
2767
|
-
getReferenceClientRect: () => this.
|
|
2768
|
-
// Améliorer le positionnement avec scroll
|
|
2860
|
+
getReferenceClientRect: () => this.getCellRect(),
|
|
2769
2861
|
popperOptions: {
|
|
2770
2862
|
modifiers: [
|
|
2771
2863
|
{
|
|
@@ -2787,218 +2879,636 @@ class TiptapImageBubbleMenuComponent {
|
|
|
2787
2879
|
// Maintenant que Tippy est initialisé, faire un premier check
|
|
2788
2880
|
this.updateMenu();
|
|
2789
2881
|
}
|
|
2790
|
-
|
|
2882
|
+
getCellRect() {
|
|
2791
2883
|
const ed = this.editor();
|
|
2792
2884
|
if (!ed)
|
|
2793
2885
|
return new DOMRect(0, 0, 0, 0);
|
|
2794
|
-
//
|
|
2795
|
-
const { from } = ed.state.selection;
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
return Array.from(editorElement.querySelectorAll("img"));
|
|
2800
|
-
};
|
|
2801
|
-
// Fonction pour trouver l'image à la position spécifique
|
|
2802
|
-
const findImageAtPosition = () => {
|
|
2803
|
-
const allImages = getAllImages();
|
|
2804
|
-
for (const img of allImages) {
|
|
2805
|
-
try {
|
|
2806
|
-
// Obtenir la position ProseMirror de cette image
|
|
2807
|
-
const imgPos = ed.view.posAtDOM(img, 0);
|
|
2808
|
-
// Vérifier si cette image correspond à la position sélectionnée
|
|
2809
|
-
if (Math.abs(imgPos - from) <= 1) {
|
|
2810
|
-
return img;
|
|
2811
|
-
}
|
|
2812
|
-
}
|
|
2813
|
-
catch (error) {
|
|
2814
|
-
// Continuer si on ne peut pas obtenir la position de cette image
|
|
2815
|
-
continue;
|
|
2816
|
-
}
|
|
2817
|
-
}
|
|
2818
|
-
return null;
|
|
2819
|
-
};
|
|
2820
|
-
// Chercher l'image à la position exacte
|
|
2821
|
-
const imageElement = findImageAtPosition();
|
|
2822
|
-
if (imageElement) {
|
|
2823
|
-
return imageElement.getBoundingClientRect();
|
|
2886
|
+
// Détecter la sélection de cellules
|
|
2887
|
+
const { from, to } = ed.state.selection;
|
|
2888
|
+
const hasCellSelection = from !== to;
|
|
2889
|
+
if (!hasCellSelection) {
|
|
2890
|
+
return new DOMRect(0, 0, 0, 0);
|
|
2824
2891
|
}
|
|
2825
|
-
|
|
2892
|
+
// Obtenir les coordonnées de la sélection
|
|
2893
|
+
const coords = ed.view.coordsAtPos(from);
|
|
2894
|
+
const endCoords = ed.view.coordsAtPos(to);
|
|
2895
|
+
// Créer un rectangle englobant la sélection
|
|
2896
|
+
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));
|
|
2897
|
+
return rect;
|
|
2826
2898
|
}
|
|
2827
2899
|
hideOtherMenus() {
|
|
2828
|
-
//
|
|
2829
|
-
|
|
2900
|
+
// Masquer tous les autres menus quand le menu de cellules est actif
|
|
2901
|
+
this.hideTableMenu();
|
|
2902
|
+
this.hideTextBubbleMenu();
|
|
2830
2903
|
}
|
|
2831
2904
|
showTippy() {
|
|
2832
2905
|
if (!this.tippyInstance)
|
|
2833
2906
|
return;
|
|
2907
|
+
// Masquer les autres menus avant d'afficher le menu de cellules
|
|
2908
|
+
this.hideTableMenu();
|
|
2909
|
+
this.hideTextBubbleMenu();
|
|
2834
2910
|
// Mettre à jour la position
|
|
2835
2911
|
this.tippyInstance.setProps({
|
|
2836
|
-
getReferenceClientRect: () => this.
|
|
2912
|
+
getReferenceClientRect: () => this.getCellRect(),
|
|
2837
2913
|
});
|
|
2838
2914
|
this.tippyInstance.show();
|
|
2839
2915
|
}
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2916
|
+
hideTableMenu() {
|
|
2917
|
+
// Masquer le menu de table quand le menu de cellules est actif
|
|
2918
|
+
const tableMenu = document.querySelector("tiptap-table-bubble-menu");
|
|
2919
|
+
if (tableMenu) {
|
|
2920
|
+
const tippyInstance = tableMenu._tippy;
|
|
2921
|
+
if (tippyInstance) {
|
|
2922
|
+
tippyInstance.hide();
|
|
2923
|
+
}
|
|
2924
|
+
}
|
|
2925
|
+
// Alternative : masquer via l'élément Angular
|
|
2926
|
+
const tableMenuComponent = document.querySelector("tiptap-table-bubble-menu");
|
|
2927
|
+
if (tableMenuComponent && tableMenuComponent.hideTippy) {
|
|
2928
|
+
tableMenuComponent.hideTippy();
|
|
2843
2929
|
}
|
|
2844
2930
|
}
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
const
|
|
2848
|
-
if (
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
this.imageService.resizeImageToMedium(ed);
|
|
2859
|
-
break;
|
|
2860
|
-
case "resizeLarge":
|
|
2861
|
-
this.imageService.resizeImageToLarge(ed);
|
|
2862
|
-
break;
|
|
2863
|
-
case "resizeOriginal":
|
|
2864
|
-
this.imageService.resizeImageToOriginal(ed);
|
|
2865
|
-
break;
|
|
2866
|
-
case "deleteImage":
|
|
2867
|
-
this.deleteImage();
|
|
2868
|
-
break;
|
|
2931
|
+
hideTextBubbleMenu() {
|
|
2932
|
+
// Masquer le menu de texte (bubble menu général) quand le menu de cellules est actif
|
|
2933
|
+
const textMenu = document.querySelector("tiptap-bubble-menu");
|
|
2934
|
+
if (textMenu) {
|
|
2935
|
+
const tippyInstance = textMenu._tippy;
|
|
2936
|
+
if (tippyInstance) {
|
|
2937
|
+
tippyInstance.hide();
|
|
2938
|
+
}
|
|
2939
|
+
}
|
|
2940
|
+
// Alternative : masquer via l'élément Angular
|
|
2941
|
+
const textMenuComponent = document.querySelector("tiptap-bubble-menu");
|
|
2942
|
+
if (textMenuComponent && textMenuComponent.hideTippy) {
|
|
2943
|
+
textMenuComponent.hideTippy();
|
|
2869
2944
|
}
|
|
2870
2945
|
}
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
if (!ed)
|
|
2946
|
+
hideTippy() {
|
|
2947
|
+
if (!this.tippyInstance)
|
|
2874
2948
|
return;
|
|
2949
|
+
this.tippyInstance.hide();
|
|
2950
|
+
}
|
|
2951
|
+
// Actions spécifiques aux cellules
|
|
2952
|
+
mergeCells() {
|
|
2953
|
+
this.commandsService.mergeCells(this.editor());
|
|
2954
|
+
}
|
|
2955
|
+
splitCell() {
|
|
2956
|
+
this.commandsService.splitCell(this.editor());
|
|
2957
|
+
}
|
|
2958
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: TiptapCellBubbleMenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
2959
|
+
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: `
|
|
2960
|
+
<div #menuElement class="bubble-menu">
|
|
2961
|
+
<!-- Actions spécifiques aux cellules -->
|
|
2962
|
+
@if (config().mergeCells !== false && !isSingleCellSelected) {
|
|
2963
|
+
<tiptap-button
|
|
2964
|
+
icon="cell_merge"
|
|
2965
|
+
title="{{ i18n.table().mergeCells }}"
|
|
2966
|
+
(click)="mergeCells()"
|
|
2967
|
+
></tiptap-button>
|
|
2968
|
+
} @if (config().splitCell !== false && isSingleCellSelected) {
|
|
2969
|
+
<tiptap-button
|
|
2970
|
+
icon="split_scene"
|
|
2971
|
+
title="{{ i18n.table().splitCell }}"
|
|
2972
|
+
(click)="splitCell()"
|
|
2973
|
+
></tiptap-button>
|
|
2974
|
+
}
|
|
2975
|
+
</div>
|
|
2976
|
+
`, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: TiptapButtonComponent, selector: "tiptap-button", inputs: ["icon", "title", "active", "disabled", "variant", "size", "iconSize"], outputs: ["onClick"] }] }); }
|
|
2977
|
+
}
|
|
2978
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: TiptapCellBubbleMenuComponent, decorators: [{
|
|
2979
|
+
type: Component,
|
|
2980
|
+
args: [{ selector: "tiptap-cell-bubble-menu", standalone: true, imports: [CommonModule, TiptapButtonComponent], template: `
|
|
2981
|
+
<div #menuElement class="bubble-menu">
|
|
2982
|
+
<!-- Actions spécifiques aux cellules -->
|
|
2983
|
+
@if (config().mergeCells !== false && !isSingleCellSelected) {
|
|
2984
|
+
<tiptap-button
|
|
2985
|
+
icon="cell_merge"
|
|
2986
|
+
title="{{ i18n.table().mergeCells }}"
|
|
2987
|
+
(click)="mergeCells()"
|
|
2988
|
+
></tiptap-button>
|
|
2989
|
+
} @if (config().splitCell !== false && isSingleCellSelected) {
|
|
2990
|
+
<tiptap-button
|
|
2991
|
+
icon="split_scene"
|
|
2992
|
+
title="{{ i18n.table().splitCell }}"
|
|
2993
|
+
(click)="splitCell()"
|
|
2994
|
+
></tiptap-button>
|
|
2995
|
+
}
|
|
2996
|
+
</div>
|
|
2997
|
+
` }]
|
|
2998
|
+
}], ctorParameters: () => [], propDecorators: { menuElement: [{
|
|
2999
|
+
type: ViewChild,
|
|
3000
|
+
args: ["menuElement", { static: true }]
|
|
3001
|
+
}] } });
|
|
3002
|
+
|
|
3003
|
+
class TiptapToolbarComponent {
|
|
3004
|
+
constructor(editorCommands) {
|
|
3005
|
+
this.editorCommands = editorCommands;
|
|
3006
|
+
this.editor = input.required();
|
|
3007
|
+
this.config = input.required();
|
|
3008
|
+
// Outputs pour les événements d'image
|
|
3009
|
+
this.imageUploaded = output();
|
|
3010
|
+
this.imageError = output();
|
|
3011
|
+
this.imageService = inject(ImageService);
|
|
3012
|
+
this.i18nService = inject(TiptapI18nService);
|
|
3013
|
+
// Computed values pour les traductions
|
|
3014
|
+
this.t = this.i18nService.toolbar;
|
|
3015
|
+
}
|
|
3016
|
+
isActive(name, attributes) {
|
|
3017
|
+
return this.editorCommands.isActive(this.editor(), name, attributes);
|
|
3018
|
+
}
|
|
3019
|
+
canExecute(command) {
|
|
3020
|
+
return this.editorCommands.canExecute(this.editor(), command);
|
|
3021
|
+
}
|
|
3022
|
+
toggleBold() {
|
|
3023
|
+
this.editorCommands.toggleBold(this.editor());
|
|
3024
|
+
}
|
|
3025
|
+
toggleItalic() {
|
|
3026
|
+
this.editorCommands.toggleItalic(this.editor());
|
|
3027
|
+
}
|
|
3028
|
+
toggleStrike() {
|
|
3029
|
+
this.editorCommands.toggleStrike(this.editor());
|
|
3030
|
+
}
|
|
3031
|
+
toggleCode() {
|
|
3032
|
+
this.editorCommands.toggleCode(this.editor());
|
|
3033
|
+
}
|
|
3034
|
+
toggleHeading(level) {
|
|
3035
|
+
this.editorCommands.toggleHeading(this.editor(), level);
|
|
3036
|
+
}
|
|
3037
|
+
toggleBulletList() {
|
|
3038
|
+
this.editorCommands.toggleBulletList(this.editor());
|
|
3039
|
+
}
|
|
3040
|
+
toggleOrderedList() {
|
|
3041
|
+
this.editorCommands.toggleOrderedList(this.editor());
|
|
3042
|
+
}
|
|
3043
|
+
toggleBlockquote() {
|
|
3044
|
+
this.editorCommands.toggleBlockquote(this.editor());
|
|
3045
|
+
}
|
|
3046
|
+
undo() {
|
|
3047
|
+
this.editorCommands.undo(this.editor());
|
|
3048
|
+
}
|
|
3049
|
+
redo() {
|
|
3050
|
+
this.editorCommands.redo(this.editor());
|
|
3051
|
+
}
|
|
3052
|
+
// Nouvelles méthodes pour les formatages supplémentaires
|
|
3053
|
+
toggleUnderline() {
|
|
3054
|
+
this.editorCommands.toggleUnderline(this.editor());
|
|
3055
|
+
}
|
|
3056
|
+
toggleSuperscript() {
|
|
3057
|
+
this.editorCommands.toggleSuperscript(this.editor());
|
|
3058
|
+
}
|
|
3059
|
+
toggleSubscript() {
|
|
3060
|
+
this.editorCommands.toggleSubscript(this.editor());
|
|
3061
|
+
}
|
|
3062
|
+
setTextAlign(alignment) {
|
|
3063
|
+
this.editorCommands.setTextAlign(this.editor(), alignment);
|
|
3064
|
+
}
|
|
3065
|
+
toggleLink() {
|
|
3066
|
+
this.editorCommands.toggleLink(this.editor());
|
|
3067
|
+
}
|
|
3068
|
+
insertHorizontalRule() {
|
|
3069
|
+
this.editorCommands.insertHorizontalRule(this.editor());
|
|
3070
|
+
}
|
|
3071
|
+
toggleHighlight() {
|
|
3072
|
+
this.editorCommands.toggleHighlight(this.editor());
|
|
3073
|
+
}
|
|
3074
|
+
// Méthode pour insérer un tableau
|
|
3075
|
+
insertTable() {
|
|
3076
|
+
this.editorCommands.insertTable(this.editor());
|
|
3077
|
+
}
|
|
3078
|
+
// Méthode pour insérer une image
|
|
3079
|
+
async insertImage() {
|
|
2875
3080
|
try {
|
|
2876
|
-
|
|
2877
|
-
await this.imageService.selectAndReplaceImage(ed, {
|
|
2878
|
-
quality: 0.8,
|
|
2879
|
-
maxWidth: 1920,
|
|
2880
|
-
maxHeight: 1080,
|
|
2881
|
-
accept: "image/*",
|
|
2882
|
-
});
|
|
3081
|
+
await this.imageService.selectAndUploadImage(this.editor());
|
|
2883
3082
|
}
|
|
2884
3083
|
catch (error) {
|
|
2885
|
-
console.error("Erreur lors
|
|
3084
|
+
console.error("Erreur lors de l'upload d'image:", error);
|
|
3085
|
+
this.imageError.emit("Erreur lors de l'upload d'image");
|
|
2886
3086
|
}
|
|
2887
3087
|
}
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
ed.chain().focus().deleteSelection().run();
|
|
2892
|
-
}
|
|
3088
|
+
// Méthode pour vider le contenu
|
|
3089
|
+
clearContent() {
|
|
3090
|
+
this.editorCommands.clearContent(this.editor());
|
|
2893
3091
|
}
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
(
|
|
2911
|
-
|
|
2912
|
-
} @if (
|
|
2913
|
-
<tiptap-button
|
|
2914
|
-
icon="
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
title="
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
3092
|
+
// Méthodes pour les événements d'image (conservées pour compatibilité)
|
|
3093
|
+
onImageSelected(result) {
|
|
3094
|
+
this.imageUploaded.emit(result);
|
|
3095
|
+
}
|
|
3096
|
+
onImageError(error) {
|
|
3097
|
+
this.imageError.emit(error);
|
|
3098
|
+
}
|
|
3099
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: TiptapToolbarComponent, deps: [{ token: EditorCommandsService }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
3100
|
+
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: `
|
|
3101
|
+
<div class="tiptap-toolbar">
|
|
3102
|
+
@if (config().bold) {
|
|
3103
|
+
<tiptap-button
|
|
3104
|
+
icon="format_bold"
|
|
3105
|
+
[title]="t().bold"
|
|
3106
|
+
[active]="isActive('bold')"
|
|
3107
|
+
[disabled]="!canExecute('toggleBold')"
|
|
3108
|
+
(onClick)="toggleBold()"
|
|
3109
|
+
/>
|
|
3110
|
+
} @if (config().italic) {
|
|
3111
|
+
<tiptap-button
|
|
3112
|
+
icon="format_italic"
|
|
3113
|
+
[title]="t().italic"
|
|
3114
|
+
[active]="isActive('italic')"
|
|
3115
|
+
[disabled]="!canExecute('toggleItalic')"
|
|
3116
|
+
(onClick)="toggleItalic()"
|
|
3117
|
+
/>
|
|
3118
|
+
} @if (config().underline) {
|
|
3119
|
+
<tiptap-button
|
|
3120
|
+
icon="format_underlined"
|
|
3121
|
+
[title]="t().underline"
|
|
3122
|
+
[active]="isActive('underline')"
|
|
3123
|
+
[disabled]="!canExecute('toggleUnderline')"
|
|
3124
|
+
(onClick)="toggleUnderline()"
|
|
3125
|
+
/>
|
|
3126
|
+
} @if (config().strike) {
|
|
3127
|
+
<tiptap-button
|
|
3128
|
+
icon="strikethrough_s"
|
|
3129
|
+
[title]="t().strike"
|
|
3130
|
+
[active]="isActive('strike')"
|
|
3131
|
+
[disabled]="!canExecute('toggleStrike')"
|
|
3132
|
+
(onClick)="toggleStrike()"
|
|
3133
|
+
/>
|
|
3134
|
+
} @if (config().code) {
|
|
3135
|
+
<tiptap-button
|
|
3136
|
+
icon="code"
|
|
3137
|
+
[title]="t().code"
|
|
3138
|
+
[active]="isActive('code')"
|
|
3139
|
+
[disabled]="!canExecute('toggleCode')"
|
|
3140
|
+
(onClick)="toggleCode()"
|
|
3141
|
+
/>
|
|
3142
|
+
} @if (config().superscript) {
|
|
3143
|
+
<tiptap-button
|
|
3144
|
+
icon="superscript"
|
|
3145
|
+
[title]="t().superscript"
|
|
3146
|
+
[active]="isActive('superscript')"
|
|
3147
|
+
[disabled]="!canExecute('toggleSuperscript')"
|
|
3148
|
+
(onClick)="toggleSuperscript()"
|
|
3149
|
+
/>
|
|
3150
|
+
} @if (config().subscript) {
|
|
3151
|
+
<tiptap-button
|
|
3152
|
+
icon="subscript"
|
|
3153
|
+
[title]="t().subscript"
|
|
3154
|
+
[active]="isActive('subscript')"
|
|
3155
|
+
[disabled]="!canExecute('toggleSubscript')"
|
|
3156
|
+
(onClick)="toggleSubscript()"
|
|
3157
|
+
/>
|
|
3158
|
+
} @if (config().highlight) {
|
|
3159
|
+
<tiptap-button
|
|
3160
|
+
icon="highlight"
|
|
3161
|
+
[title]="t().highlight"
|
|
3162
|
+
[active]="isActive('highlight')"
|
|
3163
|
+
[disabled]="!canExecute('toggleHighlight')"
|
|
3164
|
+
(onClick)="toggleHighlight()"
|
|
3165
|
+
/>
|
|
3166
|
+
} @if (config().separator && (config().heading1 || config().heading2 ||
|
|
3167
|
+
config().heading3)) {
|
|
3168
|
+
<tiptap-separator />
|
|
3169
|
+
} @if (config().heading1) {
|
|
3170
|
+
<tiptap-button
|
|
3171
|
+
icon="format_h1"
|
|
3172
|
+
[title]="t().heading1"
|
|
3173
|
+
variant="text"
|
|
3174
|
+
[active]="isActive('heading', { level: 1 })"
|
|
3175
|
+
(onClick)="toggleHeading(1)"
|
|
3176
|
+
/>
|
|
3177
|
+
} @if (config().heading2) {
|
|
3178
|
+
<tiptap-button
|
|
3179
|
+
icon="format_h2"
|
|
3180
|
+
[title]="t().heading2"
|
|
3181
|
+
variant="text"
|
|
3182
|
+
[active]="isActive('heading', { level: 2 })"
|
|
3183
|
+
(onClick)="toggleHeading(2)"
|
|
3184
|
+
/>
|
|
3185
|
+
} @if (config().heading3) {
|
|
3186
|
+
<tiptap-button
|
|
3187
|
+
icon="format_h3"
|
|
3188
|
+
[title]="t().heading3"
|
|
3189
|
+
variant="text"
|
|
3190
|
+
[active]="isActive('heading', { level: 3 })"
|
|
3191
|
+
(onClick)="toggleHeading(3)"
|
|
3192
|
+
/>
|
|
3193
|
+
} @if (config().separator && (config().bulletList || config().orderedList
|
|
3194
|
+
|| config().blockquote)) {
|
|
3195
|
+
<tiptap-separator />
|
|
3196
|
+
} @if (config().bulletList) {
|
|
3197
|
+
<tiptap-button
|
|
3198
|
+
icon="format_list_bulleted"
|
|
3199
|
+
[title]="t().bulletList"
|
|
3200
|
+
[active]="isActive('bulletList')"
|
|
3201
|
+
(onClick)="toggleBulletList()"
|
|
3202
|
+
/>
|
|
3203
|
+
} @if (config().orderedList) {
|
|
3204
|
+
<tiptap-button
|
|
3205
|
+
icon="format_list_numbered"
|
|
3206
|
+
[title]="t().orderedList"
|
|
3207
|
+
[active]="isActive('orderedList')"
|
|
3208
|
+
(onClick)="toggleOrderedList()"
|
|
3209
|
+
/>
|
|
3210
|
+
} @if (config().blockquote) {
|
|
3211
|
+
<tiptap-button
|
|
3212
|
+
icon="format_quote"
|
|
3213
|
+
[title]="t().blockquote"
|
|
3214
|
+
[active]="isActive('blockquote')"
|
|
3215
|
+
(onClick)="toggleBlockquote()"
|
|
3216
|
+
/>
|
|
3217
|
+
} @if (config().separator && (config().alignLeft || config().alignCenter
|
|
3218
|
+
|| config().alignRight || config().alignJustify)) {
|
|
3219
|
+
<tiptap-separator />
|
|
3220
|
+
} @if (config().alignLeft) {
|
|
3221
|
+
<tiptap-button
|
|
3222
|
+
icon="format_align_left"
|
|
3223
|
+
[title]="t().alignLeft"
|
|
3224
|
+
[active]="isActive('textAlign', { textAlign: 'left' })"
|
|
3225
|
+
(onClick)="setTextAlign('left')"
|
|
3226
|
+
/>
|
|
3227
|
+
} @if (config().alignCenter) {
|
|
3228
|
+
<tiptap-button
|
|
3229
|
+
icon="format_align_center"
|
|
3230
|
+
[title]="t().alignCenter"
|
|
3231
|
+
[active]="isActive('textAlign', { textAlign: 'center' })"
|
|
3232
|
+
(onClick)="setTextAlign('center')"
|
|
3233
|
+
/>
|
|
3234
|
+
} @if (config().alignRight) {
|
|
3235
|
+
<tiptap-button
|
|
3236
|
+
icon="format_align_right"
|
|
3237
|
+
[title]="t().alignRight"
|
|
3238
|
+
[active]="isActive('textAlign', { textAlign: 'right' })"
|
|
3239
|
+
(onClick)="setTextAlign('right')"
|
|
3240
|
+
/>
|
|
3241
|
+
} @if (config().alignJustify) {
|
|
3242
|
+
<tiptap-button
|
|
3243
|
+
icon="format_align_justify"
|
|
3244
|
+
[title]="t().alignJustify"
|
|
3245
|
+
[active]="isActive('textAlign', { textAlign: 'justify' })"
|
|
3246
|
+
(onClick)="setTextAlign('justify')"
|
|
3247
|
+
/>
|
|
3248
|
+
} @if (config().separator && (config().link || config().horizontalRule)) {
|
|
3249
|
+
<tiptap-separator />
|
|
3250
|
+
} @if (config().link) {
|
|
3251
|
+
<tiptap-button
|
|
3252
|
+
icon="link"
|
|
3253
|
+
[title]="t().link"
|
|
3254
|
+
[active]="isActive('link')"
|
|
3255
|
+
(onClick)="toggleLink()"
|
|
3256
|
+
/>
|
|
3257
|
+
} @if (config().horizontalRule) {
|
|
3258
|
+
<tiptap-button
|
|
3259
|
+
icon="horizontal_rule"
|
|
3260
|
+
[title]="t().horizontalRule"
|
|
3261
|
+
(onClick)="insertHorizontalRule()"
|
|
3262
|
+
/>
|
|
3263
|
+
} @if (config().table) {
|
|
3264
|
+
<tiptap-button
|
|
3265
|
+
icon="table_view"
|
|
3266
|
+
[title]="t().table"
|
|
3267
|
+
(onClick)="insertTable()"
|
|
3268
|
+
/>
|
|
3269
|
+
} @if (config().separator && config().image) {
|
|
3270
|
+
<tiptap-separator />
|
|
3271
|
+
} @if (config().image) {
|
|
3272
|
+
<tiptap-button
|
|
3273
|
+
icon="image"
|
|
3274
|
+
[title]="t().image"
|
|
3275
|
+
(onClick)="insertImage()"
|
|
3276
|
+
/>
|
|
3277
|
+
} @if (config().separator && (config().undo || config().redo)) {
|
|
3278
|
+
<tiptap-separator />
|
|
3279
|
+
} @if (config().undo) {
|
|
3280
|
+
<tiptap-button
|
|
3281
|
+
icon="undo"
|
|
3282
|
+
[title]="t().undo"
|
|
3283
|
+
[disabled]="!canExecute('undo')"
|
|
3284
|
+
(onClick)="undo()"
|
|
3285
|
+
/>
|
|
3286
|
+
} @if (config().redo) {
|
|
3287
|
+
<tiptap-button
|
|
3288
|
+
icon="redo"
|
|
3289
|
+
[title]="t().redo"
|
|
3290
|
+
[disabled]="!canExecute('redo')"
|
|
3291
|
+
(onClick)="redo()"
|
|
3292
|
+
/>
|
|
3293
|
+
} @if (config().separator && config().clear) {
|
|
3294
|
+
<tiptap-separator />
|
|
3295
|
+
} @if (config().clear) {
|
|
3296
|
+
<tiptap-button
|
|
3297
|
+
icon="delete"
|
|
3298
|
+
[title]="t().clear"
|
|
3299
|
+
(onClick)="clearContent()"
|
|
3300
|
+
/>
|
|
3301
|
+
}
|
|
3302
|
+
</div>
|
|
3303
|
+
`, 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
3304
|
}
|
|
2946
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type:
|
|
3305
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: TiptapToolbarComponent, decorators: [{
|
|
2947
3306
|
type: Component,
|
|
2948
|
-
args: [{ selector: "tiptap-
|
|
2949
|
-
<div
|
|
2950
|
-
@if (
|
|
2951
|
-
<tiptap-button
|
|
2952
|
-
icon="
|
|
2953
|
-
title="
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
} @if (
|
|
2959
|
-
<tiptap-button
|
|
2960
|
-
icon="
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
title="
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3307
|
+
args: [{ selector: "tiptap-toolbar", standalone: true, imports: [TiptapButtonComponent, TiptapSeparatorComponent], template: `
|
|
3308
|
+
<div class="tiptap-toolbar">
|
|
3309
|
+
@if (config().bold) {
|
|
3310
|
+
<tiptap-button
|
|
3311
|
+
icon="format_bold"
|
|
3312
|
+
[title]="t().bold"
|
|
3313
|
+
[active]="isActive('bold')"
|
|
3314
|
+
[disabled]="!canExecute('toggleBold')"
|
|
3315
|
+
(onClick)="toggleBold()"
|
|
3316
|
+
/>
|
|
3317
|
+
} @if (config().italic) {
|
|
3318
|
+
<tiptap-button
|
|
3319
|
+
icon="format_italic"
|
|
3320
|
+
[title]="t().italic"
|
|
3321
|
+
[active]="isActive('italic')"
|
|
3322
|
+
[disabled]="!canExecute('toggleItalic')"
|
|
3323
|
+
(onClick)="toggleItalic()"
|
|
3324
|
+
/>
|
|
3325
|
+
} @if (config().underline) {
|
|
3326
|
+
<tiptap-button
|
|
3327
|
+
icon="format_underlined"
|
|
3328
|
+
[title]="t().underline"
|
|
3329
|
+
[active]="isActive('underline')"
|
|
3330
|
+
[disabled]="!canExecute('toggleUnderline')"
|
|
3331
|
+
(onClick)="toggleUnderline()"
|
|
3332
|
+
/>
|
|
3333
|
+
} @if (config().strike) {
|
|
3334
|
+
<tiptap-button
|
|
3335
|
+
icon="strikethrough_s"
|
|
3336
|
+
[title]="t().strike"
|
|
3337
|
+
[active]="isActive('strike')"
|
|
3338
|
+
[disabled]="!canExecute('toggleStrike')"
|
|
3339
|
+
(onClick)="toggleStrike()"
|
|
3340
|
+
/>
|
|
3341
|
+
} @if (config().code) {
|
|
3342
|
+
<tiptap-button
|
|
3343
|
+
icon="code"
|
|
3344
|
+
[title]="t().code"
|
|
3345
|
+
[active]="isActive('code')"
|
|
3346
|
+
[disabled]="!canExecute('toggleCode')"
|
|
3347
|
+
(onClick)="toggleCode()"
|
|
3348
|
+
/>
|
|
3349
|
+
} @if (config().superscript) {
|
|
3350
|
+
<tiptap-button
|
|
3351
|
+
icon="superscript"
|
|
3352
|
+
[title]="t().superscript"
|
|
3353
|
+
[active]="isActive('superscript')"
|
|
3354
|
+
[disabled]="!canExecute('toggleSuperscript')"
|
|
3355
|
+
(onClick)="toggleSuperscript()"
|
|
3356
|
+
/>
|
|
3357
|
+
} @if (config().subscript) {
|
|
3358
|
+
<tiptap-button
|
|
3359
|
+
icon="subscript"
|
|
3360
|
+
[title]="t().subscript"
|
|
3361
|
+
[active]="isActive('subscript')"
|
|
3362
|
+
[disabled]="!canExecute('toggleSubscript')"
|
|
3363
|
+
(onClick)="toggleSubscript()"
|
|
3364
|
+
/>
|
|
3365
|
+
} @if (config().highlight) {
|
|
3366
|
+
<tiptap-button
|
|
3367
|
+
icon="highlight"
|
|
3368
|
+
[title]="t().highlight"
|
|
3369
|
+
[active]="isActive('highlight')"
|
|
3370
|
+
[disabled]="!canExecute('toggleHighlight')"
|
|
3371
|
+
(onClick)="toggleHighlight()"
|
|
3372
|
+
/>
|
|
3373
|
+
} @if (config().separator && (config().heading1 || config().heading2 ||
|
|
3374
|
+
config().heading3)) {
|
|
3375
|
+
<tiptap-separator />
|
|
3376
|
+
} @if (config().heading1) {
|
|
3377
|
+
<tiptap-button
|
|
3378
|
+
icon="format_h1"
|
|
3379
|
+
[title]="t().heading1"
|
|
3380
|
+
variant="text"
|
|
3381
|
+
[active]="isActive('heading', { level: 1 })"
|
|
3382
|
+
(onClick)="toggleHeading(1)"
|
|
3383
|
+
/>
|
|
3384
|
+
} @if (config().heading2) {
|
|
3385
|
+
<tiptap-button
|
|
3386
|
+
icon="format_h2"
|
|
3387
|
+
[title]="t().heading2"
|
|
3388
|
+
variant="text"
|
|
3389
|
+
[active]="isActive('heading', { level: 2 })"
|
|
3390
|
+
(onClick)="toggleHeading(2)"
|
|
3391
|
+
/>
|
|
3392
|
+
} @if (config().heading3) {
|
|
3393
|
+
<tiptap-button
|
|
3394
|
+
icon="format_h3"
|
|
3395
|
+
[title]="t().heading3"
|
|
3396
|
+
variant="text"
|
|
3397
|
+
[active]="isActive('heading', { level: 3 })"
|
|
3398
|
+
(onClick)="toggleHeading(3)"
|
|
3399
|
+
/>
|
|
3400
|
+
} @if (config().separator && (config().bulletList || config().orderedList
|
|
3401
|
+
|| config().blockquote)) {
|
|
3402
|
+
<tiptap-separator />
|
|
3403
|
+
} @if (config().bulletList) {
|
|
3404
|
+
<tiptap-button
|
|
3405
|
+
icon="format_list_bulleted"
|
|
3406
|
+
[title]="t().bulletList"
|
|
3407
|
+
[active]="isActive('bulletList')"
|
|
3408
|
+
(onClick)="toggleBulletList()"
|
|
3409
|
+
/>
|
|
3410
|
+
} @if (config().orderedList) {
|
|
3411
|
+
<tiptap-button
|
|
3412
|
+
icon="format_list_numbered"
|
|
3413
|
+
[title]="t().orderedList"
|
|
3414
|
+
[active]="isActive('orderedList')"
|
|
3415
|
+
(onClick)="toggleOrderedList()"
|
|
3416
|
+
/>
|
|
3417
|
+
} @if (config().blockquote) {
|
|
3418
|
+
<tiptap-button
|
|
3419
|
+
icon="format_quote"
|
|
3420
|
+
[title]="t().blockquote"
|
|
3421
|
+
[active]="isActive('blockquote')"
|
|
3422
|
+
(onClick)="toggleBlockquote()"
|
|
3423
|
+
/>
|
|
3424
|
+
} @if (config().separator && (config().alignLeft || config().alignCenter
|
|
3425
|
+
|| config().alignRight || config().alignJustify)) {
|
|
3426
|
+
<tiptap-separator />
|
|
3427
|
+
} @if (config().alignLeft) {
|
|
3428
|
+
<tiptap-button
|
|
3429
|
+
icon="format_align_left"
|
|
3430
|
+
[title]="t().alignLeft"
|
|
3431
|
+
[active]="isActive('textAlign', { textAlign: 'left' })"
|
|
3432
|
+
(onClick)="setTextAlign('left')"
|
|
3433
|
+
/>
|
|
3434
|
+
} @if (config().alignCenter) {
|
|
3435
|
+
<tiptap-button
|
|
3436
|
+
icon="format_align_center"
|
|
3437
|
+
[title]="t().alignCenter"
|
|
3438
|
+
[active]="isActive('textAlign', { textAlign: 'center' })"
|
|
3439
|
+
(onClick)="setTextAlign('center')"
|
|
3440
|
+
/>
|
|
3441
|
+
} @if (config().alignRight) {
|
|
3442
|
+
<tiptap-button
|
|
3443
|
+
icon="format_align_right"
|
|
3444
|
+
[title]="t().alignRight"
|
|
3445
|
+
[active]="isActive('textAlign', { textAlign: 'right' })"
|
|
3446
|
+
(onClick)="setTextAlign('right')"
|
|
3447
|
+
/>
|
|
3448
|
+
} @if (config().alignJustify) {
|
|
3449
|
+
<tiptap-button
|
|
3450
|
+
icon="format_align_justify"
|
|
3451
|
+
[title]="t().alignJustify"
|
|
3452
|
+
[active]="isActive('textAlign', { textAlign: 'justify' })"
|
|
3453
|
+
(onClick)="setTextAlign('justify')"
|
|
3454
|
+
/>
|
|
3455
|
+
} @if (config().separator && (config().link || config().horizontalRule)) {
|
|
3456
|
+
<tiptap-separator />
|
|
3457
|
+
} @if (config().link) {
|
|
3458
|
+
<tiptap-button
|
|
3459
|
+
icon="link"
|
|
3460
|
+
[title]="t().link"
|
|
3461
|
+
[active]="isActive('link')"
|
|
3462
|
+
(onClick)="toggleLink()"
|
|
3463
|
+
/>
|
|
3464
|
+
} @if (config().horizontalRule) {
|
|
3465
|
+
<tiptap-button
|
|
3466
|
+
icon="horizontal_rule"
|
|
3467
|
+
[title]="t().horizontalRule"
|
|
3468
|
+
(onClick)="insertHorizontalRule()"
|
|
3469
|
+
/>
|
|
3470
|
+
} @if (config().table) {
|
|
3471
|
+
<tiptap-button
|
|
3472
|
+
icon="table_view"
|
|
3473
|
+
[title]="t().table"
|
|
3474
|
+
(onClick)="insertTable()"
|
|
3475
|
+
/>
|
|
3476
|
+
} @if (config().separator && config().image) {
|
|
3477
|
+
<tiptap-separator />
|
|
3478
|
+
} @if (config().image) {
|
|
3479
|
+
<tiptap-button
|
|
3480
|
+
icon="image"
|
|
3481
|
+
[title]="t().image"
|
|
3482
|
+
(onClick)="insertImage()"
|
|
3483
|
+
/>
|
|
3484
|
+
} @if (config().separator && (config().undo || config().redo)) {
|
|
3485
|
+
<tiptap-separator />
|
|
3486
|
+
} @if (config().undo) {
|
|
3487
|
+
<tiptap-button
|
|
3488
|
+
icon="undo"
|
|
3489
|
+
[title]="t().undo"
|
|
3490
|
+
[disabled]="!canExecute('undo')"
|
|
3491
|
+
(onClick)="undo()"
|
|
3492
|
+
/>
|
|
3493
|
+
} @if (config().redo) {
|
|
3494
|
+
<tiptap-button
|
|
3495
|
+
icon="redo"
|
|
3496
|
+
[title]="t().redo"
|
|
3497
|
+
[disabled]="!canExecute('redo')"
|
|
3498
|
+
(onClick)="redo()"
|
|
3499
|
+
/>
|
|
3500
|
+
} @if (config().separator && config().clear) {
|
|
3501
|
+
<tiptap-separator />
|
|
3502
|
+
} @if (config().clear) {
|
|
3503
|
+
<tiptap-button
|
|
3504
|
+
icon="delete"
|
|
3505
|
+
[title]="t().clear"
|
|
3506
|
+
(onClick)="clearContent()"
|
|
3507
|
+
/>
|
|
3508
|
+
}
|
|
3509
|
+
</div>
|
|
3510
|
+
`, 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"] }]
|
|
3511
|
+
}], ctorParameters: () => [{ type: EditorCommandsService }] });
|
|
3002
3512
|
|
|
3003
3513
|
const DEFAULT_SLASH_COMMANDS = [
|
|
3004
3514
|
{
|
|
@@ -3448,54 +3958,400 @@ class TiptapSlashCommandsComponent {
|
|
|
3448
3958
|
plugins: [keyboardPlugin, ...ed.view.state.plugins],
|
|
3449
3959
|
}));
|
|
3450
3960
|
}
|
|
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) {
|
|
3961
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: TiptapSlashCommandsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
3962
|
+
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: `
|
|
3963
|
+
<div #menuRef class="slash-commands-menu">
|
|
3964
|
+
@for (command of filteredCommands(); track command.title) {
|
|
3965
|
+
<div
|
|
3966
|
+
class="slash-command-item"
|
|
3967
|
+
[class.selected]="$index === selectedIndex()"
|
|
3968
|
+
(click)="executeCommand(command)"
|
|
3969
|
+
(mouseenter)="selectedIndex.set($index)"
|
|
3970
|
+
>
|
|
3971
|
+
<div class="slash-command-icon">
|
|
3972
|
+
<span class="material-symbols-outlined">{{ command.icon }}</span>
|
|
3973
|
+
</div>
|
|
3974
|
+
<div class="slash-command-content">
|
|
3975
|
+
<div class="slash-command-title">{{ command.title }}</div>
|
|
3976
|
+
<div class="slash-command-description">{{ command.description }}</div>
|
|
3977
|
+
</div>
|
|
3978
|
+
</div>
|
|
3979
|
+
}
|
|
3980
|
+
</div>
|
|
3981
|
+
`, 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"] }); }
|
|
3982
|
+
}
|
|
3983
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: TiptapSlashCommandsComponent, decorators: [{
|
|
3984
|
+
type: Component,
|
|
3985
|
+
args: [{ selector: "tiptap-slash-commands", standalone: true, template: `
|
|
3986
|
+
<div #menuRef class="slash-commands-menu">
|
|
3987
|
+
@for (command of filteredCommands(); track command.title) {
|
|
3988
|
+
<div
|
|
3989
|
+
class="slash-command-item"
|
|
3990
|
+
[class.selected]="$index === selectedIndex()"
|
|
3991
|
+
(click)="executeCommand(command)"
|
|
3992
|
+
(mouseenter)="selectedIndex.set($index)"
|
|
3993
|
+
>
|
|
3994
|
+
<div class="slash-command-icon">
|
|
3995
|
+
<span class="material-symbols-outlined">{{ command.icon }}</span>
|
|
3996
|
+
</div>
|
|
3997
|
+
<div class="slash-command-content">
|
|
3998
|
+
<div class="slash-command-title">{{ command.title }}</div>
|
|
3999
|
+
<div class="slash-command-description">{{ command.description }}</div>
|
|
4000
|
+
</div>
|
|
4001
|
+
</div>
|
|
4002
|
+
}
|
|
4003
|
+
</div>
|
|
4004
|
+
`, 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"] }]
|
|
4005
|
+
}], ctorParameters: () => [], propDecorators: { menuRef: [{
|
|
4006
|
+
type: ViewChild,
|
|
4007
|
+
args: ["menuRef", { static: false }]
|
|
4008
|
+
}] } });
|
|
4009
|
+
|
|
4010
|
+
// Main component
|
|
4011
|
+
|
|
4012
|
+
class TiptapImageUploadComponent {
|
|
4013
|
+
constructor() {
|
|
4014
|
+
// Inputs
|
|
4015
|
+
this.config = input({
|
|
4016
|
+
maxSize: 5, // 5MB par défaut
|
|
4017
|
+
maxWidth: 1920, // largeur max par défaut
|
|
4018
|
+
maxHeight: 1080, // hauteur max par défaut
|
|
4019
|
+
allowedTypes: ["image/jpeg", "image/png", "image/gif", "image/webp"],
|
|
4020
|
+
enableDragDrop: true,
|
|
4021
|
+
showPreview: true,
|
|
4022
|
+
multiple: false,
|
|
4023
|
+
compressImages: true,
|
|
4024
|
+
quality: 0.8,
|
|
4025
|
+
});
|
|
4026
|
+
// Outputs
|
|
4027
|
+
this.imageSelected = output();
|
|
4028
|
+
this.error = output();
|
|
4029
|
+
// Signals internes
|
|
4030
|
+
this.isDragOver = signal(false);
|
|
4031
|
+
this.isUploading = signal(false);
|
|
4032
|
+
this.uploadProgress = signal(0);
|
|
4033
|
+
this.previewImage = signal(null);
|
|
4034
|
+
this.previewInfo = signal("");
|
|
4035
|
+
this.errorMessage = signal(null);
|
|
4036
|
+
// Computed
|
|
4037
|
+
this.acceptedTypes = computed(() => {
|
|
4038
|
+
const types = this.config().allowedTypes || ["image/*"];
|
|
4039
|
+
return types.join(",");
|
|
4040
|
+
});
|
|
4041
|
+
}
|
|
4042
|
+
triggerFileInput() {
|
|
4043
|
+
const input = document.querySelector('input[type="file"]');
|
|
4044
|
+
if (input) {
|
|
4045
|
+
input.click();
|
|
4046
|
+
}
|
|
4047
|
+
}
|
|
4048
|
+
onFileSelected(event) {
|
|
4049
|
+
const input = event.target;
|
|
4050
|
+
const files = input.files;
|
|
4051
|
+
if (files && files.length > 0) {
|
|
4052
|
+
this.processFiles(Array.from(files));
|
|
4053
|
+
}
|
|
4054
|
+
// Reset input
|
|
4055
|
+
input.value = "";
|
|
4056
|
+
}
|
|
4057
|
+
onDragOver(event) {
|
|
4058
|
+
event.preventDefault();
|
|
4059
|
+
event.stopPropagation();
|
|
4060
|
+
this.isDragOver.set(true);
|
|
4061
|
+
}
|
|
4062
|
+
onDrop(event) {
|
|
4063
|
+
event.preventDefault();
|
|
4064
|
+
event.stopPropagation();
|
|
4065
|
+
this.isDragOver.set(false);
|
|
4066
|
+
const files = event.dataTransfer?.files;
|
|
4067
|
+
if (files && files.length > 0) {
|
|
4068
|
+
this.processFiles(Array.from(files));
|
|
4069
|
+
}
|
|
4070
|
+
}
|
|
4071
|
+
onDragLeave(event) {
|
|
4072
|
+
event.preventDefault();
|
|
4073
|
+
event.stopPropagation();
|
|
4074
|
+
this.isDragOver.set(false);
|
|
4075
|
+
}
|
|
4076
|
+
processFiles(files) {
|
|
4077
|
+
const config = this.config();
|
|
4078
|
+
const maxSize = (config.maxSize || 5) * 1024 * 1024; // Convertir en bytes
|
|
4079
|
+
const allowedTypes = config.allowedTypes || ["image/*"];
|
|
4080
|
+
// Vérifier le nombre de fichiers
|
|
4081
|
+
if (!config.multiple && files.length > 1) {
|
|
4082
|
+
this.showError("Veuillez sélectionner une seule image");
|
|
4083
|
+
return;
|
|
4084
|
+
}
|
|
4085
|
+
// Traiter chaque fichier
|
|
4086
|
+
files.forEach((file) => {
|
|
4087
|
+
// Vérifier le type
|
|
4088
|
+
if (!this.isValidFileType(file, allowedTypes)) {
|
|
4089
|
+
this.showError(`Type de fichier non supporté: ${file.name}`);
|
|
4090
|
+
return;
|
|
4091
|
+
}
|
|
4092
|
+
// Vérifier la taille
|
|
4093
|
+
if (file.size > maxSize) {
|
|
4094
|
+
this.showError(`Fichier trop volumineux: ${file.name} (max ${config.maxSize}MB)`);
|
|
4095
|
+
return;
|
|
4096
|
+
}
|
|
4097
|
+
// Traiter l'image avec compression si nécessaire
|
|
4098
|
+
this.processImage(file);
|
|
4099
|
+
});
|
|
4100
|
+
}
|
|
4101
|
+
isValidFileType(file, allowedTypes) {
|
|
4102
|
+
if (allowedTypes.includes("image/*")) {
|
|
4103
|
+
return file.type.startsWith("image/");
|
|
4104
|
+
}
|
|
4105
|
+
return allowedTypes.includes(file.type);
|
|
4106
|
+
}
|
|
4107
|
+
processImage(file) {
|
|
4108
|
+
this.isUploading.set(true);
|
|
4109
|
+
this.uploadProgress.set(10);
|
|
4110
|
+
const config = this.config();
|
|
4111
|
+
const originalSize = file.size;
|
|
4112
|
+
// Créer un canvas pour la compression
|
|
4113
|
+
const canvas = document.createElement("canvas");
|
|
4114
|
+
const ctx = canvas.getContext("2d");
|
|
4115
|
+
const img = new Image();
|
|
4116
|
+
img.onload = () => {
|
|
4117
|
+
this.uploadProgress.set(30);
|
|
4118
|
+
// Vérifier les dimensions
|
|
4119
|
+
const maxWidth = config.maxWidth || 1920;
|
|
4120
|
+
const maxHeight = config.maxHeight || 1080;
|
|
4121
|
+
let { width, height } = img;
|
|
4122
|
+
// Redimensionner si nécessaire
|
|
4123
|
+
if (width > maxWidth || height > maxHeight) {
|
|
4124
|
+
const ratio = Math.min(maxWidth / width, maxHeight / height);
|
|
4125
|
+
width *= ratio;
|
|
4126
|
+
height *= ratio;
|
|
4127
|
+
}
|
|
4128
|
+
canvas.width = width;
|
|
4129
|
+
canvas.height = height;
|
|
4130
|
+
// Dessiner l'image redimensionnée
|
|
4131
|
+
ctx?.drawImage(img, 0, 0, width, height);
|
|
4132
|
+
this.uploadProgress.set(70);
|
|
4133
|
+
// Convertir en base64 avec compression
|
|
4134
|
+
const quality = config.quality || 0.8;
|
|
4135
|
+
const mimeType = file.type;
|
|
4136
|
+
canvas.toBlob((blob) => {
|
|
4137
|
+
this.uploadProgress.set(90);
|
|
4138
|
+
if (blob) {
|
|
4139
|
+
const reader = new FileReader();
|
|
4140
|
+
reader.onload = (e) => {
|
|
4141
|
+
const base64 = e.target?.result;
|
|
4142
|
+
if (base64) {
|
|
4143
|
+
const result = {
|
|
4144
|
+
src: base64,
|
|
4145
|
+
name: file.name,
|
|
4146
|
+
size: blob.size,
|
|
4147
|
+
type: file.type,
|
|
4148
|
+
width: Math.round(width),
|
|
4149
|
+
height: Math.round(height),
|
|
4150
|
+
originalSize: originalSize,
|
|
4151
|
+
};
|
|
4152
|
+
// Afficher la prévisualisation si activée
|
|
4153
|
+
if (config.showPreview) {
|
|
4154
|
+
this.previewImage.set(base64);
|
|
4155
|
+
this.previewInfo.set(`${result.width}×${result.height} • ${this.formatFileSize(blob.size)}`);
|
|
4156
|
+
}
|
|
4157
|
+
// Émettre l'événement
|
|
4158
|
+
this.imageSelected.emit(result);
|
|
4159
|
+
this.clearError();
|
|
4160
|
+
}
|
|
4161
|
+
this.uploadProgress.set(100);
|
|
4162
|
+
setTimeout(() => {
|
|
4163
|
+
this.isUploading.set(false);
|
|
4164
|
+
this.uploadProgress.set(0);
|
|
4165
|
+
}, 500);
|
|
4166
|
+
};
|
|
4167
|
+
reader.readAsDataURL(blob);
|
|
4168
|
+
}
|
|
4169
|
+
else {
|
|
4170
|
+
this.showError("Erreur lors de la compression de l'image");
|
|
4171
|
+
this.isUploading.set(false);
|
|
4172
|
+
this.uploadProgress.set(0);
|
|
4173
|
+
}
|
|
4174
|
+
}, mimeType, quality);
|
|
4175
|
+
};
|
|
4176
|
+
img.onerror = () => {
|
|
4177
|
+
this.showError("Erreur lors du chargement de l'image");
|
|
4178
|
+
this.isUploading.set(false);
|
|
4179
|
+
this.uploadProgress.set(0);
|
|
4180
|
+
};
|
|
4181
|
+
img.src = URL.createObjectURL(file);
|
|
4182
|
+
}
|
|
4183
|
+
formatFileSize(bytes) {
|
|
4184
|
+
if (bytes === 0)
|
|
4185
|
+
return "0 B";
|
|
4186
|
+
const k = 1024;
|
|
4187
|
+
const sizes = ["B", "KB", "MB", "GB"];
|
|
4188
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
4189
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + " " + sizes[i];
|
|
4190
|
+
}
|
|
4191
|
+
showError(message) {
|
|
4192
|
+
this.errorMessage.set(message);
|
|
4193
|
+
this.error.emit(message);
|
|
4194
|
+
this.isUploading.set(false);
|
|
4195
|
+
this.uploadProgress.set(0);
|
|
4196
|
+
// Auto-clear après 5 secondes
|
|
4197
|
+
setTimeout(() => {
|
|
4198
|
+
this.clearError();
|
|
4199
|
+
}, 5000);
|
|
4200
|
+
}
|
|
4201
|
+
clearError() {
|
|
4202
|
+
this.errorMessage.set(null);
|
|
4203
|
+
}
|
|
4204
|
+
clearPreview() {
|
|
4205
|
+
this.previewImage.set(null);
|
|
4206
|
+
this.previewInfo.set("");
|
|
4207
|
+
}
|
|
4208
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: TiptapImageUploadComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
4209
|
+
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: `
|
|
4210
|
+
<div class="image-upload-container">
|
|
4211
|
+
<!-- Bouton d'upload -->
|
|
4212
|
+
<tiptap-button
|
|
4213
|
+
icon="image"
|
|
4214
|
+
title="Ajouter une image"
|
|
4215
|
+
[disabled]="isUploading()"
|
|
4216
|
+
(onClick)="triggerFileInput()"
|
|
4217
|
+
/>
|
|
4218
|
+
|
|
4219
|
+
<!-- Input file caché -->
|
|
4220
|
+
<input
|
|
4221
|
+
#fileInput
|
|
4222
|
+
type="file"
|
|
4223
|
+
[accept]="acceptedTypes()"
|
|
4224
|
+
[multiple]="config().multiple"
|
|
4225
|
+
(change)="onFileSelected($event)"
|
|
4226
|
+
style="display: none;"
|
|
4227
|
+
/>
|
|
4228
|
+
|
|
4229
|
+
<!-- Zone de drag & drop -->
|
|
4230
|
+
@if (config().enableDragDrop && isDragOver()) {
|
|
3455
4231
|
<div
|
|
3456
|
-
class="
|
|
3457
|
-
|
|
3458
|
-
(
|
|
3459
|
-
(
|
|
4232
|
+
class="drag-overlay"
|
|
4233
|
+
(dragover)="onDragOver($event)"
|
|
4234
|
+
(drop)="onDrop($event)"
|
|
4235
|
+
(dragleave)="onDragLeave($event)"
|
|
3460
4236
|
>
|
|
3461
|
-
<div class="
|
|
3462
|
-
<span class="material-symbols-outlined">
|
|
4237
|
+
<div class="drag-content">
|
|
4238
|
+
<span class="material-symbols-outlined">cloud_upload</span>
|
|
4239
|
+
<p>Déposez votre image ici</p>
|
|
3463
4240
|
</div>
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
4241
|
+
</div>
|
|
4242
|
+
}
|
|
4243
|
+
|
|
4244
|
+
<!-- Barre de progression -->
|
|
4245
|
+
@if (isUploading() && uploadProgress() > 0) {
|
|
4246
|
+
<div class="upload-progress">
|
|
4247
|
+
<div class="progress-bar">
|
|
4248
|
+
<div class="progress-fill" [style.width.%]="uploadProgress()"></div>
|
|
4249
|
+
</div>
|
|
4250
|
+
<div class="progress-text">{{ uploadProgress() }}%</div>
|
|
4251
|
+
</div>
|
|
4252
|
+
}
|
|
4253
|
+
|
|
4254
|
+
<!-- Prévisualisation -->
|
|
4255
|
+
@if (config().showPreview && previewImage()) {
|
|
4256
|
+
<div class="image-preview">
|
|
4257
|
+
<img [src]="previewImage()" alt="Prévisualisation" />
|
|
4258
|
+
<div class="preview-info">
|
|
4259
|
+
<span>{{ previewInfo() }}</span>
|
|
3467
4260
|
</div>
|
|
4261
|
+
<button
|
|
4262
|
+
class="preview-close"
|
|
4263
|
+
(click)="clearPreview()"
|
|
4264
|
+
title="Fermer la prévisualisation"
|
|
4265
|
+
>
|
|
4266
|
+
<span class="material-symbols-outlined">close</span>
|
|
4267
|
+
</button>
|
|
4268
|
+
</div>
|
|
4269
|
+
}
|
|
4270
|
+
|
|
4271
|
+
<!-- Messages d'erreur -->
|
|
4272
|
+
@if (errorMessage()) {
|
|
4273
|
+
<div class="error-message">
|
|
4274
|
+
<span class="material-symbols-outlined">error</span>
|
|
4275
|
+
{{ errorMessage() }}
|
|
3468
4276
|
</div>
|
|
3469
4277
|
}
|
|
3470
4278
|
</div>
|
|
3471
|
-
`, isInline: true, styles: [".
|
|
4279
|
+
`, 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
4280
|
}
|
|
3473
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type:
|
|
4281
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: TiptapImageUploadComponent, decorators: [{
|
|
3474
4282
|
type: Component,
|
|
3475
|
-
args: [{ selector: "tiptap-
|
|
3476
|
-
<div
|
|
3477
|
-
|
|
4283
|
+
args: [{ selector: "tiptap-image-upload", standalone: true, imports: [TiptapButtonComponent], template: `
|
|
4284
|
+
<div class="image-upload-container">
|
|
4285
|
+
<!-- Bouton d'upload -->
|
|
4286
|
+
<tiptap-button
|
|
4287
|
+
icon="image"
|
|
4288
|
+
title="Ajouter une image"
|
|
4289
|
+
[disabled]="isUploading()"
|
|
4290
|
+
(onClick)="triggerFileInput()"
|
|
4291
|
+
/>
|
|
4292
|
+
|
|
4293
|
+
<!-- Input file caché -->
|
|
4294
|
+
<input
|
|
4295
|
+
#fileInput
|
|
4296
|
+
type="file"
|
|
4297
|
+
[accept]="acceptedTypes()"
|
|
4298
|
+
[multiple]="config().multiple"
|
|
4299
|
+
(change)="onFileSelected($event)"
|
|
4300
|
+
style="display: none;"
|
|
4301
|
+
/>
|
|
4302
|
+
|
|
4303
|
+
<!-- Zone de drag & drop -->
|
|
4304
|
+
@if (config().enableDragDrop && isDragOver()) {
|
|
3478
4305
|
<div
|
|
3479
|
-
class="
|
|
3480
|
-
|
|
3481
|
-
(
|
|
3482
|
-
(
|
|
4306
|
+
class="drag-overlay"
|
|
4307
|
+
(dragover)="onDragOver($event)"
|
|
4308
|
+
(drop)="onDrop($event)"
|
|
4309
|
+
(dragleave)="onDragLeave($event)"
|
|
3483
4310
|
>
|
|
3484
|
-
<div class="
|
|
3485
|
-
<span class="material-symbols-outlined">
|
|
4311
|
+
<div class="drag-content">
|
|
4312
|
+
<span class="material-symbols-outlined">cloud_upload</span>
|
|
4313
|
+
<p>Déposez votre image ici</p>
|
|
3486
4314
|
</div>
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
4315
|
+
</div>
|
|
4316
|
+
}
|
|
4317
|
+
|
|
4318
|
+
<!-- Barre de progression -->
|
|
4319
|
+
@if (isUploading() && uploadProgress() > 0) {
|
|
4320
|
+
<div class="upload-progress">
|
|
4321
|
+
<div class="progress-bar">
|
|
4322
|
+
<div class="progress-fill" [style.width.%]="uploadProgress()"></div>
|
|
3490
4323
|
</div>
|
|
4324
|
+
<div class="progress-text">{{ uploadProgress() }}%</div>
|
|
4325
|
+
</div>
|
|
4326
|
+
}
|
|
4327
|
+
|
|
4328
|
+
<!-- Prévisualisation -->
|
|
4329
|
+
@if (config().showPreview && previewImage()) {
|
|
4330
|
+
<div class="image-preview">
|
|
4331
|
+
<img [src]="previewImage()" alt="Prévisualisation" />
|
|
4332
|
+
<div class="preview-info">
|
|
4333
|
+
<span>{{ previewInfo() }}</span>
|
|
4334
|
+
</div>
|
|
4335
|
+
<button
|
|
4336
|
+
class="preview-close"
|
|
4337
|
+
(click)="clearPreview()"
|
|
4338
|
+
title="Fermer la prévisualisation"
|
|
4339
|
+
>
|
|
4340
|
+
<span class="material-symbols-outlined">close</span>
|
|
4341
|
+
</button>
|
|
4342
|
+
</div>
|
|
4343
|
+
}
|
|
4344
|
+
|
|
4345
|
+
<!-- Messages d'erreur -->
|
|
4346
|
+
@if (errorMessage()) {
|
|
4347
|
+
<div class="error-message">
|
|
4348
|
+
<span class="material-symbols-outlined">error</span>
|
|
4349
|
+
{{ errorMessage() }}
|
|
3491
4350
|
</div>
|
|
3492
4351
|
}
|
|
3493
4352
|
</div>
|
|
3494
|
-
`, styles: [".
|
|
3495
|
-
}]
|
|
3496
|
-
type: ViewChild,
|
|
3497
|
-
args: ["menuRef", { static: false }]
|
|
3498
|
-
}] } });
|
|
4353
|
+
`, 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"] }]
|
|
4354
|
+
}] });
|
|
3499
4355
|
|
|
3500
4356
|
class NoopValueAccessorDirective {
|
|
3501
4357
|
writeValue(obj) { }
|
|
@@ -3547,8 +4403,10 @@ const DEFAULT_TOOLBAR_CONFIG = {
|
|
|
3547
4403
|
link: true,
|
|
3548
4404
|
image: true,
|
|
3549
4405
|
horizontalRule: true,
|
|
4406
|
+
table: true,
|
|
3550
4407
|
undo: true,
|
|
3551
4408
|
redo: true,
|
|
4409
|
+
clear: false, // Désactivé par défaut (opt-in)
|
|
3552
4410
|
separator: true,
|
|
3553
4411
|
};
|
|
3554
4412
|
// Configuration par défaut du bubble menu
|
|
@@ -3574,6 +4432,23 @@ const DEFAULT_IMAGE_BUBBLE_MENU_CONFIG = {
|
|
|
3574
4432
|
deleteImage: true,
|
|
3575
4433
|
separator: true,
|
|
3576
4434
|
};
|
|
4435
|
+
// Configuration par défaut du menu de table
|
|
4436
|
+
const DEFAULT_TABLE_MENU_CONFIG = {
|
|
4437
|
+
addRowBefore: true,
|
|
4438
|
+
addRowAfter: true,
|
|
4439
|
+
deleteRow: true,
|
|
4440
|
+
addColumnBefore: true,
|
|
4441
|
+
addColumnAfter: true,
|
|
4442
|
+
deleteColumn: true,
|
|
4443
|
+
toggleHeaderRow: true,
|
|
4444
|
+
toggleHeaderColumn: true,
|
|
4445
|
+
deleteTable: true,
|
|
4446
|
+
separator: true,
|
|
4447
|
+
};
|
|
4448
|
+
const DEFAULT_CELL_MENU_CONFIG = {
|
|
4449
|
+
mergeCells: true,
|
|
4450
|
+
splitCell: true,
|
|
4451
|
+
};
|
|
3577
4452
|
class AngularTiptapEditorComponent {
|
|
3578
4453
|
constructor() {
|
|
3579
4454
|
// Nouveaux inputs avec signal
|
|
@@ -3607,11 +4482,18 @@ class AngularTiptapEditorComponent {
|
|
|
3607
4482
|
this.editorBlur = output();
|
|
3608
4483
|
// ViewChild avec signal
|
|
3609
4484
|
this.editorElement = viewChild.required("editorElement");
|
|
3610
|
-
// Signals pour l'état interne
|
|
3611
|
-
this.
|
|
3612
|
-
this.
|
|
3613
|
-
this.
|
|
3614
|
-
this.
|
|
4485
|
+
// Signals privés pour l'état interne
|
|
4486
|
+
this._editor = signal(null);
|
|
4487
|
+
this._characterCount = signal(0);
|
|
4488
|
+
this._wordCount = signal(0);
|
|
4489
|
+
this._isDragOver = signal(false);
|
|
4490
|
+
this._editorFullyInitialized = signal(false);
|
|
4491
|
+
// Accès en lecture seule aux signaux
|
|
4492
|
+
this.editor = this._editor.asReadonly();
|
|
4493
|
+
this.characterCount = this._characterCount.asReadonly();
|
|
4494
|
+
this.wordCount = this._wordCount.asReadonly();
|
|
4495
|
+
this.isDragOver = this._isDragOver.asReadonly();
|
|
4496
|
+
this.editorFullyInitialized = this._editorFullyInitialized.asReadonly();
|
|
3615
4497
|
// Computed pour les états de l'éditeur
|
|
3616
4498
|
this.isEditorReady = computed(() => this.editor() !== null);
|
|
3617
4499
|
// Computed pour la configuration de la toolbar
|
|
@@ -3626,6 +4508,23 @@ class AngularTiptapEditorComponent {
|
|
|
3626
4508
|
this.imageBubbleMenuConfig = computed(() => Object.keys(this.imageBubbleMenu()).length === 0
|
|
3627
4509
|
? DEFAULT_IMAGE_BUBBLE_MENU_CONFIG
|
|
3628
4510
|
: { ...DEFAULT_IMAGE_BUBBLE_MENU_CONFIG, ...this.imageBubbleMenu() });
|
|
4511
|
+
// Computed pour la configuration du bubble menu table
|
|
4512
|
+
this.tableBubbleMenuConfig = computed(() => ({
|
|
4513
|
+
addRowBefore: true,
|
|
4514
|
+
addRowAfter: true,
|
|
4515
|
+
deleteRow: true,
|
|
4516
|
+
addColumnBefore: true,
|
|
4517
|
+
addColumnAfter: true,
|
|
4518
|
+
deleteColumn: true,
|
|
4519
|
+
deleteTable: true,
|
|
4520
|
+
toggleHeaderRow: true,
|
|
4521
|
+
toggleHeaderColumn: true,
|
|
4522
|
+
}));
|
|
4523
|
+
// Computed pour la configuration du menu de cellules
|
|
4524
|
+
this.cellBubbleMenuConfig = computed(() => ({
|
|
4525
|
+
mergeCells: true,
|
|
4526
|
+
splitCell: true,
|
|
4527
|
+
}));
|
|
3629
4528
|
// Computed pour la configuration de l'upload d'images
|
|
3630
4529
|
this.imageUploadConfig = computed(() => ({
|
|
3631
4530
|
maxSize: 5,
|
|
@@ -3646,6 +4545,7 @@ class AngularTiptapEditorComponent {
|
|
|
3646
4545
|
this.ngControl = inject(NgControl, { self: true, optional: true });
|
|
3647
4546
|
this.i18nService = inject(TiptapI18nService);
|
|
3648
4547
|
this.imageService = inject(ImageService);
|
|
4548
|
+
this.editorCommandsService = inject(EditorCommandsService);
|
|
3649
4549
|
// Effet pour gérer le changement de langue
|
|
3650
4550
|
effect(() => {
|
|
3651
4551
|
const locale = this.locale();
|
|
@@ -3686,9 +4586,16 @@ class AngularTiptapEditorComponent {
|
|
|
3686
4586
|
const currentEditor = this.editor();
|
|
3687
4587
|
const isEditable = this.editable();
|
|
3688
4588
|
if (currentEditor) {
|
|
3689
|
-
|
|
4589
|
+
this.editorCommandsService.setEditable(currentEditor, isEditable);
|
|
3690
4590
|
}
|
|
3691
4591
|
});
|
|
4592
|
+
// Effect pour la détection du survol des tables
|
|
4593
|
+
effect(() => {
|
|
4594
|
+
const currentEditor = this.editor();
|
|
4595
|
+
if (!currentEditor)
|
|
4596
|
+
return;
|
|
4597
|
+
// Table hover detection supprimée car remplacée par le menu bubble
|
|
4598
|
+
});
|
|
3692
4599
|
}
|
|
3693
4600
|
ngAfterViewInit() {
|
|
3694
4601
|
// La vue est déjà complètement initialisée dans ngAfterViewInit
|
|
@@ -3701,7 +4608,7 @@ class AngularTiptapEditorComponent {
|
|
|
3701
4608
|
if (currentEditor) {
|
|
3702
4609
|
currentEditor.destroy();
|
|
3703
4610
|
}
|
|
3704
|
-
this.
|
|
4611
|
+
this._editorFullyInitialized.set(false);
|
|
3705
4612
|
}
|
|
3706
4613
|
initEditor() {
|
|
3707
4614
|
const extensions = [
|
|
@@ -3739,6 +4646,7 @@ class AngularTiptapEditorComponent {
|
|
|
3739
4646
|
uploadProgress: () => this.imageService.uploadProgress(),
|
|
3740
4647
|
uploadMessage: () => this.imageService.uploadMessage(),
|
|
3741
4648
|
}),
|
|
4649
|
+
TableExtension,
|
|
3742
4650
|
];
|
|
3743
4651
|
// Ajouter l'extension Office Paste si activée
|
|
3744
4652
|
if (this.enableOfficePaste()) {
|
|
@@ -3778,7 +4686,7 @@ class AngularTiptapEditorComponent {
|
|
|
3778
4686
|
// Marquer l'éditeur comme complètement initialisé après un court délai
|
|
3779
4687
|
// pour s'assurer que tous les plugins et extensions sont prêts
|
|
3780
4688
|
setTimeout(() => {
|
|
3781
|
-
this.
|
|
4689
|
+
this._editorFullyInitialized.set(true);
|
|
3782
4690
|
}, 100);
|
|
3783
4691
|
},
|
|
3784
4692
|
onFocus: ({ editor, event }) => {
|
|
@@ -3793,15 +4701,13 @@ class AngularTiptapEditorComponent {
|
|
|
3793
4701
|
},
|
|
3794
4702
|
});
|
|
3795
4703
|
// Stocker la référence de l'éditeur immédiatement
|
|
3796
|
-
this.
|
|
4704
|
+
this._editor.set(newEditor);
|
|
3797
4705
|
}
|
|
3798
4706
|
updateCharacterCount(editor) {
|
|
3799
4707
|
if (this.showCharacterCount() && editor.storage["characterCount"]) {
|
|
3800
4708
|
const storage = editor.storage["characterCount"];
|
|
3801
|
-
this.
|
|
3802
|
-
|
|
3803
|
-
words: storage.words(),
|
|
3804
|
-
});
|
|
4709
|
+
this._characterCount.set(storage.characters());
|
|
4710
|
+
this._wordCount.set(storage.words());
|
|
3805
4711
|
}
|
|
3806
4712
|
}
|
|
3807
4713
|
// Méthodes pour l'upload d'images
|
|
@@ -3835,12 +4741,12 @@ class AngularTiptapEditorComponent {
|
|
|
3835
4741
|
onDragOver(event) {
|
|
3836
4742
|
event.preventDefault();
|
|
3837
4743
|
event.stopPropagation();
|
|
3838
|
-
this.
|
|
4744
|
+
this._isDragOver.set(true);
|
|
3839
4745
|
}
|
|
3840
4746
|
onDrop(event) {
|
|
3841
4747
|
event.preventDefault();
|
|
3842
4748
|
event.stopPropagation();
|
|
3843
|
-
this.
|
|
4749
|
+
this._isDragOver.set(false);
|
|
3844
4750
|
const files = event.dataTransfer?.files;
|
|
3845
4751
|
if (files && files.length > 0) {
|
|
3846
4752
|
const file = files[0];
|
|
@@ -3871,16 +4777,32 @@ class AngularTiptapEditorComponent {
|
|
|
3871
4777
|
return this.editor()?.getText() || "";
|
|
3872
4778
|
}
|
|
3873
4779
|
setContent(content, emitUpdate = true) {
|
|
3874
|
-
this.editor()
|
|
4780
|
+
const editor = this.editor();
|
|
4781
|
+
if (editor) {
|
|
4782
|
+
this.editorCommandsService.setContent(editor, content, emitUpdate);
|
|
4783
|
+
}
|
|
3875
4784
|
}
|
|
3876
4785
|
focus() {
|
|
3877
|
-
this.editor()
|
|
4786
|
+
const editor = this.editor();
|
|
4787
|
+
if (editor) {
|
|
4788
|
+
this.editorCommandsService.focus(editor);
|
|
4789
|
+
}
|
|
3878
4790
|
}
|
|
3879
4791
|
blur() {
|
|
3880
|
-
this.editor()
|
|
4792
|
+
const editor = this.editor();
|
|
4793
|
+
if (editor) {
|
|
4794
|
+
this.editorCommandsService.blur(editor);
|
|
4795
|
+
}
|
|
3881
4796
|
}
|
|
3882
4797
|
clearContent() {
|
|
3883
|
-
this.editor()
|
|
4798
|
+
const editor = this.editor();
|
|
4799
|
+
if (editor) {
|
|
4800
|
+
this.editorCommandsService.clearContent(editor);
|
|
4801
|
+
}
|
|
4802
|
+
}
|
|
4803
|
+
// Méthode publique pour obtenir l'éditeur
|
|
4804
|
+
getEditor() {
|
|
4805
|
+
return this.editor();
|
|
3884
4806
|
}
|
|
3885
4807
|
setupFormControlSubscription() {
|
|
3886
4808
|
const control = this.ngControl?.control;
|
|
@@ -3900,7 +4822,7 @@ class AngularTiptapEditorComponent {
|
|
|
3900
4822
|
setDisabledState(isDisabled) {
|
|
3901
4823
|
const currentEditor = this.editor();
|
|
3902
4824
|
if (currentEditor) {
|
|
3903
|
-
|
|
4825
|
+
this.editorCommandsService.setEditable(currentEditor, !isDisabled);
|
|
3904
4826
|
}
|
|
3905
4827
|
}
|
|
3906
4828
|
onEditorClick(event) {
|
|
@@ -3975,19 +4897,40 @@ class AngularTiptapEditorComponent {
|
|
|
3975
4897
|
></tiptap-slash-commands>
|
|
3976
4898
|
}
|
|
3977
4899
|
|
|
4900
|
+
<!-- Table Menu -->
|
|
4901
|
+
@if (editor()) {
|
|
4902
|
+
<tiptap-table-bubble-menu
|
|
4903
|
+
[editor]="editor()!"
|
|
4904
|
+
[config]="tableBubbleMenuConfig()"
|
|
4905
|
+
[style.display]="editorFullyInitialized() ? 'block' : 'none'"
|
|
4906
|
+
></tiptap-table-bubble-menu>
|
|
4907
|
+
}
|
|
4908
|
+
|
|
4909
|
+
<!-- Cell Menu -->
|
|
4910
|
+
@if (editor()) {
|
|
4911
|
+
<tiptap-cell-bubble-menu
|
|
4912
|
+
[editor]="editor()!"
|
|
4913
|
+
[config]="cellBubbleMenuConfig()"
|
|
4914
|
+
[style.display]="editorFullyInitialized() ? 'block' : 'none'"
|
|
4915
|
+
></tiptap-cell-bubble-menu>
|
|
4916
|
+
}
|
|
4917
|
+
|
|
4918
|
+
<!-- Table Edit Button - Supprimé car remplacé par le menu bubble -->
|
|
4919
|
+
|
|
3978
4920
|
<!-- Compteur de caractères -->
|
|
3979
|
-
@if (showCharacterCount()
|
|
4921
|
+
@if (showCharacterCount()) {
|
|
3980
4922
|
<div class="character-count">
|
|
3981
|
-
{{
|
|
3982
|
-
{{ i18nService.editor().
|
|
3983
|
-
{{
|
|
3984
|
-
(
|
|
4923
|
+
{{ characterCount() }}
|
|
4924
|
+
{{ i18nService.editor().character
|
|
4925
|
+
}}{{ characterCount() > 1 ? "s" : "" }}, {{ wordCount() }}
|
|
4926
|
+
{{ i18nService.editor().word }}{{ wordCount() > 1 ? "s" : "" }}
|
|
4927
|
+
@if (maxCharacters()) { /
|
|
3985
4928
|
{{ maxCharacters() }}
|
|
3986
4929
|
}
|
|
3987
4930
|
</div>
|
|
3988
4931
|
}
|
|
3989
4932
|
</div>
|
|
3990
|
-
`, isInline: true, styles: [".tiptap-editor{border:2px solid #e2e8f0;border-radius:8px;background:#fff;
|
|
4933
|
+
`, 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
4934
|
}
|
|
3992
4935
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: AngularTiptapEditorComponent, decorators: [{
|
|
3993
4936
|
type: Component,
|
|
@@ -3996,6 +4939,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.0", ngImpor
|
|
|
3996
4939
|
TiptapImageUploadComponent,
|
|
3997
4940
|
TiptapBubbleMenuComponent,
|
|
3998
4941
|
TiptapImageBubbleMenuComponent,
|
|
4942
|
+
TiptapTableBubbleMenuComponent,
|
|
4943
|
+
TiptapCellBubbleMenuComponent,
|
|
3999
4944
|
TiptapSlashCommandsComponent,
|
|
4000
4945
|
], template: `
|
|
4001
4946
|
<div class="tiptap-editor">
|
|
@@ -4050,21 +4995,57 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.0", ngImpor
|
|
|
4050
4995
|
></tiptap-slash-commands>
|
|
4051
4996
|
}
|
|
4052
4997
|
|
|
4998
|
+
<!-- Table Menu -->
|
|
4999
|
+
@if (editor()) {
|
|
5000
|
+
<tiptap-table-bubble-menu
|
|
5001
|
+
[editor]="editor()!"
|
|
5002
|
+
[config]="tableBubbleMenuConfig()"
|
|
5003
|
+
[style.display]="editorFullyInitialized() ? 'block' : 'none'"
|
|
5004
|
+
></tiptap-table-bubble-menu>
|
|
5005
|
+
}
|
|
5006
|
+
|
|
5007
|
+
<!-- Cell Menu -->
|
|
5008
|
+
@if (editor()) {
|
|
5009
|
+
<tiptap-cell-bubble-menu
|
|
5010
|
+
[editor]="editor()!"
|
|
5011
|
+
[config]="cellBubbleMenuConfig()"
|
|
5012
|
+
[style.display]="editorFullyInitialized() ? 'block' : 'none'"
|
|
5013
|
+
></tiptap-cell-bubble-menu>
|
|
5014
|
+
}
|
|
5015
|
+
|
|
5016
|
+
<!-- Table Edit Button - Supprimé car remplacé par le menu bubble -->
|
|
5017
|
+
|
|
4053
5018
|
<!-- Compteur de caractères -->
|
|
4054
|
-
@if (showCharacterCount()
|
|
5019
|
+
@if (showCharacterCount()) {
|
|
4055
5020
|
<div class="character-count">
|
|
4056
|
-
{{
|
|
4057
|
-
{{ i18nService.editor().
|
|
4058
|
-
{{
|
|
4059
|
-
(
|
|
5021
|
+
{{ characterCount() }}
|
|
5022
|
+
{{ i18nService.editor().character
|
|
5023
|
+
}}{{ characterCount() > 1 ? "s" : "" }}, {{ wordCount() }}
|
|
5024
|
+
{{ i18nService.editor().word }}{{ wordCount() > 1 ? "s" : "" }}
|
|
5025
|
+
@if (maxCharacters()) { /
|
|
4060
5026
|
{{ maxCharacters() }}
|
|
4061
5027
|
}
|
|
4062
5028
|
</div>
|
|
4063
5029
|
}
|
|
4064
5030
|
</div>
|
|
4065
|
-
`, styles: [".tiptap-editor{border:2px solid #e2e8f0;border-radius:8px;background:#fff;
|
|
5031
|
+
`, 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
5032
|
}], ctorParameters: () => [] });
|
|
4067
5033
|
|
|
5034
|
+
/**
|
|
5035
|
+
* Clés des commandes dans l'ordre de création
|
|
5036
|
+
*/
|
|
5037
|
+
const SLASH_COMMAND_KEYS = [
|
|
5038
|
+
"heading1",
|
|
5039
|
+
"heading2",
|
|
5040
|
+
"heading3",
|
|
5041
|
+
"bulletList",
|
|
5042
|
+
"orderedList",
|
|
5043
|
+
"blockquote",
|
|
5044
|
+
"code",
|
|
5045
|
+
"image",
|
|
5046
|
+
"horizontalRule",
|
|
5047
|
+
"table",
|
|
5048
|
+
];
|
|
4068
5049
|
/**
|
|
4069
5050
|
* Factory function pour créer les slash commands traduits
|
|
4070
5051
|
*/
|
|
@@ -4202,22 +5183,28 @@ function createI18nSlashCommands(i18nService) {
|
|
|
4202
5183
|
keywords: slashCommands.horizontalRule.keywords,
|
|
4203
5184
|
command: (editor) => editor.chain().focus().setHorizontalRule().run(),
|
|
4204
5185
|
},
|
|
5186
|
+
{
|
|
5187
|
+
title: slashCommands.table.title,
|
|
5188
|
+
description: slashCommands.table.description,
|
|
5189
|
+
icon: "table_view",
|
|
5190
|
+
keywords: slashCommands.table.keywords,
|
|
5191
|
+
command: (editor) => editor.chain().focus().insertTable({ rows: 3, cols: 3 }).run(),
|
|
5192
|
+
},
|
|
4205
5193
|
];
|
|
4206
5194
|
}
|
|
4207
5195
|
/**
|
|
4208
|
-
*
|
|
5196
|
+
* Fonction utilitaire pour filtrer les slash commands selon les commandes actives
|
|
5197
|
+
* Utilise le service i18n injecté en interne
|
|
4209
5198
|
*/
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
horizontalRule: "horizontalRule",
|
|
4220
|
-
};
|
|
5199
|
+
function filterSlashCommands(activeCommands) {
|
|
5200
|
+
// Injecter le service i18n en interne
|
|
5201
|
+
const i18nService = inject(TiptapI18nService);
|
|
5202
|
+
const allCommands = createI18nSlashCommands(i18nService);
|
|
5203
|
+
return allCommands.filter((command, index) => {
|
|
5204
|
+
const commandKey = SLASH_COMMAND_KEYS[index];
|
|
5205
|
+
return commandKey && activeCommands.has(commandKey);
|
|
5206
|
+
});
|
|
5207
|
+
}
|
|
4221
5208
|
|
|
4222
5209
|
/*
|
|
4223
5210
|
* Public API Surface of tiptap-editor
|
|
@@ -4228,5 +5215,5 @@ const SLASH_COMMAND_KEYS = {
|
|
|
4228
5215
|
* Generated bundle index. Do not edit.
|
|
4229
5216
|
*/
|
|
4230
5217
|
|
|
4231
|
-
export { AngularTiptapEditorComponent, DEFAULT_BUBBLE_MENU_CONFIG, DEFAULT_IMAGE_BUBBLE_MENU_CONFIG, DEFAULT_SLASH_COMMANDS, DEFAULT_TOOLBAR_CONFIG, NoopValueAccessorDirective, TiptapI18nService, createI18nSlashCommands };
|
|
5218
|
+
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, EditorCommandsService, ImageService, NoopValueAccessorDirective, SLASH_COMMAND_KEYS, TiptapI18nService, createI18nSlashCommands, filterSlashCommands };
|
|
4232
5219
|
//# sourceMappingURL=flogeez-angular-tiptap-editor.mjs.map
|