@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.
- package/README.md +127 -0
- package/dist/document/normalize.d.ts +61 -0
- package/dist/document/normalize.d.ts.map +1 -0
- package/dist/document/normalize.js +373 -0
- package/dist/document/normalize.js.map +1 -0
- package/dist/document/types.d.ts +47 -0
- package/dist/document/types.d.ts.map +1 -0
- package/dist/document/types.js +9 -0
- package/dist/document/types.js.map +1 -0
- package/dist/editor/handle.d.ts +28 -0
- package/dist/editor/handle.d.ts.map +1 -0
- package/dist/editor/handle.js +34 -0
- package/dist/editor/handle.js.map +1 -0
- package/dist/editor/slashCommands.d.ts +21 -0
- package/dist/editor/slashCommands.d.ts.map +1 -0
- package/dist/editor/slashCommands.js +98 -0
- package/dist/editor/slashCommands.js.map +1 -0
- package/dist/editor/vocabulary.d.ts +110 -0
- package/dist/editor/vocabulary.d.ts.map +1 -0
- package/dist/editor/vocabulary.js +11 -0
- package/dist/editor/vocabulary.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/schema/contract.d.ts +14 -0
- package/dist/schema/contract.d.ts.map +1 -0
- package/dist/schema/contract.js +51 -0
- package/dist/schema/contract.js.map +1 -0
- package/dist/schema/extensions.d.ts +23 -0
- package/dist/schema/extensions.d.ts.map +1 -0
- package/dist/schema/extensions.js +81 -0
- package/dist/schema/extensions.js.map +1 -0
- package/dist/schema/image.d.ts +14 -0
- package/dist/schema/image.d.ts.map +1 -0
- package/dist/schema/image.js +28 -0
- package/dist/schema/image.js.map +1 -0
- package/dist/schema/index.d.ts +17 -0
- package/dist/schema/index.d.ts.map +1 -0
- package/dist/schema/index.js +18 -0
- package/dist/schema/index.js.map +1 -0
- package/dist/schema/slashCommand.d.ts +19 -0
- package/dist/schema/slashCommand.d.ts.map +1 -0
- package/dist/schema/slashCommand.js +146 -0
- package/dist/schema/slashCommand.js.map +1 -0
- package/package.json +134 -0
- package/src/document/normalize.ts +456 -0
- package/src/document/types.ts +74 -0
- package/src/editor/handle.ts +65 -0
- package/src/editor/slashCommands.ts +120 -0
- package/src/editor/vocabulary.ts +126 -0
- package/src/index.ts +52 -0
- package/src/schema/contract.ts +76 -0
- package/src/schema/extensions.ts +120 -0
- package/src/schema/image.ts +44 -0
- package/src/schema/index.ts +29 -0
- 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":"AAAA;;;;;;;GAOG;AAWH;;;;GAIG;AACH,MAAM,CAAC,MAAM,cAAc,GAAmB,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;AAE3E,MAAM,iBAAiB,GAAG,CAAC,CAAC;AAC5B;;;;GAIG;AACH,MAAM,cAAc,GAAG,EAAE,CAAC;AAC1B,MAAM,mBAAmB,GAAG,CAAC,OAAO,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC;AAC3D,MAAM,oBAAoB,GAAG,CAAC,OAAO,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;AAE1D;;;;;;GAMG;AACH,MAAM,UAAU,GAAiC;IAC/C,IAAI,EAAE,IAAI;IACV,MAAM,EAAE,IAAI;IACZ,SAAS,EAAE,IAAI;IACf,MAAM,EAAE,IAAI;IACZ,IAAI,EAAE,IAAI;IACV,IAAI,EAAE,IAAI;CACX,CAAC;AAqBF,MAAM,WAAW,GAAsC;IACrD,SAAS,EAAE,IAAI;IACf,OAAO,EAAE,IAAI;IACb,UAAU,EAAE,IAAI;IAChB,WAAW,EAAE,IAAI;IACjB,QAAQ,EAAE,IAAI;IACd,UAAU,EAAE,IAAI;IAChB,SAAS,EAAE,IAAI;IACf,cAAc,EAAE,IAAI;IACpB,KAAK,EAAE,IAAI;CACZ,CAAC;AAEF,MAAM,WAAW,GAAG,CAAC,IAAoB,EAAW,EAAE,CACpD,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;AAE1D,MAAM,YAAY,GAAwC;IACxD,GAAG,EAAE,OAAO;IACZ,SAAS,EAAE,QAAQ;IACnB,IAAI,EAAE,MAAM;IACZ,SAAS,EAAE,MAAM;IACjB,OAAO,EAAE,QAAQ;IACjB,UAAU,EAAE,UAAU;IACtB,WAAW,EAAE,UAAU;IACvB,QAAQ,EAAE,OAAO;IACjB,QAAQ,EAAE,UAAU;IACpB,QAAQ,EAAE,OAAO;IACjB,UAAU,EAAE,OAAO;IACnB,SAAS,EAAE,MAAM;IACjB,cAAc,EAAE,MAAM;IACtB,KAAK,EAAE,MAAM;CACd,CAAC;AAEF,MAAM,aAAa,GAAG,CAAC,KAAc,EAAoC,EAAE,CACzE,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAEvE,MAAM,OAAO,GAAG,CAAC,KAAc,EAAa,EAAE,CAC5C,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;AAEpC,MAAM,iBAAiB,GAAG,CAAC,KAAc,EAAa,EAAE;IACtD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IAExC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC;QAAE,OAAO,CAAC,CAAC;IAEnD,OAAO,CAAC,KAAK,GAAG,iBAAiB,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,KAAK,CAAc,CAAC;AAC9E,CAAC,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,KAAc,EAAiB,EAAE;IAChE,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IAE3C,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IAC1B,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAC;IAEvB,IAAI,CAAC;QACH,MAAM,EAAE,QAAQ,EAAE,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC;QACnC,OAAO,mBAAmB,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;IAC9D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC,CAAC;AAEF,0EAA0E;AAC1E,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,KAAc,EAAiB,EAAE;IAChE,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IAE3C,MAAM,GAAG,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IACzB,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IAEtB,IAAI,CAAC;QACH,MAAM,EAAE,QAAQ,EAAE,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;QAClC,OAAO,oBAAoB,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;IAC9D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,aAAa,GAAG,CAAC,KAAc,EAAqB,EAAE;IAC1D,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAEvC,MAAM,EAAE,IAAI,EAAE,GAAG,KAAK,CAAC;IACvB,IAAI,OAAO,IAAI,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IAC1C,qEAAqE;IACrE,8DAA8D;IAC9D,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAEzE,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;QACpB,OAAO,EAAE,IAAI,EAAE,IAAsB,EAAE,CAAC;IAC1C,CAAC;IAED,MAAM,KAAK,GAAG,aAAa,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;IAC5D,MAAM,IAAI,GAAG,gBAAgB,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC1C,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAC;IAEvB,OAAO;QACL,IAAI,EAAE,MAAM;QACZ,KAAK,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,EAAE,8BAA8B,EAAE;KACvE,CAAC;AACJ,CAAC,CAAC;AAEF,MAAM,cAAc,GAAG,CAAC,KAAc,EAA4B,EAAE;IAClE,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC;SACzB,GAAG,CAAC,aAAa,CAAC;SAClB,MAAM,CAAC,CAAC,IAAI,EAAsB,EAAE,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;IAEvD,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;AAC1C,CAAC,CAAC;AAEF,MAAM,cAAc,GAAG,CACrB,IAAoB,EACpB,KAAc,EACgB,EAAE;IAChC,MAAM,KAAK,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;IAEhD,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,SAAS;YACZ,OAAO,EAAE,KAAK,EAAE,iBAAiB,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;QACnD,KAAK,aAAa,CAAC,CAAC,CAAC;YACnB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;YAC9C,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACpE,CAAC;QACD,KAAK,UAAU;YACb,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,KAAK,IAAI,EAAE,CAAC;QAC7C,KAAK,WAAW;YACd,OAAO;gBACL,QAAQ,EAAE,OAAO,KAAK,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI;aACrE,CAAC;QACJ;YACE,OAAO,SAAS,CAAC;IACrB,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,YAAY,GAAG,CACnB,IAAiB,EACjB,SAAyB,EAChB,EAAE;IACX,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,OAAO;YACV,OAAO,WAAW,CAAC,SAAS,CAAC,CAAC;QAChC,KAAK,QAAQ;YACX,OAAO,SAAS,KAAK,MAAM,IAAI,SAAS,KAAK,WAAW,CAAC;QAC3D,KAAK,UAAU;YACb,OAAO,SAAS,KAAK,UAAU,CAAC;QAClC,KAAK,UAAU;YACb,OAAO,SAAS,KAAK,UAAU,CAAC;QAClC,KAAK,MAAM;YACT,OAAO,SAAS,KAAK,MAAM,CAAC;QAC9B,KAAK,MAAM,CAAC;QACZ;YACE,OAAO,KAAK,CAAC;IACjB,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,UAAU,GAAG,CAAC,IAAgB,EAAc,EAAE;IAClD,IAAI,CAAC,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IAE7B,MAAM,QAAQ,GAAG,EAAE,GAAG,IAAI,EAAE,CAAC;IAC7B,OAAO,QAAQ,CAAC,KAAK,CAAC;IAEtB,OAAO,QAAQ,CAAC;AAClB,CAAC,CAAC;AAEF,MAAM,aAAa,GAAG,CAAC,KAAc,EAAE,KAAK,GAAG,CAAC,EAAqB,EAAE;IACrE,4EAA4E;IAC5E,sDAAsD;IACtD,IAAI,KAAK,GAAG,cAAc;QAAE,OAAO,IAAI,CAAC;IACxC,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAEvC,MAAM,EAAE,IAAI,EAAE,GAAG,KAAK,CAAC;IACvB,IAAI,OAAO,IAAI,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IAC1C,4EAA4E;IAC5E,uCAAuC;IACvC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,YAAY,EAAE,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAC3E,IAAI,IAAI,KAAK,KAAK;QAAE,OAAO,IAAI,CAAC;IAEhC,MAAM,QAAQ,GAAG,IAAsB,CAAC;IAExC,IAAI,QAAQ,KAAK,MAAM,EAAE,CAAC;QACxB,gEAAgE;QAChE,IAAI,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,KAAK,EAAE;YAAE,OAAO,IAAI,CAAC;QAErE,MAAM,KAAK,GAAG,cAAc,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAC1C,OAAO,KAAK;YACV,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE;YAC3C,CAAC,CAAC;gBACE,IAAI,EAAE,MAAM;gBACZ,IAAI,EAAE,KAAK,CAAC,IAAI;aACjB,CAAC;IACR,CAAC;IAED,IAAI,QAAQ,KAAK,OAAO,EAAE,CAAC;QACzB,wEAAwE;QACxE,2BAA2B;QAC3B,MAAM,KAAK,GAAG,aAAa,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;QAC5D,MAAM,GAAG,GAAG,gBAAgB,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACxC,IAAI,CAAC,GAAG;YAAE,OAAO,IAAI,CAAC;QAEtB,MAAM,GAAG,GAAG,OAAO,KAAK,CAAC,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC;QAClE,OAAO,GAAG;YACR,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;YACxC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,EAAE,CAAC;IACxC,CAAC;IAED,MAAM,IAAI,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC;IACpC,MAAM,KAAK,GAAG,cAAc,CAAC,QAAQ,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;IACpD,MAAM,IAAI,GAAe,KAAK;QAC5B,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE;QAC3B,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;IAEvB,IAAI,IAAI,KAAK,MAAM;QAAE,OAAO,IAAI,CAAC;IAEjC,uEAAuE;IACvE,wEAAwE;IACxE,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC;SACpC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,aAAa,CAAC,KAAK,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC;SAC/C,MAAM,CAAC,CAAC,KAAK,EAAuB,EAAE,CAAC,KAAK,KAAK,IAAI,CAAC;SACtD,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;IAErD,kEAAkE;IAClE,MAAM,OAAO,GACX,QAAQ,KAAK,WAAW,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;IAEjE,4EAA4E;IAC5E,6EAA6E;IAC7E,IACE,CAAC,OAAO,CAAC,MAAM;QACf,CAAC,IAAI,KAAK,OAAO,IAAI,IAAI,KAAK,UAAU,IAAI,IAAI,KAAK,UAAU,CAAC,EAChE,CAAC;QACD,IACE,QAAQ,KAAK,UAAU;YACvB,QAAQ,KAAK,UAAU;YACvB,QAAQ,KAAK,YAAY,EACzB,CAAC;YACD,OAAO,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,EAAE,CAAC;QACvD,CAAC;QACD,IAAI,QAAQ,KAAK,YAAY,IAAI,QAAQ,KAAK,aAAa,EAAE,CAAC;YAC5D,OAAO;gBACL,GAAG,IAAI;gBACP,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,EAAE,CAAC;aAClE,CAAC;QACJ,CAAC;QACD,IAAI,QAAQ,KAAK,UAAU,EAAE,CAAC;YAC5B,OAAO;gBACL,GAAG,IAAI;gBACP,OAAO,EAAE;oBACP;wBACE,IAAI,EAAE,UAAU;wBAChB,KAAK,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE;wBACzB,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;qBACjC;iBACF;aACF,CAAC;QACJ,CAAC;IACH,CAAC;IAED,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;AACtD,CAAC,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,KAAc,EAAkB,EAAE;IAClE,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,IAAI,KAAK,KAAK;QAAE,OAAO,cAAc,CAAC;IAEzE,MAAM,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC;SACnC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;SAClC,MAAM,CAAC,CAAC,IAAI,EAAsB,EAAE,CAAC,IAAI,KAAK,IAAI,CAAC;SACnD,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IAE5C,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;AAClC,CAAC,CAAC;AAEF,uFAAuF;AACvF,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,KAAc,EAA2B,EAAE,CAC1E,aAAa,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,IAAI,KAAK,KAAK,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;AAE/E;;;;GAIG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,GAAmB,EAAkB,EAAE,CACrE,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,EAAE,CAAC;AAE/E,MAAM,WAAW,GAAG,CAAC,IAAgB,EAAW,EAAE;IAChD,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM;QAAE,OAAO,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;IAC5D,IAAI,IAAI,CAAC,IAAI,KAAK,gBAAgB,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO;QAAE,OAAO,IAAI,CAAC;IAEzE,OAAO,CAAC,IAAI,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;AAChD,CAAC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,GAAmB,EAAW,EAAE,CAC9D,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;AAEjC,MAAM,UAAU,GAAG,CAAC,CAAU,EAAE,CAAU,EAAW,EAAE;IACrD,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACzB,IAAI,OAAO,CAAC,KAAK,OAAO,CAAC;QAAE,OAAO,KAAK,CAAC;IACxC,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IAE3C,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;QACzC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM,EAAE,CAAC;YACpE,OAAO,KAAK,CAAC;QACf,CAAC;QAED,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAC9D,CAAC;IAED,IAAI,OAAO,CAAC,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAExC,MAAM,IAAI,GAAG,CAA4B,CAAC;IAC1C,MAAM,KAAK,GAAG,CAA4B,CAAC;IAC3C,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAE/B,IAAI,IAAI,CAAC,MAAM,KAAK,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IAE5D,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,IAAI,KAAK,IAAI,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;AAChF,CAAC,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAC/B,CAAiB,EACjB,CAAiB,EACR,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AAE/B;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,GAAmB,EAAU,EAAE;IACjE,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,MAAM,IAAI,GAAG,CAAC,KAAmB,EAAE,IAAc,EAAE,EAAE;QACnD,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE;YACrB,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;gBACzB,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;gBAC3B,OAAO;YACT,CAAC;YACD,IAAI,IAAI,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;gBAC9B,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;gBACf,OAAO;YACT,CAAC;YACD,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;gBAC1B,yEAAyE;gBACzE,wEAAwE;gBACxE,kDAAkD;gBAClD,MAAM,GAAG,GACP,OAAO,IAAI,CAAC,KAAK,EAAE,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACnE,IAAI,GAAG;oBAAE,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;gBACzB,OAAO;YACT,CAAC;YAED,MAAM,OAAO,GAAG,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACvC,MAAM,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;YACnC,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,EAAE,EAAE,MAAM,CAAC,CAAC;YAEjC,IAAI,OAAO,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;gBAC/B,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;gBACpC,IAAI,IAAI;oBAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC7B,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC,CAAC;IAEF,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;IAEtB,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC,CAAC"}
|
|
@@ -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"}
|