@ceedcv-maya/shared-editor-react 0.6.0 → 0.7.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/README.md CHANGED
@@ -4,7 +4,7 @@ Unified TipTap editor for the Maya ecosystem.
4
4
 
5
5
  ## Components
6
6
 
7
- - **`<MayaEditor mode="lite" | "full" />`** — single editor with two visual modes. `lite` for short comments and alerts; `full` for templates and documents (BlockNote parity).
7
+ - **`<MayaEditor mode="lite" | "full" />`** — single editor with two visual modes. `lite` for short comments and alerts; `full` for templates and documents.
8
8
  - **`<EditorContentHtml html />`** — read-only renderer with DOMPurify sanitisation (aligned with the server-side `TiptapHtmlRenderer`).
9
9
  - **`<EditorToolbar />`** — toolbar builder, used internally by `MayaEditor` and exposed for custom integrations.
10
10
 
@@ -14,10 +14,6 @@ Unified TipTap editor for the Maya ecosystem.
14
14
  - `AlertBlock` — variants info / warning / success / danger.
15
15
  - `CommentMark` — anchored-comment mark (paired with `AnchoredCommentController` server-side).
16
16
 
17
- ## Conversion
18
-
19
- - `convertBlockNoteToTiptap(blocks)` — legacy → ProseMirror conversion. Mirror of the PHP `Maya\Editor\Renderers\BlockNoteToTiptap`.
20
-
21
17
  ## Document import & block splitting
22
18
 
23
19
  Helpers behind the "Import from Word → blocks" flow (`DocxBlockSplitter` in maya_dms):
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ceedcv-maya/shared-editor-react",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "type": "module",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
@@ -57,12 +57,11 @@
57
57
  "publishConfig": {
58
58
  "access": "public"
59
59
  },
60
- "description": "Unified rich-text editor for Maya: MayaEditor (TipTap) with lite/full modes, BlockNote→TipTap converter, HTML SSR renderer, anchored-comments support.",
60
+ "description": "Unified rich-text editor for Maya: MayaEditor (TipTap) with lite/full modes, HTML SSR renderer, anchored-comments support.",
61
61
  "keywords": [
62
62
  "react",
63
63
  "tiptap",
64
64
  "prosemirror",
65
- "blocknote",
66
65
  "editor",
67
66
  "wysiwyg",
68
67
  "ceedcv",
@@ -198,7 +198,8 @@ export function EditorToolbar({
198
198
  <ListAndBlockButtons editor={editor} labels={L} />
199
199
  <TableAndMediaButtons editor={editor} labels={L} onImage={onImage} />
200
200
 
201
- <span className="maya-editor-toolbar__sep" aria-hidden />
201
+
202
+ <div className="flex items-end gap-0.5 shrink-0 pl-1 border-l border-ui-border dark:border-ui-dark-border ml-auto">
202
203
  <DocumentButtons
203
204
  editor={editor}
204
205
  labels={L}
@@ -218,6 +219,7 @@ export function EditorToolbar({
218
219
  onInsertMarkdown={onInsertMarkdown}
219
220
  onToggleFullscreen={onToggleFullscreen}
220
221
  />
222
+ </div>
221
223
  </>
222
224
  )}
223
225
  </div>
@@ -388,13 +388,14 @@ export function ViewModeButtons({
388
388
  <span style={{ fontFamily: 'ui-monospace, monospace', fontSize: 11 }}>{'<>'}</span>
389
389
  </Btn>
390
390
  )}
391
+ <span className="maya-editor-toolbar__sep" aria-hidden />
391
392
  {onToggleFullscreen && (
392
- <Btn
393
- onClick={onToggleFullscreen}
394
- title={isFullscreen ? L.exitFullscreen : L.fullscreen}
395
- >
396
- {isFullscreen ? '' : ''}
397
- </Btn>
393
+ <Btn
394
+ onClick={onToggleFullscreen}
395
+ title={isFullscreen ? L.exitFullscreen : L.fullscreen}
396
+ >
397
+ {isFullscreen ? '🗗' : '🗖'}
398
+ </Btn>
398
399
  )}
399
400
  </>
400
401
  );
package/src/index.ts CHANGED
@@ -12,7 +12,6 @@ export { Indent } from './extensions/Indent';
12
12
  export type { IndentOptions } from './extensions/Indent';
13
13
  export { ColorPicker } from './components/ColorPicker';
14
14
 
15
- export { convertBlockNoteToTiptap } from './serializers/BlockNoteToTiptap';
16
15
  export { useEditorContent } from './hooks/useEditorContent';
17
16
  export { getAnchorRange, setAnchorRange, rebaseAnchors } from './lib/CommentAnchor';
18
17
  export type { AnchorRange } from './lib/CommentAnchor';
@@ -38,9 +37,6 @@ export type {
38
37
  TiptapNode,
39
38
  TiptapDoc,
40
39
  AnchoredComment,
41
- BlockNoteBlock,
42
- BlockNoteInline,
43
- BlockNoteStyles,
44
40
  } from './types';
45
41
 
46
42
  export { default as esTranslations } from './i18n/es.json';
package/src/types.ts CHANGED
@@ -29,28 +29,3 @@ export interface AnchoredComment {
29
29
  anchorIsValid: boolean;
30
30
  anchorLastSyncedAt: string | null;
31
31
  }
32
-
33
- export interface BlockNoteStyles {
34
- bold?: boolean;
35
- italic?: boolean;
36
- underline?: boolean;
37
- strike?: boolean;
38
- code?: boolean;
39
- textColor?: string;
40
- backgroundColor?: string;
41
- }
42
-
43
- export interface BlockNoteInline {
44
- type: string;
45
- text?: string;
46
- styles?: BlockNoteStyles;
47
- href?: string;
48
- content?: BlockNoteInline[];
49
- }
50
-
51
- export interface BlockNoteBlock {
52
- type: string;
53
- props?: Record<string, unknown>;
54
- content?: BlockNoteInline[] | { rows?: Array<{ cells: unknown[] }> };
55
- children?: BlockNoteBlock[];
56
- }
@@ -1,223 +0,0 @@
1
- /**
2
- * JS mirror of Maya\Editor\Renderers\BlockNoteToTiptap (PHP).
3
- *
4
- * Same input/output as the PHP version — used for round-trip oracle tests
5
- * (BlockNote → Tiptap → HTML via both renderers must produce identical
6
- * output) and at-runtime when the frontend reads legacy `content` columns
7
- * during the migration window.
8
- */
9
- import type {
10
- BlockNoteBlock,
11
- BlockNoteInline,
12
- BlockNoteStyles,
13
- TiptapDoc,
14
- TiptapMark,
15
- TiptapNode,
16
- } from '../types';
17
-
18
- type ListBlockType = 'bulletListItem' | 'numberedListItem' | 'checkListItem';
19
-
20
- const LIST_TO_LIST_NODE: Record<ListBlockType, 'bulletList' | 'orderedList' | 'taskList'> = {
21
- bulletListItem: 'bulletList',
22
- numberedListItem: 'orderedList',
23
- checkListItem: 'taskList',
24
- };
25
-
26
- export function convertBlockNoteToTiptap(blocks: BlockNoteBlock[]): TiptapDoc {
27
- const content: TiptapNode[] = [];
28
- let i = 0;
29
- const n = blocks.length;
30
- while (i < n) {
31
- const block = blocks[i];
32
- if (!block || typeof block !== 'object') {
33
- i++;
34
- continue;
35
- }
36
- const type = String(block.type ?? 'paragraph');
37
-
38
- if (type === 'bulletListItem' || type === 'numberedListItem' || type === 'checkListItem') {
39
- const listType = LIST_TO_LIST_NODE[type as ListBlockType];
40
- const items: TiptapNode[] = [];
41
- while (
42
- i < n &&
43
- blocks[i] &&
44
- (blocks[i].type === type)
45
- ) {
46
- items.push(convertListItem(blocks[i], type as ListBlockType));
47
- i++;
48
- }
49
- content.push({ type: listType, content: items });
50
- continue;
51
- }
52
-
53
- content.push(convertBlock(block));
54
- i++;
55
- }
56
-
57
- return { type: 'doc', content };
58
- }
59
-
60
- function convertBlock(block: BlockNoteBlock): TiptapNode {
61
- const type = String(block.type ?? 'paragraph');
62
- const props = (block.props ?? {}) as Record<string, unknown>;
63
- const inline = convertInline((block.content as BlockNoteInline[] | undefined) ?? []);
64
- const attrs = propsToAttrs(props);
65
-
66
- switch (type) {
67
- case 'heading': {
68
- const lvl = Math.max(1, Math.min(6, Number(props.level ?? 2) || 2));
69
- return { type: 'heading', attrs: { ...attrs, level: lvl }, content: inline };
70
- }
71
- case 'paragraph':
72
- return { type: 'paragraph', attrs, content: inline };
73
- case 'quote':
74
- return {
75
- type: 'blockquote',
76
- attrs,
77
- content: [{ type: 'paragraph', content: inline }],
78
- };
79
- case 'codeBlock':
80
- return { type: 'codeBlock', attrs, content: inline };
81
- case 'image':
82
- return {
83
- type: 'image',
84
- attrs: {
85
- src: String(props.url ?? ''),
86
- alt: String(props.caption ?? ''),
87
- caption: String(props.caption ?? ''),
88
- },
89
- };
90
- case 'table':
91
- return convertTable((block.content as { rows?: unknown[] } | undefined) ?? {});
92
- default:
93
- return {
94
- type: 'paragraph',
95
- attrs: { ...attrs, 'data-original-type': type },
96
- content: inline,
97
- };
98
- }
99
- }
100
-
101
- function convertListItem(block: BlockNoteBlock, blockType: ListBlockType): TiptapNode {
102
- const props = (block.props ?? {}) as Record<string, unknown>;
103
- const inline = convertInline((block.content as BlockNoteInline[] | undefined) ?? []);
104
- const attrs = propsToAttrs(props);
105
-
106
- const itemContent: TiptapNode[] = [{ type: 'paragraph', content: inline }];
107
-
108
- const children = block.children ?? [];
109
- if (children.length > 0) {
110
- const childDoc = convertBlockNoteToTiptap(children);
111
- for (const childNode of childDoc.content) {
112
- itemContent.push(childNode);
113
- }
114
- }
115
-
116
- if (blockType === 'checkListItem') {
117
- return {
118
- type: 'taskItem',
119
- attrs: { ...attrs, checked: !!props.checked },
120
- content: itemContent,
121
- };
122
- }
123
-
124
- return { type: 'listItem', attrs, content: itemContent };
125
- }
126
-
127
- function convertTable(content: { rows?: unknown[] }): TiptapNode {
128
- const rows = Array.isArray(content.rows) ? content.rows : [];
129
- const proseRows: TiptapNode[] = [];
130
- let isFirstRow = true;
131
- for (const row of rows) {
132
- if (!row || typeof row !== 'object' || !Array.isArray((row as { cells?: unknown[] }).cells)) {
133
- continue;
134
- }
135
- const cells: TiptapNode[] = [];
136
- for (const cell of (row as { cells: unknown[] }).cells) {
137
- let cellContent: TiptapNode[] = [];
138
- const cellAttrs: Record<string, unknown> = {};
139
- if (cell && typeof cell === 'object') {
140
- const cellObj = cell as { content?: unknown; props?: { colspan?: unknown; rowspan?: unknown } };
141
- if (Array.isArray(cellObj.content)) {
142
- cellContent = convertInline(cellObj.content as BlockNoteInline[]);
143
- if (cellObj.props) {
144
- if (cellObj.props.colspan != null) {
145
- cellAttrs.colspan = Number(cellObj.props.colspan) || 1;
146
- }
147
- if (cellObj.props.rowspan != null) {
148
- cellAttrs.rowspan = Number(cellObj.props.rowspan) || 1;
149
- }
150
- }
151
- } else if (Array.isArray(cell)) {
152
- cellContent = convertInline(cell as BlockNoteInline[]);
153
- }
154
- }
155
- cells.push({
156
- type: isFirstRow ? 'tableHeader' : 'tableCell',
157
- attrs: cellAttrs,
158
- content: [{ type: 'paragraph', content: cellContent }],
159
- });
160
- }
161
- proseRows.push({ type: 'tableRow', content: cells });
162
- isFirstRow = false;
163
- }
164
-
165
- return { type: 'table', content: proseRows };
166
- }
167
-
168
- function convertInline(content: BlockNoteInline[]): TiptapNode[] {
169
- const out: TiptapNode[] = [];
170
- for (const span of content) {
171
- if (!span || typeof span !== 'object') continue;
172
- const type = String(span.type ?? 'text');
173
- if (type === 'text') {
174
- const text = String(span.text ?? '');
175
- if (text === '') continue;
176
- const marks = stylesToMarks(span.styles ?? {});
177
- const node: TiptapNode = { type: 'text', text };
178
- if (marks.length > 0) node.marks = marks;
179
- out.push(node);
180
- } else if (type === 'link') {
181
- const href = String(span.href ?? '');
182
- const linkMark: TiptapMark = { type: 'link', attrs: { href } };
183
- for (const inner of span.content ?? []) {
184
- if (!inner || inner.type !== 'text') continue;
185
- const text = String(inner.text ?? '');
186
- if (text === '') continue;
187
- const marks = [...stylesToMarks(inner.styles ?? {}), linkMark];
188
- out.push({ type: 'text', text, marks });
189
- }
190
- }
191
- }
192
- return out;
193
- }
194
-
195
- function stylesToMarks(styles: BlockNoteStyles): TiptapMark[] {
196
- const marks: TiptapMark[] = [];
197
- if (styles.bold) marks.push({ type: 'bold' });
198
- if (styles.italic) marks.push({ type: 'italic' });
199
- if (styles.underline) marks.push({ type: 'underline' });
200
- if (styles.strike) marks.push({ type: 'strike' });
201
- if (styles.code) marks.push({ type: 'code' });
202
- if (styles.textColor && styles.textColor !== 'default') {
203
- marks.push({ type: 'textStyle', attrs: { color: styles.textColor } });
204
- }
205
- if (styles.backgroundColor && styles.backgroundColor !== 'default') {
206
- marks.push({ type: 'highlight', attrs: { color: styles.backgroundColor } });
207
- }
208
- return marks;
209
- }
210
-
211
- function propsToAttrs(props: Record<string, unknown>): Record<string, unknown> {
212
- const attrs: Record<string, unknown> = {};
213
- if (props.textColor && props.textColor !== 'default') {
214
- attrs.textColor = String(props.textColor);
215
- }
216
- if (props.backgroundColor && props.backgroundColor !== 'default') {
217
- attrs.backgroundColor = String(props.backgroundColor);
218
- }
219
- if (props.textAlignment) {
220
- attrs.textAlign = String(props.textAlignment);
221
- }
222
- return attrs;
223
- }