payload-richtext-tiptap 0.0.159 → 0.0.161
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/src/fields/TiptapEditor/extensions/Iframe/IframeEmbed.d.ts +2 -7
- package/dist/src/fields/TiptapEditor/extensions/Iframe/IframeEmbed.d.ts.map +1 -1
- package/dist/src/fields/TiptapEditor/extensions/Iframe/IframeEmbed.js +20 -7
- package/dist/src/fields/TiptapEditor/extensions/Iframe/IframeEmbed.js.map +1 -1
- package/dist/src/fields/TiptapEditor/extensions/Iframe/iframe.d.ts +2 -2
- package/dist/src/fields/TiptapEditor/extensions/Iframe/iframe.d.ts.map +1 -1
- package/dist/src/fields/TiptapEditor/extensions/Iframe/iframe.js +42 -30
- package/dist/src/fields/TiptapEditor/extensions/Iframe/iframe.js.map +1 -1
- package/dist/src/fields/TiptapEditor/extensions/Paragraph/Paragraph.d.ts.map +1 -1
- package/dist/src/fields/TiptapEditor/extensions/Paragraph/Paragraph.js +27 -4
- package/dist/src/fields/TiptapEditor/extensions/Paragraph/Paragraph.js.map +1 -1
- package/dist/src/fields/TiptapEditor/extensions/PasteHandler/PasteHandler.d.ts +4 -0
- package/dist/src/fields/TiptapEditor/extensions/PasteHandler/PasteHandler.d.ts.map +1 -0
- package/dist/src/fields/TiptapEditor/extensions/PasteHandler/PasteHandler.js +242 -0
- package/dist/src/fields/TiptapEditor/extensions/PasteHandler/PasteHandler.js.map +1 -0
- package/dist/src/fields/TiptapEditor/extensions/PasteHandler/index.d.ts +2 -0
- package/dist/src/fields/TiptapEditor/extensions/PasteHandler/index.d.ts.map +1 -0
- package/dist/src/fields/TiptapEditor/extensions/PasteHandler/index.js +3 -0
- package/dist/src/fields/TiptapEditor/extensions/PasteHandler/index.js.map +1 -0
- package/dist/src/fields/TiptapEditor/extensions/SlashCommand/groups.d.ts +1 -1
- package/dist/src/fields/TiptapEditor/extensions/SlashCommand/groups.d.ts.map +1 -1
- package/dist/src/fields/TiptapEditor/extensions/SlashCommand/groups.js +108 -84
- package/dist/src/fields/TiptapEditor/extensions/SlashCommand/groups.js.map +1 -1
- package/dist/src/fields/TiptapEditor/extensions/Table/Table.d.ts.map +1 -1
- package/dist/src/fields/TiptapEditor/extensions/Table/Table.js +2 -1
- package/dist/src/fields/TiptapEditor/extensions/Table/Table.js.map +1 -1
- package/dist/src/fields/TiptapEditor/extensions/extension-kit.d.ts +3 -3
- package/dist/src/fields/TiptapEditor/extensions/extension-kit.d.ts.map +1 -1
- package/dist/src/fields/TiptapEditor/extensions/extension-kit.js +29 -22
- package/dist/src/fields/TiptapEditor/extensions/extension-kit.js.map +1 -1
- package/dist/src/fields/TiptapEditor/extensions/index.d.ts +50 -49
- package/dist/src/fields/TiptapEditor/extensions/index.d.ts.map +1 -1
- package/dist/src/fields/TiptapEditor/extensions/index.js +50 -49
- package/dist/src/fields/TiptapEditor/extensions/index.js.map +1 -1
- package/dist/src/fields/TiptapEditor/extensions/serverside/IFrameServerside.d.ts +2 -2
- package/dist/src/fields/TiptapEditor/extensions/serverside/IFrameServerside.d.ts.map +1 -1
- package/dist/src/fields/TiptapEditor/extensions/serverside/IFrameServerside.js +26 -8
- package/dist/src/fields/TiptapEditor/extensions/serverside/IFrameServerside.js.map +1 -1
- package/dist/src/fields/TiptapEditor/extensions/serverside/TableServerside.d.ts +10 -0
- package/dist/src/fields/TiptapEditor/extensions/serverside/TableServerside.d.ts.map +1 -0
- package/dist/src/fields/TiptapEditor/extensions/serverside/TableServerside.js +90 -0
- package/dist/src/fields/TiptapEditor/extensions/serverside/TableServerside.js.map +1 -0
- package/dist/src/fields/TiptapEditor/extensions/serverside/index.d.ts +15 -13
- package/dist/src/fields/TiptapEditor/extensions/serverside/index.d.ts.map +1 -1
- package/dist/src/fields/TiptapEditor/extensions/serverside/index.js +15 -13
- package/dist/src/fields/TiptapEditor/extensions/serverside/index.js.map +1 -1
- package/dist/src/fields/TiptapEditor/features/BlockEditor/BlockEditor.d.ts.map +1 -1
- package/dist/src/fields/TiptapEditor/features/BlockEditor/BlockEditor.js +9 -0
- package/dist/src/fields/TiptapEditor/features/BlockEditor/BlockEditor.js.map +1 -1
- package/dist/src/fields/TiptapEditor/features/panels/IframeLinkEditorPanel/IframeLinkEditorPanel.d.ts +2 -2
- package/dist/src/fields/TiptapEditor/features/panels/IframeLinkEditorPanel/IframeLinkEditorPanel.d.ts.map +1 -1
- package/dist/src/fields/TiptapEditor/features/panels/IframeLinkEditorPanel/IframeLinkEditorPanel.js +20 -18
- package/dist/src/fields/TiptapEditor/features/panels/IframeLinkEditorPanel/IframeLinkEditorPanel.js.map +1 -1
- package/dist/src/fields/TiptapEditor/features/ui/SlashCommand/SlashCommandTriggerButton.d.ts +4 -0
- package/dist/src/fields/TiptapEditor/features/ui/SlashCommand/SlashCommandTriggerButton.d.ts.map +1 -0
- package/dist/src/fields/TiptapEditor/features/ui/SlashCommand/SlashCommandTriggerButton.js +32 -0
- package/dist/src/fields/TiptapEditor/features/ui/SlashCommand/SlashCommandTriggerButton.js.map +1 -0
- package/dist/src/fields/TiptapEditor/features/ui/SlashCommand/SlashDropdownMenu.d.ts +4 -0
- package/dist/src/fields/TiptapEditor/features/ui/SlashCommand/SlashDropdownMenu.d.ts.map +1 -0
- package/dist/src/fields/TiptapEditor/features/ui/SlashCommand/SlashDropdownMenu.js +70 -0
- package/dist/src/fields/TiptapEditor/features/ui/SlashCommand/SlashDropdownMenu.js.map +1 -0
- package/dist/src/fields/TiptapEditor/features/ui/SlashCommand/SlashMenuList.d.ts +10 -0
- package/dist/src/fields/TiptapEditor/features/ui/SlashCommand/SlashMenuList.d.ts.map +1 -0
- package/dist/src/fields/TiptapEditor/features/ui/SlashCommand/SlashMenuList.js +249 -0
- package/dist/src/fields/TiptapEditor/features/ui/SlashCommand/SlashMenuList.js.map +1 -0
- package/dist/src/fields/TiptapEditor/features/ui/SlashCommand/SuggestionMenu.d.ts +15 -0
- package/dist/src/fields/TiptapEditor/features/ui/SlashCommand/SuggestionMenu.d.ts.map +1 -0
- package/dist/src/fields/TiptapEditor/features/ui/SlashCommand/SuggestionMenu.js +291 -0
- package/dist/src/fields/TiptapEditor/features/ui/SlashCommand/SuggestionMenu.js.map +1 -0
- package/dist/src/fields/TiptapEditor/features/ui/SlashCommand/index.d.ts +7 -0
- package/dist/src/fields/TiptapEditor/features/ui/SlashCommand/index.d.ts.map +1 -0
- package/dist/src/fields/TiptapEditor/features/ui/SlashCommand/index.js +7 -0
- package/dist/src/fields/TiptapEditor/features/ui/SlashCommand/index.js.map +1 -0
- package/dist/src/fields/TiptapEditor/features/ui/SlashCommand/types.d.ts +28 -0
- package/dist/src/fields/TiptapEditor/features/ui/SlashCommand/types.d.ts.map +1 -0
- package/dist/src/fields/TiptapEditor/features/ui/SlashCommand/types.js +3 -0
- package/dist/src/fields/TiptapEditor/features/ui/SlashCommand/types.js.map +1 -0
- package/dist/src/fields/TiptapEditor/features/ui/SlashCommand/utils.d.ts +6 -0
- package/dist/src/fields/TiptapEditor/features/ui/SlashCommand/utils.d.ts.map +1 -0
- package/dist/src/fields/TiptapEditor/features/ui/SlashCommand/utils.js +222 -0
- package/dist/src/fields/TiptapEditor/features/ui/SlashCommand/utils.js.map +1 -0
- package/dist/src/fields/TiptapEditor/lib/utils/updateImageUrl.d.ts +2 -0
- package/dist/src/fields/TiptapEditor/lib/utils/updateImageUrl.d.ts.map +1 -0
- package/dist/src/fields/TiptapEditor/lib/utils/updateImageUrl.js +17 -0
- package/dist/src/fields/TiptapEditor/lib/utils/updateImageUrl.js.map +1 -0
- package/dist/src/mobile.css +1 -1
- package/dist/src/styles.css +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
|
@@ -1,9 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
attrs: {
|
|
4
|
-
src?: string;
|
|
5
|
-
};
|
|
6
|
-
};
|
|
7
|
-
}) => import("react").JSX.Element;
|
|
1
|
+
import { NodeViewProps } from '@tiptap/react';
|
|
2
|
+
declare const _default: (props: NodeViewProps) => import("react").JSX.Element;
|
|
8
3
|
export default _default;
|
|
9
4
|
//# sourceMappingURL=IframeEmbed.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"IframeEmbed.d.ts","sourceRoot":"","sources":["../../../../../../src/fields/TiptapEditor/extensions/Iframe/IframeEmbed.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"IframeEmbed.d.ts","sourceRoot":"","sources":["../../../../../../src/fields/TiptapEditor/extensions/Iframe/IframeEmbed.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;gCAEtB,aAAa;AAApC,wBA4BC"}
|
|
@@ -1,16 +1,29 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
3
|
-
import SocialMediaEmbed from
|
|
2
|
+
import { Code } from 'lucide-react';
|
|
3
|
+
import SocialMediaEmbed from '../SocialMedia/SocialMediaEmbed.js';
|
|
4
|
+
import i18next from 'i18next';
|
|
4
5
|
export default ((props)=>{
|
|
6
|
+
const { src, width = '100%', height = '400', title } = props?.node?.attrs ?? {};
|
|
5
7
|
return /*#__PURE__*/ _jsx(SocialMediaEmbed, {
|
|
6
8
|
props: props,
|
|
7
|
-
Icon:
|
|
8
|
-
text:
|
|
9
|
+
Icon: Code,
|
|
10
|
+
text: i18next.t('embedIframe') || 'Embed an Iframe (Flourish, Datawrapper, etc.)',
|
|
9
11
|
children: /*#__PURE__*/ _jsx("div", {
|
|
10
|
-
className: "iframe-wrapper",
|
|
12
|
+
className: "iframe-wrapper w-full",
|
|
11
13
|
children: /*#__PURE__*/ _jsx("iframe", {
|
|
12
|
-
src:
|
|
13
|
-
|
|
14
|
+
src: src ?? '',
|
|
15
|
+
width: width,
|
|
16
|
+
height: height,
|
|
17
|
+
title: title ?? 'Embedded content',
|
|
18
|
+
allowFullScreen: true,
|
|
19
|
+
loading: "lazy",
|
|
20
|
+
sandbox: "allow-scripts allow-same-origin allow-forms allow-popups allow-presentation",
|
|
21
|
+
referrerPolicy: "no-referrer-when-downgrade",
|
|
22
|
+
style: {
|
|
23
|
+
border: 'none',
|
|
24
|
+
width: '100%',
|
|
25
|
+
minHeight: '300px'
|
|
26
|
+
}
|
|
14
27
|
})
|
|
15
28
|
})
|
|
16
29
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../src/fields/TiptapEditor/extensions/Iframe/IframeEmbed.tsx"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"sources":["../../../../../../src/fields/TiptapEditor/extensions/Iframe/IframeEmbed.tsx"],"sourcesContent":["import { Code } from 'lucide-react'\r\nimport SocialMediaEmbed from '../SocialMedia/SocialMediaEmbed.js'\r\nimport i18next from 'i18next'\r\nimport { NodeViewProps } from '@tiptap/react'\r\n\r\nexport default (props: NodeViewProps) => {\r\n const { src, width = '100%', height = '400', title } = props?.node?.attrs ?? {}\r\n\r\n return (\r\n <SocialMediaEmbed\r\n props={props}\r\n Icon={Code}\r\n text={i18next.t('embedIframe') || 'Embed an Iframe (Flourish, Datawrapper, etc.)'}\r\n >\r\n <div className=\"iframe-wrapper w-full\">\r\n <iframe\r\n src={src ?? ''}\r\n width={width}\r\n height={height}\r\n title={title ?? 'Embedded content'}\r\n allowFullScreen\r\n loading=\"lazy\"\r\n sandbox=\"allow-scripts allow-same-origin allow-forms allow-popups allow-presentation\"\r\n referrerPolicy=\"no-referrer-when-downgrade\"\r\n style={{\r\n border: 'none',\r\n width: '100%',\r\n minHeight: '300px',\r\n }}\r\n />\r\n </div>\r\n </SocialMediaEmbed>\r\n )\r\n}\r\n"],"names":["Code","SocialMediaEmbed","i18next","props","src","width","height","title","node","attrs","Icon","text","t","div","className","iframe","allowFullScreen","loading","sandbox","referrerPolicy","style","border","minHeight"],"mappings":";AAAA,SAASA,IAAI,QAAQ,eAAc;AACnC,OAAOC,sBAAsB,qCAAoC;AACjE,OAAOC,aAAa,UAAS;AAG7B,eAAe,CAAA,CAACC;IACd,MAAM,EAAEC,GAAG,EAAEC,QAAQ,MAAM,EAAEC,SAAS,KAAK,EAAEC,KAAK,EAAE,GAAGJ,OAAOK,MAAMC,SAAS,CAAC;IAE9E,qBACE,KAACR;QACCE,OAAOA;QACPO,MAAMV;QACNW,MAAMT,QAAQU,CAAC,CAAC,kBAAkB;kBAElC,cAAA,KAACC;YAAIC,WAAU;sBACb,cAAA,KAACC;gBACCX,KAAKA,OAAO;gBACZC,OAAOA;gBACPC,QAAQA;gBACRC,OAAOA,SAAS;gBAChBS,eAAe;gBACfC,SAAQ;gBACRC,SAAQ;gBACRC,gBAAe;gBACfC,OAAO;oBACLC,QAAQ;oBACRhB,OAAO;oBACPiB,WAAW;gBACb;;;;AAKV,CAAA,EAAC"}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { Node } from
|
|
1
|
+
import { Node } from '@tiptap/core';
|
|
2
2
|
export interface IframeOptions {
|
|
3
3
|
allowFullscreen: boolean;
|
|
4
4
|
HTMLAttributes: {
|
|
5
5
|
[key: string]: any;
|
|
6
6
|
};
|
|
7
7
|
}
|
|
8
|
-
declare module
|
|
8
|
+
declare module '@tiptap/core' {
|
|
9
9
|
interface Commands<ReturnType> {
|
|
10
10
|
iframe: {
|
|
11
11
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"iframe.d.ts","sourceRoot":"","sources":["../../../../../../src/fields/TiptapEditor/extensions/Iframe/iframe.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,IAAI,EAAE,MAAM,cAAc,
|
|
1
|
+
{"version":3,"file":"iframe.d.ts","sourceRoot":"","sources":["../../../../../../src/fields/TiptapEditor/extensions/Iframe/iframe.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,IAAI,EAAE,MAAM,cAAc,CAAA;AAIpD,MAAM,WAAW,aAAa;IAC5B,eAAe,EAAE,OAAO,CAAA;IACxB,cAAc,EAAE;QACd,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;KACnB,CAAA;CACF;AAED,OAAO,QAAQ,cAAc,CAAC;IAC5B,UAAU,QAAQ,CAAC,UAAU;QAC3B,MAAM,EAAE;YACN;;eAEG;YACH,SAAS,EAAE,CAAC,OAAO,CAAC,EAAE;gBAAE,GAAG,EAAE,MAAM,CAAA;aAAE,KAAK,UAAU,CAAA;YACpD,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,UAAU,CAAA;YACtC,YAAY,EAAE,CAAC,GAAG,CAAC,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,KAAK,UAAU,CAAA;SAC/D,CAAA;KACF;CACF;AAED,eAAO,MAAM,MAAM,0BAqKjB,CAAA"}
|
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
import { mergeAttributes, Node } from
|
|
2
|
-
import { ReactNodeViewRenderer } from
|
|
3
|
-
import IframeEmbed from
|
|
1
|
+
import { mergeAttributes, Node } from '@tiptap/core';
|
|
2
|
+
import { ReactNodeViewRenderer } from '@tiptap/react';
|
|
3
|
+
import IframeEmbed from './IframeEmbed.js';
|
|
4
4
|
export const Iframe = Node.create({
|
|
5
|
-
name:
|
|
5
|
+
name: 'iframe',
|
|
6
6
|
inline: false,
|
|
7
|
-
group:
|
|
8
|
-
content:
|
|
7
|
+
group: 'block',
|
|
8
|
+
content: 'inline*',
|
|
9
9
|
draggable: true,
|
|
10
10
|
atom: true,
|
|
11
|
-
|
|
11
|
+
addOptions () {
|
|
12
12
|
return {
|
|
13
13
|
allowFullscreen: true,
|
|
14
14
|
HTMLAttributes: {
|
|
15
|
-
class:
|
|
15
|
+
class: 'iframe-wrapper'
|
|
16
16
|
}
|
|
17
17
|
};
|
|
18
18
|
},
|
|
@@ -29,23 +29,35 @@ export const Iframe = Node.create({
|
|
|
29
29
|
parseHTML: ()=>this.options.allowFullscreen
|
|
30
30
|
},
|
|
31
31
|
sandbox: {
|
|
32
|
-
default:
|
|
32
|
+
default: 'allow-scripts allow-same-origin allow-forms allow-popups allow-presentation'
|
|
33
33
|
},
|
|
34
34
|
referrerpolicy: {
|
|
35
|
-
default:
|
|
35
|
+
default: 'no-referrer-when-downgrade'
|
|
36
|
+
},
|
|
37
|
+
width: {
|
|
38
|
+
default: '100%'
|
|
39
|
+
},
|
|
40
|
+
height: {
|
|
41
|
+
default: 'auto'
|
|
42
|
+
},
|
|
43
|
+
title: {
|
|
44
|
+
default: null
|
|
45
|
+
},
|
|
46
|
+
loading: {
|
|
47
|
+
default: 'lazy'
|
|
36
48
|
}
|
|
37
49
|
};
|
|
38
50
|
},
|
|
39
51
|
renderHTML ({ HTMLAttributes }) {
|
|
40
52
|
return [
|
|
41
|
-
|
|
53
|
+
'iframe',
|
|
42
54
|
mergeAttributes(this.options.HTMLAttributes, HTMLAttributes)
|
|
43
55
|
];
|
|
44
56
|
},
|
|
45
57
|
parseHTML () {
|
|
46
58
|
return [
|
|
47
59
|
{
|
|
48
|
-
tag:
|
|
60
|
+
tag: 'iframe'
|
|
49
61
|
}
|
|
50
62
|
];
|
|
51
63
|
},
|
|
@@ -59,15 +71,15 @@ export const Iframe = Node.create({
|
|
|
59
71
|
let attributes = {
|
|
60
72
|
...options
|
|
61
73
|
};
|
|
62
|
-
if (options?.src?.includes(
|
|
74
|
+
if (options?.src?.includes('iframe')) {
|
|
63
75
|
const parser = new DOMParser();
|
|
64
|
-
const html = parser.parseFromString(options?.src,
|
|
65
|
-
const attrNames = html.getElementsByTagName(
|
|
76
|
+
const html = parser.parseFromString(options?.src, 'text/html');
|
|
77
|
+
const attrNames = html.getElementsByTagName('iframe')?.[0]?.getAttributeNames();
|
|
66
78
|
attrNames?.forEach((attr)=>{
|
|
67
|
-
attributes[attr] = html.getElementsByTagName(
|
|
79
|
+
attributes[attr] = html.getElementsByTagName('iframe')?.[0]?.getAttribute(attr);
|
|
68
80
|
});
|
|
69
81
|
} else if (!isValidHttpUrl(options?.src)) {
|
|
70
|
-
const htmlData =
|
|
82
|
+
const htmlData = 'data:text/html;charset=utf-8,' + encodeURI(options?.src);
|
|
71
83
|
attributes = {
|
|
72
84
|
...attributes,
|
|
73
85
|
src: htmlData
|
|
@@ -88,34 +100,34 @@ export const Iframe = Node.create({
|
|
|
88
100
|
let attributes = {
|
|
89
101
|
editorValue: value
|
|
90
102
|
};
|
|
91
|
-
if (value?.includes(
|
|
92
|
-
const ytValue = value?.split(
|
|
103
|
+
if (value?.includes('youtube.com')) {
|
|
104
|
+
const ytValue = value?.split('v=')?.[1]?.split('&')?.[0];
|
|
93
105
|
attributes = {
|
|
94
106
|
...attributes,
|
|
95
107
|
src: `https://www.youtube.com/embed/${ytValue}?controls=0`,
|
|
96
|
-
title:
|
|
108
|
+
title: 'YouTube video player'
|
|
97
109
|
};
|
|
98
|
-
} else if (value?.includes(
|
|
99
|
-
const instagramValue = value?.split(
|
|
110
|
+
} else if (value?.includes('instagram.com')) {
|
|
111
|
+
const instagramValue = value?.split('p/')?.[1]?.split('/')?.[0];
|
|
100
112
|
attributes = {
|
|
101
113
|
...attributes,
|
|
102
114
|
src: `https://www.instagram.com/p/${instagramValue}/embed`,
|
|
103
|
-
title:
|
|
115
|
+
title: 'Instagram'
|
|
104
116
|
};
|
|
105
117
|
} else if (isValidHttpUrl(value)) {
|
|
106
118
|
attributes = {
|
|
107
119
|
...attributes,
|
|
108
120
|
src: value
|
|
109
121
|
};
|
|
110
|
-
} else if (value?.includes(
|
|
122
|
+
} else if (value?.includes('iframe')) {
|
|
111
123
|
const parser = new DOMParser();
|
|
112
|
-
const html = parser.parseFromString(value,
|
|
113
|
-
const attrNames = html.getElementsByTagName(
|
|
124
|
+
const html = parser.parseFromString(value, 'text/html');
|
|
125
|
+
const attrNames = html.getElementsByTagName('iframe')?.[0]?.getAttributeNames();
|
|
114
126
|
attrNames?.forEach((attr)=>{
|
|
115
|
-
attributes[attr] = html.getElementsByTagName(
|
|
127
|
+
attributes[attr] = html.getElementsByTagName('iframe')?.[0]?.getAttribute(attr);
|
|
116
128
|
});
|
|
117
129
|
} else {
|
|
118
|
-
const htmlData =
|
|
130
|
+
const htmlData = 'data:text/html;charset=utf-8,' + encodeURI(value);
|
|
119
131
|
attributes = {
|
|
120
132
|
...attributes,
|
|
121
133
|
src: htmlData
|
|
@@ -125,7 +137,7 @@ export const Iframe = Node.create({
|
|
|
125
137
|
},
|
|
126
138
|
insertIframe: (src, className)=>({ commands, state })=>{
|
|
127
139
|
return commands.insertContent({
|
|
128
|
-
type:
|
|
140
|
+
type: 'iframe',
|
|
129
141
|
attrs: {
|
|
130
142
|
src,
|
|
131
143
|
class: className
|
|
@@ -142,7 +154,7 @@ function isValidHttpUrl(string) {
|
|
|
142
154
|
} catch (_) {
|
|
143
155
|
return false;
|
|
144
156
|
}
|
|
145
|
-
return url.protocol ===
|
|
157
|
+
return url.protocol === 'http:' || url.protocol === 'https:';
|
|
146
158
|
}
|
|
147
159
|
|
|
148
160
|
//# sourceMappingURL=iframe.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../src/fields/TiptapEditor/extensions/Iframe/iframe.ts"],"sourcesContent":["import { mergeAttributes, Node } from
|
|
1
|
+
{"version":3,"sources":["../../../../../../src/fields/TiptapEditor/extensions/Iframe/iframe.ts"],"sourcesContent":["import { mergeAttributes, Node } from '@tiptap/core'\r\nimport { ReactNodeViewRenderer } from '@tiptap/react'\r\nimport IframeEmbed from './IframeEmbed.js'\r\n\r\nexport interface IframeOptions {\r\n allowFullscreen: boolean\r\n HTMLAttributes: {\r\n [key: string]: any\r\n }\r\n}\r\n\r\ndeclare module '@tiptap/core' {\r\n interface Commands<ReturnType> {\r\n iframe: {\r\n /**\r\n * Add an iframe\r\n */\r\n setIframe: (options?: { src: string }) => ReturnType\r\n setHtml: (value: string) => ReturnType\r\n insertIframe: (src?: string, className?: string) => ReturnType\r\n }\r\n }\r\n}\r\n\r\nexport const Iframe = Node.create<IframeOptions>({\r\n name: 'iframe',\r\n inline: false,\r\n group: 'block',\r\n content: 'inline*',\r\n draggable: true,\r\n\r\n atom: true,\r\n\r\n addOptions() {\r\n return {\r\n allowFullscreen: true,\r\n HTMLAttributes: {\r\n class: 'iframe-wrapper',\r\n },\r\n }\r\n },\r\n\r\n addAttributes() {\r\n return {\r\n src: {\r\n default: null,\r\n },\r\n frameborder: {\r\n default: 0,\r\n },\r\n allowfullscreen: {\r\n default: this.options.allowFullscreen,\r\n parseHTML: () => this.options.allowFullscreen,\r\n },\r\n sandbox: {\r\n default: 'allow-scripts allow-same-origin allow-forms allow-popups allow-presentation',\r\n },\r\n referrerpolicy: {\r\n default: 'no-referrer-when-downgrade',\r\n },\r\n width: {\r\n default: '100%',\r\n },\r\n height: {\r\n default: 'auto',\r\n },\r\n title: {\r\n default: null,\r\n },\r\n loading: {\r\n default: 'lazy',\r\n },\r\n }\r\n },\r\n renderHTML({ HTMLAttributes }) {\r\n return ['iframe', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes)]\r\n },\r\n parseHTML() {\r\n return [\r\n {\r\n tag: 'iframe',\r\n },\r\n ]\r\n },\r\n\r\n addNodeView() {\r\n return ReactNodeViewRenderer(IframeEmbed)\r\n },\r\n\r\n addCommands() {\r\n return {\r\n setIframe:\r\n (options: { src: string }) =>\r\n ({ tr, dispatch }) => {\r\n const { selection } = tr\r\n let attributes: Record<string, any> = {\r\n ...options,\r\n }\r\n\r\n if (options?.src?.includes('iframe')) {\r\n const parser = new DOMParser()\r\n const html = parser.parseFromString(options?.src, 'text/html')\r\n const attrNames = html.getElementsByTagName('iframe')?.[0]?.getAttributeNames()\r\n\r\n attrNames?.forEach((attr) => {\r\n attributes[attr] = html.getElementsByTagName('iframe')?.[0]?.getAttribute(attr)\r\n })\r\n } else if (!isValidHttpUrl(options?.src)) {\r\n const htmlData = 'data:text/html;charset=utf-8,' + encodeURI(options?.src)\r\n attributes = {\r\n ...attributes,\r\n src: htmlData,\r\n }\r\n }\r\n const node = this.type.create(attributes)\r\n\r\n if (dispatch) {\r\n tr.replaceRangeWith(selection.from, selection.to, node)\r\n }\r\n\r\n // return commands.insertContent({\r\n // type: 'iframe',\r\n // attrs: attributes,\r\n // })\r\n\r\n return true\r\n },\r\n\r\n setHtml:\r\n (value) =>\r\n ({ commands, state }) => {\r\n console.log(value)\r\n let attributes: Record<string, any> = {\r\n editorValue: value,\r\n }\r\n\r\n if (value?.includes('youtube.com')) {\r\n const ytValue = value?.split('v=')?.[1]?.split('&')?.[0]\r\n attributes = {\r\n ...attributes,\r\n\r\n src: `https://www.youtube.com/embed/${ytValue}?controls=0`,\r\n title: 'YouTube video player',\r\n }\r\n } else if (value?.includes('instagram.com')) {\r\n const instagramValue = value?.split('p/')?.[1]?.split('/')?.[0]\r\n attributes = {\r\n ...attributes,\r\n\r\n src: `https://www.instagram.com/p/${instagramValue}/embed`,\r\n title: 'Instagram',\r\n }\r\n } else if (isValidHttpUrl(value)) {\r\n attributes = {\r\n ...attributes,\r\n\r\n src: value,\r\n }\r\n } else if (value?.includes('iframe')) {\r\n const parser = new DOMParser()\r\n const html = parser.parseFromString(value, 'text/html')\r\n const attrNames = html.getElementsByTagName('iframe')?.[0]?.getAttributeNames()\r\n\r\n attrNames?.forEach((attr) => {\r\n attributes[attr] = html.getElementsByTagName('iframe')?.[0]?.getAttribute(attr)\r\n })\r\n } else {\r\n const htmlData = 'data:text/html;charset=utf-8,' + encodeURI(value)\r\n attributes = {\r\n ...attributes,\r\n src: htmlData,\r\n }\r\n }\r\n\r\n return true\r\n },\r\n insertIframe:\r\n (src, className) =>\r\n ({ commands, state }) => {\r\n return commands.insertContent({\r\n type: 'iframe',\r\n attrs: {\r\n src,\r\n class: className,\r\n },\r\n })\r\n },\r\n }\r\n },\r\n})\r\n\r\nfunction isValidHttpUrl(string: string) {\r\n let url\r\n\r\n try {\r\n url = new URL(string)\r\n } catch (_) {\r\n return false\r\n }\r\n\r\n return url.protocol === 'http:' || url.protocol === 'https:'\r\n}\r\n"],"names":["mergeAttributes","Node","ReactNodeViewRenderer","IframeEmbed","Iframe","create","name","inline","group","content","draggable","atom","addOptions","allowFullscreen","HTMLAttributes","class","addAttributes","src","default","frameborder","allowfullscreen","options","parseHTML","sandbox","referrerpolicy","width","height","title","loading","renderHTML","tag","addNodeView","addCommands","setIframe","tr","dispatch","selection","attributes","includes","parser","DOMParser","html","parseFromString","attrNames","getElementsByTagName","getAttributeNames","forEach","attr","getAttribute","isValidHttpUrl","htmlData","encodeURI","node","type","replaceRangeWith","from","to","setHtml","value","commands","state","console","log","editorValue","ytValue","split","instagramValue","insertIframe","className","insertContent","attrs","string","url","URL","_","protocol"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Paragraph.d.ts","sourceRoot":"","sources":["../../../../../../src/fields/TiptapEditor/extensions/Paragraph/Paragraph.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,SAAS,
|
|
1
|
+
{"version":3,"file":"Paragraph.d.ts","sourceRoot":"","sources":["../../../../../../src/fields/TiptapEditor/extensions/Paragraph/Paragraph.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,SAAS,0FAsCpB,CAAA;AAEF,eAAe,SAAS,CAAA"}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { mergeAttributes } from
|
|
2
|
-
import TiptapParagraph from
|
|
1
|
+
import { mergeAttributes } from '@tiptap/core';
|
|
2
|
+
import TiptapParagraph from '@tiptap/extension-paragraph';
|
|
3
3
|
export const Paragraph = TiptapParagraph.extend({
|
|
4
4
|
addAttributes () {
|
|
5
5
|
return {
|
|
6
6
|
textAlign: {
|
|
7
|
-
default:
|
|
7
|
+
default: ''
|
|
8
8
|
},
|
|
9
9
|
class: {
|
|
10
|
-
default:
|
|
10
|
+
default: ''
|
|
11
11
|
}
|
|
12
12
|
};
|
|
13
13
|
},
|
|
@@ -17,6 +17,29 @@ export const Paragraph = TiptapParagraph.extend({
|
|
|
17
17
|
mergeAttributes(this.options.HTMLAttributes, HTMLAttributes),
|
|
18
18
|
0
|
|
19
19
|
];
|
|
20
|
+
},
|
|
21
|
+
addKeyboardShortcuts () {
|
|
22
|
+
return {
|
|
23
|
+
Backspace: ({ editor })=>{
|
|
24
|
+
const { state } = editor;
|
|
25
|
+
const { selection } = state;
|
|
26
|
+
const { $from, empty } = selection;
|
|
27
|
+
if (!empty) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
if ($from.parent.type.name !== 'paragraph') {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
const isEmpty = $from.parent.content.size === 0;
|
|
34
|
+
const isAtStart = $from.parentOffset === 0;
|
|
35
|
+
// If the paragraph is empty and we're at the start, delete it
|
|
36
|
+
if (isEmpty && isAtStart) {
|
|
37
|
+
const pos = $from.before();
|
|
38
|
+
return editor.chain().setNodeSelection(pos).deleteSelection().run();
|
|
39
|
+
}
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
20
43
|
}
|
|
21
44
|
});
|
|
22
45
|
export default Paragraph;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../src/fields/TiptapEditor/extensions/Paragraph/Paragraph.ts"],"sourcesContent":["import { mergeAttributes } from
|
|
1
|
+
{"version":3,"sources":["../../../../../../src/fields/TiptapEditor/extensions/Paragraph/Paragraph.ts"],"sourcesContent":["import { mergeAttributes } from '@tiptap/core'\r\nimport TiptapParagraph from '@tiptap/extension-paragraph'\r\n\r\nexport const Paragraph = TiptapParagraph.extend({\r\n addAttributes() {\r\n return {\r\n textAlign: { default: '' },\r\n class: { default: '' },\r\n }\r\n },\r\n renderHTML({ node, HTMLAttributes }) {\r\n return ['p', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0]\r\n },\r\n addKeyboardShortcuts() {\r\n return {\r\n Backspace: ({ editor }) => {\r\n const { state } = editor\r\n const { selection } = state\r\n const { $from, empty } = selection\r\n\r\n if (!empty) {\r\n return false\r\n }\r\n\r\n if ($from.parent.type.name !== 'paragraph') {\r\n return false\r\n }\r\n\r\n const isEmpty = $from.parent.content.size === 0\r\n const isAtStart = $from.parentOffset === 0\r\n\r\n // If the paragraph is empty and we're at the start, delete it\r\n if (isEmpty && isAtStart) {\r\n const pos = $from.before()\r\n return editor.chain().setNodeSelection(pos).deleteSelection().run()\r\n }\r\n\r\n return false\r\n },\r\n }\r\n },\r\n})\r\n\r\nexport default Paragraph\r\n"],"names":["mergeAttributes","TiptapParagraph","Paragraph","extend","addAttributes","textAlign","default","class","renderHTML","node","HTMLAttributes","options","addKeyboardShortcuts","Backspace","editor","state","selection","$from","empty","parent","type","name","isEmpty","content","size","isAtStart","parentOffset","pos","before","chain","setNodeSelection","deleteSelection","run"],"mappings":"AAAA,SAASA,eAAe,QAAQ,eAAc;AAC9C,OAAOC,qBAAqB,8BAA6B;AAEzD,OAAO,MAAMC,YAAYD,gBAAgBE,MAAM,CAAC;IAC9CC;QACE,OAAO;YACLC,WAAW;gBAAEC,SAAS;YAAG;YACzBC,OAAO;gBAAED,SAAS;YAAG;QACvB;IACF;IACAE,YAAW,EAAEC,IAAI,EAAEC,cAAc,EAAE;QACjC,OAAO;YAAC;YAAKV,gBAAgB,IAAI,CAACW,OAAO,CAACD,cAAc,EAAEA;YAAiB;SAAE;IAC/E;IACAE;QACE,OAAO;YACLC,WAAW,CAAC,EAAEC,MAAM,EAAE;gBACpB,MAAM,EAAEC,KAAK,EAAE,GAAGD;gBAClB,MAAM,EAAEE,SAAS,EAAE,GAAGD;gBACtB,MAAM,EAAEE,KAAK,EAAEC,KAAK,EAAE,GAAGF;gBAEzB,IAAI,CAACE,OAAO;oBACV,OAAO;gBACT;gBAEA,IAAID,MAAME,MAAM,CAACC,IAAI,CAACC,IAAI,KAAK,aAAa;oBAC1C,OAAO;gBACT;gBAEA,MAAMC,UAAUL,MAAME,MAAM,CAACI,OAAO,CAACC,IAAI,KAAK;gBAC9C,MAAMC,YAAYR,MAAMS,YAAY,KAAK;gBAEzC,8DAA8D;gBAC9D,IAAIJ,WAAWG,WAAW;oBACxB,MAAME,MAAMV,MAAMW,MAAM;oBACxB,OAAOd,OAAOe,KAAK,GAAGC,gBAAgB,CAACH,KAAKI,eAAe,GAAGC,GAAG;gBACnE;gBAEA,OAAO;YACT;QACF;IACF;AACF,GAAE;AAEF,eAAe9B,UAAS"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PasteHandler.d.ts","sourceRoot":"","sources":["../../../../../../src/fields/TiptapEditor/extensions/PasteHandler/PasteHandler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAmHxC,eAAO,MAAM,YAAY,qBAoGvB,CAAA;AA2DF,eAAe,YAAY,CAAA"}
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { Extension } from '@tiptap/core';
|
|
2
|
+
import { Plugin, PluginKey } from '@tiptap/pm/state';
|
|
3
|
+
/**
|
|
4
|
+
* Checks if the HTML contains a table element
|
|
5
|
+
*/ function containsTable(html) {
|
|
6
|
+
return /<table[\s>]/i.test(html);
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Checks if the HTML contains an iframe element
|
|
10
|
+
*/ function containsIframe(html) {
|
|
11
|
+
return /<iframe[\s>]/i.test(html);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Extracts iframe attributes from HTML string
|
|
15
|
+
*/ function extractIframeAttributes(html) {
|
|
16
|
+
const parser = new DOMParser();
|
|
17
|
+
const doc = parser.parseFromString(html, 'text/html');
|
|
18
|
+
const iframe = doc.querySelector('iframe');
|
|
19
|
+
if (!iframe) return null;
|
|
20
|
+
const attributes = {};
|
|
21
|
+
for (const attr of iframe.getAttributeNames()){
|
|
22
|
+
const value = iframe.getAttribute(attr);
|
|
23
|
+
if (value !== null) {
|
|
24
|
+
attributes[attr] = value;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return attributes;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Checks if text is a Flourish, Datawrapper, or similar embed URL
|
|
31
|
+
*/ function isEmbedUrl(text) {
|
|
32
|
+
const embedPatterns = [
|
|
33
|
+
/flourish\.studio/i,
|
|
34
|
+
/datawrapper\.dwcdn\.net/i,
|
|
35
|
+
/infogram\.com/i,
|
|
36
|
+
/public\.tableau\.com/i,
|
|
37
|
+
/plotly\.com/i,
|
|
38
|
+
/app\.powerbi\.com/i
|
|
39
|
+
];
|
|
40
|
+
return embedPatterns.some((pattern)=>pattern.test(text));
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Parses HTML table string and converts it to TipTap table JSON format
|
|
44
|
+
*/ function parseHtmlTableToJson(html) {
|
|
45
|
+
const parser = new DOMParser();
|
|
46
|
+
const doc = parser.parseFromString(html, 'text/html');
|
|
47
|
+
const table = doc.querySelector('table');
|
|
48
|
+
if (!table) return null;
|
|
49
|
+
const rows = [];
|
|
50
|
+
const tableRows = table.querySelectorAll('tr');
|
|
51
|
+
tableRows.forEach((tr, rowIndex)=>{
|
|
52
|
+
const cells = [];
|
|
53
|
+
const tableCells = tr.querySelectorAll('td, th');
|
|
54
|
+
tableCells.forEach((cell)=>{
|
|
55
|
+
const isHeader = cell.tagName.toLowerCase() === 'th';
|
|
56
|
+
const cellText = cell.textContent?.trim() || '';
|
|
57
|
+
// Get colspan and rowspan attributes
|
|
58
|
+
const colspan = parseInt(cell.getAttribute('colspan') || '1', 10);
|
|
59
|
+
const rowspan = parseInt(cell.getAttribute('rowspan') || '1', 10);
|
|
60
|
+
const cellNode = {
|
|
61
|
+
type: isHeader ? 'tableHeader' : 'tableCell',
|
|
62
|
+
content: [
|
|
63
|
+
{
|
|
64
|
+
type: 'paragraph',
|
|
65
|
+
content: cellText ? [
|
|
66
|
+
{
|
|
67
|
+
type: 'text',
|
|
68
|
+
text: cellText
|
|
69
|
+
}
|
|
70
|
+
] : []
|
|
71
|
+
}
|
|
72
|
+
]
|
|
73
|
+
};
|
|
74
|
+
// Add attrs if colspan or rowspan is not 1
|
|
75
|
+
if (colspan !== 1 || rowspan !== 1) {
|
|
76
|
+
cellNode.attrs = {
|
|
77
|
+
colspan,
|
|
78
|
+
rowspan
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
cells.push(cellNode);
|
|
82
|
+
});
|
|
83
|
+
if (cells.length > 0) {
|
|
84
|
+
rows.push({
|
|
85
|
+
type: 'tableRow',
|
|
86
|
+
content: cells
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
if (rows.length === 0) return null;
|
|
91
|
+
return {
|
|
92
|
+
type: 'table',
|
|
93
|
+
content: rows
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
export const PasteHandler = Extension.create({
|
|
97
|
+
name: 'pasteHandler',
|
|
98
|
+
addProseMirrorPlugins () {
|
|
99
|
+
const { editor } = this;
|
|
100
|
+
return [
|
|
101
|
+
new Plugin({
|
|
102
|
+
key: new PluginKey('pasteHandler'),
|
|
103
|
+
props: {
|
|
104
|
+
handlePaste: (view, event, slice)=>{
|
|
105
|
+
const clipboardData = event.clipboardData;
|
|
106
|
+
if (!clipboardData) return false;
|
|
107
|
+
const html = clipboardData.getData('text/html');
|
|
108
|
+
const text = clipboardData.getData('text/plain');
|
|
109
|
+
// Handle iframe paste (from HTML)
|
|
110
|
+
if (html && containsIframe(html)) {
|
|
111
|
+
const attrs = extractIframeAttributes(html);
|
|
112
|
+
if (attrs && attrs.src) {
|
|
113
|
+
event.preventDefault();
|
|
114
|
+
editor.chain().focus().setIframe({
|
|
115
|
+
src: attrs.src
|
|
116
|
+
}).run();
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
// Handle iframe embed code paste (as plain text containing <iframe>)
|
|
121
|
+
if (text && containsIframe(text)) {
|
|
122
|
+
const attrs = extractIframeAttributes(text);
|
|
123
|
+
if (attrs && attrs.src) {
|
|
124
|
+
event.preventDefault();
|
|
125
|
+
editor.chain().focus().setIframe({
|
|
126
|
+
src: attrs.src
|
|
127
|
+
}).run();
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// Handle embed URL paste (Flourish, Datawrapper, etc.)
|
|
132
|
+
if (text && isEmbedUrl(text) && !html) {
|
|
133
|
+
event.preventDefault();
|
|
134
|
+
editor.chain().focus().setIframe({
|
|
135
|
+
src: text.trim()
|
|
136
|
+
}).run();
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
// Handle plain text that contains HTML table markup (copy-pasted HTML code as text)
|
|
140
|
+
// Check this FIRST before checking html, because pasting HTML code as text
|
|
141
|
+
// will have the raw tags in text but might also have html content
|
|
142
|
+
if (text && containsTable(text)) {
|
|
143
|
+
// Check if the text itself contains raw HTML tags (not rendered)
|
|
144
|
+
// This happens when someone copies HTML source code
|
|
145
|
+
const hasRawHtmlTags = /<\/?(?:table|tr|td|th|thead|tbody)[\s>]/i.test(text);
|
|
146
|
+
if (hasRawHtmlTags) {
|
|
147
|
+
event.preventDefault();
|
|
148
|
+
try {
|
|
149
|
+
const tableJson = parseHtmlTableToJson(text);
|
|
150
|
+
if (tableJson) {
|
|
151
|
+
editor.chain().focus().insertContent(tableJson).run();
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
154
|
+
} catch (error) {
|
|
155
|
+
console.error('Error parsing table from text:', error);
|
|
156
|
+
}
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
// Handle HTML table paste (from clipboard HTML - e.g., copying from a webpage)
|
|
161
|
+
if (html && containsTable(html)) {
|
|
162
|
+
event.preventDefault();
|
|
163
|
+
try {
|
|
164
|
+
const tableJson = parseHtmlTableToJson(html);
|
|
165
|
+
if (tableJson) {
|
|
166
|
+
editor.chain().focus().insertContent(tableJson).run();
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
} catch (error) {
|
|
170
|
+
console.error('Error parsing table from HTML:', error);
|
|
171
|
+
}
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
// Handle plain text table paste (tab-separated or similar)
|
|
175
|
+
if (text && !html && isTabularData(text)) {
|
|
176
|
+
event.preventDefault();
|
|
177
|
+
const tableJson = convertTextToTable(text);
|
|
178
|
+
if (tableJson) {
|
|
179
|
+
editor.chain().focus().insertContent(tableJson).run();
|
|
180
|
+
return true;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
})
|
|
187
|
+
];
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
/**
|
|
191
|
+
* Checks if text appears to be tabular data (tab or comma separated)
|
|
192
|
+
*/ function isTabularData(text) {
|
|
193
|
+
const lines = text.trim().split('\n');
|
|
194
|
+
if (lines.length < 2) return false;
|
|
195
|
+
// Check if lines have consistent tab or comma separators
|
|
196
|
+
const firstLineTabs = (lines[0].match(/\t/g) || []).length;
|
|
197
|
+
const firstLineCommas = (lines[0].match(/,/g) || []).length;
|
|
198
|
+
if (firstLineTabs === 0 && firstLineCommas === 0) return false;
|
|
199
|
+
// Check consistency across lines
|
|
200
|
+
const separator = firstLineTabs > 0 ? '\t' : ',';
|
|
201
|
+
const expectedColumns = lines[0].split(separator).length;
|
|
202
|
+
return lines.every((line)=>{
|
|
203
|
+
const columns = line.split(separator).length;
|
|
204
|
+
return columns === expectedColumns || columns === expectedColumns - 1;
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Converts tab/comma separated text to table JSON
|
|
209
|
+
*/ function convertTextToTable(text) {
|
|
210
|
+
const lines = text.trim().split('\n');
|
|
211
|
+
if (lines.length < 1) return null;
|
|
212
|
+
const firstLineTabs = (lines[0].match(/\t/g) || []).length;
|
|
213
|
+
const separator = firstLineTabs > 0 ? '\t' : ',';
|
|
214
|
+
const rows = lines.map((line, rowIndex)=>{
|
|
215
|
+
const cells = line.split(separator);
|
|
216
|
+
const cellType = rowIndex === 0 ? 'tableHeader' : 'tableCell';
|
|
217
|
+
return {
|
|
218
|
+
type: 'tableRow',
|
|
219
|
+
content: cells.map((cellText)=>({
|
|
220
|
+
type: cellType,
|
|
221
|
+
content: [
|
|
222
|
+
{
|
|
223
|
+
type: 'paragraph',
|
|
224
|
+
content: cellText.trim() ? [
|
|
225
|
+
{
|
|
226
|
+
type: 'text',
|
|
227
|
+
text: cellText.trim()
|
|
228
|
+
}
|
|
229
|
+
] : []
|
|
230
|
+
}
|
|
231
|
+
]
|
|
232
|
+
}))
|
|
233
|
+
};
|
|
234
|
+
});
|
|
235
|
+
return {
|
|
236
|
+
type: 'table',
|
|
237
|
+
content: rows
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
export default PasteHandler;
|
|
241
|
+
|
|
242
|
+
//# sourceMappingURL=PasteHandler.js.map
|