@lowdefy/blocks-tiptap 5.5.1 → 6.0.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.
- package/dist/blocks/TiptapInput/TiptapInput.js +27 -7
- package/dist/blocks/TiptapInput/gallery.yaml +210 -0
- package/dist/blocks/TiptapInput/meta.js +10 -2
- package/dist/blocks/TiptapInput/style.css +60 -0
- package/dist/blocks/TiptapInput/tests/TiptapInput.e2e.yaml +53 -0
- package/dist/blocks/TiptapInput/tests.yaml +65 -0
- package/dist/blocks/TiptapInput/useTiptapState.js +6 -5
- package/dist/blocks/TiptapMentionInput/TiptapMentionInput.js +91 -11
- package/dist/blocks/TiptapMentionInput/gallery.yaml +296 -0
- package/dist/blocks/TiptapMentionInput/meta.js +10 -2
- package/dist/blocks/TiptapMentionInput/{style.module.css → style.css} +39 -39
- package/dist/blocks/TiptapMentionInput/tests/TiptapMentionInput.e2e.yaml +140 -0
- package/dist/blocks/TiptapMentionInput/tests.yaml +90 -0
- package/dist/blocks/TiptapMentionInput/useTiptapMentionState.js +8 -5
- package/dist/blocks/utils/fileUpload.js +63 -0
- package/dist/blocks/utils/statusClass.js +1 -1
- package/dist/blocks/utils/{style.module.css → style.css} +56 -50
- package/package.json +11 -10
- package/dist/blocks/TiptapInput/style.module.css +0 -60
- package/dist/blocks/utils/s3FileUpload.js +0 -53
|
@@ -22,14 +22,17 @@ import buildExtensions from '../utils/buildExtensions.js';
|
|
|
22
22
|
import computeHeightStyle from '../utils/computeHeightStyle.js';
|
|
23
23
|
import statusClass from '../utils/statusClass.js';
|
|
24
24
|
import useTiptapState from './useTiptapState.js';
|
|
25
|
-
import './style.
|
|
25
|
+
import './style.css';
|
|
26
26
|
const TiptapInput = ({ blockId, components: { Icon, Link }, events, loading, methods, properties, required, validation, value })=>{
|
|
27
|
+
const uploadPolicyRequestId = properties.uploadPolicyRequestId ?? properties.s3PostPolicyRequestId;
|
|
28
|
+
const downloadPolicyRequestId = properties.downloadPolicyRequestId;
|
|
27
29
|
const { emit, insertImage } = useTiptapState({
|
|
28
30
|
value,
|
|
29
|
-
methods
|
|
31
|
+
methods,
|
|
32
|
+
hasDownloadRequest: !type.isNone(downloadPolicyRequestId)
|
|
30
33
|
});
|
|
31
34
|
const disabled = properties.disabled === true || loading;
|
|
32
|
-
const uploadEnabled = !type.isNone(
|
|
35
|
+
const uploadEnabled = !type.isNone(uploadPolicyRequestId);
|
|
33
36
|
const extensions = buildExtensions({
|
|
34
37
|
properties,
|
|
35
38
|
insertImage,
|
|
@@ -51,21 +54,38 @@ const TiptapInput = ({ blockId, components: { Icon, Link }, events, loading, met
|
|
|
51
54
|
});
|
|
52
55
|
}
|
|
53
56
|
});
|
|
54
|
-
// Register upload-policy
|
|
57
|
+
// Register upload-policy and download-policy events once (if configured).
|
|
55
58
|
useEffect(()=>{
|
|
56
59
|
if (!uploadEnabled) return;
|
|
60
|
+
if (!type.isNone(properties.s3PostPolicyRequestId)) {
|
|
61
|
+
console.warn('TiptapInput property "s3PostPolicyRequestId" is deprecated. Use "uploadPolicyRequestId" instead.');
|
|
62
|
+
}
|
|
57
63
|
methods.registerEvent({
|
|
58
|
-
name: '
|
|
64
|
+
name: '__getUploadPolicy',
|
|
59
65
|
actions: [
|
|
60
66
|
{
|
|
61
|
-
id: '
|
|
67
|
+
id: '__getUploadPolicy',
|
|
62
68
|
type: 'Request',
|
|
63
69
|
params: [
|
|
64
|
-
|
|
70
|
+
uploadPolicyRequestId
|
|
65
71
|
]
|
|
66
72
|
}
|
|
67
73
|
]
|
|
68
74
|
});
|
|
75
|
+
if (!type.isNone(downloadPolicyRequestId)) {
|
|
76
|
+
methods.registerEvent({
|
|
77
|
+
name: '__getDownloadPolicy',
|
|
78
|
+
actions: [
|
|
79
|
+
{
|
|
80
|
+
id: '__getDownloadPolicy',
|
|
81
|
+
type: 'Request',
|
|
82
|
+
params: [
|
|
83
|
+
downloadPolicyRequestId
|
|
84
|
+
]
|
|
85
|
+
}
|
|
86
|
+
]
|
|
87
|
+
});
|
|
88
|
+
}
|
|
69
89
|
}, []);
|
|
70
90
|
// Register methods as soon as the editor is available.
|
|
71
91
|
useEffect(()=>{
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# Copyright 2020-2026 Lowdefy, Inc
|
|
2
|
+
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
- title: Default
|
|
16
|
+
fullWidth: true
|
|
17
|
+
blocks:
|
|
18
|
+
- id: tiptap_default
|
|
19
|
+
type: TiptapInput
|
|
20
|
+
properties:
|
|
21
|
+
title: Notes
|
|
22
|
+
placeholder: 'Start typing here. Try **bold**, *italic*, or headings.'
|
|
23
|
+
|
|
24
|
+
- title: Pre-populated content
|
|
25
|
+
fullWidth: true
|
|
26
|
+
blocks:
|
|
27
|
+
- id: tiptap_prepopulated
|
|
28
|
+
type: TiptapInput
|
|
29
|
+
events:
|
|
30
|
+
onMount:
|
|
31
|
+
- id: tiptap_prepopulated_set
|
|
32
|
+
type: SetState
|
|
33
|
+
params:
|
|
34
|
+
tiptap_prepopulated:
|
|
35
|
+
html: >
|
|
36
|
+
<h2>What's new</h2>
|
|
37
|
+
<p>Highlights from this release:</p>
|
|
38
|
+
<ul>
|
|
39
|
+
<li>Rich-text editor built on <a href="https://tiptap.dev">TipTap</a></li>
|
|
40
|
+
<li>Drag-and-drop image uploads</li>
|
|
41
|
+
<li>Inline <strong>bold</strong>, <em>italic</em>, and <mark>highlights</mark></li>
|
|
42
|
+
</ul>
|
|
43
|
+
<blockquote><p>Pair it with a TextArea when you need a simple fallback.</p></blockquote>
|
|
44
|
+
properties:
|
|
45
|
+
title: Release notes
|
|
46
|
+
|
|
47
|
+
- title: Disabled (read-only)
|
|
48
|
+
fullWidth: true
|
|
49
|
+
blocks:
|
|
50
|
+
- id: tiptap_disabled
|
|
51
|
+
type: TiptapInput
|
|
52
|
+
events:
|
|
53
|
+
onMount:
|
|
54
|
+
- id: tiptap_disabled_set
|
|
55
|
+
type: SetState
|
|
56
|
+
params:
|
|
57
|
+
tiptap_disabled:
|
|
58
|
+
html: '<p>This editor is disabled and cannot be edited.</p>'
|
|
59
|
+
properties:
|
|
60
|
+
title: Read only
|
|
61
|
+
disabled: true
|
|
62
|
+
|
|
63
|
+
- title: Borderless
|
|
64
|
+
fullWidth: true
|
|
65
|
+
blocks:
|
|
66
|
+
- id: tiptap_borderless
|
|
67
|
+
type: TiptapInput
|
|
68
|
+
properties:
|
|
69
|
+
title: Borderless editor
|
|
70
|
+
bordered: false
|
|
71
|
+
placeholder: 'No border, just content.'
|
|
72
|
+
|
|
73
|
+
- title: Fixed rows — rows = 2
|
|
74
|
+
fullWidth: true
|
|
75
|
+
blocks:
|
|
76
|
+
- id: tiptap_rows_2
|
|
77
|
+
type: TiptapInput
|
|
78
|
+
properties:
|
|
79
|
+
title: Short note
|
|
80
|
+
placeholder: 'Exactly 2 rows tall.'
|
|
81
|
+
rows: 2
|
|
82
|
+
|
|
83
|
+
- title: autoSize — minRows 2, maxRows 5
|
|
84
|
+
fullWidth: true
|
|
85
|
+
blocks:
|
|
86
|
+
- id: tiptap_autosize_range
|
|
87
|
+
type: TiptapInput
|
|
88
|
+
properties:
|
|
89
|
+
title: Grows between 2 and 5 rows
|
|
90
|
+
placeholder: 'Keep typing — scrolls past 5 rows.'
|
|
91
|
+
autoSize:
|
|
92
|
+
minRows: 2
|
|
93
|
+
maxRows: 5
|
|
94
|
+
|
|
95
|
+
- title: Validation — required, with error state
|
|
96
|
+
fullWidth: true
|
|
97
|
+
blocks:
|
|
98
|
+
- id: tiptap_validation
|
|
99
|
+
type: TiptapInput
|
|
100
|
+
required: true
|
|
101
|
+
properties:
|
|
102
|
+
title: Release notes (required)
|
|
103
|
+
placeholder: 'Type at least 10 characters, then click Validate.'
|
|
104
|
+
rows: 3
|
|
105
|
+
validate:
|
|
106
|
+
- status: error
|
|
107
|
+
message: 'Release notes must be at least 10 characters.'
|
|
108
|
+
pass:
|
|
109
|
+
_gte:
|
|
110
|
+
- _string.length:
|
|
111
|
+
_state: tiptap_validation.text
|
|
112
|
+
- 10
|
|
113
|
+
- id: tiptap_validation_button
|
|
114
|
+
type: Button
|
|
115
|
+
properties:
|
|
116
|
+
title: Validate
|
|
117
|
+
events:
|
|
118
|
+
onClick:
|
|
119
|
+
- id: tiptap_validation_run
|
|
120
|
+
type: Validate
|
|
121
|
+
params:
|
|
122
|
+
- tiptap_validation
|
|
123
|
+
|
|
124
|
+
- title: autoSize = true — grows with content
|
|
125
|
+
fullWidth: true
|
|
126
|
+
blocks:
|
|
127
|
+
- id: tiptap_autosize_true
|
|
128
|
+
type: TiptapInput
|
|
129
|
+
properties:
|
|
130
|
+
title: Unconstrained
|
|
131
|
+
placeholder: 'No height cap.'
|
|
132
|
+
autoSize: true
|
|
133
|
+
|
|
134
|
+
- title: Minimal — tables, images and highlights disabled
|
|
135
|
+
fullWidth: true
|
|
136
|
+
blocks:
|
|
137
|
+
- id: tiptap_minimal
|
|
138
|
+
type: TiptapInput
|
|
139
|
+
properties:
|
|
140
|
+
title: Plain note
|
|
141
|
+
placeholder: 'Bold/italic/lists only.'
|
|
142
|
+
image:
|
|
143
|
+
disabled: true
|
|
144
|
+
table:
|
|
145
|
+
disabled: true
|
|
146
|
+
highlight:
|
|
147
|
+
disabled: true
|
|
148
|
+
|
|
149
|
+
- title: StarterKit overrides — disable code block and blockquote
|
|
150
|
+
fullWidth: true
|
|
151
|
+
blocks:
|
|
152
|
+
- id: tiptap_starterkit
|
|
153
|
+
type: TiptapInput
|
|
154
|
+
properties:
|
|
155
|
+
title: Short-form
|
|
156
|
+
placeholder: 'No code block or blockquote.'
|
|
157
|
+
starterKit:
|
|
158
|
+
codeBlock: false
|
|
159
|
+
blockquote: false
|
|
160
|
+
horizontalRule: false
|
|
161
|
+
|
|
162
|
+
- title: With table (tables enabled by default)
|
|
163
|
+
fullWidth: true
|
|
164
|
+
blocks:
|
|
165
|
+
- id: tiptap_with_table
|
|
166
|
+
type: TiptapInput
|
|
167
|
+
events:
|
|
168
|
+
onMount:
|
|
169
|
+
- id: tiptap_with_table_set
|
|
170
|
+
type: SetState
|
|
171
|
+
params:
|
|
172
|
+
tiptap_with_table:
|
|
173
|
+
html: >
|
|
174
|
+
<h3>Weekly sync</h3>
|
|
175
|
+
<p>Schedule for today's meeting:</p>
|
|
176
|
+
<table>
|
|
177
|
+
<thead>
|
|
178
|
+
<tr><th>Time</th><th>Topic</th><th>Owner</th></tr>
|
|
179
|
+
</thead>
|
|
180
|
+
<tbody>
|
|
181
|
+
<tr><td>09:00</td><td>Kickoff</td><td>Alice</td></tr>
|
|
182
|
+
<tr><td>09:15</td><td>Review last week</td><td>Bob</td></tr>
|
|
183
|
+
<tr><td>09:45</td><td>This week's plan</td><td>Carol</td></tr>
|
|
184
|
+
<tr><td>10:00</td><td>Wrap-up</td><td>Alice</td></tr>
|
|
185
|
+
</tbody>
|
|
186
|
+
</table>
|
|
187
|
+
<p>Notes:</p>
|
|
188
|
+
<ul>
|
|
189
|
+
<li>Bring the latest metrics</li>
|
|
190
|
+
<li>Send follow-ups within 24h</li>
|
|
191
|
+
</ul>
|
|
192
|
+
properties:
|
|
193
|
+
title: Agenda
|
|
194
|
+
|
|
195
|
+
- title: Link configuration — open in place (not new tab)
|
|
196
|
+
fullWidth: true
|
|
197
|
+
blocks:
|
|
198
|
+
- id: tiptap_links
|
|
199
|
+
type: TiptapInput
|
|
200
|
+
events:
|
|
201
|
+
onMount:
|
|
202
|
+
- id: tiptap_links_set
|
|
203
|
+
type: SetState
|
|
204
|
+
params:
|
|
205
|
+
tiptap_links:
|
|
206
|
+
html: '<p>Visit <a href="https://lowdefy.com">lowdefy.com</a> or type a URL and press space.</p>'
|
|
207
|
+
properties:
|
|
208
|
+
title: Open links in place
|
|
209
|
+
link:
|
|
210
|
+
openOnClick: false
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
items: {
|
|
35
35
|
type: 'string'
|
|
36
36
|
},
|
|
37
|
-
description: 'Mime-types accepted by the drag/drop and paste file handler. Defaults to common image types (jpeg, png, gif, webp). Only used when
|
|
37
|
+
description: 'Mime-types accepted by the drag/drop and paste file handler. Defaults to common image types (jpeg, png, gif, webp). Only used when uploadPolicyRequestId is set.'
|
|
38
38
|
},
|
|
39
39
|
autoSize: {
|
|
40
40
|
oneOf: [
|
|
@@ -166,9 +166,17 @@
|
|
|
166
166
|
minimum: 1,
|
|
167
167
|
description: 'Fix the editor height to exactly this many rows. Takes precedence over autoSize.'
|
|
168
168
|
},
|
|
169
|
+
downloadPolicyRequestId: {
|
|
170
|
+
type: 'string',
|
|
171
|
+
description: 'Id of a download request (e.g. AwsS3PresignedGetObject) used to resolve the inserted image URL after upload. Inline images persist in saved content, so the request should set public: true (and the upload request the provider public-read acl) to return a stable, non-expiring URL. When unset, a legacy unsigned object URL is constructed from the upload response (S3-shaped, deprecated).'
|
|
172
|
+
},
|
|
169
173
|
s3PostPolicyRequestId: {
|
|
170
174
|
type: 'string',
|
|
171
|
-
description: '
|
|
175
|
+
description: 'Deprecated — use uploadPolicyRequestId instead.'
|
|
176
|
+
},
|
|
177
|
+
uploadPolicyRequestId: {
|
|
178
|
+
type: 'string',
|
|
179
|
+
description: 'Id of an upload-policy request (e.g. AwsS3PresignedPostPolicy, GcsSignedPostPolicy, AzureBlobUploadSas). When set, images dragged or pasted into the editor are uploaded via that request and inserted as <img> nodes. Leave unset to disable image uploads.'
|
|
172
180
|
},
|
|
173
181
|
size: {
|
|
174
182
|
type: 'string',
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
@import '../utils/style.css';
|
|
2
|
+
|
|
3
|
+
.tableWrapper,
|
|
4
|
+
.tiptap-table {
|
|
5
|
+
display: block;
|
|
6
|
+
overflow-x: auto;
|
|
7
|
+
max-width: 80%;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.tableWrapper table,
|
|
11
|
+
.tiptap-table table {
|
|
12
|
+
border-collapse: collapse;
|
|
13
|
+
width: 100%;
|
|
14
|
+
min-width: 75px;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.tableWrapper colgroup col,
|
|
18
|
+
.tiptap-table colgroup col {
|
|
19
|
+
width: auto;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.tableWrapper table,
|
|
23
|
+
.tableWrapper th,
|
|
24
|
+
.tableWrapper td,
|
|
25
|
+
.tiptap-table table,
|
|
26
|
+
.tiptap-table th,
|
|
27
|
+
.tiptap-table td {
|
|
28
|
+
border: 1px solid var(--ant-color-border);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.tableWrapper th,
|
|
32
|
+
.tableWrapper td,
|
|
33
|
+
.tiptap-table th,
|
|
34
|
+
.tiptap-table td {
|
|
35
|
+
padding: 2px;
|
|
36
|
+
text-align: left;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.tableWrapper th,
|
|
40
|
+
.tiptap-table th {
|
|
41
|
+
background-color: var(--ant-color-fill-tertiary);
|
|
42
|
+
font-weight: bold;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.tableWrapper tr:nth-child(even),
|
|
46
|
+
.tiptap-table tr:nth-child(even) {
|
|
47
|
+
background-color: var(--ant-color-fill-quaternary);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.tableWrapper tr:hover,
|
|
51
|
+
.tiptap-table tr:hover {
|
|
52
|
+
background-color: var(--ant-color-primary-bg);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.tableWrapper td p,
|
|
56
|
+
.tableWrapper th p,
|
|
57
|
+
.tiptap-table td p,
|
|
58
|
+
.tiptap-table th p {
|
|
59
|
+
margin: 0;
|
|
60
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Copyright 2020-2026 Lowdefy, Inc
|
|
2
|
+
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
id: tiptap-input
|
|
16
|
+
type: Box
|
|
17
|
+
|
|
18
|
+
events:
|
|
19
|
+
onMount:
|
|
20
|
+
- id: seed_content
|
|
21
|
+
type: SetState
|
|
22
|
+
params:
|
|
23
|
+
tiptap_with_value:
|
|
24
|
+
html: '<p>Hello <strong>world</strong>.</p>'
|
|
25
|
+
tiptap_disabled:
|
|
26
|
+
html: '<p>Disabled content.</p>'
|
|
27
|
+
tiptap_borderless:
|
|
28
|
+
html: '<p>Borderless.</p>'
|
|
29
|
+
|
|
30
|
+
blocks:
|
|
31
|
+
- id: tiptap_empty
|
|
32
|
+
type: TiptapInput
|
|
33
|
+
|
|
34
|
+
- id: tiptap_placeholder
|
|
35
|
+
type: TiptapInput
|
|
36
|
+
properties:
|
|
37
|
+
placeholder: 'Start typing...'
|
|
38
|
+
|
|
39
|
+
- id: tiptap_with_value
|
|
40
|
+
type: TiptapInput
|
|
41
|
+
|
|
42
|
+
- id: tiptap_disabled
|
|
43
|
+
type: TiptapInput
|
|
44
|
+
properties:
|
|
45
|
+
disabled: true
|
|
46
|
+
|
|
47
|
+
- id: tiptap_borderless
|
|
48
|
+
type: TiptapInput
|
|
49
|
+
properties:
|
|
50
|
+
bordered: false
|
|
51
|
+
|
|
52
|
+
- id: tiptap_typing
|
|
53
|
+
type: TiptapInput
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Copyright 2020-2026 Lowdefy, Inc
|
|
2
|
+
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
- id: default
|
|
16
|
+
type: TiptapInput
|
|
17
|
+
- id: properties.placeholder
|
|
18
|
+
type: TiptapInput
|
|
19
|
+
properties:
|
|
20
|
+
placeholder: 'Start typing...'
|
|
21
|
+
- id: properties.title
|
|
22
|
+
type: TiptapInput
|
|
23
|
+
properties:
|
|
24
|
+
title: 'Note'
|
|
25
|
+
- id: properties.disabled
|
|
26
|
+
type: TiptapInput
|
|
27
|
+
properties:
|
|
28
|
+
disabled: true
|
|
29
|
+
- id: properties.bordered = false
|
|
30
|
+
type: TiptapInput
|
|
31
|
+
properties:
|
|
32
|
+
bordered: false
|
|
33
|
+
placeholder: 'No border.'
|
|
34
|
+
- id: properties.image and table disabled
|
|
35
|
+
type: TiptapInput
|
|
36
|
+
properties:
|
|
37
|
+
image:
|
|
38
|
+
disabled: true
|
|
39
|
+
table:
|
|
40
|
+
disabled: true
|
|
41
|
+
- id: properties.starterKit overrides
|
|
42
|
+
type: TiptapInput
|
|
43
|
+
properties:
|
|
44
|
+
starterKit:
|
|
45
|
+
codeBlock: false
|
|
46
|
+
blockquote: false
|
|
47
|
+
- id: properties.rows = 2
|
|
48
|
+
type: TiptapInput
|
|
49
|
+
properties:
|
|
50
|
+
rows: 2
|
|
51
|
+
- id: properties.autoSize = {minRows 2, maxRows 5}
|
|
52
|
+
type: TiptapInput
|
|
53
|
+
properties:
|
|
54
|
+
autoSize:
|
|
55
|
+
minRows: 2
|
|
56
|
+
maxRows: 5
|
|
57
|
+
- id: properties.autoSize = true
|
|
58
|
+
type: TiptapInput
|
|
59
|
+
properties:
|
|
60
|
+
autoSize: true
|
|
61
|
+
- id: properties.link.openOnClick = false
|
|
62
|
+
type: TiptapInput
|
|
63
|
+
properties:
|
|
64
|
+
link:
|
|
65
|
+
openOnClick: false
|
|
@@ -14,12 +14,12 @@
|
|
|
14
14
|
limitations under the License.
|
|
15
15
|
*/ import { useRef } from 'react';
|
|
16
16
|
import TurndownService from 'turndown';
|
|
17
|
-
import
|
|
17
|
+
import fileUpload from '../utils/fileUpload.js';
|
|
18
18
|
// Ref-tracked controller for the editor's value. No useState — the only
|
|
19
19
|
// source of truth for `fileList` is the `value` prop from Lowdefy. We mirror
|
|
20
20
|
// it into a ref each render so callbacks (onUpdate, insertImage) captured in
|
|
21
21
|
// closures always read the current value instead of a stale render snapshot.
|
|
22
|
-
function useTiptapState({ value, methods }) {
|
|
22
|
+
function useTiptapState({ value, methods, hasDownloadRequest }) {
|
|
23
23
|
const valueRef = useRef(value);
|
|
24
24
|
valueRef.current = value;
|
|
25
25
|
const turndownService = new TurndownService();
|
|
@@ -31,7 +31,7 @@ function useTiptapState({ value, methods }) {
|
|
|
31
31
|
}
|
|
32
32
|
});
|
|
33
33
|
// Emit a full `{fileList, html, text, markdown}` value to Lowdefy based on
|
|
34
|
-
// the editor's current document. When appendFile is supplied (an
|
|
34
|
+
// the editor's current document. When appendFile is supplied (an upload
|
|
35
35
|
// result), it is added to the current fileList before filtering by
|
|
36
36
|
// in-document image urls.
|
|
37
37
|
const emit = (editor, appendFile)=>{
|
|
@@ -54,9 +54,10 @@ function useTiptapState({ value, methods }) {
|
|
|
54
54
|
});
|
|
55
55
|
};
|
|
56
56
|
const insertImage = async (editor, file, pos)=>{
|
|
57
|
-
const url = await
|
|
57
|
+
const url = await fileUpload({
|
|
58
58
|
file,
|
|
59
|
-
methods
|
|
59
|
+
methods,
|
|
60
|
+
hasDownloadRequest
|
|
60
61
|
});
|
|
61
62
|
// The upload is async: bail if the editor was torn down meanwhile (e.g. the
|
|
62
63
|
// surrounding page navigated away before the upload resolved), otherwise the
|
|
@@ -26,27 +26,91 @@ import statusClass from '../utils/statusClass.js';
|
|
|
26
26
|
import suggestion from './suggestion.js';
|
|
27
27
|
import useGroupMembersPopover from './useGroupMembersPopover.js';
|
|
28
28
|
import useTiptapMentionState from './useTiptapMentionState.js';
|
|
29
|
-
import './style.
|
|
29
|
+
import './style.css';
|
|
30
|
+
// A mention node's `id` attribute holds whatever option the user picked — a
|
|
31
|
+
// plain string, or the option object ({ label, value, tag }) — so the label
|
|
32
|
+
// and the DOM identifier are both derived from it. Content loaded back from
|
|
33
|
+
// saved html arrives with a STRING id (tiptap's parseHTML reads data-id) and
|
|
34
|
+
// the label in `data-label`, which is why both render paths take the node's
|
|
35
|
+
// attributes rather than the id alone.
|
|
30
36
|
function mentionLabel(id) {
|
|
31
37
|
return id?.tag?.title ?? id?.label ?? id;
|
|
32
38
|
}
|
|
39
|
+
// The string that goes into the DOM as data-id. Option objects are commonly
|
|
40
|
+
// { label, value } with the identity inside `value` (a string, or an object
|
|
41
|
+
// with _id / id); fall back to the object's own _id / id. Anything without a
|
|
42
|
+
// scalar identity yields undefined and data-id is omitted — never the
|
|
43
|
+
// "[object Object]" tiptap's stock attribute renderer produces when it
|
|
44
|
+
// stringifies the whole option.
|
|
45
|
+
function scalarId(candidate) {
|
|
46
|
+
if (type.isString(candidate) || type.isNumber(candidate)) return String(candidate);
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
function mentionId(id) {
|
|
50
|
+
if (type.isNone(id)) return undefined;
|
|
51
|
+
if (type.isString(id) || type.isNumber(id)) return String(id);
|
|
52
|
+
if (!type.isObject(id)) return undefined;
|
|
53
|
+
const value = id.value;
|
|
54
|
+
if (type.isObject(value)) {
|
|
55
|
+
return scalarId(value._id) ?? scalarId(value.id) ?? scalarId(value.value);
|
|
56
|
+
}
|
|
57
|
+
return scalarId(value) ?? scalarId(id._id) ?? scalarId(id.id);
|
|
58
|
+
}
|
|
59
|
+
function nodeLabel(attrs) {
|
|
60
|
+
const label = type.isString(attrs.label) && attrs.label !== '' ? attrs.label : undefined;
|
|
61
|
+
const derived = mentionLabel(attrs.id);
|
|
62
|
+
return label ?? (type.isString(derived) || type.isNumber(derived) ? derived : undefined);
|
|
63
|
+
}
|
|
64
|
+
// Mention with its `id` and `label` attributes rendered through the helpers
|
|
65
|
+
// above: data-id is always a scalar identifier (or absent), and data-label
|
|
66
|
+
// always carries the display label so saved html round-trips with a readable
|
|
67
|
+
// mention even though the id comes back as a string.
|
|
68
|
+
const LowdefyMention = Mention.extend({
|
|
69
|
+
addAttributes () {
|
|
70
|
+
const parent = this.parent?.() ?? {};
|
|
71
|
+
return {
|
|
72
|
+
...parent,
|
|
73
|
+
id: {
|
|
74
|
+
...parent.id,
|
|
75
|
+
renderHTML: (attributes)=>{
|
|
76
|
+
const id = mentionId(attributes.id);
|
|
77
|
+
return type.isNone(id) ? {} : {
|
|
78
|
+
'data-id': id
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
label: {
|
|
83
|
+
...parent.label,
|
|
84
|
+
renderHTML: (attributes)=>{
|
|
85
|
+
const label = nodeLabel(attributes);
|
|
86
|
+
return type.isNone(label) ? {} : {
|
|
87
|
+
'data-label': label
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
});
|
|
33
94
|
const TiptapMentionInput = ({ blockId, components: { Icon, Link }, events, loading, methods, properties, required, validation, value })=>{
|
|
95
|
+
const uploadPolicyRequestId = properties.uploadPolicyRequestId ?? properties.s3PostPolicyRequestId;
|
|
96
|
+
const downloadPolicyRequestId = properties.downloadPolicyRequestId;
|
|
34
97
|
const { emit, insertImage } = useTiptapMentionState({
|
|
35
98
|
value,
|
|
36
|
-
methods
|
|
99
|
+
methods,
|
|
100
|
+
hasDownloadRequest: !type.isNone(downloadPolicyRequestId)
|
|
37
101
|
});
|
|
38
102
|
const disabled = properties.disabled === true || loading;
|
|
39
|
-
const uploadEnabled = !type.isNone(
|
|
103
|
+
const uploadEnabled = !type.isNone(uploadPolicyRequestId);
|
|
40
104
|
const char = properties.mentions?.char ?? '@';
|
|
41
105
|
const allowSpaces = properties.mentions?.allowSpaces !== false;
|
|
42
106
|
const limit = properties.mentions?.limit ?? 5;
|
|
43
|
-
const mentionExtension =
|
|
107
|
+
const mentionExtension = LowdefyMention.configure({
|
|
44
108
|
HTMLAttributes: {
|
|
45
109
|
class: 'tiptap-mention'
|
|
46
110
|
},
|
|
47
111
|
renderHTML ({ options, node }) {
|
|
48
112
|
const id = node.attrs.id;
|
|
49
|
-
const label =
|
|
113
|
+
const label = nodeLabel(node.attrs) ?? '';
|
|
50
114
|
const group = id?.tag?.group;
|
|
51
115
|
const modifier = {};
|
|
52
116
|
if (!type.isNone(group)) {
|
|
@@ -62,8 +126,7 @@ const TiptapMentionInput = ({ blockId, components: { Icon, Link }, events, loadi
|
|
|
62
126
|
return [
|
|
63
127
|
'a',
|
|
64
128
|
mergeAttributes({
|
|
65
|
-
href
|
|
66
|
-
'data-id': id?._id
|
|
129
|
+
href
|
|
67
130
|
}, options.HTMLAttributes, modifier),
|
|
68
131
|
`${char}${label}`
|
|
69
132
|
];
|
|
@@ -75,7 +138,7 @@ const TiptapMentionInput = ({ blockId, components: { Icon, Link }, events, loadi
|
|
|
75
138
|
];
|
|
76
139
|
},
|
|
77
140
|
renderText ({ node }) {
|
|
78
|
-
return `${char}${
|
|
141
|
+
return `${char}${nodeLabel(node.attrs) ?? ''}`;
|
|
79
142
|
},
|
|
80
143
|
suggestion: suggestion({
|
|
81
144
|
methods,
|
|
@@ -126,18 +189,35 @@ const TiptapMentionInput = ({ blockId, components: { Icon, Link }, events, loadi
|
|
|
126
189
|
]);
|
|
127
190
|
useEffect(()=>{
|
|
128
191
|
if (uploadEnabled) {
|
|
192
|
+
if (!type.isNone(properties.s3PostPolicyRequestId)) {
|
|
193
|
+
console.warn('TiptapMentionInput property "s3PostPolicyRequestId" is deprecated. Use "uploadPolicyRequestId" instead.');
|
|
194
|
+
}
|
|
129
195
|
methods.registerEvent({
|
|
130
|
-
name: '
|
|
196
|
+
name: '__getUploadPolicy',
|
|
131
197
|
actions: [
|
|
132
198
|
{
|
|
133
|
-
id: '
|
|
199
|
+
id: '__getUploadPolicy',
|
|
134
200
|
type: 'Request',
|
|
135
201
|
params: [
|
|
136
|
-
|
|
202
|
+
uploadPolicyRequestId
|
|
137
203
|
]
|
|
138
204
|
}
|
|
139
205
|
]
|
|
140
206
|
});
|
|
207
|
+
if (!type.isNone(downloadPolicyRequestId)) {
|
|
208
|
+
methods.registerEvent({
|
|
209
|
+
name: '__getDownloadPolicy',
|
|
210
|
+
actions: [
|
|
211
|
+
{
|
|
212
|
+
id: '__getDownloadPolicy',
|
|
213
|
+
type: 'Request',
|
|
214
|
+
params: [
|
|
215
|
+
downloadPolicyRequestId
|
|
216
|
+
]
|
|
217
|
+
}
|
|
218
|
+
]
|
|
219
|
+
});
|
|
220
|
+
}
|
|
141
221
|
}
|
|
142
222
|
if (!type.isNone(properties.mentionsRequestId)) {
|
|
143
223
|
methods.registerEvent({
|