stilo-editor 0.0.0-stage → 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 (73) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +172 -2
  3. package/dist/characters--8h5uTeF.js +854 -0
  4. package/dist/content.css +161 -0
  5. package/dist/core/drop.d.ts +6 -0
  6. package/dist/core/editor.d.ts +108 -0
  7. package/dist/core/errors.d.ts +6 -0
  8. package/dist/core/events.d.ts +19 -0
  9. package/dist/core/extension-manager.d.ts +57 -0
  10. package/dist/core/extension.d.ts +43 -0
  11. package/dist/core/helpers/active.d.ts +33 -0
  12. package/dist/core/helpers/input-rules.d.ts +21 -0
  13. package/dist/core/helpers/lists.d.ts +12 -0
  14. package/dist/core/helpers/marks.d.ts +38 -0
  15. package/dist/core/helpers/nodes.d.ts +14 -0
  16. package/dist/core/helpers/parse-rules.d.ts +8 -0
  17. package/dist/core/html/css.d.ts +91 -0
  18. package/dist/core/html/parse.d.ts +7 -0
  19. package/dist/core/html/sanitize.d.ts +49 -0
  20. package/dist/core/html/serialize.d.ts +31 -0
  21. package/dist/core/types.d.ts +107 -0
  22. package/dist/extensions/blockquote/index.d.ts +15 -0
  23. package/dist/extensions/bold/index.d.ts +17 -0
  24. package/dist/extensions/bullet-list/index.d.ts +9 -0
  25. package/dist/extensions/clear-formatting/index.d.ts +12 -0
  26. package/dist/extensions/code/index.d.ts +15 -0
  27. package/dist/extensions/default-extensions/index.d.ts +46 -0
  28. package/dist/extensions/document/index.d.ts +17 -0
  29. package/dist/extensions/drop-cursor/index.d.ts +15 -0
  30. package/dist/extensions/gap-cursor/index.d.ts +6 -0
  31. package/dist/extensions/hard-break/index.d.ts +11 -0
  32. package/dist/extensions/heading/index.d.ts +24 -0
  33. package/dist/extensions/highlight/index.d.ts +24 -0
  34. package/dist/extensions/history/index.d.ts +21 -0
  35. package/dist/extensions/horizontal-rule/index.d.ts +11 -0
  36. package/dist/extensions/index.d.ts +27 -0
  37. package/dist/extensions/italic/index.d.ts +16 -0
  38. package/dist/extensions/link/index.d.ts +23 -0
  39. package/dist/extensions/list-item/index.d.ts +16 -0
  40. package/dist/extensions/move-block/index.d.ts +17 -0
  41. package/dist/extensions/ordered-list/index.d.ts +12 -0
  42. package/dist/extensions/paragraph/index.d.ts +14 -0
  43. package/dist/extensions/placeholder/index.d.ts +13 -0
  44. package/dist/extensions/strike/index.d.ts +16 -0
  45. package/dist/extensions/subscript/index.d.ts +16 -0
  46. package/dist/extensions/superscript/index.d.ts +17 -0
  47. package/dist/extensions/text/index.d.ts +12 -0
  48. package/dist/extensions/text-style/index.d.ts +14 -0
  49. package/dist/extensions/underline/index.d.ts +15 -0
  50. package/dist/index.d.ts +13 -0
  51. package/dist/index.js +1 -0
  52. package/dist/src-3tAxKI0c.js +1316 -0
  53. package/dist/style.css +797 -0
  54. package/dist/ui/components/character-picker.d.ts +22 -0
  55. package/dist/ui/components/characters.d.ts +10 -0
  56. package/dist/ui/components/color-button.d.ts +23 -0
  57. package/dist/ui/components/dropdown.d.ts +25 -0
  58. package/dist/ui/components/icon.d.ts +3 -0
  59. package/dist/ui/components/link-bubble.d.ts +19 -0
  60. package/dist/ui/components/link-popover.d.ts +20 -0
  61. package/dist/ui/components/popover.d.ts +27 -0
  62. package/dist/ui/components/shortcut.d.ts +11 -0
  63. package/dist/ui/i18n/en.d.ts +3 -0
  64. package/dist/ui/i18n/it.d.ts +3 -0
  65. package/dist/ui/i18n/types.d.ts +4 -0
  66. package/dist/ui/index.d.ts +19 -0
  67. package/dist/ui/toolbar/items.d.ts +12 -0
  68. package/dist/ui/toolbar/palette.d.ts +6 -0
  69. package/dist/ui/toolbar/toolbar.d.ts +28 -0
  70. package/dist/ui/toolbar/types.d.ts +87 -0
  71. package/dist/ui.css +635 -0
  72. package/dist/ui.js +1292 -0
  73. package/package.json +86 -4
@@ -0,0 +1,161 @@
1
+ /* Stilo content styles: ProseMirror base CSS and defaults for the editable content. */
2
+
3
+ /*
4
+ * prosemirror-view/style/prosemirror.css and prosemirror-gapcursor/style/gapcursor.css.
5
+ * Copyright (C) 2015-2017 by Marijn Haverbeke and others, MIT license. Adapted to the lint
6
+ * rules: no `white-space: pre-wrap` fallback before `break-spaces`, and the separator image
7
+ * rule gains specificity instead of `!important`.
8
+ */
9
+ .ProseMirror {
10
+ position: relative;
11
+ }
12
+
13
+ .ProseMirror {
14
+ word-wrap: break-word;
15
+ white-space: break-spaces;
16
+ -webkit-font-variant-ligatures: none;
17
+ font-variant-ligatures: none;
18
+ font-feature-settings: "liga" 0; /* the above doesn't seem to work in Edge */
19
+ }
20
+
21
+ .ProseMirror pre {
22
+ white-space: pre-wrap;
23
+ }
24
+
25
+ .ProseMirror li {
26
+ position: relative;
27
+ }
28
+
29
+ .ProseMirror-hideselection *::selection {
30
+ background: transparent;
31
+ }
32
+ .ProseMirror-hideselection *::-moz-selection {
33
+ background: transparent;
34
+ }
35
+ .ProseMirror-hideselection {
36
+ caret-color: transparent;
37
+ }
38
+
39
+ /* See https://code.haverbeke.berlin/prosemirror/prosemirror/issues/1421#issuecomment-1759320191 */
40
+ .ProseMirror [draggable][contenteditable="false"] {
41
+ user-select: text;
42
+ }
43
+
44
+ .ProseMirror-selectednode {
45
+ outline: 2px solid #8cf;
46
+ }
47
+
48
+ /* Make sure li selections wrap around markers */
49
+
50
+ li.ProseMirror-selectednode {
51
+ outline: none;
52
+ }
53
+
54
+ li.ProseMirror-selectednode:after {
55
+ content: "";
56
+ position: absolute;
57
+ left: -32px;
58
+ right: -2px;
59
+ top: -2px;
60
+ bottom: -2px;
61
+ border: 2px solid #8cf;
62
+ pointer-events: none;
63
+ }
64
+
65
+ /* Protect against generic img rules */
66
+
67
+ .ProseMirror img.ProseMirror-separator {
68
+ display: inline;
69
+ border: none;
70
+ margin: 0;
71
+ }
72
+
73
+ .ProseMirror-gapcursor {
74
+ display: none;
75
+ pointer-events: none;
76
+ position: absolute;
77
+ }
78
+
79
+ .ProseMirror-gapcursor:after {
80
+ content: "";
81
+ display: block;
82
+ position: absolute;
83
+ top: -2px;
84
+ width: 20px;
85
+ border-top: 1px solid black;
86
+ animation: ProseMirror-cursor-blink 1.1s steps(2, start) infinite;
87
+ }
88
+
89
+ @keyframes ProseMirror-cursor-blink {
90
+ to {
91
+ visibility: hidden;
92
+ }
93
+ }
94
+
95
+ .ProseMirror-focused .ProseMirror-gapcursor {
96
+ display: block;
97
+ }
98
+
99
+ /* Stilo content defaults */
100
+
101
+ /* Lists: list items hold paragraphs, which must not add their own margins. */
102
+ .stilo-content ul,
103
+ .stilo-content ol {
104
+ margin: 0.5em 0;
105
+ padding-left: 1.75em;
106
+ }
107
+
108
+ .stilo-content li > p,
109
+ .stilo-content li > ul,
110
+ .stilo-content li > ol {
111
+ margin: 0;
112
+ }
113
+
114
+ .stilo-content blockquote {
115
+ margin: 0.5em 0;
116
+ padding-left: 1em;
117
+ border-left: 3px solid var(--stilo-color-border, #d1d9e0);
118
+ color: var(--stilo-color-muted, #59636e);
119
+ }
120
+
121
+ .stilo-content hr {
122
+ margin: 1em 0;
123
+ border: none;
124
+ border-top: 1px solid var(--stilo-color-border, #d1d9e0);
125
+ }
126
+
127
+ .stilo-content code {
128
+ padding: 0.1em 0.3em;
129
+ border-radius: 0.25em;
130
+ background: var(--stilo-color-hover, #eef1f4);
131
+ font-family: ui-monospace, monospace;
132
+ font-size: 0.9em;
133
+ }
134
+
135
+ /* Placeholder: shown only while the content is editable. */
136
+ .stilo-content[contenteditable="true"] .stilo-placeholder::before {
137
+ content: attr(data-placeholder);
138
+ float: left;
139
+ height: 0;
140
+ color: var(--stilo-color-muted, #59636e);
141
+ pointer-events: none;
142
+ }
143
+
144
+ /*
145
+ * Large documents (ADR 0003): the browser skips style, layout and paint of the top-level blocks
146
+ * outside the viewport. Real typing in 10k paragraphs went from 144 ms to 24 ms per key (median,
147
+ * Chromium). A skipped block is 3em tall until it has been rendered once.
148
+ */
149
+ .stilo-content > * {
150
+ content-visibility: auto;
151
+ contain-intrinsic-size: auto 3em;
152
+ }
153
+
154
+ /* Containment stops margins from collapsing through a block: trim them inside a blockquote. */
155
+ .stilo-content blockquote > :first-child {
156
+ margin-top: 0;
157
+ }
158
+
159
+ .stilo-content blockquote > :last-child {
160
+ margin-bottom: 0;
161
+ }
@@ -0,0 +1,6 @@
1
+ import { Plugin } from 'prosemirror-state';
2
+ /**
3
+ * Makes a drag-and-drop move of whole textblocks behave as a move of blocks (ARCHITECTURE §8.2):
4
+ * dropped inside text they stay blocks, and the blocks they came from do not stay behind empty.
5
+ */
6
+ export declare function createDropMovePlugin(): Plugin;
@@ -0,0 +1,108 @@
1
+ import type { Schema } from 'prosemirror-model';
2
+ import { EditorState } from 'prosemirror-state';
3
+ import { EditorView } from 'prosemirror-view';
4
+ import { type EventHandler } from './events';
5
+ import type { CommandRunners, EditorEvents, EditorOptions, JSONContent, SetContentOptions } from './types';
6
+ /**
7
+ * The Stilo editor. See docs/ARCHITECTURE.md §5.
8
+ *
9
+ * ```ts
10
+ * const editor = new Editor({
11
+ * element: document.querySelector('#editor')!,
12
+ * extensions: [defaultExtensions()],
13
+ * content: '<p>Hello <strong>world</strong></p>',
14
+ * });
15
+ * ```
16
+ */
17
+ export declare class Editor {
18
+ /** The ProseMirror view (escape hatch). */
19
+ readonly view: EditorView;
20
+ /** The schema built from the extensions. */
21
+ readonly schema: Schema;
22
+ /**
23
+ * Runs a command: `editor.commands.toggleBold()` returns whether it applied;
24
+ * `editor.commands.toggleBold.can()` tells whether it would apply, without applying it.
25
+ */
26
+ readonly commands: CommandRunners;
27
+ private readonly manager;
28
+ private readonly plugins;
29
+ private readonly emitter;
30
+ private readonly compactParagraphs;
31
+ private destroyed;
32
+ /**
33
+ * Creates the editor:
34
+ *
35
+ * 1. `ExtensionManager` from `options.extensions` → `schema`;
36
+ * 2. `createPlugins(this)`, then the core `createDropMovePlugin()`; command runners are built
37
+ * from the manager's commands;
38
+ * 3. initial document from `parseHTML(schema, options.content ?? '')`;
39
+ * 4. `EditorView` mounted inside `options.element`, with attributes `class="stilo-content"`,
40
+ * `role="textbox"`, `aria-multiline="true"`, `aria-label` (if given); `editable` from
41
+ * options (default `true`); `transformPastedHTML` → `sanitizeToString`;
42
+ * `clipboardSerializer` → `createClipboardSerializer`; transactions go
43
+ * through the editor's dispatch (see events in `EditorEvents`);
44
+ * 5. `autofocus`: `true`/`'start'` → focus at start, `'end'` → focus at end.
45
+ */
46
+ constructor(options: EditorOptions);
47
+ /** Current editor state. */
48
+ get state(): EditorState;
49
+ /** `true` when the document is a single empty paragraph. */
50
+ get isEmpty(): boolean;
51
+ /** Whether the content is editable. */
52
+ get isEditable(): boolean;
53
+ /** Whether {@link destroy} has been called. */
54
+ get isDestroyed(): boolean;
55
+ /**
56
+ * Canonical HTML of the document (`serializeHTML`, honoring `compactParagraphs`). Empty
57
+ * document → `<p></p>`.
58
+ */
59
+ getHTML(): string;
60
+ /**
61
+ * Replaces the content with `html` (sanitized and parsed with `parseHTML`).
62
+ *
63
+ * - `resetHistory: false` (default): one transaction replacing the whole content
64
+ * (`tr.replaceWith(0, doc.content.size, newDoc.content)`), selection at document start;
65
+ * undoable as its own step (`closeHistory`: never grouped with the changes before it).
66
+ * - `resetHistory: true`: a new `EditorState` with the same plugins; `input` is emitted with
67
+ * `transaction: null`.
68
+ * - `emitInput: false` suppresses the `input` event.
69
+ */
70
+ setHTML(html: string, options?: SetContentOptions): void;
71
+ /** The document as ProseMirror JSON (`doc.toJSON()`). */
72
+ getJSON(): JSONContent;
73
+ /** Same as {@link setHTML}, from ProseMirror JSON (`Node.fromJSON`). */
74
+ setJSON(json: JSONContent, options?: SetContentOptions): void;
75
+ /**
76
+ * Plain text: `doc.textBetween(0, doc.content.size, '\n\n')`. Leaf nodes provide their own
77
+ * text through `leafText` in their node spec (hard break → `\n`).
78
+ */
79
+ getText(): string;
80
+ /**
81
+ * Whether a mark or node named `name` is active (`isMarkActive` / `isNodeActive`).
82
+ * Unknown names return `false`.
83
+ */
84
+ isActive(name: string, attrs?: Record<string, unknown>): boolean;
85
+ /**
86
+ * Attributes of the mark or node named `name` at the selection (`getMarkAttributes` /
87
+ * `getNodeAttributes`), e.g. the `href` of the link at the cursor. `{}` when there is none or
88
+ * the name is unknown.
89
+ */
90
+ getAttributes(name: string): Record<string, unknown>;
91
+ /** Switches read-only mode on/off. */
92
+ setEditable(editable: boolean): void;
93
+ /** Focuses the editor; optionally moves the cursor to the start or end of the document. */
94
+ focus(position?: 'start' | 'end'): void;
95
+ /** Removes focus from the editor. */
96
+ blur(): void;
97
+ /** Registers an event handler. */
98
+ on<E extends keyof EditorEvents>(event: E, handler: EventHandler<EditorEvents[E]>): void;
99
+ /** Removes an event handler. */
100
+ off<E extends keyof EditorEvents>(event: E, handler: EventHandler<EditorEvents[E]>): void;
101
+ /** Emits `destroy`, destroys the view, removes all handlers. Calling it twice is a no-op. */
102
+ destroy(): void;
103
+ private createState;
104
+ /** Builds `commands` from the extension commands; `can` runs without `dispatch`. */
105
+ private createRunners;
106
+ private setContent;
107
+ private dispatch;
108
+ }
@@ -0,0 +1,6 @@
1
+ /** Error thrown by Stilo for invalid configuration or API misuse. */
2
+ export declare class StiloError extends Error {
3
+ constructor(message: string);
4
+ }
5
+ /** Placeholder for functions whose implementation is assigned to a task. */
6
+ export declare function notImplemented(name: string): never;
@@ -0,0 +1,19 @@
1
+ /** A handler for an event payload. */
2
+ export type EventHandler<T> = (payload: T) => void;
3
+ /**
4
+ * Minimal typed event emitter.
5
+ *
6
+ * - `on` registers a handler; registering the same handler twice for the same event has no
7
+ * additional effect.
8
+ * - `off` removes a handler; removing an unknown handler is a no-op.
9
+ * - `emit` calls the handlers of the event in registration order. Handlers added or removed
10
+ * during an `emit` do not affect the current emit.
11
+ * - `clear` removes all handlers of all events.
12
+ */
13
+ export declare class EventEmitter<Events extends object> {
14
+ private handlers;
15
+ on<E extends keyof Events>(event: E, handler: EventHandler<Events[E]>): void;
16
+ off<E extends keyof Events>(event: E, handler: EventHandler<Events[E]>): void;
17
+ emit<E extends keyof Events>(event: E, payload: Events[E]): void;
18
+ clear(): void;
19
+ }
@@ -0,0 +1,57 @@
1
+ import { Schema } from 'prosemirror-model';
2
+ import type { Plugin } from 'prosemirror-state';
3
+ import type { Editor } from './editor';
4
+ import type { Extension } from './extension';
5
+ import type { CommandFactory } from './types';
6
+ /**
7
+ * Schema order of the built-in marks, outermost first. It defines how marks nest in the HTML
8
+ * output (docs/ARCHITECTURE.md §6.2). Marks not listed here come after these, in extension
9
+ * order.
10
+ */
11
+ export declare const MARK_ORDER: readonly string[];
12
+ /** Nodes every schema must contain. */
13
+ export declare const REQUIRED_NODES: readonly string[];
14
+ /**
15
+ * Flattens nested arrays one level and sorts extensions by `priority` (descending), keeping the
16
+ * order of appearance for equal priorities (stable sort).
17
+ */
18
+ export declare function sortExtensions(extensions: Array<Extension | Extension[]>): Extension[];
19
+ /**
20
+ * Builds the schema, plugins and commands from a list of extensions.
21
+ * See docs/ARCHITECTURE.md §6.2 for the rules.
22
+ */
23
+ export declare class ExtensionManager {
24
+ /** The extensions, flattened and sorted with {@link sortExtensions}. */
25
+ readonly extensions: readonly Extension[];
26
+ readonly schema: Schema;
27
+ private readonly commandFactories;
28
+ /**
29
+ * Sorts the extensions and creates the schema:
30
+ *
31
+ * - duplicate extension names → `StiloError`;
32
+ * - nodes from `spec.nodes(options)` in extension order; duplicate node name → `StiloError`;
33
+ * - marks from `spec.marks(options)`, ordered by {@link MARK_ORDER} then extension order;
34
+ * duplicate mark name → `StiloError`;
35
+ * - any of {@link REQUIRED_NODES} missing → `StiloError`;
36
+ * - `topNode` is `doc`.
37
+ */
38
+ constructor(extensions: Array<Extension | Extension[]>);
39
+ /**
40
+ * Command factories of all extensions, by command name. Empty until {@link createPlugins}
41
+ * has been called.
42
+ */
43
+ get commands(): ReadonlyMap<string, CommandFactory>;
44
+ /**
45
+ * Called once by the editor. For each extension in order, with context
46
+ * `{ options, schema, editor }`: collects `commands` (duplicate name → `StiloError`),
47
+ * `inputRules`, `keymap`, `plugins`. Returns, in this order:
48
+ *
49
+ * 1. one `inputRules({ rules })` plugin with all input rules;
50
+ * 2. `keymap({ Backspace: undoInputRule })`: it applies only right after an input rule, so it
51
+ * takes precedence over the extensions (Backspace after `- ` gives back `- `);
52
+ * 3. one `keymap()` plugin per extension that defines a keymap, in extension order;
53
+ * 4. all extension plugins, in extension order;
54
+ * 5. `keymap(baseKeymap)` from prosemirror-commands.
55
+ */
56
+ createPlugins(editor: Editor): Plugin[];
57
+ }
@@ -0,0 +1,43 @@
1
+ import type { InputRule } from 'prosemirror-inputrules';
2
+ import type { MarkSpec, NodeSpec, Schema } from 'prosemirror-model';
3
+ import type { Command, Plugin } from 'prosemirror-state';
4
+ import type { Editor } from './editor';
5
+ import type { CommandFactories } from './types';
6
+ /** Context passed to the runtime parts of an extension. */
7
+ export interface ExtensionContext<Options> {
8
+ options: Readonly<Options>;
9
+ schema: Schema;
10
+ editor: Editor;
11
+ }
12
+ /** Definition of an extension. See docs/ARCHITECTURE.md §6. */
13
+ export interface ExtensionSpec<Options extends object = Record<string, never>> {
14
+ /** Unique camelCase name, equal to the main node/mark of the extension. */
15
+ name: string;
16
+ /** Default 100. Higher = earlier in schema order and higher keymap precedence. */
17
+ priority?: number;
18
+ defaultOptions?: Options;
19
+ nodes?(options: Readonly<Options>): Record<string, NodeSpec>;
20
+ marks?(options: Readonly<Options>): Record<string, MarkSpec>;
21
+ commands?(ctx: ExtensionContext<Options>): Partial<CommandFactories>;
22
+ keymap?(ctx: ExtensionContext<Options>): Record<string, Command>;
23
+ inputRules?(ctx: ExtensionContext<Options>): InputRule[];
24
+ plugins?(ctx: ExtensionContext<Options>): Plugin[];
25
+ }
26
+ /** A configured extension instance, created by the factory returned by {@link defineExtension}. */
27
+ export interface Extension<Options extends object = object> {
28
+ readonly name: string;
29
+ readonly priority: number;
30
+ readonly options: Readonly<Options>;
31
+ readonly spec: ExtensionSpec<Options>;
32
+ }
33
+ /** Default extension priority. */
34
+ export declare const DEFAULT_PRIORITY = 100;
35
+ /**
36
+ * Defines an extension. Returns a factory that creates configured instances:
37
+ *
38
+ * ```ts
39
+ * export const Heading = defineExtension<{ levels: number[] }>({ name: 'heading', ... });
40
+ * const heading = Heading({ levels: [1, 2, 3] });
41
+ * ```
42
+ */
43
+ export declare function defineExtension<Options extends object = Record<string, never>>(spec: ExtensionSpec<Options>): (options?: Partial<Options>) => Extension<Options>;
@@ -0,0 +1,33 @@
1
+ import type { Attrs, MarkType, NodeType } from 'prosemirror-model';
2
+ import { type EditorState } from 'prosemirror-state';
3
+ /**
4
+ * Whether `actual` contains every key of `expected` with a strictly equal (`===`) value.
5
+ * `undefined` `expected` always matches.
6
+ */
7
+ export declare function attrsMatch(actual: Attrs, expected?: Record<string, unknown>): boolean;
8
+ /**
9
+ * Whether a mark of `type` (with matching `attrs`, see {@link attrsMatch}) is active:
10
+ *
11
+ * - empty selection: present in `state.storedMarks`, or if those are `null`, in
12
+ * `selection.$from.marks()`;
13
+ * - non-empty selection: every text node between `from` and `to` has the mark, and there is at
14
+ * least one text node. Traverse only the selected range (`doc.nodesBetween(from, to)`).
15
+ */
16
+ export declare function isMarkActive(state: EditorState, type: MarkType, attrs?: Record<string, unknown>): boolean;
17
+ /**
18
+ * Whether a node of `type` (with matching `attrs`) is active: the selection is a
19
+ * `NodeSelection` of such a node, or `selection.$from` has such an ancestor (any depth,
20
+ * including the document itself).
21
+ */
22
+ export declare function isNodeActive(state: EditorState, type: NodeType, attrs?: Record<string, unknown>): boolean;
23
+ /**
24
+ * Attributes of the mark of `type` at the selection, `{}` when there is none: for an empty
25
+ * selection the mark in `state.storedMarks` (or `$from.marks()`), otherwise the first one found
26
+ * in the selected range.
27
+ */
28
+ export declare function getMarkAttributes(state: EditorState, type: MarkType): Attrs;
29
+ /**
30
+ * Attributes of the node of `type` at the selection, `{}` when there is none: the selected node
31
+ * (`NodeSelection`), or the deepest ancestor of `selection.$from`.
32
+ */
33
+ export declare function getNodeAttributes(state: EditorState, type: NodeType): Attrs;
@@ -0,0 +1,21 @@
1
+ import { InputRule } from 'prosemirror-inputrules';
2
+ import type { Attrs, MarkType } from 'prosemirror-model';
3
+ /**
4
+ * Creates an input rule that turns markdown-like syntax into a mark.
5
+ *
6
+ * Contract for `regexp`: it ends with `$`; capture group 1 is the text to mark; the syntax
7
+ * characters immediately around group 1 (e.g. `**`) are part of the match, and the opening and
8
+ * closing syntax have the same length; an optional leading context (e.g. `(?:^|[^*])`) may
9
+ * precede the syntax but must be a **non-capturing** group that consumes at most one character.
10
+ *
11
+ * When the rule fires: the opening and closing syntax are deleted and the mark is added to the
12
+ * text (its other marks are kept); the leading context character (if any) is left untouched;
13
+ * the mark is removed from the stored marks so that typing continues unmarked. Returns `null`
14
+ * (rule does not apply) if group 1 is empty or the parent node does not allow the mark.
15
+ *
16
+ * ```ts
17
+ * markInputRule(/(?:^|[^*])(\*\*([^*]+)\*\*)$/, schema.marks.bold!) // NOT this: group 1 = text
18
+ * markInputRule(/(?:^|[^*])\*\*([^*]+)\*\*$/, schema.marks.bold!) // correct
19
+ * ```
20
+ */
21
+ export declare function markInputRule(regexp: RegExp, type: MarkType, attrs?: Attrs | null): InputRule;
@@ -0,0 +1,12 @@
1
+ import type { Attrs, NodeType } from 'prosemirror-model';
2
+ import type { Command } from 'prosemirror-state';
3
+ /**
4
+ * Command: toggles a list of `listType` (whose items are `itemType`) at the selection. List
5
+ * nodes are recognized by the `list` group in their spec. Looking at the nearest list around
6
+ * the start of the selection:
7
+ *
8
+ * - a list of `listType`: the selected items are lifted out one level (`liftListItem`);
9
+ * - a list of another type: that list becomes a `listType` list with `attrs`, items unchanged;
10
+ * - no list: the selected blocks are wrapped in a new list (`wrapInList`).
11
+ */
12
+ export declare function toggleList(listType: NodeType, itemType: NodeType, attrs?: Attrs | null): Command;
@@ -0,0 +1,38 @@
1
+ import type { Attrs, MarkType, ResolvedPos } from 'prosemirror-model';
2
+ import type { Command } from 'prosemirror-state';
3
+ /**
4
+ * Command: adds a mark of `type` with `attrs` to the selection. A mark that `type` excludes
5
+ * and that excludes `type` is replaced (superscript becomes subscript).
6
+ *
7
+ * - Empty selection: adds the mark to the stored marks (applies to the next typed text).
8
+ * Returns `false` if the parent node does not allow the mark, or a mark at the cursor
9
+ * excludes it (e.g. bold inside code).
10
+ * - Non-empty selection: adds the mark to every selection range. Returns `false` if no text in
11
+ * the selection accepts the mark (parent allows it, no existing mark excludes it). The
12
+ * transaction scrolls into view.
13
+ */
14
+ export declare function setMark(type: MarkType, attrs?: Attrs | null): Command;
15
+ /**
16
+ * Command: removes marks of `type` from the selection.
17
+ *
18
+ * - Empty selection: removes the mark from the stored marks. Returns `false` if the mark is
19
+ * not in the stored marks / cursor marks.
20
+ * - Non-empty selection: removes the mark from every range. Returns `false` if no range has the
21
+ * mark. The transaction scrolls into view.
22
+ */
23
+ export declare function unsetMark(type: MarkType): Command;
24
+ /**
25
+ * Command: {@link unsetMark} when the mark is active (see `isMarkActive`), otherwise
26
+ * {@link setMark}. Unlike `toggleMark` from prosemirror-commands, a partially marked selection
27
+ * gets the mark added, consistently with `isActive`.
28
+ */
29
+ export declare function toggleMark(type: MarkType, attrs?: Attrs | null): Command;
30
+ /**
31
+ * The range of the mark of `type` at `$pos` (the one in `$pos.marks()`), extended over the
32
+ * adjacent inline nodes that carry the same mark with the same attributes. `null` if there is no
33
+ * such mark at `$pos`. Lets a command edit or remove a whole link with the cursor inside it.
34
+ */
35
+ export declare function markRangeAt($pos: ResolvedPos, type: MarkType): {
36
+ from: number;
37
+ to: number;
38
+ } | null;
@@ -0,0 +1,14 @@
1
+ import type { Attrs, Node, NodeType } from 'prosemirror-model';
2
+ import { type Command, type Transaction } from 'prosemirror-state';
3
+ /**
4
+ * Inserts a block `node` in place of the selection and puts the cursor at the start of the
5
+ * textblock after it, creating a paragraph when no textblock follows. Used by commands and
6
+ * input rules that insert atom blocks (horizontal rule, figure).
7
+ */
8
+ export declare function insertBlockNode(tr: Transaction, node: Node): Transaction;
9
+ /**
10
+ * Command: inserts a block of `type` with {@link insertBlockNode}. Returns `false` where the
11
+ * container of the selection (the parent of the selected textblock, or the parent of a selected
12
+ * block node) does not allow `type`.
13
+ */
14
+ export declare function insertBlock(type: NodeType, attrs?: Attrs | null): Command;
@@ -0,0 +1,8 @@
1
+ import type { StyleParseRule } from 'prosemirror-model';
2
+ /**
3
+ * Parse rules that match a `text-decoration` / `text-decoration-line` style containing
4
+ * `keyword` (e.g. `underline`). Both properties are listed because ProseMirror reads only the
5
+ * properties named in rules. The rules are not consuming, so several marks can match one
6
+ * declaration (`text-decoration: underline line-through`).
7
+ */
8
+ export declare function textDecorationRules(keyword: string): StyleParseRule[];
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Inline style handling: allowlist, parsing, normalization and building of `style` strings.
3
+ * See docs/ARCHITECTURE.md §7.3. Extensions never handle raw style strings themselves.
4
+ */
5
+ /** CSS properties allowed in the HTML output, in output order. */
6
+ export declare const STYLE_PROPERTIES: readonly ['font-family', 'font-size', 'color', 'background-color', 'text-align'];
7
+ /** A property allowed in the HTML output. */
8
+ export type StyleProperty = (typeof STYLE_PROPERTIES)[number];
9
+ /**
10
+ * Properties accepted on input only: parse rules read them (e.g. `font-weight` → bold), but they
11
+ * never appear in the output.
12
+ */
13
+ export declare const INPUT_ONLY_STYLE_PROPERTIES: readonly ['font-weight', 'font-style', 'text-decoration', 'text-decoration-line', 'vertical-align'];
14
+ /** CSS-wide keywords rejected as color values. */
15
+ export declare const CSS_WIDE_KEYWORDS: readonly string[];
16
+ /**
17
+ * Values containing any of these are always rejected: `url(`, `expression(` (case-insensitive,
18
+ * optional spaces before `(`), `javascript:`, and the characters `<`, `>`, `{`, `}`, `\`.
19
+ */
20
+ export declare const UNSAFE_VALUE: RegExp;
21
+ /**
22
+ * Parses a `style` attribute into a map of property → value.
23
+ *
24
+ * - Declarations are separated by `;`; property and value by the first `:`.
25
+ * - Properties are trimmed and lowercased; values are trimmed and `!important` is removed.
26
+ * - Declarations without `:` or with an empty property/value are skipped.
27
+ * - When a property appears twice, the last one wins.
28
+ */
29
+ export declare function parseStyle(style: string): Map<string, string>;
30
+ /**
31
+ * Normalizes a color value (input is trimmed and lowercased first):
32
+ *
33
+ * - `#rgb` → `#rrggbb`; `#rrggbb` kept (lowercase).
34
+ * - `rgb(r, g, b)` (channels 0–255, any spacing) → `#rrggbb`.
35
+ * - `rgba(r, g, b, a)` with `a` = 1 (or 100%) → `#rrggbb`; with 0 ≤ `a` < 1 →
36
+ * `rgba(r, g, b, a)` with `, ` separators and `a` rounded to 3 decimals.
37
+ * - A keyword matching `^[a-z]{3,20}$` is kept, unless it is in {@link CSS_WIDE_KEYWORDS}.
38
+ * - Anything else (channel > 255, alpha out of range, other formats) → `null`.
39
+ */
40
+ export declare function normalizeColor(value: string): string | null;
41
+ /**
42
+ * Normalizes a font size: trimmed and lowercased, it must match
43
+ * `^\d+(\.\d+)?(px|pt|em|rem|%)$`, otherwise `null`.
44
+ */
45
+ export declare function normalizeFontSize(value: string): string | null;
46
+ /**
47
+ * Normalizes a font family list:
48
+ *
49
+ * - split by `,`; each name trimmed, surrounding `"` or `'` removed, inner whitespace collapsed;
50
+ * - empty names and names containing `"`, `'`, `<`, `>`, `;` are dropped;
51
+ * - names made only of letters, digits and `-` stay unquoted, others are wrapped in `'`;
52
+ * - joined with `, `. Returns `null` if no name remains.
53
+ *
54
+ * Example: `"Times New Roman" ,serif` → `'Times New Roman', serif`.
55
+ */
56
+ export declare function normalizeFontFamily(value: string): string | null;
57
+ /**
58
+ * Normalizes `text-align` (trimmed, lowercased): `left`, `center`, `right`, `justify` kept;
59
+ * `start` → `left`; anything else → `null`.
60
+ */
61
+ export declare function normalizeTextAlign(value: string): string | null;
62
+ /**
63
+ * Normalizes the value of an output property with the matching normalizer above.
64
+ * Returns `null` if the value matches {@link UNSAFE_VALUE} or the normalizer returns `null`.
65
+ */
66
+ export declare function normalizeStyleValue(property: StyleProperty, value: string): string | null;
67
+ /**
68
+ * Builds a canonical `style` string: only {@link STYLE_PROPERTIES}, in that order, values
69
+ * normalized with {@link normalizeStyleValue} (invalid ones skipped), formatted as
70
+ * `prop: value` joined with `; ` and no trailing `;`. `null`/`undefined` values are skipped.
71
+ * Returns `null` if nothing remains.
72
+ *
73
+ * Example: `{ color: 'RGB(255,0,0)', 'font-size': '12PT' }` → `font-size: 12pt; color: #ff0000`.
74
+ */
75
+ export declare function buildStyle(values: Partial<Record<StyleProperty, string | null | undefined>>): string | null;
76
+ /**
77
+ * Reads one output property from an element's `style` attribute (via {@link parseStyle}) and
78
+ * normalizes it (via {@link normalizeStyleValue}). Returns `null` if absent or invalid.
79
+ * Use this in parse rules (`getAttrs`).
80
+ */
81
+ export declare function readStyle(element: HTMLElement, property: StyleProperty): string | null;
82
+ /**
83
+ * Filters a `style` attribute for sanitization:
84
+ *
85
+ * 1. the output properties, normalized, in {@link STYLE_PROPERTIES} order (invalid dropped);
86
+ * 2. then the {@link INPUT_ONLY_STYLE_PROPERTIES}, in that order, kept only if the value matches
87
+ * `^[a-z0-9 .,%#()-]+$` (case-insensitive) and not {@link UNSAFE_VALUE}; value lowercased.
88
+ *
89
+ * Formatted like {@link buildStyle}. Returns `null` if nothing remains.
90
+ */
91
+ export declare function sanitizeStyle(style: string): string | null;
@@ -0,0 +1,7 @@
1
+ import { type Node, type Schema } from 'prosemirror-model';
2
+ /**
3
+ * Parses HTML into a ProseMirror document: the HTML is sanitized with `sanitizeToDOM` and the
4
+ * resulting element is parsed with `DOMParser.fromSchema(schema)` from prosemirror-model.
5
+ * An empty string produces the empty document (one empty paragraph).
6
+ */
7
+ export declare function parseHTML(schema: Schema, html: string): Node;