@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.
@@ -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
- <div
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
- <EditorContent
867
- as={contentWrapperAs}
868
- editor={$editor}
869
- class="fl-rich-text-content"
870
- data-fl-editable="true"
871
- />
872
-
873
- <!-- Warning message for node limit -->
874
- {#if showLimitWarning && nodesLimit}
875
- <div class="fl-node-limit-warning">
876
- {limitWarningMessage ||
877
- ` No se pueden añadir más nodos a este editor. Max: ${nodesLimit}`}
878
- </div>
879
- {/if}
880
-
881
- <!-- Bottom bar showing node count -->
882
- {#if showCountersBar || percentage >= 90}
883
- <div class="fl-counters-bar">
884
- <div
885
- class="fl-character-count"
886
- class:fl-character-count--warning={percentage >= 100}
887
- >
888
- {#if charactersLimit}
889
- <svg height="20" width="20" viewBox="0 0 20 20">
890
- <circle r="10" cx="10" cy="10" fill="#ffffff30" />
891
- <circle
892
- r="5"
893
- cx="10"
894
- cy="10"
895
- fill="transparent"
896
- stroke="currentColor"
897
- stroke-width="10"
898
- stroke-dasharray={`calc(${percentage} * 31.4 / 100) 31.4`}
899
- transform="rotate(-90) translate(-20)"
900
- />
901
- <circle r="6" cx="10" cy="10" fill="var(--fl-editor-bg)" />
902
- </svg>
903
- {/if}
904
-
905
- <span>
906
- Characters: {$editor?.storage?.characterCount?.characters()}
907
- {#if charactersLimit}
908
- / {charactersLimit}{/if}
909
- </span>
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
- {#if showCountersBar}
913
- <span>
914
- Words: {$editor?.storage?.characterCount?.words()}
915
- </span>
916
- {/if}
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
- {#if nodesLimit}
919
- <span class="fl-node-count-text">
920
- Nodes: {currentNodeCount} / {nodesLimit}
921
- </span>
922
- {/if}
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
- <!-- <div class="fl-node-count-progress">
925
- <div
926
- class="fl-node-count-progress-bar"
927
- style="width: {Math.min((currentNodeCount / nodesLimit) * 100, 100)}%"
928
- ></div>
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
- {/if}
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
- {#if !$editor.isActive("heading") && !$editor.isActive("h1")}
30
- <svg
31
- width="24"
32
- height="24"
33
- class="tiptap-button-icon"
34
- viewBox="0 0 24 24"
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
- ></path></svg
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;
@@ -34,6 +34,12 @@ export function createExtensions({ customExtensions = [], editable = true, } = {
34
34
  ListKit,
35
35
  TextAlign.configure({
36
36
  types: [
37
+ "h1",
38
+ "h2",
39
+ "h3",
40
+ "h4",
41
+ "h5",
42
+ "h6",
37
43
  "heading",
38
44
  "paragraph",
39
45
  "bulletList",
@@ -0,0 +1,7 @@
1
+ import { Extension } from '@tiptap/core';
2
+ export interface ResizableNodesOptions {
3
+ nodes: string[];
4
+ minWidth?: number;
5
+ handleWidth?: number;
6
+ }
7
+ export declare const ResizableNodes: Extension<ResizableNodesOptions, any>;
@@ -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
+ });
@@ -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?: {
@@ -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 DocHeading = Heading.extend({
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
- DocHeading,
34
- Heading.configure({ levels: [2, 3, 4, 5, 6] }),
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
@@ -1,6 +1,6 @@
1
1
  export const exampleJSONContent = [
2
2
  {
3
- type: "heading",
3
+ type: "h1",
4
4
  attrs: { level: 1 },
5
5
  content: [{ type: "text", text: "Hi there," }],
6
6
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flexiui/svelte-rich-text",
3
- "version": "0.0.67",
3
+ "version": "0.0.69",
4
4
  "description": "A lightweight and flexible rich text editor component for Svelte",
5
5
  "keywords": [
6
6
  "svelte",