@lowdefy/blocks-tiptap 5.6.0 → 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.
@@ -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.module.css';
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(properties.s3PostPolicyRequestId);
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 event once (if configured).
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: '__getS3PostPolicy',
64
+ name: '__getUploadPolicy',
59
65
  actions: [
60
66
  {
61
- id: '__getS3PostPolicy',
67
+ id: '__getUploadPolicy',
62
68
  type: 'Request',
63
69
  params: [
64
- properties.s3PostPolicyRequestId
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 s3PostPolicyRequestId is set.'
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: 'Id of a request that returns an S3 presigned POST policy. When set, images dragged or pasted into the editor are uploaded via that policy and inserted as <img> nodes. Leave unset to disable image uploads.'
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 s3FileUpload from '../utils/s3FileUpload.js';
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 S3 upload
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 s3FileUpload({
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,7 +26,7 @@ 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.module.css';
29
+ import './style.css';
30
30
  // A mention node's `id` attribute holds whatever option the user picked — a
31
31
  // plain string, or the option object ({ label, value, tag }) — so the label
32
32
  // and the DOM identifier are both derived from it. Content loaded back from
@@ -92,12 +92,15 @@ const LowdefyMention = Mention.extend({
92
92
  }
93
93
  });
94
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;
95
97
  const { emit, insertImage } = useTiptapMentionState({
96
98
  value,
97
- methods
99
+ methods,
100
+ hasDownloadRequest: !type.isNone(downloadPolicyRequestId)
98
101
  });
99
102
  const disabled = properties.disabled === true || loading;
100
- const uploadEnabled = !type.isNone(properties.s3PostPolicyRequestId);
103
+ const uploadEnabled = !type.isNone(uploadPolicyRequestId);
101
104
  const char = properties.mentions?.char ?? '@';
102
105
  const allowSpaces = properties.mentions?.allowSpaces !== false;
103
106
  const limit = properties.mentions?.limit ?? 5;
@@ -186,18 +189,35 @@ const TiptapMentionInput = ({ blockId, components: { Icon, Link }, events, loadi
186
189
  ]);
187
190
  useEffect(()=>{
188
191
  if (uploadEnabled) {
192
+ if (!type.isNone(properties.s3PostPolicyRequestId)) {
193
+ console.warn('TiptapMentionInput property "s3PostPolicyRequestId" is deprecated. Use "uploadPolicyRequestId" instead.');
194
+ }
189
195
  methods.registerEvent({
190
- name: '__getS3PostPolicy',
196
+ name: '__getUploadPolicy',
191
197
  actions: [
192
198
  {
193
- id: '__getS3PostPolicy',
199
+ id: '__getUploadPolicy',
194
200
  type: 'Request',
195
201
  params: [
196
- properties.s3PostPolicyRequestId
202
+ uploadPolicyRequestId
197
203
  ]
198
204
  }
199
205
  ]
200
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
+ }
201
221
  }
202
222
  if (!type.isNone(properties.mentionsRequestId)) {
203
223
  methods.registerEvent({