@lowdefy/blocks-tiptap 0.0.0-experimental-20260428103147
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/LICENSE +201 -0
- package/README.md +1 -0
- package/dist/blocks/TiptapInput/TiptapInput.js +170 -0
- package/dist/blocks/TiptapInput/e2e.js +25 -0
- package/dist/blocks/TiptapInput/meta.js +212 -0
- package/dist/blocks/TiptapInput/style.module.css +60 -0
- package/dist/blocks/TiptapInput/useTiptapState.js +104 -0
- package/dist/blocks/TiptapMentionInput/MentionList.js +69 -0
- package/dist/blocks/TiptapMentionInput/TiptapMentionInput.js +238 -0
- package/dist/blocks/TiptapMentionInput/e2e.js +25 -0
- package/dist/blocks/TiptapMentionInput/meta.js +231 -0
- package/dist/blocks/TiptapMentionInput/style.module.css +96 -0
- package/dist/blocks/TiptapMentionInput/suggestion.js +82 -0
- package/dist/blocks/TiptapMentionInput/useTiptapMentionState.js +108 -0
- package/dist/blocks/utils/PopoverMenu.js +82 -0
- package/dist/blocks/utils/buildExtensions.js +127 -0
- package/dist/blocks/utils/computeHeightStyle.js +57 -0
- package/dist/blocks/utils/s3FileUpload.js +51 -0
- package/dist/blocks/utils/statusClass.js +24 -0
- package/dist/blocks/utils/style.module.css +128 -0
- package/dist/blocks.js +16 -0
- package/dist/e2e.js +16 -0
- package/dist/metas.js +16 -0
- package/dist/types.js +17 -0
- package/package.json +87 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
@import '../utils/style.module.css';
|
|
2
|
+
|
|
3
|
+
:global(.tiptap-mention) {
|
|
4
|
+
box-decoration-break: clone;
|
|
5
|
+
font-weight: 600;
|
|
6
|
+
color: var(--ant-color-link);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
:global(.tiptap-mention-items) {
|
|
10
|
+
background: var(--ant-color-bg-container);
|
|
11
|
+
border-radius: var(--ant-border-radius, 8px);
|
|
12
|
+
box-shadow:
|
|
13
|
+
0 0 0 1px rgba(0, 0, 0, 0.05),
|
|
14
|
+
0px 10px 20px rgba(0, 0, 0, 0.1);
|
|
15
|
+
color: var(--ant-color-text);
|
|
16
|
+
font-size: var(--ant-font-size);
|
|
17
|
+
overflow: hidden;
|
|
18
|
+
padding: 0.2rem;
|
|
19
|
+
position: relative;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
:global(.tiptap-mention-item) {
|
|
23
|
+
background: transparent;
|
|
24
|
+
border: 1px solid transparent;
|
|
25
|
+
border-radius: var(--ant-border-radius, 8px);
|
|
26
|
+
display: block;
|
|
27
|
+
margin: 0;
|
|
28
|
+
padding: 0.2rem 0.4rem;
|
|
29
|
+
text-align: left;
|
|
30
|
+
line-height: normal;
|
|
31
|
+
width: 100%;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
:global(.tiptap-mention-item.is-selected) {
|
|
35
|
+
border-color: var(--ant-color-primary);
|
|
36
|
+
background: var(--ant-control-item-bg-hover);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
:global(.tableWrapper),
|
|
40
|
+
:global(.tiptap-table) {
|
|
41
|
+
display: block;
|
|
42
|
+
overflow-x: auto;
|
|
43
|
+
max-width: 80%;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
:global(.tableWrapper) :global(table),
|
|
47
|
+
:global(.tiptap-table) :global(table) {
|
|
48
|
+
border-collapse: collapse;
|
|
49
|
+
width: 100%;
|
|
50
|
+
min-width: 75px;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
:global(.tableWrapper) :global(colgroup) :global(col),
|
|
54
|
+
:global(.tiptap-table) :global(colgroup) :global(col) {
|
|
55
|
+
width: auto;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
:global(.tableWrapper) :global(table),
|
|
59
|
+
:global(.tableWrapper) :global(th),
|
|
60
|
+
:global(.tableWrapper) :global(td),
|
|
61
|
+
:global(.tiptap-table) :global(table),
|
|
62
|
+
:global(.tiptap-table) :global(th),
|
|
63
|
+
:global(.tiptap-table) :global(td) {
|
|
64
|
+
border: 1px solid var(--ant-color-border);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
:global(.tableWrapper) :global(th),
|
|
68
|
+
:global(.tableWrapper) :global(td),
|
|
69
|
+
:global(.tiptap-table) :global(th),
|
|
70
|
+
:global(.tiptap-table) :global(td) {
|
|
71
|
+
padding: 2px;
|
|
72
|
+
text-align: left;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
:global(.tableWrapper) :global(th),
|
|
76
|
+
:global(.tiptap-table) :global(th) {
|
|
77
|
+
background-color: var(--ant-color-fill-tertiary);
|
|
78
|
+
font-weight: bold;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
:global(.tableWrapper) :global(tr):nth-child(even),
|
|
82
|
+
:global(.tiptap-table) :global(tr):nth-child(even) {
|
|
83
|
+
background-color: var(--ant-color-fill-quaternary);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
:global(.tableWrapper) :global(tr):hover,
|
|
87
|
+
:global(.tiptap-table) :global(tr):hover {
|
|
88
|
+
background-color: var(--ant-color-primary-bg);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
:global(.tableWrapper) :global(td) :global(p),
|
|
92
|
+
:global(.tableWrapper) :global(th) :global(p),
|
|
93
|
+
:global(.tiptap-table) :global(td) :global(p),
|
|
94
|
+
:global(.tiptap-table) :global(th) :global(p) {
|
|
95
|
+
margin: 0;
|
|
96
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/ import { ReactRenderer } from '@tiptap/react';
|
|
16
|
+
import { type } from '@lowdefy/helpers';
|
|
17
|
+
import tippy from 'tippy.js';
|
|
18
|
+
import MentionList from './MentionList.js';
|
|
19
|
+
function suggestion({ methods, char = '@', allowSpaces = true }) {
|
|
20
|
+
return {
|
|
21
|
+
// `char` must be set explicitly: Mention.configure does a shallow merge
|
|
22
|
+
// on its options.suggestion, replacing the extension's default `@`.
|
|
23
|
+
char,
|
|
24
|
+
allowSpaces,
|
|
25
|
+
items: ({ query, editor })=>{
|
|
26
|
+
const itemsList = editor.options.editorProps.items ?? [];
|
|
27
|
+
return itemsList.filter((item)=>{
|
|
28
|
+
if (type.isString(item)) {
|
|
29
|
+
return item.toLowerCase().includes(query.toLowerCase());
|
|
30
|
+
}
|
|
31
|
+
if (type.isString(item?.label)) {
|
|
32
|
+
return item.label.toLowerCase().includes(query.toLowerCase());
|
|
33
|
+
}
|
|
34
|
+
return false;
|
|
35
|
+
}).slice(0, 5);
|
|
36
|
+
},
|
|
37
|
+
render: ()=>{
|
|
38
|
+
let component;
|
|
39
|
+
let popup;
|
|
40
|
+
return {
|
|
41
|
+
onStart: (props)=>{
|
|
42
|
+
component = new ReactRenderer(MentionList, {
|
|
43
|
+
props: {
|
|
44
|
+
...props,
|
|
45
|
+
methods
|
|
46
|
+
},
|
|
47
|
+
editor: props.editor
|
|
48
|
+
});
|
|
49
|
+
if (!props.clientRect) return;
|
|
50
|
+
popup = tippy('body', {
|
|
51
|
+
getReferenceClientRect: props.clientRect,
|
|
52
|
+
appendTo: ()=>document.body,
|
|
53
|
+
content: component.element,
|
|
54
|
+
showOnCreate: true,
|
|
55
|
+
interactive: true,
|
|
56
|
+
trigger: 'manual',
|
|
57
|
+
placement: 'bottom-start'
|
|
58
|
+
});
|
|
59
|
+
},
|
|
60
|
+
onUpdate (props) {
|
|
61
|
+
component.updateProps(props);
|
|
62
|
+
if (!props.clientRect) return;
|
|
63
|
+
popup[0].setProps({
|
|
64
|
+
getReferenceClientRect: props.clientRect
|
|
65
|
+
});
|
|
66
|
+
},
|
|
67
|
+
onKeyDown (props) {
|
|
68
|
+
if (props.event.key === 'Escape') {
|
|
69
|
+
popup[0].hide();
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
return component.ref?.onKeyDown(props);
|
|
73
|
+
},
|
|
74
|
+
onExit () {
|
|
75
|
+
popup[0].destroy();
|
|
76
|
+
component.destroy();
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
export default suggestion;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/ import { useRef } from 'react';
|
|
16
|
+
import TurndownService from 'turndown';
|
|
17
|
+
import s3FileUpload from '../utils/s3FileUpload.js';
|
|
18
|
+
// Ref-tracked controller for the mention editor's value. Mirrors TiptapInput's
|
|
19
|
+
// useTiptapState but also extracts mentions from the document.
|
|
20
|
+
function useTiptapMentionState({ value, methods }) {
|
|
21
|
+
const valueRef = useRef(value);
|
|
22
|
+
valueRef.current = value;
|
|
23
|
+
const turndownService = new TurndownService();
|
|
24
|
+
turndownService.addRule('encodeImgUrl', {
|
|
25
|
+
filter: 'img',
|
|
26
|
+
replacement: (content, node)=>{
|
|
27
|
+
const src = node.getAttribute('src');
|
|
28
|
+
return `})`;
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
const emit = (editor, appendFile)=>{
|
|
32
|
+
const html = editor.getHTML();
|
|
33
|
+
const markdown = turndownService.turndown(html);
|
|
34
|
+
const json = editor.getJSON();
|
|
35
|
+
const text = editor.getText().trim() === '' ? null : editor.getText();
|
|
36
|
+
const urls = (json.content ?? []).filter((c)=>c.type === 'image').map((c)=>c.attrs.src);
|
|
37
|
+
const base = valueRef.current?.fileList ?? [];
|
|
38
|
+
const next = appendFile ? [
|
|
39
|
+
...base,
|
|
40
|
+
appendFile
|
|
41
|
+
] : base;
|
|
42
|
+
const fileList = next.filter((f)=>urls.includes(f.url));
|
|
43
|
+
const mentionsMap = new Map();
|
|
44
|
+
(json.content ?? []).filter((c)=>c.type === 'paragraph').forEach((c)=>{
|
|
45
|
+
c.content?.forEach((cc)=>{
|
|
46
|
+
if (cc.type === 'mention') {
|
|
47
|
+
mentionsMap.set(JSON.stringify(cc.attrs.id.value), cc.attrs.id.value);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
const mentions = Array.from(mentionsMap.values());
|
|
52
|
+
methods.setValue({
|
|
53
|
+
fileList,
|
|
54
|
+
html,
|
|
55
|
+
mentions,
|
|
56
|
+
text,
|
|
57
|
+
markdown
|
|
58
|
+
});
|
|
59
|
+
};
|
|
60
|
+
const insertImage = async (editor, file, pos)=>{
|
|
61
|
+
const url = await s3FileUpload({
|
|
62
|
+
file,
|
|
63
|
+
methods
|
|
64
|
+
});
|
|
65
|
+
editor.chain().insertContentAt(pos, [
|
|
66
|
+
{
|
|
67
|
+
type: 'image',
|
|
68
|
+
attrs: {
|
|
69
|
+
src: url
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
type: 'paragraph',
|
|
74
|
+
content: [
|
|
75
|
+
{
|
|
76
|
+
type: 'text',
|
|
77
|
+
marks: [
|
|
78
|
+
{
|
|
79
|
+
type: 'link',
|
|
80
|
+
attrs: {
|
|
81
|
+
href: url,
|
|
82
|
+
target: '_blank'
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
],
|
|
86
|
+
text: 'Enlarge Image'
|
|
87
|
+
}
|
|
88
|
+
]
|
|
89
|
+
}
|
|
90
|
+
]).focus().run();
|
|
91
|
+
const fileObj = {
|
|
92
|
+
bucket: file.bucket,
|
|
93
|
+
key: file.key,
|
|
94
|
+
lastModified: file.lastModified,
|
|
95
|
+
name: file.name,
|
|
96
|
+
size: file.size,
|
|
97
|
+
status: file.status,
|
|
98
|
+
type: file.type,
|
|
99
|
+
url
|
|
100
|
+
};
|
|
101
|
+
emit(editor, fileObj);
|
|
102
|
+
};
|
|
103
|
+
return {
|
|
104
|
+
emit,
|
|
105
|
+
insertImage
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
export default useTiptapMentionState;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/ import React from 'react';
|
|
16
|
+
import { BubbleMenu } from '@tiptap/react';
|
|
17
|
+
import { AiOutlineBold, AiOutlineItalic, AiOutlineStrikethrough, AiOutlineHighlight } from 'react-icons/ai';
|
|
18
|
+
import { isTextSelection } from '@tiptap/core';
|
|
19
|
+
const HIGHLIGHT_SWATCHES = [
|
|
20
|
+
{
|
|
21
|
+
color: 'rgba(170, 255, 0, 1)',
|
|
22
|
+
fill: 'rgba(170, 255, 0, 0.5)'
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
color: 'rgba(255, 170, 0, 1)',
|
|
26
|
+
fill: 'rgba(255, 170, 0, 0.5)'
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
color: 'rgba(255, 0, 170, 1)',
|
|
30
|
+
fill: 'rgba(255, 0, 170, 0.5)'
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
color: 'rgba(170, 0, 255, 1)',
|
|
34
|
+
fill: 'rgba(170, 0, 255, 0.5)'
|
|
35
|
+
}
|
|
36
|
+
];
|
|
37
|
+
function hasExt(editor, name) {
|
|
38
|
+
return editor.extensionManager.extensions.some((ext)=>ext.name === name);
|
|
39
|
+
}
|
|
40
|
+
const PopoverMenu = ({ editor })=>{
|
|
41
|
+
const showBold = hasExt(editor, 'bold');
|
|
42
|
+
const showItalic = hasExt(editor, 'italic');
|
|
43
|
+
const showStrike = hasExt(editor, 'strike');
|
|
44
|
+
const showHighlight = hasExt(editor, 'highlight');
|
|
45
|
+
if (!showBold && !showItalic && !showStrike && !showHighlight) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
return /*#__PURE__*/ React.createElement(BubbleMenu, {
|
|
49
|
+
className: "tiptap-popover",
|
|
50
|
+
editor: editor,
|
|
51
|
+
shouldShow: ({ editor, view, state, from, to })=>{
|
|
52
|
+
if (editor.isActive('image')) return false;
|
|
53
|
+
const { doc, selection } = state;
|
|
54
|
+
const { empty } = selection;
|
|
55
|
+
const isEmptyTextBlock = !doc.textBetween(from, to).length && isTextSelection(state.selection);
|
|
56
|
+
const hasEditorFocus = view.hasFocus();
|
|
57
|
+
if (!hasEditorFocus || empty || isEmptyTextBlock || !editor.isEditable) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
}, showBold && /*#__PURE__*/ React.createElement(AiOutlineBold, {
|
|
63
|
+
className: "tiptap-icon",
|
|
64
|
+
onClick: ()=>editor.chain().focus().toggleBold().run()
|
|
65
|
+
}), showItalic && /*#__PURE__*/ React.createElement(AiOutlineItalic, {
|
|
66
|
+
className: "tiptap-icon",
|
|
67
|
+
onClick: ()=>editor.chain().focus().toggleItalic().run()
|
|
68
|
+
}), showStrike && /*#__PURE__*/ React.createElement(AiOutlineStrikethrough, {
|
|
69
|
+
className: "tiptap-icon",
|
|
70
|
+
onClick: ()=>editor.chain().focus().toggleStrike().run()
|
|
71
|
+
}), showHighlight && HIGHLIGHT_SWATCHES.map(({ color, fill })=>/*#__PURE__*/ React.createElement(AiOutlineHighlight, {
|
|
72
|
+
key: color,
|
|
73
|
+
className: "tiptap-icon",
|
|
74
|
+
style: {
|
|
75
|
+
color
|
|
76
|
+
},
|
|
77
|
+
onClick: ()=>editor.chain().focus().toggleHighlight({
|
|
78
|
+
color: fill
|
|
79
|
+
}).run()
|
|
80
|
+
})));
|
|
81
|
+
};
|
|
82
|
+
export default PopoverMenu;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/ import FileHandler from '@tiptap/extension-file-handler';
|
|
16
|
+
import Highlight from '@tiptap/extension-highlight';
|
|
17
|
+
import Image from '@tiptap/extension-image';
|
|
18
|
+
import Placeholder from '@tiptap/extension-placeholder';
|
|
19
|
+
import StarterKit from '@tiptap/starter-kit';
|
|
20
|
+
import LinkExtension from '@tiptap/extension-link';
|
|
21
|
+
import Table from '@tiptap/extension-table';
|
|
22
|
+
import TableCell from '@tiptap/extension-table-cell';
|
|
23
|
+
import TableHeader from '@tiptap/extension-table-header';
|
|
24
|
+
import TableRow from '@tiptap/extension-table-row';
|
|
25
|
+
import { type } from '@lowdefy/helpers';
|
|
26
|
+
const DEFAULT_IMAGE_MIME_TYPES = [
|
|
27
|
+
'image/jpeg',
|
|
28
|
+
'image/png',
|
|
29
|
+
'image/gif',
|
|
30
|
+
'image/webp'
|
|
31
|
+
];
|
|
32
|
+
const DEFAULTS = {
|
|
33
|
+
image: {
|
|
34
|
+
disabled: false,
|
|
35
|
+
maxWidth: '80%',
|
|
36
|
+
zoom: 0.5
|
|
37
|
+
},
|
|
38
|
+
table: {
|
|
39
|
+
disabled: false,
|
|
40
|
+
resizable: true
|
|
41
|
+
},
|
|
42
|
+
link: {
|
|
43
|
+
disabled: false,
|
|
44
|
+
openOnClick: true,
|
|
45
|
+
autolink: true,
|
|
46
|
+
linkOnPaste: true,
|
|
47
|
+
defaultProtocol: 'https'
|
|
48
|
+
},
|
|
49
|
+
highlight: {
|
|
50
|
+
disabled: false,
|
|
51
|
+
multicolor: true
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
function merge(defaults, overrides) {
|
|
55
|
+
if (!type.isObject(overrides)) return defaults;
|
|
56
|
+
return {
|
|
57
|
+
...defaults,
|
|
58
|
+
...overrides
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function buildExtensions({ properties, insertImage, mentionExtension, uploadEnabled }) {
|
|
62
|
+
const image = merge(DEFAULTS.image, properties.image);
|
|
63
|
+
const table = merge(DEFAULTS.table, properties.table);
|
|
64
|
+
const link = merge(DEFAULTS.link, properties.link);
|
|
65
|
+
const highlight = merge(DEFAULTS.highlight, properties.highlight);
|
|
66
|
+
const starterKitOptions = type.isObject(properties.starterKit) ? properties.starterKit : {};
|
|
67
|
+
const allowedMimeTypes = type.isArray(properties.allowedMimeTypes) ? properties.allowedMimeTypes : DEFAULT_IMAGE_MIME_TYPES;
|
|
68
|
+
const extensions = [
|
|
69
|
+
StarterKit.configure(starterKitOptions)
|
|
70
|
+
];
|
|
71
|
+
if (!table.disabled) {
|
|
72
|
+
extensions.push(Table.configure({
|
|
73
|
+
HTMLAttributes: {
|
|
74
|
+
class: 'tiptap-table'
|
|
75
|
+
},
|
|
76
|
+
resizable: table.resizable
|
|
77
|
+
}), TableRow, TableHeader, TableCell);
|
|
78
|
+
}
|
|
79
|
+
if (!image.disabled) {
|
|
80
|
+
extensions.push(Image.configure({
|
|
81
|
+
HTMLAttributes: {
|
|
82
|
+
style: `max-width: ${image.maxWidth}; display: block; zoom: ${image.zoom};`
|
|
83
|
+
}
|
|
84
|
+
}));
|
|
85
|
+
}
|
|
86
|
+
extensions.push(Placeholder.configure({
|
|
87
|
+
placeholder: ()=>properties.placeholder ?? '',
|
|
88
|
+
showOnlyWhenEditable: false,
|
|
89
|
+
considerAnyAsEmpty: true
|
|
90
|
+
}));
|
|
91
|
+
if (!highlight.disabled) {
|
|
92
|
+
extensions.push(Highlight.configure({
|
|
93
|
+
multicolor: highlight.multicolor,
|
|
94
|
+
HTMLAttributes: {
|
|
95
|
+
style: 'padding: 0;'
|
|
96
|
+
}
|
|
97
|
+
}));
|
|
98
|
+
}
|
|
99
|
+
if (!link.disabled) {
|
|
100
|
+
extensions.push(LinkExtension.configure({
|
|
101
|
+
autolink: link.autolink,
|
|
102
|
+
linkOnPaste: link.linkOnPaste,
|
|
103
|
+
openOnClick: link.openOnClick,
|
|
104
|
+
defaultProtocol: link.defaultProtocol
|
|
105
|
+
}));
|
|
106
|
+
}
|
|
107
|
+
if (mentionExtension) {
|
|
108
|
+
extensions.push(mentionExtension);
|
|
109
|
+
}
|
|
110
|
+
if (uploadEnabled) {
|
|
111
|
+
extensions.push(FileHandler.configure({
|
|
112
|
+
onDrop: (editor, files, pos)=>{
|
|
113
|
+
files.forEach((file)=>insertImage(editor, file, pos));
|
|
114
|
+
},
|
|
115
|
+
onPaste: (editor, files, htmlContent)=>{
|
|
116
|
+
if (htmlContent) return false;
|
|
117
|
+
files.forEach((file)=>{
|
|
118
|
+
const pos = editor.state.selection.anchor;
|
|
119
|
+
insertImage(editor, file, pos);
|
|
120
|
+
});
|
|
121
|
+
},
|
|
122
|
+
allowedMimeTypes
|
|
123
|
+
}));
|
|
124
|
+
}
|
|
125
|
+
return extensions;
|
|
126
|
+
}
|
|
127
|
+
export default buildExtensions;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/ import { type } from '@lowdefy/helpers';
|
|
16
|
+
// Matches ant-design Input's line-height ratio at the default font size,
|
|
17
|
+
// plus the 8px top/bottom padding on the ProseMirror editable area.
|
|
18
|
+
const LINE_HEIGHT = '1.5714em';
|
|
19
|
+
const VERTICAL_PADDING = 16;
|
|
20
|
+
function calcRows(n) {
|
|
21
|
+
return `calc(${n} * ${LINE_HEIGHT} + ${VERTICAL_PADDING}px)`;
|
|
22
|
+
}
|
|
23
|
+
const DEFAULT_MIN_ROWS = 5;
|
|
24
|
+
// Translate TextArea-style rows/autoSize props into React inline CSS applied
|
|
25
|
+
// to the EditorContent wrapper. The wrapper becomes the scroll container;
|
|
26
|
+
// ProseMirror's editable inside grows naturally.
|
|
27
|
+
function computeHeightStyle({ rows, autoSize }) {
|
|
28
|
+
if (type.isInt(rows) && rows >= 1) {
|
|
29
|
+
const h = calcRows(rows);
|
|
30
|
+
return {
|
|
31
|
+
minHeight: h,
|
|
32
|
+
maxHeight: h,
|
|
33
|
+
overflowY: 'auto'
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
if (autoSize === true) {
|
|
37
|
+
return {
|
|
38
|
+
minHeight: calcRows(1),
|
|
39
|
+
maxHeight: 'none'
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
if (type.isObject(autoSize)) {
|
|
43
|
+
const style = {};
|
|
44
|
+
if (type.isInt(autoSize.minRows) && autoSize.minRows >= 1) {
|
|
45
|
+
style.minHeight = calcRows(autoSize.minRows);
|
|
46
|
+
}
|
|
47
|
+
if (type.isInt(autoSize.maxRows) && autoSize.maxRows >= 1) {
|
|
48
|
+
style.maxHeight = calcRows(autoSize.maxRows);
|
|
49
|
+
style.overflowY = 'auto';
|
|
50
|
+
}
|
|
51
|
+
if (Object.keys(style).length > 0) return style;
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
minHeight: calcRows(DEFAULT_MIN_ROWS)
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
export default computeHeightStyle;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/ async function s3FileUpload({ file, methods }) {
|
|
16
|
+
if (!file) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const { lastModified, name, size, type, uid } = file;
|
|
20
|
+
const s3PostPolicyResponse = await methods.triggerEvent({
|
|
21
|
+
name: '__getS3PostPolicy',
|
|
22
|
+
event: {
|
|
23
|
+
file: {
|
|
24
|
+
name,
|
|
25
|
+
lastModified,
|
|
26
|
+
size,
|
|
27
|
+
type,
|
|
28
|
+
uid
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
if (s3PostPolicyResponse.success !== true) {
|
|
33
|
+
throw new Error('S3 post policy request error.');
|
|
34
|
+
}
|
|
35
|
+
const { url, fields } = s3PostPolicyResponse.responses.__getS3PostPolicy.response[0];
|
|
36
|
+
const { bucket, key } = fields;
|
|
37
|
+
file.bucket = bucket;
|
|
38
|
+
file.key = key;
|
|
39
|
+
const formData = new FormData();
|
|
40
|
+
Object.keys(fields).forEach((field)=>{
|
|
41
|
+
formData.append(field, fields[field]);
|
|
42
|
+
});
|
|
43
|
+
formData.append('file', file);
|
|
44
|
+
await fetch(url, {
|
|
45
|
+
method: 'POST',
|
|
46
|
+
body: formData
|
|
47
|
+
});
|
|
48
|
+
file.url = `${url}/${key}`;
|
|
49
|
+
return file.url;
|
|
50
|
+
}
|
|
51
|
+
export default s3FileUpload;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/ // Map validation.status → wrapper class. Matches the visual behavior of
|
|
16
|
+
// passing status="error" | "warning" to an antd <Input />, but rendered
|
|
17
|
+
// with our own CSS (see utils/style.module.css) because antd v6 uses
|
|
18
|
+
// CSS-in-JS, so the .ant-input-status-* classes are not global.
|
|
19
|
+
function statusClass(status) {
|
|
20
|
+
if (status === 'error') return 'tiptap-wrapper-error';
|
|
21
|
+
if (status === 'warning') return 'tiptap-wrapper-warning';
|
|
22
|
+
return '';
|
|
23
|
+
}
|
|
24
|
+
export default statusClass;
|