@pixerate/editor 0.5.1 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8,6 +8,8 @@ import { MentionOptions as MentionOptions$1 } from '@tiptap/extension-mention';
8
8
  import { SuggestionOptions } from '@tiptap/suggestion';
9
9
  import { MarkdownOptions } from 'tiptap-markdown';
10
10
  export { MarkdownOptions } from 'tiptap-markdown';
11
+ import { ImageOptions as ImageOptions$1 } from '@tiptap/extension-image';
12
+ import { PluginKey } from '@tiptap/pm/state';
11
13
 
12
14
  interface GradientTextOptions {
13
15
  templateColorMap: Map<string, ColorGradient>;
@@ -100,6 +102,52 @@ declare function createMentionExtension(options?: Partial<MentionOptions>): _tip
100
102
  */
101
103
  declare const Markdown: any;
102
104
 
105
+ interface ImageOptions extends Partial<ImageOptions$1> {
106
+ /**
107
+ * Custom upload handler called when an image is pasted or dropped.
108
+ * Resolves to the uploaded image URL string.
109
+ */
110
+ upload?: (file: File) => Promise<string> | string;
111
+ /**
112
+ * Callback invoked if the upload handler throws or rejects, or if an image
113
+ * exceeds maxBase64Size when no upload handler is configured.
114
+ */
115
+ onUploadError?: (error: Error, file: File) => void;
116
+ /**
117
+ * Maximum allowed file size (in bytes) when embedding images as base64 data URLs
118
+ * without an upload handler. Defaults to 65536 (64 KB).
119
+ * Files larger than this will be rejected to avoid memory bloat and prompt/stream saturation.
120
+ */
121
+ maxBase64Size?: number;
122
+ /**
123
+ * Whether to enable automatic image pasting from clipboard. Defaults to true.
124
+ */
125
+ enablePaste?: boolean;
126
+ /**
127
+ * Whether to enable automatic image drop. Defaults to true.
128
+ */
129
+ enableDrop?: boolean;
130
+ }
131
+ declare const imagePastePluginKey: PluginKey<any>;
132
+ /**
133
+ * Inserts an image node into the editor view at the current selection.
134
+ */
135
+ declare function insertImageNode(view: any, src: string, alt?: string): void;
136
+ /**
137
+ * Updates an existing image node's src attribute across document positions.
138
+ */
139
+ declare function updateImageSrc(view: any, oldSrc: string, newSrc: string): void;
140
+ /**
141
+ * Removes an existing image node matching src attribute across document positions.
142
+ */
143
+ declare function removeImageNode(view: any, src: string): void;
144
+ /**
145
+ * Handles inserting a pasted or dropped image file, supporting both async upload
146
+ * handlers and direct base64/object-URL fallbacks.
147
+ */
148
+ declare function handleImageInsertion(view: any, file: File, options: ImageOptions): void;
149
+ declare const Image: any;
150
+
103
151
  interface RichTextPresetOptions {
104
152
  placeholder?: string | ((props: {
105
153
  node: any;
@@ -111,10 +159,11 @@ interface RichTextPresetOptions {
111
159
  headingLevels?: (1 | 2 | 3 | 4 | 5 | 6)[];
112
160
  mention?: MentionOptions | boolean;
113
161
  markdown?: MarkdownOptions | boolean;
162
+ image?: ImageOptions | boolean;
114
163
  }
115
164
  /**
116
165
  * Creates a standard rich-text extensions bundle with full formatting support.
117
166
  */
118
167
  declare function createRichTextPreset(options?: RichTextPresetOptions): Extensions;
119
168
 
120
- export { ColorHighlighter, FontSize, GradientText, type GradientTextOptions, LoadingNode, Markdown, Mention, type MentionEntity, type MentionOptions, type RichTextPresetOptions, type SlashCommandMatchData, SlashCommands, type SlashCommandsOptions, SmilieReplacer, TemplateSuggestions, type TemplateSuggestionsOptions, createMentionExtension, createRichTextPreset, findHexColors };
169
+ export { ColorHighlighter, FontSize, GradientText, type GradientTextOptions, Image, type ImageOptions, LoadingNode, Markdown, Mention, type MentionEntity, type MentionOptions, type RichTextPresetOptions, type SlashCommandMatchData, SlashCommands, type SlashCommandsOptions, SmilieReplacer, TemplateSuggestions, type TemplateSuggestionsOptions, createMentionExtension, createRichTextPreset, findHexColors, handleImageInsertion, imagePastePluginKey, insertImageNode, removeImageNode, updateImageSrc };
@@ -8,6 +8,8 @@ import { MentionOptions as MentionOptions$1 } from '@tiptap/extension-mention';
8
8
  import { SuggestionOptions } from '@tiptap/suggestion';
9
9
  import { MarkdownOptions } from 'tiptap-markdown';
10
10
  export { MarkdownOptions } from 'tiptap-markdown';
11
+ import { ImageOptions as ImageOptions$1 } from '@tiptap/extension-image';
12
+ import { PluginKey } from '@tiptap/pm/state';
11
13
 
12
14
  interface GradientTextOptions {
13
15
  templateColorMap: Map<string, ColorGradient>;
@@ -100,6 +102,52 @@ declare function createMentionExtension(options?: Partial<MentionOptions>): _tip
100
102
  */
101
103
  declare const Markdown: any;
102
104
 
105
+ interface ImageOptions extends Partial<ImageOptions$1> {
106
+ /**
107
+ * Custom upload handler called when an image is pasted or dropped.
108
+ * Resolves to the uploaded image URL string.
109
+ */
110
+ upload?: (file: File) => Promise<string> | string;
111
+ /**
112
+ * Callback invoked if the upload handler throws or rejects, or if an image
113
+ * exceeds maxBase64Size when no upload handler is configured.
114
+ */
115
+ onUploadError?: (error: Error, file: File) => void;
116
+ /**
117
+ * Maximum allowed file size (in bytes) when embedding images as base64 data URLs
118
+ * without an upload handler. Defaults to 65536 (64 KB).
119
+ * Files larger than this will be rejected to avoid memory bloat and prompt/stream saturation.
120
+ */
121
+ maxBase64Size?: number;
122
+ /**
123
+ * Whether to enable automatic image pasting from clipboard. Defaults to true.
124
+ */
125
+ enablePaste?: boolean;
126
+ /**
127
+ * Whether to enable automatic image drop. Defaults to true.
128
+ */
129
+ enableDrop?: boolean;
130
+ }
131
+ declare const imagePastePluginKey: PluginKey<any>;
132
+ /**
133
+ * Inserts an image node into the editor view at the current selection.
134
+ */
135
+ declare function insertImageNode(view: any, src: string, alt?: string): void;
136
+ /**
137
+ * Updates an existing image node's src attribute across document positions.
138
+ */
139
+ declare function updateImageSrc(view: any, oldSrc: string, newSrc: string): void;
140
+ /**
141
+ * Removes an existing image node matching src attribute across document positions.
142
+ */
143
+ declare function removeImageNode(view: any, src: string): void;
144
+ /**
145
+ * Handles inserting a pasted or dropped image file, supporting both async upload
146
+ * handlers and direct base64/object-URL fallbacks.
147
+ */
148
+ declare function handleImageInsertion(view: any, file: File, options: ImageOptions): void;
149
+ declare const Image: any;
150
+
103
151
  interface RichTextPresetOptions {
104
152
  placeholder?: string | ((props: {
105
153
  node: any;
@@ -111,10 +159,11 @@ interface RichTextPresetOptions {
111
159
  headingLevels?: (1 | 2 | 3 | 4 | 5 | 6)[];
112
160
  mention?: MentionOptions | boolean;
113
161
  markdown?: MarkdownOptions | boolean;
162
+ image?: ImageOptions | boolean;
114
163
  }
115
164
  /**
116
165
  * Creates a standard rich-text extensions bundle with full formatting support.
117
166
  */
118
167
  declare function createRichTextPreset(options?: RichTextPresetOptions): Extensions;
119
168
 
120
- export { ColorHighlighter, FontSize, GradientText, type GradientTextOptions, LoadingNode, Markdown, Mention, type MentionEntity, type MentionOptions, type RichTextPresetOptions, type SlashCommandMatchData, SlashCommands, type SlashCommandsOptions, SmilieReplacer, TemplateSuggestions, type TemplateSuggestionsOptions, createMentionExtension, createRichTextPreset, findHexColors };
169
+ export { ColorHighlighter, FontSize, GradientText, type GradientTextOptions, Image, type ImageOptions, LoadingNode, Markdown, Mention, type MentionEntity, type MentionOptions, type RichTextPresetOptions, type SlashCommandMatchData, SlashCommands, type SlashCommandsOptions, SmilieReplacer, TemplateSuggestions, type TemplateSuggestionsOptions, createMentionExtension, createRichTextPreset, findHexColors, handleImageInsertion, imagePastePluginKey, insertImageNode, removeImageNode, updateImageSrc };
@@ -1552,20 +1552,183 @@ var Markdown = Extension6.create({
1552
1552
  var resolveExt = (mod, name) => mod?.[name] || mod?.default?.[name] || mod?.default?.default || mod?.default || mod;
1553
1553
  var Markdown2 = resolveExt(Markdown, "Markdown");
1554
1554
 
1555
- // src/extensions/rich-text-preset.ts
1555
+ // src/extensions/image.ts
1556
+ import * as _Image from "@tiptap/extension-image";
1557
+ import { Plugin as Plugin5, PluginKey as PluginKey2 } from "@tiptap/pm/state";
1556
1558
  var resolveExt2 = (mod, name) => mod?.[name] || mod?.default?.[name] || mod?.default?.default || mod?.default || mod;
1557
- var StarterKit = resolveExt2(_StarterKit, "StarterKit");
1558
- var Color = resolveExt2(_Color, "Color");
1559
- var Highlight = resolveExt2(_Highlight, "Highlight");
1560
- var Link2 = resolveExt2(_Link, "Link");
1561
- var TaskList2 = resolveExt2(_TaskList, "TaskList");
1562
- var TaskItem2 = resolveExt2(_TaskItem, "TaskItem");
1563
- var TextAlign = resolveExt2(_TextAlign, "TextAlign");
1564
- var TextStyle = resolveExt2(_TextStyle, "TextStyle");
1565
- var Typography = resolveExt2(_Typography, "Typography");
1566
- var Underline = resolveExt2(_Underline, "Underline");
1567
- var Placeholder = resolveExt2(_Placeholder, "Placeholder");
1568
- var CharacterCount = resolveExt2(_CharacterCount, "CharacterCount");
1559
+ var BaseImage = resolveExt2(_Image, "Image");
1560
+ var imagePastePluginKey = new PluginKey2("imagePasteHandler");
1561
+ function insertImageNode(view, src, alt) {
1562
+ const { schema } = view.state;
1563
+ const imageType = schema.nodes.image;
1564
+ if (!imageType) return;
1565
+ const node = imageType.create({ src, alt: alt || "" });
1566
+ const tr = view.state.tr.replaceSelectionWith(node);
1567
+ view.dispatch(tr);
1568
+ }
1569
+ function updateImageSrc(view, oldSrc, newSrc) {
1570
+ const { doc } = view.state;
1571
+ let foundPos = null;
1572
+ let foundAttrs = null;
1573
+ doc.descendants((node, pos) => {
1574
+ if (node.type.name === "image" && node.attrs.src === oldSrc) {
1575
+ foundPos = pos;
1576
+ foundAttrs = node.attrs;
1577
+ return false;
1578
+ }
1579
+ });
1580
+ if (foundPos !== null && foundAttrs) {
1581
+ const tr = view.state.tr.setNodeMarkup(foundPos, void 0, {
1582
+ ...foundAttrs,
1583
+ src: newSrc
1584
+ });
1585
+ view.dispatch(tr);
1586
+ }
1587
+ }
1588
+ function removeImageNode(view, src) {
1589
+ const { doc } = view.state;
1590
+ let foundPos = null;
1591
+ let nodeSize = 0;
1592
+ doc.descendants((node, pos) => {
1593
+ if (node.type.name === "image" && node.attrs.src === src) {
1594
+ foundPos = pos;
1595
+ nodeSize = node.nodeSize;
1596
+ return false;
1597
+ }
1598
+ });
1599
+ if (foundPos !== null) {
1600
+ const tr = view.state.tr.delete(foundPos, foundPos + nodeSize);
1601
+ view.dispatch(tr);
1602
+ }
1603
+ }
1604
+ function handleImageInsertion(view, file, options) {
1605
+ if (typeof options.upload === "function") {
1606
+ const hasObjectUrl = typeof URL !== "undefined" && typeof URL.createObjectURL === "function";
1607
+ const tempUrl = hasObjectUrl ? URL.createObjectURL(file) : null;
1608
+ if (tempUrl) {
1609
+ insertImageNode(view, tempUrl, file.name);
1610
+ }
1611
+ Promise.resolve(options.upload(file)).then((uploadedUrl) => {
1612
+ if (!uploadedUrl) {
1613
+ throw new Error("Upload handler returned empty URL");
1614
+ }
1615
+ if (tempUrl) {
1616
+ updateImageSrc(view, tempUrl, uploadedUrl);
1617
+ if (typeof URL.revokeObjectURL === "function") {
1618
+ URL.revokeObjectURL(tempUrl);
1619
+ }
1620
+ } else {
1621
+ insertImageNode(view, uploadedUrl, file.name);
1622
+ }
1623
+ }).catch((err) => {
1624
+ const error = err instanceof Error ? err : new Error(String(err));
1625
+ console.error("Failed to upload pasted image:", error);
1626
+ if (tempUrl) {
1627
+ removeImageNode(view, tempUrl);
1628
+ if (typeof URL.revokeObjectURL === "function") {
1629
+ URL.revokeObjectURL(tempUrl);
1630
+ }
1631
+ }
1632
+ if (typeof options.onUploadError === "function") {
1633
+ options.onUploadError(error, file);
1634
+ }
1635
+ });
1636
+ } else if (options.allowBase64 !== false) {
1637
+ const maxBase64Size = options.maxBase64Size ?? 65536;
1638
+ if (file.size > maxBase64Size) {
1639
+ const err = new Error(
1640
+ `Image size (${Math.round(file.size / 1024)}KB) exceeds maxBase64Size (${Math.round(
1641
+ maxBase64Size / 1024
1642
+ )}KB). Provide an upload handler to support large images.`
1643
+ );
1644
+ console.warn(`[Editor] ${err.message}`);
1645
+ if (typeof options.onUploadError === "function") {
1646
+ options.onUploadError(err, file);
1647
+ }
1648
+ return;
1649
+ }
1650
+ if (typeof FileReader !== "undefined") {
1651
+ const reader = new FileReader();
1652
+ reader.onload = () => {
1653
+ const src = reader.result;
1654
+ if (src) {
1655
+ insertImageNode(view, src, file.name);
1656
+ }
1657
+ };
1658
+ reader.readAsDataURL(file);
1659
+ }
1660
+ }
1661
+ }
1662
+ var Image2 = BaseImage.extend({
1663
+ addOptions() {
1664
+ return {
1665
+ ...this.parent?.(),
1666
+ allowBase64: true,
1667
+ maxBase64Size: 65536,
1668
+ enablePaste: true,
1669
+ enableDrop: true,
1670
+ upload: void 0,
1671
+ onUploadError: void 0,
1672
+ HTMLAttributes: {
1673
+ class: "rounded-md max-w-full h-auto my-2"
1674
+ }
1675
+ };
1676
+ },
1677
+ addProseMirrorPlugins() {
1678
+ const parentPlugins = this.parent?.() || [];
1679
+ const options = this.options;
1680
+ if (!options.enablePaste && !options.enableDrop) {
1681
+ return parentPlugins;
1682
+ }
1683
+ const pastePlugin = new Plugin5({
1684
+ key: imagePastePluginKey,
1685
+ props: {
1686
+ handlePaste(view, event) {
1687
+ if (!options.enablePaste) return false;
1688
+ const items = Array.from(event.clipboardData?.items || []);
1689
+ const imageItems = items.filter(
1690
+ (item) => item.kind === "file" && item.type.startsWith("image/")
1691
+ );
1692
+ if (imageItems.length === 0) return false;
1693
+ event.preventDefault();
1694
+ for (const item of imageItems) {
1695
+ const file = item.getAsFile();
1696
+ if (!file) continue;
1697
+ handleImageInsertion(view, file, options);
1698
+ }
1699
+ return true;
1700
+ },
1701
+ handleDrop(view, event) {
1702
+ if (!options.enableDrop) return false;
1703
+ const files = Array.from(event.dataTransfer?.files || []);
1704
+ const imageFiles = files.filter((f) => f.type.startsWith("image/"));
1705
+ if (imageFiles.length === 0) return false;
1706
+ event.preventDefault();
1707
+ for (const file of imageFiles) {
1708
+ handleImageInsertion(view, file, options);
1709
+ }
1710
+ return true;
1711
+ }
1712
+ }
1713
+ });
1714
+ return [...parentPlugins, pastePlugin];
1715
+ }
1716
+ });
1717
+
1718
+ // src/extensions/rich-text-preset.ts
1719
+ var resolveExt3 = (mod, name) => mod?.[name] || mod?.default?.[name] || mod?.default?.default || mod?.default || mod;
1720
+ var StarterKit = resolveExt3(_StarterKit, "StarterKit");
1721
+ var Color = resolveExt3(_Color, "Color");
1722
+ var Highlight = resolveExt3(_Highlight, "Highlight");
1723
+ var Link2 = resolveExt3(_Link, "Link");
1724
+ var TaskList2 = resolveExt3(_TaskList, "TaskList");
1725
+ var TaskItem2 = resolveExt3(_TaskItem, "TaskItem");
1726
+ var TextAlign = resolveExt3(_TextAlign, "TextAlign");
1727
+ var TextStyle = resolveExt3(_TextStyle, "TextStyle");
1728
+ var Typography = resolveExt3(_Typography, "Typography");
1729
+ var Underline = resolveExt3(_Underline, "Underline");
1730
+ var Placeholder = resolveExt3(_Placeholder, "Placeholder");
1731
+ var CharacterCount = resolveExt3(_CharacterCount, "CharacterCount");
1569
1732
  function createRichTextPreset(options = {}) {
1570
1733
  const {
1571
1734
  placeholder = "Start typing...",
@@ -1687,12 +1850,25 @@ function createRichTextPreset(options = {}) {
1687
1850
  })
1688
1851
  );
1689
1852
  }
1853
+ if (options.image !== false) {
1854
+ const imageConfig = typeof options.image === "object" ? options.image : {};
1855
+ extensions2.push(
1856
+ Image2.configure({
1857
+ HTMLAttributes: {
1858
+ class: "rounded-md max-w-full h-auto my-2",
1859
+ ...imageConfig.HTMLAttributes || {}
1860
+ },
1861
+ ...imageConfig
1862
+ })
1863
+ );
1864
+ }
1690
1865
  return extensions2;
1691
1866
  }
1692
1867
  export {
1693
1868
  ColorHighlighter,
1694
1869
  FontSize,
1695
1870
  GradientText,
1871
+ Image2 as Image,
1696
1872
  LoadingNode,
1697
1873
  Markdown2 as Markdown,
1698
1874
  Mention,
@@ -1701,6 +1877,11 @@ export {
1701
1877
  TemplateSuggestions,
1702
1878
  createMentionExtension,
1703
1879
  createRichTextPreset,
1704
- findHexColors
1880
+ findHexColors,
1881
+ handleImageInsertion,
1882
+ imagePastePluginKey,
1883
+ insertImageNode,
1884
+ removeImageNode,
1885
+ updateImageSrc
1705
1886
  };
1706
1887
  //# sourceMappingURL=index.js.map