@medway-ui/notes-core 0.1.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.
Files changed (57) hide show
  1. package/README.md +127 -0
  2. package/dist/document/normalize.d.ts +61 -0
  3. package/dist/document/normalize.d.ts.map +1 -0
  4. package/dist/document/normalize.js +373 -0
  5. package/dist/document/normalize.js.map +1 -0
  6. package/dist/document/types.d.ts +47 -0
  7. package/dist/document/types.d.ts.map +1 -0
  8. package/dist/document/types.js +9 -0
  9. package/dist/document/types.js.map +1 -0
  10. package/dist/editor/handle.d.ts +28 -0
  11. package/dist/editor/handle.d.ts.map +1 -0
  12. package/dist/editor/handle.js +34 -0
  13. package/dist/editor/handle.js.map +1 -0
  14. package/dist/editor/slashCommands.d.ts +21 -0
  15. package/dist/editor/slashCommands.d.ts.map +1 -0
  16. package/dist/editor/slashCommands.js +98 -0
  17. package/dist/editor/slashCommands.js.map +1 -0
  18. package/dist/editor/vocabulary.d.ts +110 -0
  19. package/dist/editor/vocabulary.d.ts.map +1 -0
  20. package/dist/editor/vocabulary.js +11 -0
  21. package/dist/editor/vocabulary.js.map +1 -0
  22. package/dist/index.d.ts +15 -0
  23. package/dist/index.d.ts.map +1 -0
  24. package/dist/index.js +11 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/schema/contract.d.ts +14 -0
  27. package/dist/schema/contract.d.ts.map +1 -0
  28. package/dist/schema/contract.js +51 -0
  29. package/dist/schema/contract.js.map +1 -0
  30. package/dist/schema/extensions.d.ts +23 -0
  31. package/dist/schema/extensions.d.ts.map +1 -0
  32. package/dist/schema/extensions.js +81 -0
  33. package/dist/schema/extensions.js.map +1 -0
  34. package/dist/schema/image.d.ts +14 -0
  35. package/dist/schema/image.d.ts.map +1 -0
  36. package/dist/schema/image.js +28 -0
  37. package/dist/schema/image.js.map +1 -0
  38. package/dist/schema/index.d.ts +17 -0
  39. package/dist/schema/index.d.ts.map +1 -0
  40. package/dist/schema/index.js +18 -0
  41. package/dist/schema/index.js.map +1 -0
  42. package/dist/schema/slashCommand.d.ts +19 -0
  43. package/dist/schema/slashCommand.d.ts.map +1 -0
  44. package/dist/schema/slashCommand.js +146 -0
  45. package/dist/schema/slashCommand.js.map +1 -0
  46. package/package.json +134 -0
  47. package/src/document/normalize.ts +456 -0
  48. package/src/document/types.ts +74 -0
  49. package/src/editor/handle.ts +65 -0
  50. package/src/editor/slashCommands.ts +120 -0
  51. package/src/editor/vocabulary.ts +126 -0
  52. package/src/index.ts +52 -0
  53. package/src/schema/contract.ts +76 -0
  54. package/src/schema/extensions.ts +120 -0
  55. package/src/schema/image.ts +44 -0
  56. package/src/schema/index.ts +29 -0
  57. package/src/schema/slashCommand.ts +181 -0
package/README.md ADDED
@@ -0,0 +1,127 @@
1
+ # @medway-ui/notes-core
2
+
3
+ Camada de domínio do editor de anotações, compartilhada entre o app mobile e a
4
+ web. **Não tem UI.** Componentes de tela — toolbar, menu `/`, renderer de
5
+ leitura — pertencem a `@medway-ui/core` (web) e `@medway-ui/native` (mobile).
6
+
7
+ O que está aqui é o que **corromperia dados se os clientes discordassem**: o
8
+ modelo do documento, a whitelist que o valida, o schema Tiptap e o vocabulário
9
+ que um host usa para dirigir o editor.
10
+
11
+ ## As duas entradas
12
+
13
+ | Import | Contém | Quem usa |
14
+ | --- | --- | --- |
15
+ | `@medway-ui/notes-core` | Modelo do documento, normalização, vocabulário, catálogo do `/` | Todo mundo — inclusive React Native |
16
+ | `@medway-ui/notes-core/schema` | Extensions do Tiptap, contrato de schema, `Editor` | Só quem **roda** um editor |
17
+
18
+ A separação é uma **restrição forte, não estilo**: a entrada raiz não pode
19
+ importar Tiptap. O bundle React Native a importa e não pode carregar um editor
20
+ que não consegue executar. Se você precisar de algo de `/schema` dentro do app
21
+ mobile, o desenho está errado.
22
+
23
+ > No app mobile, mapeie **só** a entrada raiz (`tsconfig paths` / `extraNodeModules`
24
+ > do Metro). Um import acidental de `/schema` em código de app falha na hora, em
25
+ > vez de silenciosamente adicionar o Tiptap ao bundle.
26
+
27
+ ## Uso
28
+
29
+ ### Ler e validar um documento (qualquer plataforma)
30
+
31
+ ```ts
32
+ import { normalizeDocument, documentsAreEqual } from '@medway-ui/notes-core';
33
+
34
+ // Tudo que vem da API ou de um editor passa por aqui antes de ser usado.
35
+ const doc = normalizeDocument(response.data.content);
36
+ ```
37
+
38
+ `normalizeDocument` nunca lança e nunca devolve `null`: entrada inutilizável vira
39
+ `EMPTY_DOCUMENT`. Ela é uma **whitelist** — nó, mark ou atributo desconhecido é
40
+ descartado, e é isso que mantém `javascript:` e payload editado à mão fora do
41
+ documento.
42
+
43
+ ### Montar um editor (web ou WebView)
44
+
45
+ ```ts
46
+ import {
47
+ Editor,
48
+ buildExtensions,
49
+ createSlashCommand,
50
+ assertSchemaContract,
51
+ } from '@medway-ui/notes-core/schema';
52
+
53
+ const slash = createSlashCommand((state) => setSlashMenu(state));
54
+
55
+ const editor = new Editor({
56
+ element,
57
+ extensions: buildExtensions({ extra: [slash.extension] }),
58
+ content: doc,
59
+ });
60
+
61
+ const missing = assertSchemaContract(editor.schema);
62
+ if (missing.length) throw new Error(`Schema incompleto: ${missing.join(', ')}`);
63
+ ```
64
+
65
+ Importe `Editor` **daqui**, não do seu próprio `@tiptap/core`. Duas cópias de
66
+ ProseMirror não só incham o bundle: o ProseMirror compara tipos de nó por
67
+ identidade de objeto, então duas cópias quebram em runtime.
68
+
69
+ ### O menu `/`
70
+
71
+ `createSlashCommand` faz só a **detecção** — quando o `/` está ativo, o que foi
72
+ digitado, onde está o caret e o range a substituir. O menu você desenha com o seu
73
+ design system e responde com `slash.run(id)`, que remove o `/query` na mesma
74
+ transação (um único undo volta ao texto digitado).
75
+
76
+ ## Adicionar um nó ao schema
77
+
78
+ Na ordem, e o compilador te obriga:
79
+
80
+ 1. `src/document/types.ts` — o union `EditorNodeType`
81
+ 2. `src/document/normalize.ts` — `CONTENT_KIND` e o que o nó aceita como filho
82
+ 3. `src/schema/contract.ts` — `NODE_TYPE_MAP`
83
+ 4. `src/schema/extensions.ts` — a extension de fato
84
+ 5. Os renderers de leitura de cada plataforma
85
+
86
+ Os passos 2 e 3 são `Record<EditorNodeType, …>`: **pular um deles não compila.**
87
+ O passo 4 é verificado em runtime por `assertSchemaContract`. O passo 5 é o único
88
+ que ninguém checa por você — veja "Renderers", abaixo.
89
+
90
+ ## Renderers de leitura
91
+
92
+ A web não escreve renderer: `generateHTML(doc, extensions)` do `@tiptap/html`
93
+ gera a leitura a partir das mesmas extensions, correto por construção.
94
+
95
+ O mobile escreve à mão (`NoteContentView`), porque React Native não tem DOM — e
96
+ por isso é o único que pode divergir do schema. É lá que vale um teste de
97
+ contrato com um documento-fixture cobrindo todos os nós e marks.
98
+
99
+ ## Desenvolvimento
100
+
101
+ ```bash
102
+ npm --prefix notes-core run typecheck # inclui o contrato de schema
103
+ npm --prefix notes-core run build # emite dist/
104
+ ```
105
+
106
+ Da raiz do app: `npm run notes-core:typecheck` / `npm run build:notes-core`. Este
107
+ último também roda sozinho antes de `npm run dev` e `npm run build` na raiz
108
+ (`predev`/`prebuild`), então um checkout novo não precisa rodar isso à mão para
109
+ o site de docs subir.
110
+
111
+ ### Como o pacote é resolvido dentro deste repo
112
+
113
+ O `notes-core/` mora na raiz do `medway-ui`, ao lado de `native/` e
114
+ `editor-bundle/` — não existe diretório `packages/`. Cada consumidor daqui de
115
+ dentro resolve de um jeito, e todos apontam para o **mesmo** fonte:
116
+
117
+ | Consumidor | Como resolve |
118
+ | --- | --- |
119
+ | Site de docs / `@medway-ui/core` (raiz) | `"@medway-ui/notes-core": "file:notes-core"` em `devDependencies` — aponta para `notes-core/dist/`, por isso o `prebuild`/`predev` roda `build:notes-core` |
120
+ | `editor-bundle` | alias no `vite.config.ts` → `../notes-core/src/{index,schema/index}.ts` |
121
+ | `@medway-ui/core` publicado | `peerDependency` **opcional** `>=0.1.0` — nada de Tiptap entra no bundle de quem não instala |
122
+
123
+ Ou seja: dentro do repo o pacote é consumido do fonte; para quem instala
124
+ `@medway-ui/core` de fora, ele é uma dependência de verdade — é isso que a
125
+ página de docs do Note Editor manda instalar.
126
+
127
+ O `editor-bundle` não tem nenhuma dependência própria — ele é só o host.
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Validation and normalization of the note document. The walk is a whitelist:
3
+ * unknown nodes, marks and attributes are dropped, not passed through, which is
4
+ * also what keeps `javascript:` hrefs out.
5
+ *
6
+ * Dependency-free on purpose: runs in React Native, the WebView bundle and the
7
+ * web, so it may not import from any of them.
8
+ */
9
+ import type { EditorDocument } from './types.js';
10
+ /**
11
+ * Minimum valid document, and the value sent as `content` when creating a note.
12
+ * Note that this is *not* what the editor is fed: ProseMirror's `doc` requires
13
+ * at least one block, so `toEditorContent` expands it to an empty paragraph.
14
+ */
15
+ export declare const EMPTY_DOCUMENT: EditorDocument;
16
+ /**
17
+ * Accepts only absolute `http(s)` and `mailto` links. Relative URLs are
18
+ * rejected too: a note is not served from a page, so there is no base to
19
+ * resolve them against.
20
+ */
21
+ export declare const sanitizeLinkHref: (value: unknown) => string | null;
22
+ /** Same idea as `sanitizeLinkHref`, with `data:` additionally allowed. */
23
+ export declare const sanitizeImageSrc: (value: unknown) => string | null;
24
+ /**
25
+ * Turns anything into a document that fits the schema. Never throws and never
26
+ * returns `null` — unusable input collapses to `EMPTY_DOCUMENT`, which is what
27
+ * keeps a corrupt payload from blanking the screen or reaching the API.
28
+ */
29
+ export declare const normalizeDocument: (value: unknown) => EditorDocument;
30
+ /** Type guard for callers that need to know whether normalization changed anything. */
31
+ export declare const isEditorDocument: (value: unknown) => value is EditorDocument;
32
+ /**
33
+ * ProseMirror's `doc` node requires `block+`, so the empty document has to grow
34
+ * a paragraph before it can be loaded into the editor. The API keeps storing
35
+ * the empty form.
36
+ */
37
+ export declare const toEditorContent: (doc: EditorDocument) => EditorDocument;
38
+ /**
39
+ * True for a document a student would consider blank. An image or a horizontal
40
+ * rule counts as content, so a note holding only a photo is not empty — which
41
+ * is why `documentToPlainText` can return `""` for a document this call
42
+ * reports as non-empty. See the note there.
43
+ */
44
+ export declare const isEmptyDocument: (doc: EditorDocument) => boolean;
45
+ /**
46
+ * Deep equality for documents, used by the autosave to skip no-op writes.
47
+ * Key order is irrelevant, so a document that only travelled through
48
+ * serialization does not read as changed.
49
+ */
50
+ export declare const documentsAreEqual: (a: EditorDocument, b: EditorDocument) => boolean;
51
+ /**
52
+ * Plain-text projection, used for previews and as a fallback note title.
53
+ *
54
+ * Returns `""` for documents that are *not* empty: an uncaptioned image, or a
55
+ * horizontal rule, is content for `isEmptyDocument` but has no text to project.
56
+ * Hosts deriving a title from this must keep their own fallback — do not read
57
+ * `documentToPlainText(doc) === ""` as "the note is blank", use
58
+ * `isEmptyDocument` for that.
59
+ */
60
+ export declare const documentToPlainText: (doc: EditorDocument) => string;
61
+ //# sourceMappingURL=normalize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"normalize.d.ts","sourceRoot":"","sources":["../../src/document/normalize.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAEV,cAAc,EAKf,MAAM,YAAY,CAAC;AAEpB;;;;GAIG;AACH,eAAO,MAAM,cAAc,EAAE,cAA6C,CAAC;AA6F3E;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,GAAI,OAAO,OAAO,KAAG,MAAM,GAAG,IAY1D,CAAC;AAEF,0EAA0E;AAC1E,eAAO,MAAM,gBAAgB,GAAI,OAAO,OAAO,KAAG,MAAM,GAAG,IAY1D,CAAC;AAuLF;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,GAAI,OAAO,OAAO,KAAG,cASlD,CAAC;AAEF,uFAAuF;AACvF,eAAO,MAAM,gBAAgB,GAAI,OAAO,OAAO,KAAG,KAAK,IAAI,cACmB,CAAC;AAE/E;;;;GAIG;AACH,eAAO,MAAM,eAAe,GAAI,KAAK,cAAc,KAAG,cACwB,CAAC;AAS/E;;;;;GAKG;AACH,eAAO,MAAM,eAAe,GAAI,KAAK,cAAc,KAAG,OACtB,CAAC;AA0BjC;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,GAC5B,GAAG,cAAc,EACjB,GAAG,cAAc,KAChB,OAA2B,CAAC;AAE/B;;;;;;;;GAQG;AACH,eAAO,MAAM,mBAAmB,GAAI,KAAK,cAAc,KAAG,MAqCzD,CAAC"}
@@ -0,0 +1,373 @@
1
+ /**
2
+ * Validation and normalization of the note document. The walk is a whitelist:
3
+ * unknown nodes, marks and attributes are dropped, not passed through, which is
4
+ * also what keeps `javascript:` hrefs out.
5
+ *
6
+ * Dependency-free on purpose: runs in React Native, the WebView bundle and the
7
+ * web, so it may not import from any of them.
8
+ */
9
+ /**
10
+ * Minimum valid document, and the value sent as `content` when creating a note.
11
+ * Note that this is *not* what the editor is fed: ProseMirror's `doc` requires
12
+ * at least one block, so `toEditorContent` expands it to an empty paragraph.
13
+ */
14
+ export const EMPTY_DOCUMENT = { type: 'doc', content: [] };
15
+ const MAX_HEADING_LEVEL = 3;
16
+ /**
17
+ * Far beyond any real document: caps `normalizeNode`'s recursion so a corrupt
18
+ * payload collapses into a dropped node instead of overflowing the call stack,
19
+ * which would violate `normalizeDocument`'s "never throws" contract.
20
+ */
21
+ const MAX_NODE_DEPTH = 64;
22
+ const SAFE_LINK_PROTOCOLS = ['http:', 'https:', 'mailto:'];
23
+ const SAFE_IMAGE_PROTOCOLS = ['http:', 'https:', 'data:'];
24
+ /*
25
+ * `Record<Union, true>` rather than an array, so the list is exhaustive by
26
+ * compilation: adding a member to `EditorMarkType` without listing it here
27
+ * stops `tsc`, instead of being silently dropped by `normalizeDocument` at
28
+ * runtime. Same pattern as `MARK_TYPE_MAP` in `../schema/contract.ts` and
29
+ * `EDITOR_MESSAGE_VALIDATORS` in `@medway-ui/native`.
30
+ */
31
+ const MARK_TYPES = {
32
+ bold: true,
33
+ italic: true,
34
+ underline: true,
35
+ strike: true,
36
+ code: true,
37
+ link: true,
38
+ };
39
+ const BLOCK_TYPES = {
40
+ paragraph: true,
41
+ heading: true,
42
+ bulletList: true,
43
+ orderedList: true,
44
+ taskList: true,
45
+ blockquote: true,
46
+ codeBlock: true,
47
+ horizontalRule: true,
48
+ image: true,
49
+ };
50
+ const isBlockType = (type) => Object.prototype.hasOwnProperty.call(BLOCK_TYPES, type);
51
+ const CONTENT_KIND = {
52
+ doc: 'block',
53
+ paragraph: 'inline',
54
+ text: 'none',
55
+ hardBreak: 'none',
56
+ heading: 'inline',
57
+ bulletList: 'listItem',
58
+ orderedList: 'listItem',
59
+ listItem: 'block',
60
+ taskList: 'taskItem',
61
+ taskItem: 'block',
62
+ blockquote: 'block',
63
+ codeBlock: 'text',
64
+ horizontalRule: 'none',
65
+ image: 'none',
66
+ };
67
+ const isPlainObject = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
68
+ const toArray = (value) => Array.isArray(value) ? value : [];
69
+ const clampHeadingLevel = (value) => {
70
+ const level = Math.trunc(Number(value));
71
+ if (!Number.isFinite(level) || level < 1)
72
+ return 1;
73
+ return (level > MAX_HEADING_LEVEL ? MAX_HEADING_LEVEL : level);
74
+ };
75
+ /**
76
+ * Accepts only absolute `http(s)` and `mailto` links. Relative URLs are
77
+ * rejected too: a note is not served from a page, so there is no base to
78
+ * resolve them against.
79
+ */
80
+ export const sanitizeLinkHref = (value) => {
81
+ if (typeof value !== 'string')
82
+ return null;
83
+ const href = value.trim();
84
+ if (!href)
85
+ return null;
86
+ try {
87
+ const { protocol } = new URL(href);
88
+ return SAFE_LINK_PROTOCOLS.includes(protocol) ? href : null;
89
+ }
90
+ catch {
91
+ return null;
92
+ }
93
+ };
94
+ /** Same idea as `sanitizeLinkHref`, with `data:` additionally allowed. */
95
+ export const sanitizeImageSrc = (value) => {
96
+ if (typeof value !== 'string')
97
+ return null;
98
+ const src = value.trim();
99
+ if (!src)
100
+ return null;
101
+ try {
102
+ const { protocol } = new URL(src);
103
+ return SAFE_IMAGE_PROTOCOLS.includes(protocol) ? src : null;
104
+ }
105
+ catch {
106
+ return null;
107
+ }
108
+ };
109
+ const normalizeMark = (value) => {
110
+ if (!isPlainObject(value))
111
+ return null;
112
+ const { type } = value;
113
+ if (typeof type !== 'string')
114
+ return null;
115
+ // `hasOwnProperty`, not `in`: `type` is raw JSON, and `in` walks the
116
+ // prototype chain, so `"toString"` would pass as a mark type.
117
+ if (!Object.prototype.hasOwnProperty.call(MARK_TYPES, type))
118
+ return null;
119
+ if (type !== 'link') {
120
+ return { type: type };
121
+ }
122
+ const attrs = isPlainObject(value.attrs) ? value.attrs : {};
123
+ const href = sanitizeLinkHref(attrs.href);
124
+ if (!href)
125
+ return null;
126
+ return {
127
+ type: 'link',
128
+ attrs: { href, target: '_blank', rel: 'noopener noreferrer nofollow' },
129
+ };
130
+ };
131
+ const normalizeMarks = (value) => {
132
+ const marks = toArray(value)
133
+ .map(normalizeMark)
134
+ .filter((mark) => mark !== null);
135
+ return marks.length ? marks : undefined;
136
+ };
137
+ const normalizeAttrs = (type, value) => {
138
+ const attrs = isPlainObject(value) ? value : {};
139
+ switch (type) {
140
+ case 'heading':
141
+ return { level: clampHeadingLevel(attrs.level) };
142
+ case 'orderedList': {
143
+ const start = Math.trunc(Number(attrs.start));
144
+ return { start: Number.isFinite(start) && start > 0 ? start : 1 };
145
+ }
146
+ case 'taskItem':
147
+ return { checked: attrs.checked === true };
148
+ case 'codeBlock':
149
+ return {
150
+ language: typeof attrs.language === 'string' ? attrs.language : null,
151
+ };
152
+ default:
153
+ return undefined;
154
+ }
155
+ };
156
+ const acceptsChild = (kind, childType) => {
157
+ switch (kind) {
158
+ case 'block':
159
+ return isBlockType(childType);
160
+ case 'inline':
161
+ return childType === 'text' || childType === 'hardBreak';
162
+ case 'listItem':
163
+ return childType === 'listItem';
164
+ case 'taskItem':
165
+ return childType === 'taskItem';
166
+ case 'text':
167
+ return childType === 'text';
168
+ case 'none':
169
+ default:
170
+ return false;
171
+ }
172
+ };
173
+ const stripMarks = (node) => {
174
+ if (!node.marks)
175
+ return node;
176
+ const stripped = { ...node };
177
+ delete stripped.marks;
178
+ return stripped;
179
+ };
180
+ const normalizeNode = (value, depth = 0) => {
181
+ // Deliberately dropped rather than throwing or truncating in place, same as
182
+ // every other "unusable input" case in this function.
183
+ if (depth > MAX_NODE_DEPTH)
184
+ return null;
185
+ if (!isPlainObject(value))
186
+ return null;
187
+ const { type } = value;
188
+ if (typeof type !== 'string')
189
+ return null;
190
+ // Same reason as in `normalizeMark`: `in` would let a prototype key such as
191
+ // `"toString"` through as a node type.
192
+ if (!Object.prototype.hasOwnProperty.call(CONTENT_KIND, type))
193
+ return null;
194
+ if (type === 'doc')
195
+ return null;
196
+ const nodeType = type;
197
+ if (nodeType === 'text') {
198
+ // A text node with no text is not representable in ProseMirror.
199
+ if (typeof value.text !== 'string' || value.text === '')
200
+ return null;
201
+ const marks = normalizeMarks(value.marks);
202
+ return marks
203
+ ? { type: 'text', text: value.text, marks }
204
+ : {
205
+ type: 'text',
206
+ text: value.text,
207
+ };
208
+ }
209
+ if (nodeType === 'image') {
210
+ // Dropped outright: unlike a block, there is no sane placeholder for "a
211
+ // photo that isn't there".
212
+ const attrs = isPlainObject(value.attrs) ? value.attrs : {};
213
+ const src = sanitizeImageSrc(attrs.src);
214
+ if (!src)
215
+ return null;
216
+ const alt = typeof attrs.alt === 'string' ? attrs.alt : undefined;
217
+ return alt
218
+ ? { type: 'image', attrs: { src, alt } }
219
+ : { type: 'image', attrs: { src } };
220
+ }
221
+ const kind = CONTENT_KIND[nodeType];
222
+ const attrs = normalizeAttrs(nodeType, value.attrs);
223
+ const node = attrs
224
+ ? { type: nodeType, attrs }
225
+ : { type: nodeType };
226
+ if (kind === 'none')
227
+ return node;
228
+ // Arrow, not `.map(normalizeNode)`: `map` passes `index` as the second
229
+ // argument, which would land in `depth` — the `.map(parseInt)` footgun.
230
+ const children = toArray(value.content)
231
+ .map((child) => normalizeNode(child, depth + 1))
232
+ .filter((child) => child !== null)
233
+ .filter((child) => acceptsChild(kind, child.type));
234
+ // Code blocks hold plain text; marks inside them are meaningless.
235
+ const content = nodeType === 'codeBlock' ? children.map(stripMarks) : children;
236
+ // Containers that cannot legally be empty get an empty paragraph instead of
237
+ // being dropped, so a malformed list keeps its structure and stays editable.
238
+ if (!content.length &&
239
+ (kind === 'block' || kind === 'listItem' || kind === 'taskItem')) {
240
+ if (nodeType === 'listItem' ||
241
+ nodeType === 'taskItem' ||
242
+ nodeType === 'blockquote') {
243
+ return { ...node, content: [{ type: 'paragraph' }] };
244
+ }
245
+ if (nodeType === 'bulletList' || nodeType === 'orderedList') {
246
+ return {
247
+ ...node,
248
+ content: [{ type: 'listItem', content: [{ type: 'paragraph' }] }],
249
+ };
250
+ }
251
+ if (nodeType === 'taskList') {
252
+ return {
253
+ ...node,
254
+ content: [
255
+ {
256
+ type: 'taskItem',
257
+ attrs: { checked: false },
258
+ content: [{ type: 'paragraph' }],
259
+ },
260
+ ],
261
+ };
262
+ }
263
+ }
264
+ return content.length ? { ...node, content } : node;
265
+ };
266
+ /**
267
+ * Turns anything into a document that fits the schema. Never throws and never
268
+ * returns `null` — unusable input collapses to `EMPTY_DOCUMENT`, which is what
269
+ * keeps a corrupt payload from blanking the screen or reaching the API.
270
+ */
271
+ export const normalizeDocument = (value) => {
272
+ if (!isPlainObject(value) || value.type !== 'doc')
273
+ return EMPTY_DOCUMENT;
274
+ const content = toArray(value.content)
275
+ .map((node) => normalizeNode(node))
276
+ .filter((node) => node !== null)
277
+ .filter((node) => isBlockType(node.type));
278
+ return { type: 'doc', content };
279
+ };
280
+ /** Type guard for callers that need to know whether normalization changed anything. */
281
+ export const isEditorDocument = (value) => isPlainObject(value) && value.type === 'doc' && Array.isArray(value.content);
282
+ /**
283
+ * ProseMirror's `doc` node requires `block+`, so the empty document has to grow
284
+ * a paragraph before it can be loaded into the editor. The API keeps storing
285
+ * the empty form.
286
+ */
287
+ export const toEditorContent = (doc) => doc.content.length ? doc : { type: 'doc', content: [{ type: 'paragraph' }] };
288
+ const nodeHasText = (node) => {
289
+ if (node.type === 'text')
290
+ return Boolean(node.text?.trim());
291
+ if (node.type === 'horizontalRule' || node.type === 'image')
292
+ return true;
293
+ return (node.content ?? []).some(nodeHasText);
294
+ };
295
+ /**
296
+ * True for a document a student would consider blank. An image or a horizontal
297
+ * rule counts as content, so a note holding only a photo is not empty — which
298
+ * is why `documentToPlainText` can return `""` for a document this call
299
+ * reports as non-empty. See the note there.
300
+ */
301
+ export const isEmptyDocument = (doc) => !doc.content.some(nodeHasText);
302
+ const jsonEquals = (a, b) => {
303
+ if (a === b)
304
+ return true;
305
+ if (typeof a !== typeof b)
306
+ return false;
307
+ if (a === null || b === null)
308
+ return false;
309
+ if (Array.isArray(a) || Array.isArray(b)) {
310
+ if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) {
311
+ return false;
312
+ }
313
+ return a.every((item, index) => jsonEquals(item, b[index]));
314
+ }
315
+ if (typeof a !== 'object')
316
+ return false;
317
+ const left = a;
318
+ const right = b;
319
+ const keys = Object.keys(left);
320
+ if (keys.length !== Object.keys(right).length)
321
+ return false;
322
+ return keys.every((key) => key in right && jsonEquals(left[key], right[key]));
323
+ };
324
+ /**
325
+ * Deep equality for documents, used by the autosave to skip no-op writes.
326
+ * Key order is irrelevant, so a document that only travelled through
327
+ * serialization does not read as changed.
328
+ */
329
+ export const documentsAreEqual = (a, b) => jsonEquals(a, b);
330
+ /**
331
+ * Plain-text projection, used for previews and as a fallback note title.
332
+ *
333
+ * Returns `""` for documents that are *not* empty: an uncaptioned image, or a
334
+ * horizontal rule, is content for `isEmptyDocument` but has no text to project.
335
+ * Hosts deriving a title from this must keep their own fallback — do not read
336
+ * `documentToPlainText(doc) === ""` as "the note is blank", use
337
+ * `isEmptyDocument` for that.
338
+ */
339
+ export const documentToPlainText = (doc) => {
340
+ const lines = [];
341
+ const walk = (nodes, into) => {
342
+ nodes.forEach((node) => {
343
+ if (node.type === 'text') {
344
+ into.push(node.text ?? '');
345
+ return;
346
+ }
347
+ if (node.type === 'hardBreak') {
348
+ into.push(' ');
349
+ return;
350
+ }
351
+ if (node.type === 'image') {
352
+ // The only text an image carries. Emitted so a note whose first block is
353
+ // a captioned image still projects something — see the contract note on
354
+ // `documentToPlainText` for the uncaptioned case.
355
+ const alt = typeof node.attrs?.alt === 'string' ? node.attrs.alt.trim() : '';
356
+ if (alt)
357
+ lines.push(alt);
358
+ return;
359
+ }
360
+ const isBlock = isBlockType(node.type);
361
+ const target = isBlock ? [] : into;
362
+ walk(node.content ?? [], target);
363
+ if (isBlock && target !== into) {
364
+ const line = target.join('').trim();
365
+ if (line)
366
+ lines.push(line);
367
+ }
368
+ });
369
+ };
370
+ walk(doc.content, []);
371
+ return lines.join('\n');
372
+ };
373
+ //# sourceMappingURL=normalize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"normalize.js","sourceRoot":"","sources":["../../src/document/normalize.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Document model of the notes editor. Mirrors Tiptap's `JSONContent` but
3
+ * declared by hand, so importing the document model never drags Tiptap in.
4
+ *
5
+ * This is the *whole* supported schema: adding a block means adding it here, in
6
+ * `./normalize.ts` and in `../schema/extensions.ts` — in that order.
7
+ */
8
+ export type EditorMarkType = 'bold' | 'italic' | 'underline' | 'strike' | 'code' | 'link';
9
+ export type EditorNodeType = 'doc' | 'paragraph' | 'text' | 'hardBreak' | 'heading' | 'bulletList' | 'orderedList' | 'listItem' | 'taskList' | 'taskItem' | 'blockquote' | 'codeBlock' | 'horizontalRule' | 'image';
10
+ export type EditorAttributes = Record<string, unknown>;
11
+ export interface EditorMark {
12
+ type: EditorMarkType;
13
+ attrs?: EditorAttributes;
14
+ }
15
+ export interface EditorLinkMark extends EditorMark {
16
+ type: 'link';
17
+ attrs: {
18
+ href: string;
19
+ target: string | null;
20
+ rel: string | null;
21
+ };
22
+ }
23
+ /**
24
+ * `src` is whatever the host's upload callback returned (bucket URL in prod,
25
+ * `data:` only in a backend-less demo). Uploading and deleting the underlying
26
+ * file is the host's job; this package only carries the reference.
27
+ */
28
+ export interface EditorImageNode extends EditorNode {
29
+ type: 'image';
30
+ attrs: {
31
+ src: string;
32
+ alt?: string | null;
33
+ };
34
+ }
35
+ export interface EditorNode {
36
+ type: EditorNodeType;
37
+ attrs?: EditorAttributes;
38
+ content?: EditorNode[];
39
+ marks?: EditorMark[];
40
+ text?: string;
41
+ }
42
+ /** Root of a note. This is the shape persisted in `Note.content`. */
43
+ export interface EditorDocument {
44
+ type: 'doc';
45
+ content: EditorNode[];
46
+ }
47
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/document/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,MAAM,cAAc,GACtB,MAAM,GACN,QAAQ,GACR,WAAW,GACX,QAAQ,GACR,MAAM,GACN,MAAM,CAAC;AAEX,MAAM,MAAM,cAAc,GACtB,KAAK,GACL,WAAW,GACX,MAAM,GACN,WAAW,GACX,SAAS,GACT,YAAY,GACZ,aAAa,GACb,UAAU,GACV,UAAU,GACV,UAAU,GACV,YAAY,GACZ,WAAW,GACX,gBAAgB,GAChB,OAAO,CAAC;AAEZ,MAAM,MAAM,gBAAgB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEvD,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,cAAc,CAAC;IACrB,KAAK,CAAC,EAAE,gBAAgB,CAAC;CAC1B;AAED,MAAM,WAAW,cAAe,SAAQ,UAAU;IAChD,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE;QACL,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;QACtB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;KACpB,CAAC;CACH;AAED;;;;GAIG;AACH,MAAM,WAAW,eAAgB,SAAQ,UAAU;IACjD,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE;QACL,GAAG,EAAE,MAAM,CAAC;QACZ,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KACrB,CAAC;CACH;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,cAAc,CAAC;IACrB,KAAK,CAAC,EAAE,gBAAgB,CAAC;IACzB,OAAO,CAAC,EAAE,UAAU,EAAE,CAAC;IACvB,KAAK,CAAC,EAAE,UAAU,EAAE,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,qEAAqE;AACrE,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,KAAK,CAAC;IACZ,OAAO,EAAE,UAAU,EAAE,CAAC;CACvB"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Document model of the notes editor. Mirrors Tiptap's `JSONContent` but
3
+ * declared by hand, so importing the document model never drags Tiptap in.
4
+ *
5
+ * This is the *whole* supported schema: adding a block means adding it here, in
6
+ * `./normalize.ts` and in `../schema/extensions.ts` — in that order.
7
+ */
8
+ export {};
9
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/document/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}