@flexiui/svelte-rich-text 0.0.67 → 0.0.69
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.
- package/dist/RichText.svelte +164 -142
- package/dist/Toolbar/action-buttons/nodes/HeadingBtn.svelte +22 -1
- package/dist/Toolbar/dropdown-buttons/HeadingsDropdownBtn.svelte +23 -14
- package/dist/extensions/NodeFontSize.js +1 -1
- package/dist/extensions/NodeLineHeight.js +1 -1
- package/dist/extensions/SemanticHeadings.js +3 -3
- package/dist/extensions/extensions.js +6 -0
- package/dist/extensions/resizable-nodes.d.ts +7 -0
- package/dist/extensions/resizable-nodes.js +116 -0
- package/dist/getExtensions.d.ts +15 -0
- package/dist/getExtensions.js +63 -3
- package/dist/styles.css +36 -0
- package/dist/utils.js +1 -1
- package/package.json +1 -1
package/dist/RichText.svelte
CHANGED
|
@@ -100,6 +100,7 @@
|
|
|
100
100
|
contentWrapperAs?: T;
|
|
101
101
|
inlineNodeMode?: boolean;
|
|
102
102
|
trailingNode?: boolean;
|
|
103
|
+
cleanMode?: boolean;
|
|
103
104
|
}
|
|
104
105
|
|
|
105
106
|
type ToolbarButton =
|
|
@@ -157,6 +158,8 @@
|
|
|
157
158
|
contentWrapperAs = "div" as T,
|
|
158
159
|
inlineNodeMode = false,
|
|
159
160
|
trailingNode = true,
|
|
161
|
+
cleanMode = false,
|
|
162
|
+
...rest
|
|
160
163
|
}: Props = $props();
|
|
161
164
|
|
|
162
165
|
let editor = $state() as Readable<Editor>;
|
|
@@ -391,7 +394,7 @@
|
|
|
391
394
|
content,
|
|
392
395
|
editorProps: {
|
|
393
396
|
attributes: {
|
|
394
|
-
class: "fl-rich-text-doc",
|
|
397
|
+
class: cleanMode ? "fl-rich-text-doc-clean" : "fl-rich-text-doc",
|
|
395
398
|
},
|
|
396
399
|
handleKeyDown: (view, event) => {
|
|
397
400
|
if (event.key === "Enter" && !event.ctrlKey) {
|
|
@@ -784,153 +787,172 @@ function onOpenChangeHighlight(open: boolean) {
|
|
|
784
787
|
}
|
|
785
788
|
</script>
|
|
786
789
|
|
|
787
|
-
|
|
788
|
-
class="fl-rich-text {className}"
|
|
789
|
-
class:editable
|
|
790
|
-
style="
|
|
791
|
-
--fl-editor-accent-color: {editorConfig.editorAccentColor};
|
|
792
|
-
--fl-editor-radius: {editorConfig.editorRadius};
|
|
793
|
-
--fl-editor-bg: {editorConfig.editorBgColor};
|
|
794
|
-
--fl-toolbar-sticky-position: {editorConfig.toolbarStickyPosition}px;
|
|
795
|
-
--fl-toolbar-z-index: {editorConfig.toolbarZIndex};
|
|
796
|
-
--fl-toolbar-padding: {editorConfig.toolbarPadding};
|
|
797
|
-
--fl-toolbar-gap: {editorConfig.toolbarGap};
|
|
798
|
-
--fl-toolbar-bg: {editorConfig.toolbarBgColor};
|
|
799
|
-
--fl-toolbar-justify-content: {editorConfig.toolbarJustifyContent};
|
|
800
|
-
--fl-toolbar-text-color: {editorConfig.toolbarTextColor};
|
|
801
|
-
--fl-toolbar-btn-padding: {editorConfig.toolbarBtnPadding};
|
|
802
|
-
--fl-toolbar-btn-radius: {editorConfig.toolbarBtnRadius};
|
|
803
|
-
--fl-toolbar-btn-min-height: {editorConfig.toolbarBtnMinHeight};
|
|
804
|
-
--fl-toolbar-btn-min-width: {editorConfig.toolbarBtnMinWidth ||
|
|
805
|
-
editorConfig.toolbarBtnMinHeight};
|
|
806
|
-
--fl-doc-max-width: {editorConfig.docMaxWidth};
|
|
807
|
-
--fl-doc-padding: {editorConfig.docPadding};
|
|
808
|
-
--fl-doc-bg: {editorConfig.docBg};
|
|
809
|
-
--fl-doc-margin-inline: {editorConfig.docMarginInline};
|
|
810
|
-
--fl-doc-margin-block: {editorConfig.docMarginBlock};
|
|
811
|
-
--fl-doc-radius: {editorConfig.docRadius};
|
|
812
|
-
--fl-doc-text-color: {editorConfig.docTextColor};
|
|
813
|
-
"
|
|
814
|
-
>
|
|
815
|
-
{#if editor && showToolbar}
|
|
816
|
-
<header class="fl-rich-text-toolbar">
|
|
817
|
-
<div class="fl-rich-text-toolbar-container container">
|
|
818
|
-
{#each toolbarGroups as toolbarGroup}
|
|
819
|
-
{#if toolbarGroup.length > 0}
|
|
820
|
-
<div role="group" class="fl-rich-text-toolbar-group">
|
|
821
|
-
{#each toolbarGroup as toolbarItem}
|
|
822
|
-
{#if Array.isArray(toolbarItem)}
|
|
823
|
-
<!-- Si por alguna razón hay un array anidado, manejarlo -->
|
|
824
|
-
<p>Array anidado (no debería pasar)</p>
|
|
825
|
-
{:else if typeof toolbarItem === "string"}
|
|
826
|
-
<RenderToolbarButton
|
|
827
|
-
type={toolbarItem}
|
|
828
|
-
{editor}
|
|
829
|
-
{nodeCounters}
|
|
830
|
-
{nodesLimit}
|
|
831
|
-
{currentNodeCount}
|
|
832
|
-
accentSoft={isAccentSoft}
|
|
833
|
-
{fontSize}
|
|
834
|
-
onToggleDropdown={(e: MouseEvent, dropdownName: string) => {
|
|
835
|
-
toogleDropdown(
|
|
836
|
-
e.currentTarget as HTMLElement,
|
|
837
|
-
dropdownName,
|
|
838
|
-
);
|
|
839
|
-
}}
|
|
840
|
-
/>
|
|
841
|
-
{:else if isButton(toolbarItem)}
|
|
842
|
-
<RenderToolbarButton
|
|
843
|
-
type={toolbarItem.type}
|
|
844
|
-
{editor}
|
|
845
|
-
{nodeCounters}
|
|
846
|
-
{nodesLimit}
|
|
847
|
-
{currentNodeCount}
|
|
848
|
-
accentSoft={isAccentSoft}
|
|
849
|
-
{fontSize}
|
|
850
|
-
onToggleDropdown={(e: MouseEvent, dropdownName: string) => {
|
|
851
|
-
toogleDropdown(
|
|
852
|
-
e.currentTarget as HTMLElement,
|
|
853
|
-
dropdownName,
|
|
854
|
-
);
|
|
855
|
-
}}
|
|
856
|
-
/>
|
|
857
|
-
{/if}
|
|
858
|
-
{/each}
|
|
859
|
-
</div>
|
|
860
|
-
{/if}
|
|
861
|
-
{/each}
|
|
862
|
-
</div>
|
|
863
|
-
</header>
|
|
864
|
-
{/if}
|
|
790
|
+
{#if cleanMode}
|
|
865
791
|
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
792
|
+
<EditorContent
|
|
793
|
+
as={contentWrapperAs}
|
|
794
|
+
editor={$editor}
|
|
795
|
+
class="fl-rich-text-content {className}"
|
|
796
|
+
data-fl-editable="true"
|
|
797
|
+
{...rest}
|
|
798
|
+
/>
|
|
799
|
+
|
|
800
|
+
<!-- Warning message for node limit -->
|
|
801
|
+
{#if showLimitWarning && nodesLimit}
|
|
802
|
+
<div class="fl-node-limit-warning">
|
|
803
|
+
{limitWarningMessage ||
|
|
804
|
+
` No se pueden añadir más nodos a este editor. Max: ${nodesLimit}`}
|
|
805
|
+
</div>
|
|
806
|
+
{/if}
|
|
807
|
+
|
|
808
|
+
{:else}
|
|
809
|
+
<div
|
|
810
|
+
class="fl-rich-text {className}"
|
|
811
|
+
class:editable
|
|
812
|
+
style="
|
|
813
|
+
--fl-editor-accent-color: {editorConfig.editorAccentColor};
|
|
814
|
+
--fl-editor-radius: {editorConfig.editorRadius};
|
|
815
|
+
--fl-editor-bg: {editorConfig.editorBgColor};
|
|
816
|
+
--fl-toolbar-sticky-position: {editorConfig.toolbarStickyPosition}px;
|
|
817
|
+
--fl-toolbar-z-index: {editorConfig.toolbarZIndex};
|
|
818
|
+
--fl-toolbar-padding: {editorConfig.toolbarPadding};
|
|
819
|
+
--fl-toolbar-gap: {editorConfig.toolbarGap};
|
|
820
|
+
--fl-toolbar-bg: {editorConfig.toolbarBgColor};
|
|
821
|
+
--fl-toolbar-justify-content: {editorConfig.toolbarJustifyContent};
|
|
822
|
+
--fl-toolbar-text-color: {editorConfig.toolbarTextColor};
|
|
823
|
+
--fl-toolbar-btn-padding: {editorConfig.toolbarBtnPadding};
|
|
824
|
+
--fl-toolbar-btn-radius: {editorConfig.toolbarBtnRadius};
|
|
825
|
+
--fl-toolbar-btn-min-height: {editorConfig.toolbarBtnMinHeight};
|
|
826
|
+
--fl-toolbar-btn-min-width: {editorConfig.toolbarBtnMinWidth ||
|
|
827
|
+
editorConfig.toolbarBtnMinHeight};
|
|
828
|
+
--fl-doc-max-width: {editorConfig.docMaxWidth};
|
|
829
|
+
--fl-doc-padding: {editorConfig.docPadding};
|
|
830
|
+
--fl-doc-bg: {editorConfig.docBg};
|
|
831
|
+
--fl-doc-margin-inline: {editorConfig.docMarginInline};
|
|
832
|
+
--fl-doc-margin-block: {editorConfig.docMarginBlock};
|
|
833
|
+
--fl-doc-radius: {editorConfig.docRadius};
|
|
834
|
+
--fl-doc-text-color: {editorConfig.docTextColor};
|
|
835
|
+
"
|
|
836
|
+
>
|
|
837
|
+
{#if editor && showToolbar}
|
|
838
|
+
<header class="fl-rich-text-toolbar">
|
|
839
|
+
<div class="fl-rich-text-toolbar-container container">
|
|
840
|
+
{#each toolbarGroups as toolbarGroup}
|
|
841
|
+
{#if toolbarGroup.length > 0}
|
|
842
|
+
<div role="group" class="fl-rich-text-toolbar-group">
|
|
843
|
+
{#each toolbarGroup as toolbarItem}
|
|
844
|
+
{#if Array.isArray(toolbarItem)}
|
|
845
|
+
<!-- Si por alguna razón hay un array anidado, manejarlo -->
|
|
846
|
+
<p>Array anidado (no debería pasar)</p>
|
|
847
|
+
{:else if typeof toolbarItem === "string"}
|
|
848
|
+
<RenderToolbarButton
|
|
849
|
+
type={toolbarItem}
|
|
850
|
+
{editor}
|
|
851
|
+
{nodeCounters}
|
|
852
|
+
{nodesLimit}
|
|
853
|
+
{currentNodeCount}
|
|
854
|
+
accentSoft={isAccentSoft}
|
|
855
|
+
{fontSize}
|
|
856
|
+
onToggleDropdown={(e: MouseEvent, dropdownName: string) => {
|
|
857
|
+
toogleDropdown(
|
|
858
|
+
e.currentTarget as HTMLElement,
|
|
859
|
+
dropdownName,
|
|
860
|
+
);
|
|
861
|
+
}}
|
|
862
|
+
/>
|
|
863
|
+
{:else if isButton(toolbarItem)}
|
|
864
|
+
<RenderToolbarButton
|
|
865
|
+
type={toolbarItem.type}
|
|
866
|
+
{editor}
|
|
867
|
+
{nodeCounters}
|
|
868
|
+
{nodesLimit}
|
|
869
|
+
{currentNodeCount}
|
|
870
|
+
accentSoft={isAccentSoft}
|
|
871
|
+
{fontSize}
|
|
872
|
+
onToggleDropdown={(e: MouseEvent, dropdownName: string) => {
|
|
873
|
+
toogleDropdown(
|
|
874
|
+
e.currentTarget as HTMLElement,
|
|
875
|
+
dropdownName,
|
|
876
|
+
);
|
|
877
|
+
}}
|
|
878
|
+
/>
|
|
879
|
+
{/if}
|
|
880
|
+
{/each}
|
|
881
|
+
</div>
|
|
882
|
+
{/if}
|
|
883
|
+
{/each}
|
|
884
|
+
</div>
|
|
885
|
+
</header>
|
|
886
|
+
{/if}
|
|
887
|
+
|
|
888
|
+
<EditorContent
|
|
889
|
+
as={contentWrapperAs}
|
|
890
|
+
editor={$editor}
|
|
891
|
+
class="fl-rich-text-content"
|
|
892
|
+
data-fl-editable="true"
|
|
893
|
+
/>
|
|
894
|
+
|
|
895
|
+
<!-- Warning message for node limit -->
|
|
896
|
+
{#if showLimitWarning && nodesLimit}
|
|
897
|
+
<div class="fl-node-limit-warning">
|
|
898
|
+
{limitWarningMessage ||
|
|
899
|
+
` No se pueden añadir más nodos a este editor. Max: ${nodesLimit}`}
|
|
910
900
|
</div>
|
|
901
|
+
{/if}
|
|
911
902
|
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
903
|
+
<!-- Bottom bar showing node count -->
|
|
904
|
+
{#if showCountersBar || percentage >= 90}
|
|
905
|
+
<div class="fl-counters-bar">
|
|
906
|
+
<div
|
|
907
|
+
class="fl-character-count"
|
|
908
|
+
class:fl-character-count--warning={percentage >= 100}
|
|
909
|
+
>
|
|
910
|
+
{#if charactersLimit}
|
|
911
|
+
<svg height="20" width="20" viewBox="0 0 20 20">
|
|
912
|
+
<circle r="10" cx="10" cy="10" fill="#ffffff30" />
|
|
913
|
+
<circle
|
|
914
|
+
r="5"
|
|
915
|
+
cx="10"
|
|
916
|
+
cy="10"
|
|
917
|
+
fill="transparent"
|
|
918
|
+
stroke="currentColor"
|
|
919
|
+
stroke-width="10"
|
|
920
|
+
stroke-dasharray={`calc(${percentage} * 31.4 / 100) 31.4`}
|
|
921
|
+
transform="rotate(-90) translate(-20)"
|
|
922
|
+
/>
|
|
923
|
+
<circle r="6" cx="10" cy="10" fill="var(--fl-editor-bg)" />
|
|
924
|
+
</svg>
|
|
925
|
+
{/if}
|
|
917
926
|
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
927
|
+
<span>
|
|
928
|
+
Characters: {$editor?.storage?.characterCount?.characters()}
|
|
929
|
+
{#if charactersLimit}
|
|
930
|
+
/ {charactersLimit}{/if}
|
|
931
|
+
</span>
|
|
932
|
+
</div>
|
|
933
|
+
|
|
934
|
+
{#if showCountersBar}
|
|
935
|
+
<span>
|
|
936
|
+
Words: {$editor?.storage?.characterCount?.words()}
|
|
937
|
+
</span>
|
|
938
|
+
{/if}
|
|
923
939
|
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
</div> -->
|
|
930
|
-
</div>
|
|
931
|
-
{/if}
|
|
932
|
-
</div>
|
|
940
|
+
{#if nodesLimit}
|
|
941
|
+
<span class="fl-node-count-text">
|
|
942
|
+
Nodes: {currentNodeCount} / {nodesLimit}
|
|
943
|
+
</span>
|
|
944
|
+
{/if}
|
|
933
945
|
|
|
946
|
+
<!-- <div class="fl-node-count-progress">
|
|
947
|
+
<div
|
|
948
|
+
class="fl-node-count-progress-bar"
|
|
949
|
+
style="width: {Math.min((currentNodeCount / nodesLimit) * 100, 100)}%"
|
|
950
|
+
></div>
|
|
951
|
+
</div> -->
|
|
952
|
+
</div>
|
|
953
|
+
{/if}
|
|
954
|
+
</div>
|
|
955
|
+
{/if}
|
|
934
956
|
<div
|
|
935
957
|
class="fl-toolbar-dropdown-panel"
|
|
936
958
|
bind:this={tooltip}
|
|
@@ -51,6 +51,15 @@
|
|
|
51
51
|
function canCreateSubheading(editor) {
|
|
52
52
|
return !hasH1AfterCursor(editor);
|
|
53
53
|
}
|
|
54
|
+
|
|
55
|
+
const headingActions = {
|
|
56
|
+
"1": () => $editor.chain().focus().toggleH1().run(),
|
|
57
|
+
"2": () => $editor.chain().focus().toggleH2().run(),
|
|
58
|
+
"3": () => $editor.chain().focus().toggleH3().run(),
|
|
59
|
+
"4": () => $editor.chain().focus().toggleH4().run(),
|
|
60
|
+
"5": () => $editor.chain().focus().toggleH5().run(),
|
|
61
|
+
"6": () => $editor.chain().focus().toggleH6().run(),
|
|
62
|
+
};
|
|
54
63
|
</script>
|
|
55
64
|
|
|
56
65
|
{#if level === 1}
|
|
@@ -65,7 +74,7 @@
|
|
|
65
74
|
{@html icons[level]}
|
|
66
75
|
</button>
|
|
67
76
|
{:else}
|
|
68
|
-
<button
|
|
77
|
+
<!-- <button
|
|
69
78
|
type="button"
|
|
70
79
|
onclick={() => $editor.chain().focus().toggleHeading({ level }).run()}
|
|
71
80
|
class:is-active={$editor.isActive("heading", { level })}
|
|
@@ -74,5 +83,17 @@
|
|
|
74
83
|
disabled={!canCreateSubheading($editor)}
|
|
75
84
|
>
|
|
76
85
|
{@html icons[level]}
|
|
86
|
+
</button> -->
|
|
87
|
+
<button
|
|
88
|
+
type="button"
|
|
89
|
+
onclick={() => {
|
|
90
|
+
headingActions[level]();
|
|
91
|
+
}}
|
|
92
|
+
class:is-active={$editor.isActive("h"+level)}
|
|
93
|
+
class:accent-soft={accenSoft}
|
|
94
|
+
aria-label={`Heading ${level}`}
|
|
95
|
+
disabled={!canCreateSubheading($editor)}
|
|
96
|
+
>
|
|
97
|
+
{@html icons[level]}
|
|
77
98
|
</button>
|
|
78
99
|
{/if}
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
<button
|
|
13
13
|
type="button"
|
|
14
14
|
onclick={handleClick}
|
|
15
|
-
class:is-active={$editor.isActive("heading") || $editor.isActive("h1")}
|
|
15
|
+
class:is-active={$editor.isActive("heading") || $editor.isActive("h1") || $editor.isActive("h2") || $editor.isActive("h3") || $editor.isActive("h4") || $editor.isActive("h5") || $editor.isActive("h6")}
|
|
16
16
|
class:accent-soft={accentSoft}
|
|
17
17
|
aria-label={ariaLabel}
|
|
18
18
|
>
|
|
@@ -24,21 +24,30 @@
|
|
|
24
24
|
{/each}
|
|
25
25
|
{:else if $editor.isActive("h1")}
|
|
26
26
|
{@html HEADINGS[0].icon}
|
|
27
|
-
{
|
|
27
|
+
{:else if $editor.isActive("h2")}
|
|
28
|
+
{@html HEADINGS[1].icon}
|
|
29
|
+
{:else if $editor.isActive("h3")}
|
|
30
|
+
{@html HEADINGS[2].icon}
|
|
31
|
+
{:else if $editor.isActive("h4")}
|
|
32
|
+
{@html HEADINGS[3].icon}
|
|
33
|
+
{:else if $editor.isActive("h5")}
|
|
34
|
+
{@html HEADINGS[4].icon}
|
|
35
|
+
{:else if $editor.isActive("h6")}
|
|
36
|
+
{@html HEADINGS[5].icon}
|
|
28
37
|
|
|
29
|
-
{
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
fill="currentColor"
|
|
36
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
37
|
-
><path
|
|
38
|
-
d="M6 3C6.55228 3 7 3.44772 7 4V11H17V4C17 3.44772 17.4477 3 18 3C18.5523 3 19 3.44772 19 4V20C19 20.5523 18.5523 21 18 21C17.4477 21 17 20.5523 17 20V13H7V20C7 20.5523 6.55228 21 6 21C5.44772 21 5 20.5523 5 20V4C5 3.44772 5.44772 3 6 3Z"
|
|
38
|
+
{:else}
|
|
39
|
+
<svg
|
|
40
|
+
width="24"
|
|
41
|
+
height="24"
|
|
42
|
+
class="tiptap-button-icon"
|
|
43
|
+
viewBox="0 0 24 24"
|
|
39
44
|
fill="currentColor"
|
|
40
|
-
|
|
41
|
-
|
|
45
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
46
|
+
><path
|
|
47
|
+
d="M6 3C6.55228 3 7 3.44772 7 4V11H17V4C17 3.44772 17.4477 3 18 3C18.5523 3 19 3.44772 19 4V20C19 20.5523 18.5523 21 18 21C17.4477 21 17 20.5523 17 20V13H7V20C7 20.5523 6.55228 21 6 21C5.44772 21 5 20.5523 5 20V4C5 3.44772 5.44772 3 6 3Z"
|
|
48
|
+
fill="currentColor"
|
|
49
|
+
></path></svg
|
|
50
|
+
>
|
|
42
51
|
{/if}
|
|
43
52
|
|
|
44
53
|
<svg
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Extension } from "@tiptap/core";
|
|
2
|
-
const allowedTypes = ["paragraph", "heading", "h1"];
|
|
2
|
+
const allowedTypes = ["paragraph", "heading", "h1", "h2", "h3", "h4", "h5", "h6"];
|
|
3
3
|
export const NodeFontSize = Extension.create({
|
|
4
4
|
name: "nodeFontSize",
|
|
5
5
|
addGlobalAttributes() {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Extension } from "@tiptap/core";
|
|
2
|
-
const allowedTypes = ["paragraph", "heading", "h1"];
|
|
2
|
+
const allowedTypes = ["paragraph", "heading", "h1", "h2", "h3", "h4", "h5", "h6"];
|
|
3
3
|
export const NodeLineHeight = Extension.create({
|
|
4
4
|
name: "nodeLineHeight",
|
|
5
5
|
addGlobalAttributes() {
|
|
@@ -16,7 +16,7 @@ export const SemanticHeadings = Extension.create({
|
|
|
16
16
|
let h1Count = 0;
|
|
17
17
|
// Recolectar todos los headings en orden
|
|
18
18
|
doc.descendants((node, pos) => {
|
|
19
|
-
if (node.type.name === 'heading') {
|
|
19
|
+
if (node.type.name === 'heading' || node.type.name === 'h1' || node.type.name === 'h2' || node.type.name === 'h3' || node.type.name === 'h4' || node.type.name === 'h5' || node.type.name === 'h6') {
|
|
20
20
|
headings.push({
|
|
21
21
|
level: node.attrs.level,
|
|
22
22
|
pos: pos
|
|
@@ -68,7 +68,7 @@ export const SemanticHeadings = Extension.create({
|
|
|
68
68
|
let hasH1 = false;
|
|
69
69
|
// Buscar el último heading y verificar si ya existe h1
|
|
70
70
|
doc.descendants((node) => {
|
|
71
|
-
if (node.type.name === 'heading') {
|
|
71
|
+
if (node.type.name === 'heading' || node.type.name === 'h1' || node.type.name === 'h2' || node.type.name === 'h3' || node.type.name === 'h4' || node.type.name === 'h5' || node.type.name === 'h6') {
|
|
72
72
|
lastHeadingLevel = node.attrs.level;
|
|
73
73
|
if (node.attrs.level === 1) {
|
|
74
74
|
hasH1 = true;
|
|
@@ -97,7 +97,7 @@ export const SemanticHeadings = Extension.create({
|
|
|
97
97
|
let lastHeadingLevel = null;
|
|
98
98
|
let hasH1 = false;
|
|
99
99
|
doc.descendants((node) => {
|
|
100
|
-
if (node.type.name === 'heading') {
|
|
100
|
+
if (node.type.name === 'heading' || node.type.name === 'h1' || node.type.name === 'h2' || node.type.name === 'h3' || node.type.name === 'h4' || node.type.name === 'h5' || node.type.name === 'h6') {
|
|
101
101
|
lastHeadingLevel = node.attrs.level;
|
|
102
102
|
if (node.attrs.level === 1) {
|
|
103
103
|
hasH1 = true;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// resizable-nodes.ts (versión completa con Decorations)
|
|
2
|
+
import { Extension } from '@tiptap/core';
|
|
3
|
+
import { Plugin, PluginKey } from '@tiptap/pm/state';
|
|
4
|
+
import { Decoration, DecorationSet } from '@tiptap/pm/view';
|
|
5
|
+
export const ResizableNodes = Extension.create({
|
|
6
|
+
name: 'resizableNodes',
|
|
7
|
+
addOptions() {
|
|
8
|
+
return {
|
|
9
|
+
nodes: [],
|
|
10
|
+
minWidth: 120,
|
|
11
|
+
handleWidth: 8,
|
|
12
|
+
};
|
|
13
|
+
},
|
|
14
|
+
// ✅ Añade maxWidth a los nodos configurados SIN tocar su NodeView
|
|
15
|
+
addGlobalAttributes() {
|
|
16
|
+
return [
|
|
17
|
+
{
|
|
18
|
+
types: this.options.nodes,
|
|
19
|
+
attributes: {
|
|
20
|
+
maxWidth: {
|
|
21
|
+
default: null,
|
|
22
|
+
parseHTML: el => {
|
|
23
|
+
const val = el.style.maxWidth;
|
|
24
|
+
return val || null;
|
|
25
|
+
},
|
|
26
|
+
renderHTML: attrs => {
|
|
27
|
+
if (!attrs.maxWidth)
|
|
28
|
+
return {};
|
|
29
|
+
return {
|
|
30
|
+
style: `max-width: ${attrs.maxWidth}; box-sizing: border-box;`,
|
|
31
|
+
};
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
];
|
|
37
|
+
},
|
|
38
|
+
addProseMirrorPlugins() {
|
|
39
|
+
const extension = this;
|
|
40
|
+
const pluginKey = new PluginKey('resizableNodes');
|
|
41
|
+
return [
|
|
42
|
+
new Plugin({
|
|
43
|
+
key: pluginKey,
|
|
44
|
+
// Decoraciones: añade el handle como widget sin reemplazar el NodeView
|
|
45
|
+
props: {
|
|
46
|
+
decorations(state) {
|
|
47
|
+
const { doc, selection } = state;
|
|
48
|
+
const decorations = [];
|
|
49
|
+
const nodeNames = extension.options.nodes;
|
|
50
|
+
doc.descendants((node, pos) => {
|
|
51
|
+
if (!nodeNames.includes(node.type.name))
|
|
52
|
+
return;
|
|
53
|
+
if (node.attrs.maxWidth === undefined)
|
|
54
|
+
return;
|
|
55
|
+
// Widget al FINAL del nodo (después del contenido)
|
|
56
|
+
const widget = Decoration.widget(pos + node.nodeSize - 1, () => {
|
|
57
|
+
const handle = document.createElement('div');
|
|
58
|
+
handle.className = 'resize-handle';
|
|
59
|
+
handle.contentEditable = 'false';
|
|
60
|
+
handle.dataset.pos = String(pos);
|
|
61
|
+
return handle;
|
|
62
|
+
}, { side: 1, key: `resize-${pos}` });
|
|
63
|
+
decorations.push(widget);
|
|
64
|
+
});
|
|
65
|
+
return DecorationSet.create(doc, decorations);
|
|
66
|
+
},
|
|
67
|
+
handleDOMEvents: {
|
|
68
|
+
mousedown(view, event) {
|
|
69
|
+
const handle = event.target.closest('.resize-handle');
|
|
70
|
+
if (!handle)
|
|
71
|
+
return false;
|
|
72
|
+
event.preventDefault();
|
|
73
|
+
const pos = parseInt(handle.dataset.pos || '0', 10);
|
|
74
|
+
const node = view.state.doc.nodeAt(pos);
|
|
75
|
+
if (!node)
|
|
76
|
+
return false;
|
|
77
|
+
const dom = view.nodeDOM(pos);
|
|
78
|
+
if (!dom)
|
|
79
|
+
return false;
|
|
80
|
+
const startX = event.clientX;
|
|
81
|
+
const startWidth = dom.offsetWidth;
|
|
82
|
+
const minWidth = extension.options.minWidth ?? 120;
|
|
83
|
+
const onMouseMove = (e) => {
|
|
84
|
+
const newWidth = Math.max(minWidth, startWidth + (e.clientX - startX));
|
|
85
|
+
console.log(newWidth);
|
|
86
|
+
dom.style.maxWidth = `${newWidth}px`; // preview live
|
|
87
|
+
// Commit al schema de ProseMirror
|
|
88
|
+
const { tr } = view.state;
|
|
89
|
+
// tr.setNodeMarkup(pos, undefined, {
|
|
90
|
+
// ...node.attrs,
|
|
91
|
+
// maxWidth: `${newWidth}px`,
|
|
92
|
+
// })
|
|
93
|
+
view.dispatch(tr);
|
|
94
|
+
};
|
|
95
|
+
const onMouseUp = (e) => {
|
|
96
|
+
document.removeEventListener('mousemove', onMouseMove);
|
|
97
|
+
document.removeEventListener('mouseup', onMouseUp);
|
|
98
|
+
const finalWidth = Math.max(minWidth, startWidth + (e.clientX - startX));
|
|
99
|
+
// Commit al schema de ProseMirror
|
|
100
|
+
const { tr } = view.state;
|
|
101
|
+
tr.setNodeMarkup(pos, undefined, {
|
|
102
|
+
...node.attrs,
|
|
103
|
+
maxWidth: `${finalWidth}px`,
|
|
104
|
+
});
|
|
105
|
+
view.dispatch(tr);
|
|
106
|
+
};
|
|
107
|
+
document.addEventListener('mousemove', onMouseMove);
|
|
108
|
+
document.addEventListener('mouseup', onMouseUp);
|
|
109
|
+
return true;
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
}),
|
|
114
|
+
];
|
|
115
|
+
},
|
|
116
|
+
});
|
package/dist/getExtensions.d.ts
CHANGED
|
@@ -3,6 +3,21 @@ declare module '@tiptap/core' {
|
|
|
3
3
|
h1: {
|
|
4
4
|
toggleH1: () => ReturnType;
|
|
5
5
|
};
|
|
6
|
+
h2: {
|
|
7
|
+
toggleH2: () => ReturnType;
|
|
8
|
+
};
|
|
9
|
+
h3: {
|
|
10
|
+
toggleH3: () => ReturnType;
|
|
11
|
+
};
|
|
12
|
+
h4: {
|
|
13
|
+
toggleH4: () => ReturnType;
|
|
14
|
+
};
|
|
15
|
+
h5: {
|
|
16
|
+
toggleH5: () => ReturnType;
|
|
17
|
+
};
|
|
18
|
+
h6: {
|
|
19
|
+
toggleH6: () => ReturnType;
|
|
20
|
+
};
|
|
6
21
|
}
|
|
7
22
|
}
|
|
8
23
|
export declare function getRichTextExtensions(options?: {
|
package/dist/getExtensions.js
CHANGED
|
@@ -17,7 +17,7 @@ import { CustomTableHeader } from "./extensions/Table/CustomTableHeader";
|
|
|
17
17
|
import { EnhancedLink } from "./extensions/EnhancedLink";
|
|
18
18
|
import { SemanticHeadings } from "./extensions/SemanticHeadings";
|
|
19
19
|
import { Heading } from "@tiptap/extension-heading";
|
|
20
|
-
const
|
|
20
|
+
const H1 = Heading.extend({
|
|
21
21
|
name: "h1",
|
|
22
22
|
addCommands() {
|
|
23
23
|
return {
|
|
@@ -27,11 +27,66 @@ const DocHeading = Heading.extend({
|
|
|
27
27
|
};
|
|
28
28
|
},
|
|
29
29
|
}).configure({ levels: [1] });
|
|
30
|
+
const H2 = Heading.extend({
|
|
31
|
+
name: "h2",
|
|
32
|
+
addCommands() {
|
|
33
|
+
return {
|
|
34
|
+
toggleH2: () => ({ commands }) => {
|
|
35
|
+
return commands.toggleNode('h2', 'paragraph');
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
},
|
|
39
|
+
}).configure({ levels: [2] });
|
|
40
|
+
const H3 = Heading.extend({
|
|
41
|
+
name: "h3",
|
|
42
|
+
addCommands() {
|
|
43
|
+
return {
|
|
44
|
+
toggleH3: () => ({ commands }) => {
|
|
45
|
+
return commands.toggleNode('h3', 'paragraph');
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
},
|
|
49
|
+
}).configure({ levels: [3] });
|
|
50
|
+
const H4 = Heading.extend({
|
|
51
|
+
name: "h4",
|
|
52
|
+
addCommands() {
|
|
53
|
+
return {
|
|
54
|
+
toggleH4: () => ({ commands }) => {
|
|
55
|
+
return commands.toggleNode('h4', 'paragraph');
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
},
|
|
59
|
+
}).configure({ levels: [4] });
|
|
60
|
+
const H5 = Heading.extend({
|
|
61
|
+
name: "h5",
|
|
62
|
+
addCommands() {
|
|
63
|
+
return {
|
|
64
|
+
toggleH5: () => ({ commands }) => {
|
|
65
|
+
return commands.toggleNode('h5', 'paragraph');
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
},
|
|
69
|
+
}).configure({ levels: [5] });
|
|
70
|
+
const H6 = Heading.extend({
|
|
71
|
+
name: "h6",
|
|
72
|
+
addCommands() {
|
|
73
|
+
return {
|
|
74
|
+
toggleH6: () => ({ commands }) => {
|
|
75
|
+
return commands.toggleNode('h6', 'paragraph');
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
},
|
|
79
|
+
}).configure({ levels: [6] });
|
|
30
80
|
export function getRichTextExtensions(options) {
|
|
31
81
|
const { editable = false, trailingNode = true, customExtensions = [] } = options ?? {};
|
|
32
82
|
return [
|
|
33
|
-
|
|
34
|
-
|
|
83
|
+
H1,
|
|
84
|
+
H2,
|
|
85
|
+
H3,
|
|
86
|
+
H4,
|
|
87
|
+
H5,
|
|
88
|
+
H6,
|
|
89
|
+
// Heading.configure({ levels: [2, 3, 4, 5, 6] }),
|
|
35
90
|
Highlight.configure({ multicolor: true }),
|
|
36
91
|
TextStyleKit.configure({
|
|
37
92
|
// fontSize: false
|
|
@@ -53,6 +108,11 @@ export function getRichTextExtensions(options) {
|
|
|
53
108
|
TextAlign.configure({
|
|
54
109
|
types: [
|
|
55
110
|
"h1",
|
|
111
|
+
"h2",
|
|
112
|
+
"h3",
|
|
113
|
+
"h4",
|
|
114
|
+
"h5",
|
|
115
|
+
"h6",
|
|
56
116
|
"heading",
|
|
57
117
|
"paragraph",
|
|
58
118
|
"bulletList",
|
package/dist/styles.css
CHANGED
|
@@ -63,6 +63,11 @@
|
|
|
63
63
|
box-sizing: border-box;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
.fl-rich-text-doc-clean {
|
|
67
|
+
outline: none;
|
|
68
|
+
position: relative;
|
|
69
|
+
}
|
|
70
|
+
|
|
66
71
|
.fl-toolbar-dropdown-panel {
|
|
67
72
|
position: absolute;
|
|
68
73
|
padding: 9px;
|
|
@@ -637,3 +642,34 @@ button {
|
|
|
637
642
|
}
|
|
638
643
|
}
|
|
639
644
|
}
|
|
645
|
+
|
|
646
|
+
|
|
647
|
+
/* El handle flota sobre el borde derecho del nodo */
|
|
648
|
+
[data-resizable], [style*="max-width"] {
|
|
649
|
+
position: relative;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
.resize-handle {
|
|
653
|
+
position: absolute;
|
|
654
|
+
right: -4px;
|
|
655
|
+
top: 0;
|
|
656
|
+
bottom: 0;
|
|
657
|
+
width: 8px;
|
|
658
|
+
cursor: col-resize;
|
|
659
|
+
opacity: 0;
|
|
660
|
+
background: #3b82f6;
|
|
661
|
+
border-radius: 4px;
|
|
662
|
+
transition: opacity 0.15s;
|
|
663
|
+
z-index: 10;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
p:hover .resize-handle,
|
|
667
|
+
h1:hover .resize-handle,
|
|
668
|
+
h2:hover .resize-handle,
|
|
669
|
+
blockquote:hover .resize-handle {
|
|
670
|
+
opacity: 0.6;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
.resize-handle:hover {
|
|
674
|
+
opacity: 1 !important;
|
|
675
|
+
}
|
package/dist/utils.js
CHANGED