@pixerate/editor 0.5.0 → 0.6.0

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,37 @@ 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
+ * Whether to enable automatic image pasting from clipboard. Defaults to true.
113
+ */
114
+ enablePaste?: boolean;
115
+ /**
116
+ * Whether to enable automatic image drop. Defaults to true.
117
+ */
118
+ enableDrop?: boolean;
119
+ }
120
+ declare const imagePastePluginKey: PluginKey<any>;
121
+ /**
122
+ * Inserts an image node into the editor view at the current selection.
123
+ */
124
+ declare function insertImageNode(view: any, src: string, alt?: string): void;
125
+ /**
126
+ * Updates an existing image node's src attribute across document positions.
127
+ */
128
+ declare function updateImageSrc(view: any, oldSrc: string, newSrc: string): void;
129
+ /**
130
+ * Handles inserting a pasted or dropped image file, supporting both async upload
131
+ * handlers and direct base64/object-URL fallbacks.
132
+ */
133
+ declare function handleImageInsertion(view: any, file: File, options: ImageOptions): void;
134
+ declare const Image: any;
135
+
103
136
  interface RichTextPresetOptions {
104
137
  placeholder?: string | ((props: {
105
138
  node: any;
@@ -111,10 +144,11 @@ interface RichTextPresetOptions {
111
144
  headingLevels?: (1 | 2 | 3 | 4 | 5 | 6)[];
112
145
  mention?: MentionOptions | boolean;
113
146
  markdown?: MarkdownOptions | boolean;
147
+ image?: ImageOptions | boolean;
114
148
  }
115
149
  /**
116
150
  * Creates a standard rich-text extensions bundle with full formatting support.
117
151
  */
118
152
  declare function createRichTextPreset(options?: RichTextPresetOptions): Extensions;
119
153
 
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 };
154
+ 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, 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,37 @@ 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
+ * Whether to enable automatic image pasting from clipboard. Defaults to true.
113
+ */
114
+ enablePaste?: boolean;
115
+ /**
116
+ * Whether to enable automatic image drop. Defaults to true.
117
+ */
118
+ enableDrop?: boolean;
119
+ }
120
+ declare const imagePastePluginKey: PluginKey<any>;
121
+ /**
122
+ * Inserts an image node into the editor view at the current selection.
123
+ */
124
+ declare function insertImageNode(view: any, src: string, alt?: string): void;
125
+ /**
126
+ * Updates an existing image node's src attribute across document positions.
127
+ */
128
+ declare function updateImageSrc(view: any, oldSrc: string, newSrc: string): void;
129
+ /**
130
+ * Handles inserting a pasted or dropped image file, supporting both async upload
131
+ * handlers and direct base64/object-URL fallbacks.
132
+ */
133
+ declare function handleImageInsertion(view: any, file: File, options: ImageOptions): void;
134
+ declare const Image: any;
135
+
103
136
  interface RichTextPresetOptions {
104
137
  placeholder?: string | ((props: {
105
138
  node: any;
@@ -111,10 +144,11 @@ interface RichTextPresetOptions {
111
144
  headingLevels?: (1 | 2 | 3 | 4 | 5 | 6)[];
112
145
  mention?: MentionOptions | boolean;
113
146
  markdown?: MarkdownOptions | boolean;
147
+ image?: ImageOptions | boolean;
114
148
  }
115
149
  /**
116
150
  * Creates a standard rich-text extensions bundle with full formatting support.
117
151
  */
118
152
  declare function createRichTextPreset(options?: RichTextPresetOptions): Extensions;
119
153
 
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 };
154
+ 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, updateImageSrc };
@@ -1552,20 +1552,140 @@ 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 handleImageInsertion(view, file, options) {
1589
+ if (typeof options.upload === "function") {
1590
+ const hasObjectUrl = typeof URL !== "undefined" && typeof URL.createObjectURL === "function";
1591
+ const tempUrl = hasObjectUrl ? URL.createObjectURL(file) : null;
1592
+ if (tempUrl) {
1593
+ insertImageNode(view, tempUrl, file.name);
1594
+ }
1595
+ Promise.resolve(options.upload(file)).then((uploadedUrl) => {
1596
+ if (!uploadedUrl) return;
1597
+ if (tempUrl) {
1598
+ updateImageSrc(view, tempUrl, uploadedUrl);
1599
+ if (typeof URL.revokeObjectURL === "function") {
1600
+ URL.revokeObjectURL(tempUrl);
1601
+ }
1602
+ } else {
1603
+ insertImageNode(view, uploadedUrl, file.name);
1604
+ }
1605
+ }).catch((err) => {
1606
+ console.error("Failed to upload pasted image:", err);
1607
+ });
1608
+ } else if (options.allowBase64 !== false) {
1609
+ if (typeof FileReader !== "undefined") {
1610
+ const reader = new FileReader();
1611
+ reader.onload = () => {
1612
+ const src = reader.result;
1613
+ if (src) {
1614
+ insertImageNode(view, src, file.name);
1615
+ }
1616
+ };
1617
+ reader.readAsDataURL(file);
1618
+ }
1619
+ }
1620
+ }
1621
+ var Image2 = BaseImage.extend({
1622
+ addOptions() {
1623
+ return {
1624
+ ...this.parent?.(),
1625
+ allowBase64: true,
1626
+ enablePaste: true,
1627
+ enableDrop: true,
1628
+ upload: void 0,
1629
+ HTMLAttributes: {
1630
+ class: "rounded-md max-w-full h-auto my-2"
1631
+ }
1632
+ };
1633
+ },
1634
+ addProseMirrorPlugins() {
1635
+ const parentPlugins = this.parent?.() || [];
1636
+ const options = this.options;
1637
+ if (!options.enablePaste && !options.enableDrop) {
1638
+ return parentPlugins;
1639
+ }
1640
+ const pastePlugin = new Plugin5({
1641
+ key: imagePastePluginKey,
1642
+ props: {
1643
+ handlePaste(view, event) {
1644
+ if (!options.enablePaste) return false;
1645
+ const items = Array.from(event.clipboardData?.items || []);
1646
+ const imageItems = items.filter(
1647
+ (item) => item.kind === "file" && item.type.startsWith("image/")
1648
+ );
1649
+ if (imageItems.length === 0) return false;
1650
+ event.preventDefault();
1651
+ for (const item of imageItems) {
1652
+ const file = item.getAsFile();
1653
+ if (!file) continue;
1654
+ handleImageInsertion(view, file, options);
1655
+ }
1656
+ return true;
1657
+ },
1658
+ handleDrop(view, event) {
1659
+ if (!options.enableDrop) return false;
1660
+ const files = Array.from(event.dataTransfer?.files || []);
1661
+ const imageFiles = files.filter((f) => f.type.startsWith("image/"));
1662
+ if (imageFiles.length === 0) return false;
1663
+ event.preventDefault();
1664
+ for (const file of imageFiles) {
1665
+ handleImageInsertion(view, file, options);
1666
+ }
1667
+ return true;
1668
+ }
1669
+ }
1670
+ });
1671
+ return [...parentPlugins, pastePlugin];
1672
+ }
1673
+ });
1674
+
1675
+ // src/extensions/rich-text-preset.ts
1676
+ var resolveExt3 = (mod, name) => mod?.[name] || mod?.default?.[name] || mod?.default?.default || mod?.default || mod;
1677
+ var StarterKit = resolveExt3(_StarterKit, "StarterKit");
1678
+ var Color = resolveExt3(_Color, "Color");
1679
+ var Highlight = resolveExt3(_Highlight, "Highlight");
1680
+ var Link2 = resolveExt3(_Link, "Link");
1681
+ var TaskList2 = resolveExt3(_TaskList, "TaskList");
1682
+ var TaskItem2 = resolveExt3(_TaskItem, "TaskItem");
1683
+ var TextAlign = resolveExt3(_TextAlign, "TextAlign");
1684
+ var TextStyle = resolveExt3(_TextStyle, "TextStyle");
1685
+ var Typography = resolveExt3(_Typography, "Typography");
1686
+ var Underline = resolveExt3(_Underline, "Underline");
1687
+ var Placeholder = resolveExt3(_Placeholder, "Placeholder");
1688
+ var CharacterCount = resolveExt3(_CharacterCount, "CharacterCount");
1569
1689
  function createRichTextPreset(options = {}) {
1570
1690
  const {
1571
1691
  placeholder = "Start typing...",
@@ -1687,12 +1807,25 @@ function createRichTextPreset(options = {}) {
1687
1807
  })
1688
1808
  );
1689
1809
  }
1810
+ if (options.image !== false) {
1811
+ const imageConfig = typeof options.image === "object" ? options.image : {};
1812
+ extensions2.push(
1813
+ Image2.configure({
1814
+ HTMLAttributes: {
1815
+ class: "rounded-md max-w-full h-auto my-2",
1816
+ ...imageConfig.HTMLAttributes || {}
1817
+ },
1818
+ ...imageConfig
1819
+ })
1820
+ );
1821
+ }
1690
1822
  return extensions2;
1691
1823
  }
1692
1824
  export {
1693
1825
  ColorHighlighter,
1694
1826
  FontSize,
1695
1827
  GradientText,
1828
+ Image2 as Image,
1696
1829
  LoadingNode,
1697
1830
  Markdown2 as Markdown,
1698
1831
  Mention,
@@ -1701,6 +1834,10 @@ export {
1701
1834
  TemplateSuggestions,
1702
1835
  createMentionExtension,
1703
1836
  createRichTextPreset,
1704
- findHexColors
1837
+ findHexColors,
1838
+ handleImageInsertion,
1839
+ imagePastePluginKey,
1840
+ insertImageNode,
1841
+ updateImageSrc
1705
1842
  };
1706
1843
  //# sourceMappingURL=index.js.map