stilo-editor 0.1.0 → 0.2.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 +61 -7
- package/dist/content.css +238 -1
- package/dist/core/editor.d.ts +15 -3
- package/dist/core/extension-manager.d.ts +3 -3
- package/dist/core/extension.d.ts +6 -1
- package/dist/core/helpers/block-style.d.ts +25 -0
- package/dist/core/helpers/input-rules.d.ts +8 -1
- package/dist/core/helpers/lists.d.ts +10 -2
- package/dist/core/helpers/marks.d.ts +14 -0
- package/dist/core/helpers/nodes.d.ts +12 -0
- package/dist/core/helpers/word-count.d.ts +14 -0
- package/dist/core/html/css.d.ts +16 -1
- package/dist/core/html/paste/base-styles.d.ts +7 -0
- package/dist/core/html/paste/google-docs.d.ts +7 -0
- package/dist/core/html/paste/index.d.ts +9 -0
- package/dist/core/html/paste/table-widths.d.ts +7 -0
- package/dist/core/html/paste/word.d.ts +8 -0
- package/dist/core/html/serialize.d.ts +2 -2
- package/dist/extensions/code-block/index.d.ts +24 -0
- package/dist/extensions/default-extensions/index.d.ts +11 -0
- package/dist/extensions/heading/index.d.ts +2 -1
- package/dist/extensions/image/image-view.d.ts +27 -0
- package/dist/extensions/image/index.d.ts +29 -0
- package/dist/extensions/index.d.ts +10 -1
- package/dist/extensions/line-height/index.d.ts +18 -0
- package/dist/extensions/list-item/index.d.ts +1 -1
- package/dist/extensions/paragraph/index.d.ts +4 -4
- package/dist/extensions/search/index.d.ts +55 -0
- package/dist/extensions/table/index.d.ts +70 -0
- package/dist/extensions/task-item/index.d.ts +24 -0
- package/dist/extensions/task-item/task-item-view.d.ts +24 -0
- package/dist/extensions/task-list/index.d.ts +11 -0
- package/dist/extensions/text-align/index.d.ts +18 -0
- package/dist/extensions/text-case/index.d.ts +14 -0
- package/dist/extensions/text-style/font-size.d.ts +21 -0
- package/dist/extensions/text-style/index.d.ts +18 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -1
- package/dist/src-CV6pgfTS.js +2662 -0
- package/dist/style.css +599 -1
- package/dist/ui/components/dropdown.d.ts +9 -0
- package/dist/ui/components/find-bar.d.ts +24 -0
- package/dist/ui/components/font-size-input.d.ts +21 -0
- package/dist/ui/components/icon-menu.d.ts +20 -0
- package/dist/ui/components/status-bar.d.ts +19 -0
- package/dist/ui/components/table-actions.d.ts +25 -0
- package/dist/ui/components/table-handles.d.ts +16 -0
- package/dist/ui/components/table-menu.d.ts +21 -0
- package/dist/ui/i18n/types.d.ts +1 -1
- package/dist/ui/index.d.ts +6 -2
- package/dist/ui/toolbar/fonts.d.ts +8 -0
- package/dist/ui/toolbar/items.d.ts +23 -2
- package/dist/ui/toolbar/types.d.ts +67 -4
- package/dist/ui.css +361 -0
- package/dist/ui.js +1361 -303
- package/package.json +18 -8
- package/dist/src-3tAxKI0c.js +0 -1316
package/README.md
CHANGED
|
@@ -1,13 +1,27 @@
|
|
|
1
|
-
<
|
|
1
|
+
<p align="center">
|
|
2
2
|
<picture>
|
|
3
3
|
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/kojak8791/stilo-editor/main/assets/logo-dark.svg">
|
|
4
4
|
<img src="https://raw.githubusercontent.com/kojak8791/stilo-editor/main/assets/logo.svg" alt="Stilo" width="300">
|
|
5
5
|
</picture>
|
|
6
|
-
</
|
|
6
|
+
</p>
|
|
7
|
+
|
|
8
|
+
<p align="center">
|
|
9
|
+
<a href="https://www.npmjs.com/package/stilo-editor"><img src="https://img.shields.io/npm/v/stilo-editor" alt="npm version"></a>
|
|
10
|
+
<a href="LICENSE"><img src="https://img.shields.io/npm/l/stilo-editor" alt="MIT license"></a>
|
|
11
|
+
<img src="https://img.shields.io/npm/types/stilo-editor" alt="TypeScript types included">
|
|
12
|
+
</p>
|
|
7
13
|
|
|
8
14
|
Open-source HTML5 WYSIWYG editor built on [ProseMirror](https://prosemirror.net).
|
|
9
15
|
Clean HTML in and out, headless core, no paid tiers.
|
|
10
16
|
|
|
17
|
+
<p align="center">
|
|
18
|
+
<img src="https://raw.githubusercontent.com/kojak8791/stilo-editor/main/assets/screenshot.png" alt="Stilo with the default toolbar: formatted text, a checklist, a table and the link popover open" width="800">
|
|
19
|
+
</p>
|
|
20
|
+
|
|
21
|
+
<h2 align="center">
|
|
22
|
+
<a href="https://kojak8791.github.io/stilo-editor/">See it in action: try the live demo →</a>
|
|
23
|
+
</h2>
|
|
24
|
+
|
|
11
25
|
> [!WARNING]
|
|
12
26
|
> **Preview release.** Versions `0.x` are early previews: the editor works, but APIs may change
|
|
13
27
|
> in any minor version until `1.0.0`. Read the release notes before updating.
|
|
@@ -15,10 +29,10 @@ Clean HTML in and out, headless core, no paid tiers.
|
|
|
15
29
|
## Why Stilo
|
|
16
30
|
|
|
17
31
|
- **Built for very large documents.** Typing stays immediate in a document longer than most
|
|
18
|
-
novels (see [Large documents](#large-documents)).
|
|
19
|
-
budgets, not assumed.
|
|
20
|
-
- **Entirely open source (MIT).** Aiming at the feature level of
|
|
21
|
-
tiers and no closed "pro" extensions.
|
|
32
|
+
novels (see [Large documents](https://github.com/kojak8791/stilo-editor#large-documents)).
|
|
33
|
+
Measured in a real browser, checked against budgets, not assumed.
|
|
34
|
+
- **Entirely open source (MIT).** Aiming at the feature level of the established rich text
|
|
35
|
+
editors, with no paid tiers and no closed "pro" extensions.
|
|
22
36
|
- **HTML is the public format.** Content goes in as HTML and comes out as clean, semantic,
|
|
23
37
|
self-contained HTML, with a strict allowlist for tags, attributes and inline styles.
|
|
24
38
|
- **Headless core, optional UI.** The core works without any UI; the default toolbar and menus are
|
|
@@ -55,28 +69,67 @@ column is what you type to get the format as you write.
|
|
|
55
69
|
| Inline code | <kbd>Ctrl</kbd>+<kbd>E</kbd> | `` `text` `` | |
|
|
56
70
|
| Subscript | <kbd>Ctrl</kbd>+<kbd>,</kbd> | | Subscript and superscript exclude each other |
|
|
57
71
|
| Superscript | <kbd>Ctrl</kbd>+<kbd>.</kbd> | | |
|
|
72
|
+
| Font family | | | Toolbar dropdown: generic families (sans serif, serif, monospace) and common fonts, each shown in its own font; a font that is not in the list (e.g. pasted from Word) is still shown |
|
|
73
|
+
| Font size | | | − and + change the size by one point; the field between them shows the size, takes a typed one and opens a list. Unit pt by default (px or rem as an option) |
|
|
58
74
|
| Text color | | | The button applies the last color; its arrow opens a palette, "No color" and a custom color |
|
|
59
75
|
| Highlight | <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>H</kbd> | `==text==` | Same button as text color; the shortcut applies the default highlight |
|
|
60
76
|
| Clear formatting | <kbd>Ctrl</kbd>+<kbd>\\</kbd> | | Links stay |
|
|
77
|
+
| Text case | | | A menu: UPPERCASE, lowercase, Title Case, Sentence case, tOGGLE cASE; the letters change, the formatting stays |
|
|
61
78
|
| **Links** | | | |
|
|
62
79
|
| Link | <kbd>Ctrl</kbd>+<kbd>K</kbd> | | A popover sets the URL and "open in new tab"; on a link, a small bubble offers open, edit and remove |
|
|
63
80
|
| **Blocks** | | | |
|
|
64
81
|
| Paragraph | <kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>0</kbd> | | |
|
|
65
82
|
| Headings 1–6 | <kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>1</kbd>…<kbd>6</kbd> | `# ` … `###### ` | Also in the toolbar dropdown |
|
|
66
83
|
| Quote | <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>B</kbd> | `> ` | |
|
|
84
|
+
| Code block | <kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>C</kbd> | ```` ``` ```` + space, ```` ```js ```` + space for a language | Plain text in a monospace font, no formatting; the selected paragraphs become one block, a line each, and back. <kbd>Enter</kbd> on an empty last line, or <kbd>Ctrl</kbd>+<kbd>Enter</kbd>, leaves it. The language is written as `class="language-js"` for syntax highlighters |
|
|
67
85
|
| Horizontal line | | `---` | |
|
|
68
86
|
| Line break | <kbd>Shift</kbd>+<kbd>Enter</kbd> | | New line in the same paragraph |
|
|
87
|
+
| Align left / center / right / justify | <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>L</kbd> / <kbd>E</kbd> / <kbd>R</kbd> / <kbd>J</kbd> | | Paragraphs and headings; the toolbar button opens the four alignments; changing the block type (also with `# `) keeps the alignment |
|
|
88
|
+
| Line spacing | | | Paragraphs and headings; the toolbar menu offers Default, 1, 1.15, 1.5 and 2; changing the block type keeps it |
|
|
69
89
|
| Move block up / down | <kbd>Alt</kbd>+<kbd>↑</kbd> / <kbd>Alt</kbd>+<kbd>↓</kbd> | | The block or list item at the cursor, or all the selected ones; sub-items move along |
|
|
70
90
|
| **Lists** | | | |
|
|
71
91
|
| Bulleted list | <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>8</kbd> | `- ` `* ` `+ ` | |
|
|
72
92
|
| Numbered list | <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>7</kbd> | `1. ` | Starts from the number you type |
|
|
93
|
+
| Checklist | <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>9</kbd> | `[ ] ` `[x] ` | Click a checkbox, or press <kbd>Ctrl</kbd>+<kbd>Enter</kbd> to check the selected items; checked items are struck through |
|
|
73
94
|
| Indent / outdent item | <kbd>Tab</kbd> / <kbd>Shift</kbd>+<kbd>Tab</kbd> | | |
|
|
95
|
+
| **Tables** | | | |
|
|
96
|
+
| Insert a table | | | Toolbar grid up to 10 × 8, with or without a header row |
|
|
97
|
+
| Rows, columns, cells | | | With the cursor in a table, the same button opens: insert or delete rows and columns, merge and split cells, header row and column, delete table |
|
|
98
|
+
| Table handles | | | Handles on the row and column under the mouse select them and open their menu (insert, delete); a corner handle opens merge, split and delete table |
|
|
99
|
+
| Move between cells | <kbd>Tab</kbd> / <kbd>Shift</kbd>+<kbd>Tab</kbd> | | <kbd>Tab</kbd> in the last cell adds a row |
|
|
100
|
+
| Select cells | | | Drag across cells |
|
|
101
|
+
| Resize a column | | | Drag the border of a column; the width is kept in the HTML |
|
|
102
|
+
| **Images** | | | |
|
|
103
|
+
| Paste an image | <kbd>Ctrl</kbd>+<kbd>V</kbd> | | A copied image or screenshot is embedded in the HTML (PNG, JPEG, GIF, WebP, up to 5 MB by default); an image copied from a web page keeps its address |
|
|
104
|
+
| Resize an image | | | Select it and drag one of its side handles; the proportions stay |
|
|
74
105
|
| **Insert** | | | |
|
|
75
106
|
| Special characters and emoji | | | A popover with search, symbols and emoji; it stays open to insert several in a row |
|
|
76
107
|
| **Editing** | | | |
|
|
77
108
|
| Undo | <kbd>Ctrl</kbd>+<kbd>Z</kbd> | | <kbd>Backspace</kbd> right after a Markdown shortcut undoes it |
|
|
78
109
|
| Redo | <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>Z</kbd> or <kbd>Ctrl</kbd>+<kbd>Y</kbd> | | |
|
|
79
|
-
| Paste | <kbd>Ctrl</kbd>+<kbd>V</kbd> | | HTML is cleaned up; plain text lines become paragraphs |
|
|
110
|
+
| Paste | <kbd>Ctrl</kbd>+<kbd>V</kbd> | | HTML is cleaned up; plain text lines become paragraphs. From Word and Google Docs the body font, size and color are dropped (text that differs keeps them) and Word lists become real lists; tables keep their column widths |
|
|
111
|
+
| Paste as plain text | <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>V</kbd> | | Only the text, without formatting |
|
|
112
|
+
| Find and replace | <kbd>Ctrl</kbd>+<kbd>F</kbd> | | A bar under the toolbar: match case, whole word, previous and next match, replace one or all; the matches are counted exactly and the first 1,000 are highlighted; <kbd>Enter</kbd> / <kbd>Shift</kbd>+<kbd>Enter</kbd> go to the next / previous match, <kbd>Esc</kbd> closes it |
|
|
113
|
+
| Word and character count | | | Optional status bar under the editor; also `getWordCount()` in the API |
|
|
114
|
+
|
|
115
|
+
<table>
|
|
116
|
+
<tr>
|
|
117
|
+
<td width="50%"><img src="https://raw.githubusercontent.com/kojak8791/stilo-editor/main/assets/screenshot-find.png" alt="The find bar under the toolbar: five matches of espresso highlighted, the current one in orange, and ristretto in the replace field" width="400"></td>
|
|
118
|
+
<td width="50%"><img src="https://raw.githubusercontent.com/kojak8791/stilo-editor/main/assets/screenshot-table.png" alt="A coffee menu table with a row selected by its handle and the row menu open: insert row above, insert row below, delete row" width="400"></td>
|
|
119
|
+
</tr>
|
|
120
|
+
<tr>
|
|
121
|
+
<td>Find and replace: match case, whole word, an exact count of the matches.</td>
|
|
122
|
+
<td>Tables: handles on rows and columns open their menu.</td>
|
|
123
|
+
</tr>
|
|
124
|
+
<tr>
|
|
125
|
+
<td width="50%"><img src="https://raw.githubusercontent.com/kojak8791/stilo-editor/main/assets/screenshot-fonts.png" alt="The font dropdown open on a Times New Roman text, each font drawn in its own font" width="400"></td>
|
|
126
|
+
<td width="50%"><img src="https://raw.githubusercontent.com/kojak8791/stilo-editor/main/assets/screenshot-color.png" alt="The text color palette open, with No color, the color grid and a custom color field" width="400"></td>
|
|
127
|
+
</tr>
|
|
128
|
+
<tr>
|
|
129
|
+
<td>Fonts and sizes: the toolbar shows the font and the size at the cursor.</td>
|
|
130
|
+
<td>Text color and highlight: a palette, the last color one click away, a custom color.</td>
|
|
131
|
+
</tr>
|
|
132
|
+
</table>
|
|
80
133
|
|
|
81
134
|
**Keyboard and accessibility.** Most shortcuts are the ones of Google Docs; the others follow
|
|
82
135
|
the usual conventions of web editors, and every toolbar tooltip shows its shortcut. The toolbar
|
|
@@ -161,6 +214,7 @@ Requires Node.js 22 or later.
|
|
|
161
214
|
npm install
|
|
162
215
|
npm run check # typecheck + lint + unit tests
|
|
163
216
|
npm run dev # demo page
|
|
217
|
+
npm run site # public site (published on GitHub Pages from main)
|
|
164
218
|
npm run bench # performance benchmarks (needs: npx playwright install chromium)
|
|
165
219
|
npm run test:e2e # E2E tests in Chromium, Firefox and WebKit (needs: npx playwright install)
|
|
166
220
|
```
|
package/dist/content.css
CHANGED
|
@@ -96,6 +96,60 @@ li.ProseMirror-selectednode:after {
|
|
|
96
96
|
display: block;
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
+
/*
|
|
100
|
+
* prosemirror-tables/style/tables.css. Copyright (C) 2015-2016 by Marijn Haverbeke and others,
|
|
101
|
+
* MIT license. Adapted: one `cursor` value, the colors from the Stilo custom properties.
|
|
102
|
+
*/
|
|
103
|
+
.ProseMirror .tableWrapper {
|
|
104
|
+
overflow-x: auto;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.ProseMirror table {
|
|
108
|
+
border-collapse: collapse;
|
|
109
|
+
table-layout: fixed;
|
|
110
|
+
width: 100%;
|
|
111
|
+
overflow: hidden;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.ProseMirror td,
|
|
115
|
+
.ProseMirror th {
|
|
116
|
+
vertical-align: top;
|
|
117
|
+
box-sizing: border-box;
|
|
118
|
+
position: relative;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.ProseMirror td:not([data-colwidth]):not(.column-resize-dragging),
|
|
122
|
+
.ProseMirror th:not([data-colwidth]):not(.column-resize-dragging) {
|
|
123
|
+
min-width: var(--default-cell-min-width);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.ProseMirror .column-resize-handle {
|
|
127
|
+
position: absolute;
|
|
128
|
+
right: -2px;
|
|
129
|
+
top: 0;
|
|
130
|
+
bottom: 0;
|
|
131
|
+
width: 4px;
|
|
132
|
+
z-index: 20;
|
|
133
|
+
background-color: var(--stilo-color-focus, #0969da);
|
|
134
|
+
pointer-events: none;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.ProseMirror.resize-cursor {
|
|
138
|
+
cursor: col-resize;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.ProseMirror .selectedCell:after {
|
|
142
|
+
z-index: 2;
|
|
143
|
+
position: absolute;
|
|
144
|
+
content: "";
|
|
145
|
+
left: 0;
|
|
146
|
+
right: 0;
|
|
147
|
+
top: 0;
|
|
148
|
+
bottom: 0;
|
|
149
|
+
background: var(--stilo-color-selected-cell, rgb(9 105 218 / 0.12));
|
|
150
|
+
pointer-events: none;
|
|
151
|
+
}
|
|
152
|
+
|
|
99
153
|
/* Stilo content defaults */
|
|
100
154
|
|
|
101
155
|
/* Lists: list items hold paragraphs, which must not add their own margins. */
|
|
@@ -107,10 +161,81 @@ li.ProseMirror-selectednode:after {
|
|
|
107
161
|
|
|
108
162
|
.stilo-content li > p,
|
|
109
163
|
.stilo-content li > ul,
|
|
110
|
-
.stilo-content li > ol
|
|
164
|
+
.stilo-content li > ol,
|
|
165
|
+
.stilo-content .stilo-task-item__content > p,
|
|
166
|
+
.stilo-content .stilo-task-item__content > ul,
|
|
167
|
+
.stilo-content .stilo-task-item__content > ol {
|
|
111
168
|
margin: 0;
|
|
112
169
|
}
|
|
113
170
|
|
|
171
|
+
/* Task lists: a checkbox in place of the bullet, done items struck through. In the editor the
|
|
172
|
+
item is a flex row: its <input> checkbox, then the content (ProseMirror needs a container that
|
|
173
|
+
holds only the content). Content shown elsewhere under the class stilo-content gets a box drawn
|
|
174
|
+
from data-checked instead. Either way the box is centered on the first line, not on the whole
|
|
175
|
+
item, which may hold more paragraphs and sub-lists. */
|
|
176
|
+
.stilo-content ul[data-type="taskList"] {
|
|
177
|
+
list-style: none;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.stilo-content.ProseMirror ul[data-type="taskList"] {
|
|
181
|
+
padding-left: 0.25em;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.stilo-content.ProseMirror ul[data-type="taskList"] > li {
|
|
185
|
+
display: flex;
|
|
186
|
+
gap: 0.5em;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.stilo-task-item__content {
|
|
190
|
+
flex: 1;
|
|
191
|
+
min-width: 0;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/* Form controls have their own font: inherit it, so that em and lh are those of the text. */
|
|
195
|
+
.stilo-task-item__checkbox {
|
|
196
|
+
flex: none;
|
|
197
|
+
width: 1em;
|
|
198
|
+
height: 1em;
|
|
199
|
+
margin: calc(0.5lh - 0.5em) 0 0;
|
|
200
|
+
font-size: inherit;
|
|
201
|
+
line-height: inherit;
|
|
202
|
+
accent-color: var(--stilo-color-focus, #0969da);
|
|
203
|
+
cursor: pointer;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.stilo-content:not(.ProseMirror) ul[data-type="taskList"] > li {
|
|
207
|
+
position: relative;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
.stilo-content:not(.ProseMirror) ul[data-type="taskList"] > li::before {
|
|
211
|
+
content: "";
|
|
212
|
+
display: flex;
|
|
213
|
+
position: absolute;
|
|
214
|
+
top: calc(0.5lh - 0.5em);
|
|
215
|
+
left: -1.5em;
|
|
216
|
+
box-sizing: border-box;
|
|
217
|
+
width: 1em;
|
|
218
|
+
height: 1em;
|
|
219
|
+
align-items: center;
|
|
220
|
+
justify-content: center;
|
|
221
|
+
border: 1px solid var(--stilo-color-muted, #59636e);
|
|
222
|
+
border-radius: 0.2em;
|
|
223
|
+
color: #ffffff;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
.stilo-content:not(.ProseMirror) ul[data-type="taskList"] > li[data-checked="true"]::before {
|
|
227
|
+
content: "\2713";
|
|
228
|
+
border-color: var(--stilo-color-focus, #0969da);
|
|
229
|
+
background: var(--stilo-color-focus, #0969da);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
.stilo-content [data-checked="true"] > .stilo-task-item__content > p:first-child,
|
|
233
|
+
.stilo-content:not(.ProseMirror) [data-checked="true"] > p:first-child,
|
|
234
|
+
.stilo-content:not(.ProseMirror) [data-checked="true"]:not(:has(> p)) {
|
|
235
|
+
color: var(--stilo-color-muted, #59636e);
|
|
236
|
+
text-decoration: line-through;
|
|
237
|
+
}
|
|
238
|
+
|
|
114
239
|
.stilo-content blockquote {
|
|
115
240
|
margin: 0.5em 0;
|
|
116
241
|
padding-left: 1em;
|
|
@@ -132,6 +257,109 @@ li.ProseMirror-selectednode:after {
|
|
|
132
257
|
font-size: 0.9em;
|
|
133
258
|
}
|
|
134
259
|
|
|
260
|
+
/* Code block: the <code> inside it drops the inline code box. */
|
|
261
|
+
.stilo-content pre {
|
|
262
|
+
margin: 0.5em 0;
|
|
263
|
+
padding: 0.75em 1em;
|
|
264
|
+
border-radius: 0.375em;
|
|
265
|
+
background: var(--stilo-color-hover, #eef1f4);
|
|
266
|
+
font-family: ui-monospace, monospace;
|
|
267
|
+
font-size: 0.9em;
|
|
268
|
+
line-height: 1.5;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
.stilo-content pre code {
|
|
272
|
+
padding: 0;
|
|
273
|
+
border-radius: 0;
|
|
274
|
+
background: none;
|
|
275
|
+
font-size: inherit;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/* Tables: a cell holds blocks, whose outer margins would pad the cell twice. */
|
|
279
|
+
.stilo-content .tableWrapper {
|
|
280
|
+
margin: 1em 0;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
.stilo-content td,
|
|
284
|
+
.stilo-content th {
|
|
285
|
+
padding: 6px 8px;
|
|
286
|
+
border: 1px solid var(--stilo-color-border, #d1d9e0);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
.stilo-content th {
|
|
290
|
+
background: var(--stilo-color-table-header, #f6f8fa);
|
|
291
|
+
font-weight: 600;
|
|
292
|
+
text-align: start;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
.stilo-content td > :first-child,
|
|
296
|
+
.stilo-content th > :first-child {
|
|
297
|
+
margin-top: 0;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/* The resize handle shown on hover is the last child of its cell: the block before it is the last
|
|
301
|
+
block, otherwise its margin would come back and the table grow under the mouse. */
|
|
302
|
+
.stilo-content td > :last-child,
|
|
303
|
+
.stilo-content th > :last-child,
|
|
304
|
+
.stilo-content td > :has(+ .column-resize-handle:last-child),
|
|
305
|
+
.stilo-content th > :has(+ .column-resize-handle:last-child) {
|
|
306
|
+
margin-bottom: 0;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/* Images: never wider than the content; with width and height they keep their ratio. */
|
|
310
|
+
.stilo-content img {
|
|
311
|
+
max-width: 100%;
|
|
312
|
+
height: auto;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/* Image in the editor: two side handles, inside the image (a top-level block clips what
|
|
316
|
+
overflows it, see "Large documents" below), shown while the image is selected. Only a selected
|
|
317
|
+
image is positioned: a positioned element paints over the caret placed right after it. */
|
|
318
|
+
.stilo-image {
|
|
319
|
+
display: inline-block;
|
|
320
|
+
max-width: 100%;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
.stilo-image.ProseMirror-selectednode {
|
|
324
|
+
position: relative;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
.stilo-image img {
|
|
328
|
+
display: block;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
.stilo-image__handle {
|
|
332
|
+
display: none;
|
|
333
|
+
position: absolute;
|
|
334
|
+
top: 50%;
|
|
335
|
+
width: 6px;
|
|
336
|
+
height: min(48px, 50%);
|
|
337
|
+
min-height: 16px;
|
|
338
|
+
transform: translateY(-50%);
|
|
339
|
+
border: 1px solid #ffffff;
|
|
340
|
+
border-radius: 3px;
|
|
341
|
+
background: var(--stilo-color-text, #1f2328);
|
|
342
|
+
opacity: 0.7;
|
|
343
|
+
cursor: ew-resize;
|
|
344
|
+
touch-action: none;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
.stilo-image__handle--left {
|
|
348
|
+
left: 6px;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
.stilo-image__handle--right {
|
|
352
|
+
right: 6px;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
.stilo-content[contenteditable="true"] .stilo-image.ProseMirror-selectednode .stilo-image__handle {
|
|
356
|
+
display: block;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
.stilo-image__handle:hover {
|
|
360
|
+
opacity: 1;
|
|
361
|
+
}
|
|
362
|
+
|
|
135
363
|
/* Placeholder: shown only while the content is editable. */
|
|
136
364
|
.stilo-content[contenteditable="true"] .stilo-placeholder::before {
|
|
137
365
|
content: attr(data-placeholder);
|
|
@@ -159,3 +387,12 @@ li.ProseMirror-selectednode:after {
|
|
|
159
387
|
.stilo-content blockquote > :last-child {
|
|
160
388
|
margin-bottom: 0;
|
|
161
389
|
}
|
|
390
|
+
|
|
391
|
+
/* Find and replace (T-043): the matches and the selected one */
|
|
392
|
+
.stilo-search-match {
|
|
393
|
+
background: var(--stilo-color-search-match, #fff1a8);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
.stilo-search-match--current {
|
|
397
|
+
background: var(--stilo-color-search-current, #ffb347);
|
|
398
|
+
}
|
package/dist/core/editor.d.ts
CHANGED
|
@@ -3,6 +3,12 @@ import { EditorState } from 'prosemirror-state';
|
|
|
3
3
|
import { EditorView } from 'prosemirror-view';
|
|
4
4
|
import { type EventHandler } from './events';
|
|
5
5
|
import type { CommandRunners, EditorEvents, EditorOptions, JSONContent, SetContentOptions } from './types';
|
|
6
|
+
/**
|
|
7
|
+
* Transaction meta key of the empty transaction applied to every new `EditorState`: plugins can
|
|
8
|
+
* normalize the loaded document in `appendTransaction` (e.g. the `Table` extension fixes
|
|
9
|
+
* irregular tables), before the view shows it and without an undo step.
|
|
10
|
+
*/
|
|
11
|
+
export declare const LOAD_META = "stiloLoad";
|
|
6
12
|
/**
|
|
7
13
|
* The Stilo editor. See docs/ARCHITECTURE.md §5.
|
|
8
14
|
*
|
|
@@ -33,13 +39,13 @@ export declare class Editor {
|
|
|
33
39
|
* Creates the editor:
|
|
34
40
|
*
|
|
35
41
|
* 1. `ExtensionManager` from `options.extensions` → `schema`;
|
|
36
|
-
* 2. `createPlugins(this)`, then the core `createDropMovePlugin()`; command runners are built
|
|
42
|
+
* 2. `createPlugins(this, emit)`, then the core `createDropMovePlugin()`; command runners are built
|
|
37
43
|
* from the manager's commands;
|
|
38
44
|
* 3. initial document from `parseHTML(schema, options.content ?? '')`;
|
|
39
45
|
* 4. `EditorView` mounted inside `options.element`, with attributes `class="stilo-content"`,
|
|
40
46
|
* `role="textbox"`, `aria-multiline="true"`, `aria-label` (if given); `editable` from
|
|
41
|
-
* options (default `true`); `transformPastedHTML` → `
|
|
42
|
-
* `clipboardSerializer` → `createClipboardSerializer`; transactions go
|
|
47
|
+
* options (default `true`); `transformPastedHTML` → `cleanPastedHTML`, then
|
|
48
|
+
* `sanitizeToString`; `clipboardSerializer` → `createClipboardSerializer`; transactions go
|
|
43
49
|
* through the editor's dispatch (see events in `EditorEvents`);
|
|
44
50
|
* 5. `autofocus`: `true`/`'start'` → focus at start, `'end'` → focus at end.
|
|
45
51
|
*/
|
|
@@ -104,5 +110,11 @@ export declare class Editor {
|
|
|
104
110
|
/** Builds `commands` from the extension commands; `can` runs without `dispatch`. */
|
|
105
111
|
private createRunners;
|
|
106
112
|
private setContent;
|
|
113
|
+
/**
|
|
114
|
+
* Removes the DOM selection when it lies inside the editor. Replacing many nodes while the
|
|
115
|
+
* browser tracks a selection in them is much slower (Chromium, 50,000 paragraphs: 42 s →
|
|
116
|
+
* 1.6 s); ProseMirror puts the selection back after the update when the editor has focus.
|
|
117
|
+
*/
|
|
118
|
+
private clearDOMSelection;
|
|
107
119
|
private dispatch;
|
|
108
120
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Schema } from 'prosemirror-model';
|
|
2
2
|
import type { Plugin } from 'prosemirror-state';
|
|
3
3
|
import type { Editor } from './editor';
|
|
4
|
-
import type { Extension } from './extension';
|
|
4
|
+
import type { Extension, ExtensionContext } from './extension';
|
|
5
5
|
import type { CommandFactory } from './types';
|
|
6
6
|
/**
|
|
7
7
|
* Schema order of the built-in marks, outermost first. It defines how marks nest in the HTML
|
|
@@ -43,7 +43,7 @@ export declare class ExtensionManager {
|
|
|
43
43
|
get commands(): ReadonlyMap<string, CommandFactory>;
|
|
44
44
|
/**
|
|
45
45
|
* Called once by the editor. For each extension in order, with context
|
|
46
|
-
* `{ options, schema, editor }`: collects `commands` (duplicate name → `StiloError`),
|
|
46
|
+
* `{ options, schema, editor, emit }`: collects `commands` (duplicate name → `StiloError`),
|
|
47
47
|
* `inputRules`, `keymap`, `plugins`. Returns, in this order:
|
|
48
48
|
*
|
|
49
49
|
* 1. one `inputRules({ rules })` plugin with all input rules;
|
|
@@ -53,5 +53,5 @@ export declare class ExtensionManager {
|
|
|
53
53
|
* 4. all extension plugins, in extension order;
|
|
54
54
|
* 5. `keymap(baseKeymap)` from prosemirror-commands.
|
|
55
55
|
*/
|
|
56
|
-
createPlugins(editor: Editor): Plugin[];
|
|
56
|
+
createPlugins(editor: Editor, emit: ExtensionContext<object>['emit']): Plugin[];
|
|
57
57
|
}
|
package/dist/core/extension.d.ts
CHANGED
|
@@ -2,12 +2,17 @@ import type { InputRule } from 'prosemirror-inputrules';
|
|
|
2
2
|
import type { MarkSpec, NodeSpec, Schema } from 'prosemirror-model';
|
|
3
3
|
import type { Command, Plugin } from 'prosemirror-state';
|
|
4
4
|
import type { Editor } from './editor';
|
|
5
|
-
import type { CommandFactories } from './types';
|
|
5
|
+
import type { CommandFactories, EditorEvents } from './types';
|
|
6
6
|
/** Context passed to the runtime parts of an extension. */
|
|
7
7
|
export interface ExtensionContext<Options> {
|
|
8
8
|
options: Readonly<Options>;
|
|
9
9
|
schema: Schema;
|
|
10
10
|
editor: Editor;
|
|
11
|
+
/**
|
|
12
|
+
* Emits an editor event. An extension adds its own events to `EditorEvents` by module
|
|
13
|
+
* augmentation, as it does for `Commands`.
|
|
14
|
+
*/
|
|
15
|
+
emit<E extends keyof EditorEvents>(event: E, payload: EditorEvents[E]): void;
|
|
11
16
|
}
|
|
12
17
|
/** Definition of an extension. See docs/ARCHITECTURE.md §6. */
|
|
13
18
|
export interface ExtensionSpec<Options extends object = Record<string, never>> {
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Attrs, DOMOutputSpec, Node, NodeType } from 'prosemirror-model';
|
|
2
|
+
/** A text alignment (docs/SPEC.md F-308). `left` is the default and is stored as `null`. */
|
|
3
|
+
export type TextAlignment = 'left' | 'center' | 'right' | 'justify';
|
|
4
|
+
/** The `textAlign` and `lineHeight` attributes of a textblock spec, both `null` by default. */
|
|
5
|
+
export declare function blockStyleAttrs(): {
|
|
6
|
+
textAlign: {
|
|
7
|
+
default: null;
|
|
8
|
+
};
|
|
9
|
+
lineHeight: {
|
|
10
|
+
default: null;
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* The style attributes of `node` that `type` also has, to keep them when a block becomes `type`
|
|
15
|
+
* (a centered paragraph stays centered as a heading).
|
|
16
|
+
*/
|
|
17
|
+
export declare function keptBlockStyles(type: NodeType, node: Node): Attrs;
|
|
18
|
+
/**
|
|
19
|
+
* The style attributes of a parsed block: its `text-align` (`null` for left) and `line-height`.
|
|
20
|
+
* `null` when it has neither, so the block keeps the shared default attributes: one object per
|
|
21
|
+
* paragraph is a lot of memory in a large document.
|
|
22
|
+
*/
|
|
23
|
+
export declare function parseBlockStyle(element: HTMLElement): Attrs | null;
|
|
24
|
+
/** The `toDOM` output of a textblock `tag` with its style attributes, if any. */
|
|
25
|
+
export declare function blockStyleDOM(tag: string, node: Node): DOMOutputSpec;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { InputRule } from 'prosemirror-inputrules';
|
|
2
|
-
import type { Attrs, MarkType } from 'prosemirror-model';
|
|
2
|
+
import type { Attrs, MarkType, NodeType } from 'prosemirror-model';
|
|
3
3
|
/**
|
|
4
4
|
* Creates an input rule that turns markdown-like syntax into a mark.
|
|
5
5
|
*
|
|
@@ -19,3 +19,10 @@ import type { Attrs, MarkType } from 'prosemirror-model';
|
|
|
19
19
|
* ```
|
|
20
20
|
*/
|
|
21
21
|
export declare function markInputRule(regexp: RegExp, type: MarkType, attrs?: Attrs | null): InputRule;
|
|
22
|
+
/**
|
|
23
|
+
* Like `textblockTypeInputRule` from prosemirror-inputrules (turns the textblock where `regexp`
|
|
24
|
+
* matches at its start into `type` with `attrs`, or with the attributes `attrs` returns for the
|
|
25
|
+
* match), but the block keeps the style attributes (`textAlign`, `lineHeight`) that `type` also
|
|
26
|
+
* has, as `setTextblockType` does.
|
|
27
|
+
*/
|
|
28
|
+
export declare function textblockTypeInputRule(regexp: RegExp, type: NodeType, attrs?: Attrs | null | ((match: RegExpMatchArray) => Attrs | null)): InputRule;
|
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
import type { Attrs, NodeType } from 'prosemirror-model';
|
|
2
|
-
import type
|
|
2
|
+
import { type Command } from 'prosemirror-state';
|
|
3
3
|
/**
|
|
4
4
|
* Command: toggles a list of `listType` (whose items are `itemType`) at the selection. List
|
|
5
5
|
* nodes are recognized by the `list` group in their spec. Looking at the nearest list around
|
|
6
6
|
* the start of the selection:
|
|
7
7
|
*
|
|
8
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
|
|
9
|
+
* - a list of another type: that list becomes a `listType` list with `attrs`, its items
|
|
10
|
+
* `itemType` items (a task item loses its `checked` state);
|
|
10
11
|
* - no list: the selected blocks are wrapped in a new list (`wrapInList`).
|
|
11
12
|
*/
|
|
12
13
|
export declare function toggleList(listType: NodeType, itemType: NodeType, attrs?: Attrs | null): Command;
|
|
14
|
+
/**
|
|
15
|
+
* Command for `Backspace` in lists whose items are `itemType`. At the start of the first
|
|
16
|
+
* textblock of an item, lifts the item (`liftListItem`). At the start of a textblock right after
|
|
17
|
+
* such a list, moves its content to the end of the list's last textblock and deletes it (an
|
|
18
|
+
* empty one just disappears); without this, ProseMirror wraps the textblock in a new item.
|
|
19
|
+
*/
|
|
20
|
+
export declare function backspaceInList(itemType: NodeType): Command;
|
|
@@ -21,6 +21,20 @@ export declare function setMark(type: MarkType, attrs?: Attrs | null): Command;
|
|
|
21
21
|
* mark. The transaction scrolls into view.
|
|
22
22
|
*/
|
|
23
23
|
export declare function unsetMark(type: MarkType): Command;
|
|
24
|
+
/**
|
|
25
|
+
* Command: merges `attrs` into the mark of `type` on the selection, keeping its other
|
|
26
|
+
* attributes; a mark whose attributes all become `null` is removed. Lets one mark carry several
|
|
27
|
+
* independent attributes (`textStyle`: font family, size, color).
|
|
28
|
+
*
|
|
29
|
+
* - Setting (some value in `attrs` is not `null`): like {@link setMark}, applies to the stored
|
|
30
|
+
* marks or to every selected text that accepts the mark; `false` when none does.
|
|
31
|
+
* - Clearing (every value is `null`): applies where the mark has one of these attributes;
|
|
32
|
+
* `false` when none has.
|
|
33
|
+
*
|
|
34
|
+
* `attrs` can be a function of the position of each selected text (the cursor for an empty
|
|
35
|
+
* selection), for values that differ along the selection; `null` leaves that text alone.
|
|
36
|
+
*/
|
|
37
|
+
export declare function updateMarkAttrs(type: MarkType, attrs: Attrs | ((pos: number) => Attrs | null)): Command;
|
|
24
38
|
/**
|
|
25
39
|
* Command: {@link unsetMark} when the mark is active (see `isMarkActive`), otherwise
|
|
26
40
|
* {@link setMark}. Unlike `toggleMark` from prosemirror-commands, a partially marked selection
|
|
@@ -12,3 +12,15 @@ export declare function insertBlockNode(tr: Transaction, node: Node): Transactio
|
|
|
12
12
|
* block node) does not allow `type`.
|
|
13
13
|
*/
|
|
14
14
|
export declare function insertBlock(type: NodeType, attrs?: Attrs | null): Command;
|
|
15
|
+
/**
|
|
16
|
+
* Command: like `setBlockType` from prosemirror-commands (turns the selected textblocks into
|
|
17
|
+
* `type` with `attrs`), but a block keeps the style attributes (`textAlign`, `lineHeight`) that
|
|
18
|
+
* `type` also has, so a centered heading stays centered as a paragraph. `false` when no selected
|
|
19
|
+
* textblock changes.
|
|
20
|
+
*/
|
|
21
|
+
export declare function setTextblockType(type: NodeType, attrs?: Attrs): Command;
|
|
22
|
+
/**
|
|
23
|
+
* Command: sets the attribute `name` to `value` on every selected textblock whose type has that
|
|
24
|
+
* attribute (`textAlign`, `lineHeight`). `false` when no selected textblock has it.
|
|
25
|
+
*/
|
|
26
|
+
export declare function setTextblockAttr(name: string, value: unknown): Command;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Editor } from '../editor';
|
|
2
|
+
/** Words and characters of a document, from {@link getWordCount}. */
|
|
3
|
+
export interface WordCount {
|
|
4
|
+
/** Runs of non-space characters with at least one letter or digit. */
|
|
5
|
+
words: number;
|
|
6
|
+
/** Characters of the text, spaces included, line and paragraph breaks excluded. */
|
|
7
|
+
characters: number;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Counts the words and characters of the document, docs/SPEC.md F-901. The first call reads the
|
|
11
|
+
* whole document, later calls only the changed top-level blocks: call it on demand or debounced,
|
|
12
|
+
* never on every transaction (ADR 0003).
|
|
13
|
+
*/
|
|
14
|
+
export declare function getWordCount(editor: Editor): WordCount;
|
package/dist/core/html/css.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* See docs/ARCHITECTURE.md §7.3. Extensions never handle raw style strings themselves.
|
|
4
4
|
*/
|
|
5
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'];
|
|
6
|
+
export declare const STYLE_PROPERTIES: readonly ['font-family', 'font-size', 'color', 'background-color', 'text-align', 'line-height', 'width'];
|
|
7
7
|
/** A property allowed in the HTML output. */
|
|
8
8
|
export type StyleProperty = (typeof STYLE_PROPERTIES)[number];
|
|
9
9
|
/**
|
|
@@ -27,6 +27,11 @@ export declare const UNSAFE_VALUE: RegExp;
|
|
|
27
27
|
* - When a property appears twice, the last one wins.
|
|
28
28
|
*/
|
|
29
29
|
export declare function parseStyle(style: string): Map<string, string>;
|
|
30
|
+
/**
|
|
31
|
+
* Formats declarations (as returned by {@link parseStyle}) as `prop: value` joined with `; `.
|
|
32
|
+
* Returns `null` when there are none.
|
|
33
|
+
*/
|
|
34
|
+
export declare function formatStyle(declarations: ReadonlyMap<string, string>): string | null;
|
|
30
35
|
/**
|
|
31
36
|
* Normalizes a color value (input is trimmed and lowercased first):
|
|
32
37
|
*
|
|
@@ -59,6 +64,16 @@ export declare function normalizeFontFamily(value: string): string | null;
|
|
|
59
64
|
* `start` → `left`; anything else → `null`.
|
|
60
65
|
*/
|
|
61
66
|
export declare function normalizeTextAlign(value: string): string | null;
|
|
67
|
+
/**
|
|
68
|
+
* Normalizes `line-height` (trimmed): a number `^\d+(\.\d+)?$` or a percentage of it (`115%` →
|
|
69
|
+
* `1.15`), rounded to 2 decimals without trailing zeros; zero, `normal` and lengths → `null`.
|
|
70
|
+
*/
|
|
71
|
+
export declare function normalizeLineHeight(value: string): string | null;
|
|
72
|
+
/**
|
|
73
|
+
* Normalizes a `width` (trimmed, lowercased): whole pixels, `^\d+(\.\d+)?px$` rounded
|
|
74
|
+
* (`120.4px` → `120px`); zero, other units and keywords → `null` (docs/decisions/0005).
|
|
75
|
+
*/
|
|
76
|
+
export declare function normalizeWidth(value: string): string | null;
|
|
62
77
|
/**
|
|
63
78
|
* Normalizes the value of an output property with the matching normalizer above.
|
|
64
79
|
* Returns `null` if the value matches {@link UNSAFE_VALUE} or the normalizer returns `null`.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Removes the base style of a document pasted from a word processor: for each of
|
|
3
|
+
* {@link BASE_PROPERTIES}, the value that covers most of the pasted text (Arial, 11pt, black)
|
|
4
|
+
* is removed everywhere, so only the text that differs keeps its value. `font-size` is also
|
|
5
|
+
* removed inside headings, whose level gives the size.
|
|
6
|
+
*/
|
|
7
|
+
export declare function removeBaseStyles(body: HTMLElement): void;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** The element that wraps the HTML Google Docs puts on the clipboard. */
|
|
2
|
+
export declare const GOOGLE_DOCS_WRAPPER = "[id^=\"docs-internal-guid-\"]";
|
|
3
|
+
/**
|
|
4
|
+
* Cleans the HTML of Google Docs: an empty paragraph arrives as a `<br>` between the blocks of the
|
|
5
|
+
* wrapper and becomes an empty `<p>`.
|
|
6
|
+
*/
|
|
7
|
+
export declare function cleanGoogleDocs(wrapper: Element): void;
|