nuvra 0.4.1 → 0.6.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 +241 -19
- package/dist/compare.vue.d.ts +19 -0
- package/dist/components/document-editor.vue.d.ts +59 -3
- package/dist/components/editor-canvas.vue.d.ts +9 -7
- package/dist/components/editor-changes.vue.d.ts +18 -0
- package/dist/components/editor-collaborators.vue.d.ts +17 -0
- package/dist/components/editor-comments.vue.d.ts +32 -0
- package/dist/components/editor-footnote-form.vue.d.ts +19 -0
- package/dist/components/editor-outline.vue.d.ts +36 -0
- package/dist/components/editor-slash-menu.vue.d.ts +13 -0
- package/dist/components/editor-toolbar.vue.d.ts +24 -1
- package/dist/core/collaboration.d.ts +25 -0
- package/dist/core/comments.d.ts +26 -0
- package/dist/core/dates.d.ts +8 -0
- package/dist/core/diff.d.ts +11 -0
- package/dist/core/document-templates.d.ts +22 -0
- package/dist/core/docx/export.d.ts +19 -0
- package/dist/core/docx/import.d.ts +19 -0
- package/dist/core/docx/zip.d.ts +23 -0
- package/dist/core/engine/dom.d.ts +40 -3
- package/dist/core/engine/editing.d.ts +11 -0
- package/dist/core/engine/engine.d.ts +161 -3
- package/dist/core/engine/input-rules.d.ts +3 -1
- package/dist/core/engine/marks.d.ts +50 -1
- package/dist/core/engine/schema.d.ts +9 -2
- package/dist/core/export.d.ts +13 -5
- package/dist/core/footnotes.d.ts +15 -0
- package/dist/core/icons.d.ts +1 -1
- package/dist/core/labels.d.ts +6 -2
- package/dist/core/locales/uz-cyrl.d.ts +3 -0
- package/dist/core/locales/uz.d.ts +72 -1
- package/dist/core/numbers.d.ts +19 -0
- package/dist/core/outline.d.ts +44 -0
- package/dist/core/page.d.ts +12 -2
- package/dist/core/pagination.d.ts +25 -3
- package/dist/core/pdf.d.ts +43 -0
- package/dist/core/signature.d.ts +15 -0
- package/dist/core/slash-commands.d.ts +19 -0
- package/dist/core/templates.d.ts +32 -0
- package/dist/core/transliterate.d.ts +13 -0
- package/dist/core/types.d.ts +1 -1
- package/dist/core/ui-state.d.ts +8 -0
- package/dist/editor.vue.d.ts +6 -2
- package/dist/export-B06RXLqn.js +96 -0
- package/dist/export-B06RXLqn.js.map +1 -0
- package/dist/footnotes-DBShImw7.js +11 -0
- package/dist/footnotes-DBShImw7.js.map +1 -0
- package/dist/form.vue.d.ts +39 -0
- package/dist/index.d.ts +28 -1
- package/dist/index.js +8391 -4198
- package/dist/index.js.map +1 -1
- package/dist/{page-DL7Oj2o8.js → page-DY5fdgcT.js} +10 -10
- package/dist/page-DY5fdgcT.js.map +1 -0
- package/dist/pdf-mIRjnoOP.js +70 -0
- package/dist/pdf-mIRjnoOP.js.map +1 -0
- package/dist/style.css +1 -1
- package/dist/zip-CpbwZtot.js +46 -0
- package/dist/zip-CpbwZtot.js.map +1 -0
- package/package.json +5 -2
- package/dist/export-VeTfgCEp.js +0 -117
- package/dist/export-VeTfgCEp.js.map +0 -1
- package/dist/page-DL7Oj2o8.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
# nuvra
|
|
2
2
|
|
|
3
3
|
Word-style document editor for Vue 3. A real page view with paper sizes and margins, a lighter web view for
|
|
4
|
-
forms, tables, images, lists, find & replace, HTML source mode, printing
|
|
4
|
+
forms, tables, images, lists, find & replace, comments, HTML source mode, printing, PDF download, export to HTML and Word
|
|
5
|
+
files (`.docx`) you can open again.
|
|
5
6
|
|
|
6
7
|
**[Documentation and live demo →](https://nuvra-docs.vercel.app)**
|
|
7
8
|
|
|
8
9
|
- No editor framework underneath: its own small editing engine with a sanitizing schema.
|
|
9
10
|
- No UI framework either: native HTML controls, built-in SVG icons and plain CSS variables. Vue is the only
|
|
10
11
|
dependency.
|
|
11
|
-
- Pagination in the page view (A4, A5, Letter, …, portrait or landscape)
|
|
12
|
+
- Pagination in the page view (A4, A5, Letter, …, portrait or landscape), with section breaks for landscape pages inside a
|
|
13
|
+
portrait document.
|
|
12
14
|
- Tables with merge / split, images with resize and alignment, task lists, links, colors, fonts.
|
|
13
15
|
- Undo / redo, keyboard shortcuts that work with non-Latin keyboard layouts, Markdown-like input rules.
|
|
14
16
|
- Office tools: format painter, letter case, paragraph spacing, formatting marks, a right-click menu and
|
|
@@ -17,6 +19,13 @@ forms, tables, images, lists, find & replace, HTML source mode, printing and exp
|
|
|
17
19
|
the editor shows them, and the Word export uses Word's own header, footer and page fields.
|
|
18
20
|
- A ruler above the page for the margins and for the first line, left and right indents of a paragraph.
|
|
19
21
|
- A page watermark such as DRAFT or COPY, drawn behind the text of every page and carried into print, HTML and Word.
|
|
22
|
+
- Real `.docx` export and import, PDF download without the print dialog, multilevel (1.1.1) numbering, a table of
|
|
23
|
+
contents and a navigation pane with headings and page thumbnails.
|
|
24
|
+
- Footnotes at the bottom of their page, carried into print and Word.
|
|
25
|
+
- Comments with replies, tracked changes that round-trip with Word, a comparison of two versions, and templates
|
|
26
|
+
filled in as a form.
|
|
27
|
+
- Hooks for editing together: other people's carets and selections, your selection as character positions.
|
|
28
|
+
- A `/` command menu and a toolbar slot for your own commands and buttons.
|
|
20
29
|
|
|
21
30
|
## Installation
|
|
22
31
|
|
|
@@ -91,9 +100,13 @@ const uploadImage: DocumentImageUploadHandler = async file => {
|
|
|
91
100
|
| Prop | Type | Default | Description |
|
|
92
101
|
| ----------------- | --------------------------------- | -------- | -------------------------------------------------------------------------- |
|
|
93
102
|
| `v-model` | `string` | `''` | Document HTML; an empty document is an empty string. |
|
|
94
|
-
| `v-model:page` | `PageSettings` | A4 | Paper size, orientation
|
|
103
|
+
| `v-model:page` | `PageSettings` | A4 | Paper size, orientation, margins, headers, footers, watermark, numbering. |
|
|
104
|
+
| `v-model:comments`| `DocumentComment[]` | — | Comments; binding it turns the comment tools on. |
|
|
105
|
+
| `v-model:trackChanges` | `boolean` | `false` | Records edits as tracked changes. |
|
|
106
|
+
| `author` | `string` | `''` | Name written on new comments, replies and tracked changes. |
|
|
95
107
|
| `autofocus` | `boolean` | `false` | Places the caret at the end of the document once ready. |
|
|
96
108
|
| `canvasPadding` | `number \| string` | `50` | Gray space around the page or web sheet. |
|
|
109
|
+
| `collaborators` | `Collaborator[]` | `[]` | Other people editing the document; their carets and selections are drawn. |
|
|
97
110
|
| `defaultViewMode` | `'page' \| 'web'` | `'page'` | View shown first. |
|
|
98
111
|
| `disabled` | `boolean` | `false` | Read-only document, disabled controls. |
|
|
99
112
|
| `height` | `number \| string` | `760` | Height of the editor, or `'auto'` to grow between `minHeight`/`maxHeight`. |
|
|
@@ -103,43 +116,252 @@ const uploadImage: DocumentImageUploadHandler = async file => {
|
|
|
103
116
|
| `maxImageSizeMb` | `number` | `10` | Largest accepted image file. |
|
|
104
117
|
| `maxLength` | `number` | `0` | Character limit; `0` means unlimited. |
|
|
105
118
|
| `placeholder` | `string` | `''` | Text shown while the document is empty. |
|
|
119
|
+
| `ruler` | `boolean` | `true` | Shows the ruler in the page view. |
|
|
120
|
+
| `slashCommands` | `SlashCommand[]` | `[]` | Your own commands, listed first in the `/` menu. |
|
|
106
121
|
| `title` | `string` | `''` | Print title and exported file name. |
|
|
107
122
|
| `uploadImage` | `(file: File) => Promise<string>` | — | Uploads an image and resolves with its URL. |
|
|
123
|
+
| `variables` | `TemplateVariable[]` | `[]` | Template variables the user can insert. |
|
|
108
124
|
|
|
109
|
-
`Editor` accepts the same props except `v-model:page`, `
|
|
125
|
+
`Editor` accepts the same props except `v-model:page`, `v-model:comments`, `v-model:trackChanges`, `author`,
|
|
126
|
+
`collaborators`, `defaultViewMode`, `height`, `ruler`, `slashCommands` and `title`.
|
|
110
127
|
|
|
111
128
|
Numbers are pixels; strings are used as CSS lengths (`'100%'`, `'50vh'`).
|
|
112
129
|
|
|
113
130
|
### Events
|
|
114
131
|
|
|
115
|
-
| Event
|
|
116
|
-
|
|
|
117
|
-
| `focus`
|
|
118
|
-
| `blur`
|
|
119
|
-
| `uploadError`
|
|
132
|
+
| Event | Payload | Description |
|
|
133
|
+
| ----------------- | ----------------------------------- | -------------------------------------------------------------- |
|
|
134
|
+
| `focus` | — | The document received focus. |
|
|
135
|
+
| `blur` | — | The document lost focus; the model is up to date. |
|
|
136
|
+
| `uploadError` | `error: unknown` | An image was rejected or its upload failed. |
|
|
137
|
+
| `importError` | `error: unknown` | A Word file could not be read. |
|
|
138
|
+
| `exportError` | `error: unknown` | The PDF could not be drawn; no file is downloaded. |
|
|
139
|
+
| `selectionChange` | `selection: SelectionOffsets \| null` | The caret or selection moved; `null` when it left the document. |
|
|
120
140
|
|
|
121
141
|
### Exposed methods (`DocumentEditor` ref)
|
|
122
142
|
|
|
123
|
-
| Method
|
|
124
|
-
|
|
|
125
|
-
| `focus()`
|
|
126
|
-
| `getHTML()`
|
|
127
|
-
| `
|
|
128
|
-
| `
|
|
129
|
-
| `
|
|
130
|
-
| `
|
|
143
|
+
| Method | Description |
|
|
144
|
+
| ------------------------- | ---------------------------------------------------------------- |
|
|
145
|
+
| `focus()` | Moves keyboard focus into the document. |
|
|
146
|
+
| `getHTML()` | Returns the document HTML, including edits not yet in the model. |
|
|
147
|
+
| `insertVariable(name)` | Inserts a template variable at the selection. |
|
|
148
|
+
| `updateTableOfContents()` | Inserts or refreshes the table of contents. |
|
|
149
|
+
| `importWord(file)` | Replaces the document with the content of a `.docx` file. |
|
|
150
|
+
| `print()` | Opens the browser print dialog. |
|
|
151
|
+
| `exportHtml()` | Downloads the document as an HTML page. |
|
|
152
|
+
| `exportWord()` | Downloads the document as a Word file (`.docx`). |
|
|
153
|
+
| `exportPdf()` | Downloads the document as a PDF drawn from its pages. |
|
|
154
|
+
| `engine` | The editing engine (`DocumentEngine`), for advanced integrations. |
|
|
131
155
|
|
|
132
156
|
### Keyboard shortcuts
|
|
133
157
|
|
|
134
158
|
`Ctrl/⌘+B`, `I`, `U` — bold, italic, underline · `Ctrl/⌘+Shift+H` — highlight · `Ctrl/⌘+Z`, `Ctrl/⌘+Shift+Z` — undo,
|
|
135
|
-
redo · `Ctrl/⌘+F` — find · `Ctrl/⌘+H` — replace · `Ctrl/⌘+K` — link · `Ctrl/⌘+P` — print.
|
|
159
|
+
redo · `Ctrl/⌘+F` — find · `Ctrl/⌘+H` — replace · `Ctrl/⌘+K` — link · `Ctrl/⌘+Alt+M` — comment · `Ctrl/⌘+P` — print.
|
|
136
160
|
|
|
137
161
|
The full list, with Markdown-like input rules, is in the
|
|
138
162
|
[keyboard shortcuts guide](https://nuvra-docs.vercel.app/docs/keyboard-shortcuts).
|
|
139
163
|
|
|
164
|
+
## Templates and signatures
|
|
165
|
+
|
|
166
|
+
Pass `variables` to let users insert template variables with the toolbar's **{ }** menu or by typing `{{name}}`.
|
|
167
|
+
They are saved as `<span data-variable="name">{{name}}</span>`; `fillTemplate` replaces them with escaped values,
|
|
168
|
+
in the browser or on a Node server:
|
|
169
|
+
|
|
170
|
+
```ts
|
|
171
|
+
import { type TemplateVariable, fillTemplate } from 'nuvra';
|
|
172
|
+
|
|
173
|
+
const variables: TemplateVariable[] = [
|
|
174
|
+
{ name: 'full_name', label: 'Full name' },
|
|
175
|
+
{ name: 'letter_date', label: 'Letter date' }
|
|
176
|
+
];
|
|
177
|
+
|
|
178
|
+
const letter = fillTemplate(template, { full_name: 'Aziz Karimov', letter_date: '14.09.2026' });
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
The pen button inserts a signature block in the editor's language: a signature line, an “Approved” or “Agreed”
|
|
182
|
+
block, or the signatures of both parties of a contract. It is a borderless table, so it prints and exports to Word
|
|
183
|
+
without lines.
|
|
184
|
+
|
|
185
|
+
The document button inserts ready-made templates (official letter, order, application, certificate, act), and the
|
|
186
|
+
toolbar writes amounts in words (`15 000 000 (o‘n besh million)`), inserts long dates (`2026-yil 14-sentabr`) and
|
|
187
|
+
converts Uzbek text between the Latin and Cyrillic alphabets. The same helpers are exported: `getDocumentTemplate`,
|
|
188
|
+
`numberToWords`, `formatAmountInWords`, `parseAmount`, `formatLongDate` and `transliterate`.
|
|
189
|
+
|
|
190
|
+
### Filling a template as a form
|
|
191
|
+
|
|
192
|
+
`DocumentForm` shows a saved template as it will be printed, with an input in place of every variable. Fields of the
|
|
193
|
+
same variable share one value:
|
|
194
|
+
|
|
195
|
+
```vue
|
|
196
|
+
<script setup lang="ts">
|
|
197
|
+
import { ref } from 'vue';
|
|
198
|
+
import { DocumentForm } from 'nuvra';
|
|
199
|
+
|
|
200
|
+
const values = ref<Record<string, string>>({});
|
|
201
|
+
const form = ref<InstanceType<typeof DocumentForm>>();
|
|
202
|
+
|
|
203
|
+
const submit = () => {
|
|
204
|
+
if (form.value?.validate().length) return; // empty fields are marked and focused
|
|
205
|
+
const html = form.value?.getHTML(); // filled with fillTemplate
|
|
206
|
+
};
|
|
207
|
+
</script>
|
|
208
|
+
|
|
209
|
+
<template>
|
|
210
|
+
<DocumentForm ref="form" v-model="values" :template="template" :variables="variables" />
|
|
211
|
+
</template>
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
## Word files and long documents
|
|
215
|
+
|
|
216
|
+
"Download as Word (.docx)" in the "More" menu writes a real Office Open XML file with the page setup, headers and
|
|
217
|
+
footers with page fields, the watermark, lists, tables with merged cells, images, footnotes and tracked changes as
|
|
218
|
+
Word revisions. "Open Word file (.docx)" (or `importWord(file)`) loads one into the editor, footnotes and revisions
|
|
219
|
+
included; a file that cannot be read emits `importError`. `buildDocx` and `readDocx` do the same in your own code. A
|
|
220
|
+
document with several sections takes the page setup of its first section, and every further section starts with a
|
|
221
|
+
section break; the content of a different first page header is not imported.
|
|
222
|
+
|
|
223
|
+
### PDF download
|
|
224
|
+
|
|
225
|
+
"Download as PDF" in the "More" menu, or `exportPdf()`, saves a PDF without the print dialog. Every sheet is drawn into a
|
|
226
|
+
picture (SVG `foreignObject` → canvas → JPEG) and written into the PDF, so it looks like the printout, but its text
|
|
227
|
+
cannot be selected or searched. Images from servers without CORS are left out, and in the web view the editor switches to
|
|
228
|
+
the page view for the moment of the export. A PDF that cannot be drawn emits `exportError`; printing with "Save as PDF"
|
|
229
|
+
still gives a PDF with real text.
|
|
230
|
+
|
|
231
|
+
### Pages in different orientations
|
|
232
|
+
|
|
233
|
+
"Section break: landscape pages" and "Section break: portrait pages" in the insert menu and the `/` menu (or
|
|
234
|
+
`engine.insertSectionBreak('landscape')`) turn the pages after the break; paper size and margins stay, and the break
|
|
235
|
+
starts a new page. It is saved as `<div data-type="section-break" data-orientation="landscape"></div>`. The page view
|
|
236
|
+
draws sheets of different sizes and gives the blocks of a turned section their sheet's text width; printing uses a named
|
|
237
|
+
`@page` for turned sheets, the PDF has turned pages, and the Word export writes every section as a Word section with its
|
|
238
|
+
own orientation.
|
|
239
|
+
|
|
240
|
+
### Footnotes
|
|
241
|
+
|
|
242
|
+
"Footnote" in the insert menu or the `/` menu adds a numbered reference and opens a small form for the note; clicking
|
|
243
|
+
a reference edits or deletes it. A footnote is saved in the reference, `<sup data-footnote="note text">1</sup>`, and
|
|
244
|
+
renumbered in document order. The page view draws the notes at the bottom of the sheet the reference is on, the web
|
|
245
|
+
view after the document; printing follows the sheets, and the Word export writes Word footnotes. Notes are plain text.
|
|
246
|
+
From code: `engine.insertFootnote(text)`, `setFootnoteText(element, text)`, `removeFootnote(element)`,
|
|
247
|
+
`getFootnotes()`.
|
|
248
|
+
|
|
249
|
+
For long documents the toolbar offers multilevel numbering (1., 1.1., 1.1.1., saved as `<ol data-numbering="legal">`),
|
|
250
|
+
a table of contents of the headings with page numbers (saved as `<table data-type="toc">`, refreshed with
|
|
251
|
+
`updateTableOfContents()`), and a navigation pane. `PageSettings` has `differentFirstPage` to hide the header, footer
|
|
252
|
+
and page number on the first page, and `firstPageNumber` for the number printed on it. The navigation pane lists the
|
|
253
|
+
headings or shows page thumbnails; clicking one scrolls to it.
|
|
254
|
+
|
|
255
|
+
## Comments, tracked changes and comparison
|
|
256
|
+
|
|
257
|
+
Bind `v-model:comments` to turn comments on. The HTML keeps only the anchors, `<span data-comment="id">`; the
|
|
258
|
+
comments are plain data you store next to the document:
|
|
259
|
+
|
|
260
|
+
```vue
|
|
261
|
+
<script setup lang="ts">
|
|
262
|
+
import { ref } from 'vue';
|
|
263
|
+
import { DocumentEditor, type DocumentComment } from 'nuvra';
|
|
264
|
+
|
|
265
|
+
const html = ref('');
|
|
266
|
+
const comments = ref<DocumentComment[]>([]);
|
|
267
|
+
|
|
268
|
+
const save = () =>
|
|
269
|
+
fetch('/api/documents/42', {
|
|
270
|
+
method: 'PUT',
|
|
271
|
+
headers: { 'Content-Type': 'application/json' },
|
|
272
|
+
body: JSON.stringify({ html: html.value, comments: comments.value })
|
|
273
|
+
});
|
|
274
|
+
</script>
|
|
275
|
+
|
|
276
|
+
<template>
|
|
277
|
+
<DocumentEditor v-model="html" v-model:comments="comments" author="Aziz Karimov" @blur="save" />
|
|
278
|
+
</template>
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
The comment and change tools are in the **Review** menu of the toolbar. Users select text and choose "Add comment"
|
|
282
|
+
(`Ctrl/⌘+Alt+M`); the "Comments" panel replies, resolves, reopens and deletes them, and flags comments whose text was
|
|
283
|
+
deleted.
|
|
284
|
+
|
|
285
|
+
### Tracked changes
|
|
286
|
+
|
|
287
|
+
Bind `v-model:trackChanges` (or choose "Track changes" in the Review menu) to record typing, deleting, cut and paste as tracked changes
|
|
288
|
+
by `author`. They are part of the HTML, `<ins data-change="id" data-author="…" data-time="…">` and `<del …>`, shown
|
|
289
|
+
and printed green-underlined and red-struck. The "Changes" panel accepts or rejects them one by one or all at once;
|
|
290
|
+
from code use `engine.getChanges()`, `engine.resolveChanges(accept, id?)` and `engine.selectChange(id)`. Formatting,
|
|
291
|
+
block changes (headings, lists, tables), Enter and joining paragraphs are not tracked. The Word export writes them as
|
|
292
|
+
revisions and the import reads Word revisions back.
|
|
293
|
+
|
|
294
|
+
```vue
|
|
295
|
+
<DocumentEditor v-model="html" v-model:track-changes="tracking" author="Aziz Karimov" />
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
`DocumentCompare` shows what changed between two versions: inserted words in green, deleted words struck through in
|
|
299
|
+
red. Unchanged blocks stay as they are, changed paragraphs are compared word by word, and added or removed blocks are
|
|
300
|
+
shown whole. `compareDocuments(before, after)` returns the same HTML and counts for your own view.
|
|
301
|
+
|
|
302
|
+
```vue
|
|
303
|
+
<DocumentCompare :before="previousVersion" :after="html" :height="600" />
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
## Editing together
|
|
307
|
+
|
|
308
|
+
The editor has hooks for several people on one document; the transport (WebSocket, WebRTC, …) is up to your app.
|
|
309
|
+
`selectionChange` reports your caret or selection as character positions, `collaborators` draws other people's carets
|
|
310
|
+
(with names) and selections, and a new `v-model` value from outside keeps your caret at the same character position.
|
|
311
|
+
The engine also offers `getSelectionOffsets()`, `getOffsetRects(offsets)` and `setContent(html, { keepSelection })`;
|
|
312
|
+
`collaboratorColor` gives the colour a collaborator is drawn in.
|
|
313
|
+
|
|
314
|
+
```vue
|
|
315
|
+
<script setup lang="ts">
|
|
316
|
+
import { ref } from 'vue';
|
|
317
|
+
import { type Collaborator, DocumentEditor, type SelectionOffsets } from 'nuvra';
|
|
318
|
+
|
|
319
|
+
const html = ref('');
|
|
320
|
+
const collaborators = ref<Collaborator[]>([]); // filled from your socket messages
|
|
321
|
+
|
|
322
|
+
const sendSelection = (selection: SelectionOffsets | null) =>
|
|
323
|
+
socket.send(JSON.stringify({ type: 'selection', id: me.id, name: me.name, selection }));
|
|
324
|
+
</script>
|
|
325
|
+
|
|
326
|
+
<template>
|
|
327
|
+
<DocumentEditor v-model="html" :collaborators="collaborators" @selection-change="sendSelection" />
|
|
328
|
+
</template>
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
This is not a CRDT: the document travels as a whole, so when two people type at the same time the document sent last
|
|
332
|
+
wins, and because positions are character offsets, a remote edit before your caret shifts it. A library such as Yjs
|
|
333
|
+
can carry the document and the selections (awareness), but edits are still not merged character by character. See the
|
|
334
|
+
[guide](https://nuvra-docs.vercel.app/docs/collaboration).
|
|
335
|
+
|
|
336
|
+
## Commands and toolbar buttons
|
|
337
|
+
|
|
338
|
+
Typing `/` at the start of a line or after a space opens a command menu: headings, lists, table, page and section breaks, footnote,
|
|
339
|
+
table of contents, dates, signature blocks and your variables. `slashCommands` adds your own commands at the top, and the
|
|
340
|
+
`toolbar` slot adds your own buttons:
|
|
341
|
+
|
|
342
|
+
```vue
|
|
343
|
+
<script setup lang="ts">
|
|
344
|
+
import { DocumentEditor, type SlashCommand } from 'nuvra';
|
|
345
|
+
|
|
346
|
+
const slashCommands: SlashCommand[] = [
|
|
347
|
+
{ id: 'director', label: 'Director’s name', icon: 'pencil', run: engine => engine.insertText('A. Karimov') }
|
|
348
|
+
];
|
|
349
|
+
</script>
|
|
350
|
+
|
|
351
|
+
<template>
|
|
352
|
+
<DocumentEditor v-model="html" :slash-commands="slashCommands">
|
|
353
|
+
<template #toolbar="{ engine, disabled }">
|
|
354
|
+
<button type="button" class="doc-tb-button" :disabled="disabled" @mousedown.prevent @click="engine.insertText('✓')">
|
|
355
|
+
✓
|
|
356
|
+
</button>
|
|
357
|
+
</template>
|
|
358
|
+
</DocumentEditor>
|
|
359
|
+
</template>
|
|
360
|
+
```
|
|
361
|
+
|
|
140
362
|
## Languages
|
|
141
363
|
|
|
142
|
-
The interface ships in Uzbek (`uz`, the default), English (`en`) and Russian (`ru`). The translations are part of
|
|
364
|
+
The interface ships in Uzbek (`uz`, the default), Uzbek Cyrillic (`uzCyrl`), English (`en`) and Russian (`ru`). The translations are part of
|
|
143
365
|
the package and cannot be changed from outside; an app only picks the language.
|
|
144
366
|
|
|
145
367
|
For one editor, pass the locale (or just its code) to the `locale` prop:
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type EditorLocaleInput } from './core/labels';
|
|
2
|
+
import './styles/document-content.css';
|
|
3
|
+
import './styles/editor-ui.css';
|
|
4
|
+
interface Props {
|
|
5
|
+
/** HTML of the earlier version. */
|
|
6
|
+
before: string;
|
|
7
|
+
/** HTML of the later version. */
|
|
8
|
+
after: string;
|
|
9
|
+
/** Interface language: a built-in locale or its code; defaults to the app-wide language. */
|
|
10
|
+
locale?: EditorLocaleInput;
|
|
11
|
+
/** Height of the component, in pixels or any CSS length; `'auto'` grows with the document. */
|
|
12
|
+
height?: number | string;
|
|
13
|
+
}
|
|
14
|
+
declare const __VLS_export: import("vue").DefineComponent<Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<Props> & Readonly<{}>, {
|
|
15
|
+
height: number | string;
|
|
16
|
+
locale: EditorLocaleInput;
|
|
17
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
18
|
+
declare const _default: typeof __VLS_export;
|
|
19
|
+
export default _default;
|
|
@@ -1,7 +1,12 @@
|
|
|
1
|
+
import type { Collaborator, SelectionOffsets } from '../core/collaboration';
|
|
2
|
+
import { type DocumentComment } from '../core/comments';
|
|
1
3
|
import { DocumentEngine } from '../core/engine/engine';
|
|
2
4
|
import { type EditorLocaleInput } from '../core/labels';
|
|
3
5
|
import { type DocumentViewMode, type PageSettings } from '../core/page';
|
|
6
|
+
import type { SlashCommand } from '../core/slash-commands';
|
|
7
|
+
import type { TemplateVariable } from '../core/templates';
|
|
4
8
|
import type { DocumentImageUploadHandler } from '../core/types';
|
|
9
|
+
import { type EditorUiState } from '../core/ui-state';
|
|
5
10
|
import '../styles/document-content.css';
|
|
6
11
|
import '../styles/editor-ui.css';
|
|
7
12
|
/** A CSS length: numbers are pixels, strings are used as written (for example, `'100%'` or `'auto'`). */
|
|
@@ -11,6 +16,10 @@ interface Props {
|
|
|
11
16
|
autofocus?: boolean;
|
|
12
17
|
/** Gray space around the page or web sheet that separates the document from the editor frame. */
|
|
13
18
|
canvasPadding?: CssSize;
|
|
19
|
+
/** Other people editing the document; their carets and selections are drawn over it. */
|
|
20
|
+
collaborators?: readonly Collaborator[];
|
|
21
|
+
/** Name written as the author of new comments, replies and tracked changes. */
|
|
22
|
+
author?: string;
|
|
14
23
|
/** View shown first; form fields use the lighter web view. */
|
|
15
24
|
defaultViewMode?: DocumentViewMode;
|
|
16
25
|
/** Makes the document read-only and disables every editing control. */
|
|
@@ -29,32 +38,59 @@ interface Props {
|
|
|
29
38
|
minHeight?: CssSize;
|
|
30
39
|
/** Text shown while the document is empty; falls back to the translated default placeholder. */
|
|
31
40
|
placeholder?: string;
|
|
41
|
+
/** Commands of the host application, listed first in the `/` menu. */
|
|
42
|
+
slashCommands?: readonly SlashCommand[];
|
|
32
43
|
/** Shows the ruler above the sheet in the page view; users can also toggle it in the document menu. */
|
|
33
44
|
ruler?: boolean;
|
|
34
45
|
/** Used as the print title and the exported file name. */
|
|
35
46
|
title?: string;
|
|
36
47
|
/** Uploads an inserted image and resolves with its URL; without it images are embedded as data URLs. */
|
|
37
48
|
uploadImage?: DocumentImageUploadHandler;
|
|
49
|
+
/** Template variables the user can insert; typing `{{name}}` of one of them inserts it as well. */
|
|
50
|
+
variables?: readonly TemplateVariable[];
|
|
38
51
|
}
|
|
39
52
|
type __VLS_Props = Props;
|
|
53
|
+
type __VLS_Slots = {
|
|
54
|
+
/**
|
|
55
|
+
* Buttons of the host application, placed at the start of the toolbar's right-hand group. `engine` runs editing
|
|
56
|
+
* commands, `state` describes the formatting at the caret and `disabled` tells whether editing is possible.
|
|
57
|
+
*/
|
|
58
|
+
toolbar?: (props: {
|
|
59
|
+
engine: DocumentEngine;
|
|
60
|
+
state: EditorUiState;
|
|
61
|
+
disabled: boolean;
|
|
62
|
+
}) => unknown;
|
|
63
|
+
};
|
|
40
64
|
type __VLS_ModelProps = {
|
|
41
65
|
/** Document HTML. An empty document is written as an empty string; typing updates it after a short delay. */
|
|
42
66
|
modelValue?: string;
|
|
43
67
|
/** Paper size, orientation, and margins used by the page view, printing, and export. */
|
|
44
68
|
'page'?: PageSettings;
|
|
69
|
+
/** Whether edits are recorded as tracked changes; the toolbar can switch it. */
|
|
70
|
+
'trackChanges'?: boolean;
|
|
71
|
+
/** Comments on the document; binding it turns the comment tools on. The HTML keeps only their anchors. */
|
|
72
|
+
'comments'?: DocumentComment[] | undefined;
|
|
45
73
|
};
|
|
46
74
|
type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
|
|
47
|
-
declare const
|
|
75
|
+
declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {
|
|
48
76
|
/** The editing engine, for advanced integrations. */
|
|
49
77
|
engine: import("vue").ShallowRef<DocumentEngine | null, DocumentEngine | null>;
|
|
50
78
|
/** Downloads the document as an HTML page. */
|
|
51
79
|
exportHtml: () => Promise<void>;
|
|
52
80
|
/** Downloads the document as a Word-compatible file. */
|
|
53
81
|
exportWord: () => Promise<void>;
|
|
82
|
+
/** Downloads the document as a PDF drawn from its pages. */
|
|
83
|
+
exportPdf: () => Promise<void>;
|
|
54
84
|
/** Moves keyboard focus into the document. */
|
|
55
85
|
focus: () => void | undefined;
|
|
56
86
|
/** Returns the document HTML, including edits not yet written to the model. */
|
|
57
87
|
getHTML: () => string;
|
|
88
|
+
/** Replaces the document with the content and page setup of a `.docx` file. */
|
|
89
|
+
importWord: (file: File) => Promise<void>;
|
|
90
|
+
/** Inserts the chip of a template variable at the selection. */
|
|
91
|
+
insertVariable: (name: string) => void | undefined;
|
|
92
|
+
/** Inserts the table of contents at the selection, or refreshes the existing one. */
|
|
93
|
+
updateTableOfContents: () => Promise<void>;
|
|
58
94
|
/** Opens the print dialog. */
|
|
59
95
|
print: () => Promise<void>;
|
|
60
96
|
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
@@ -62,28 +98,48 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {
|
|
|
62
98
|
blur: () => any;
|
|
63
99
|
"update:page": (value: PageSettings) => any;
|
|
64
100
|
uploadError: (error: unknown) => any;
|
|
101
|
+
importError: (error: unknown) => any;
|
|
102
|
+
exportError: (error: unknown) => any;
|
|
103
|
+
selectionChange: (selection: SelectionOffsets | null) => any;
|
|
65
104
|
"update:modelValue": (value: string) => any;
|
|
105
|
+
"update:trackChanges": (value: boolean) => any;
|
|
106
|
+
"update:comments": (value: DocumentComment[] | undefined) => any;
|
|
66
107
|
}, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
|
|
67
108
|
onFocus?: (() => any) | undefined;
|
|
68
109
|
onBlur?: (() => any) | undefined;
|
|
69
110
|
"onUpdate:page"?: ((value: PageSettings) => any) | undefined;
|
|
70
111
|
onUploadError?: ((error: unknown) => any) | undefined;
|
|
112
|
+
onImportError?: ((error: unknown) => any) | undefined;
|
|
113
|
+
onExportError?: ((error: unknown) => any) | undefined;
|
|
114
|
+
onSelectionChange?: ((selection: SelectionOffsets | null) => any) | undefined;
|
|
71
115
|
"onUpdate:modelValue"?: ((value: string) => any) | undefined;
|
|
116
|
+
"onUpdate:trackChanges"?: ((value: boolean) => any) | undefined;
|
|
117
|
+
"onUpdate:comments"?: ((value: DocumentComment[] | undefined) => any) | undefined;
|
|
72
118
|
}>, {
|
|
73
119
|
title: string;
|
|
74
120
|
height: CssSize;
|
|
121
|
+
locale: EditorLocaleInput;
|
|
75
122
|
ruler: boolean;
|
|
76
123
|
placeholder: string;
|
|
77
124
|
disabled: boolean;
|
|
78
125
|
minHeight: CssSize;
|
|
79
|
-
|
|
126
|
+
collaborators: readonly Collaborator[];
|
|
80
127
|
maxHeight: CssSize;
|
|
128
|
+
maxLength: number;
|
|
129
|
+
variables: readonly TemplateVariable[];
|
|
130
|
+
author: string;
|
|
81
131
|
autofocus: boolean;
|
|
82
132
|
canvasPadding: CssSize;
|
|
83
133
|
defaultViewMode: DocumentViewMode;
|
|
84
|
-
locale: EditorLocaleInput;
|
|
85
134
|
maxImageSizeMb: number;
|
|
135
|
+
slashCommands: readonly SlashCommand[];
|
|
86
136
|
uploadImage: DocumentImageUploadHandler;
|
|
87
137
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
138
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
88
139
|
declare const _default: typeof __VLS_export;
|
|
89
140
|
export default _default;
|
|
141
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
142
|
+
new (): {
|
|
143
|
+
$slots: S;
|
|
144
|
+
};
|
|
145
|
+
};
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { ParagraphIndents } from '../core/engine/blocks';
|
|
2
2
|
import type { DocumentEngine } from '../core/engine/engine';
|
|
3
|
-
import { type
|
|
3
|
+
import { type SheetFootnote } from '../core/footnotes';
|
|
4
|
+
import { type DocumentViewMode, type PageMargins, type PageMetrics, type PageSettings } from '../core/page';
|
|
5
|
+
import { type SheetGeometry } from '../core/pagination';
|
|
4
6
|
interface Props {
|
|
5
7
|
/** Grow with the content between the editor's min and max height instead of filling a fixed height. */
|
|
6
8
|
autoHeight: boolean;
|
|
@@ -10,22 +12,22 @@ interface Props {
|
|
|
10
12
|
documentTitle: string;
|
|
11
13
|
/** Engine working on the editable element; `null` until the parent has created it. */
|
|
12
14
|
engine: DocumentEngine | null;
|
|
13
|
-
/**
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
|
|
15
|
+
/** Footnotes of every sheet, drawn at its bottom; the web view shows all of them after the document. */
|
|
16
|
+
footnotes: SheetFootnote[][];
|
|
17
|
+
/** Page setup: running texts, watermark and page numbering of every sheet. */
|
|
18
|
+
page: PageSettings;
|
|
17
19
|
/** Indents of the paragraph at the caret, shown by the ruler. */
|
|
18
20
|
indents: ParagraphIndents;
|
|
19
21
|
/** Page size and margins in pixels. */
|
|
20
22
|
metrics: PageMetrics;
|
|
21
23
|
/** Number of sheets drawn behind the content in page view. */
|
|
22
24
|
pageCount: number;
|
|
25
|
+
/** Position, size and orientation of every sheet, as laid out by pagination; empty until the first layout. */
|
|
26
|
+
sheets: readonly SheetGeometry[];
|
|
23
27
|
/** Whether the ruler is drawn above the sheet; it is only ever shown in the page view. */
|
|
24
28
|
rulerVisible: boolean;
|
|
25
29
|
/** Paginated sheets or a single web sheet. */
|
|
26
30
|
viewMode: DocumentViewMode;
|
|
27
|
-
/** Watermark drawn behind the text of every sheet. */
|
|
28
|
-
watermark?: PageWatermark;
|
|
29
31
|
/** Zoom in percent. */
|
|
30
32
|
zoom: number;
|
|
31
33
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { TrackedChange } from '../core/engine/engine';
|
|
2
|
+
interface Props {
|
|
3
|
+
/** Tracked changes in document order. */
|
|
4
|
+
changes: readonly TrackedChange[];
|
|
5
|
+
/** Read-only document: changes can be reviewed but not accepted or rejected. */
|
|
6
|
+
readonly: boolean | undefined;
|
|
7
|
+
}
|
|
8
|
+
declare const __VLS_export: import("vue").DefineComponent<Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
9
|
+
select: (id: string) => any;
|
|
10
|
+
close: () => any;
|
|
11
|
+
resolve: (accept: boolean, id?: string | undefined) => any;
|
|
12
|
+
}, string, import("vue").PublicProps, Readonly<Props> & Readonly<{
|
|
13
|
+
onSelect?: ((id: string) => any) | undefined;
|
|
14
|
+
onClose?: (() => any) | undefined;
|
|
15
|
+
onResolve?: ((accept: boolean, id?: string | undefined) => any) | undefined;
|
|
16
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
17
|
+
declare const _default: typeof __VLS_export;
|
|
18
|
+
export default _default;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type Collaborator } from '../core/collaboration';
|
|
2
|
+
import type { DocumentEngine } from '../core/engine/engine';
|
|
3
|
+
interface Props {
|
|
4
|
+
/** Engine that measures positions in the document. */
|
|
5
|
+
engine: DocumentEngine;
|
|
6
|
+
/** People whose carets are drawn. */
|
|
7
|
+
collaborators: readonly Collaborator[];
|
|
8
|
+
/** Element the marks are positioned in; it must be positioned itself. */
|
|
9
|
+
container: HTMLElement;
|
|
10
|
+
/** Scrolling canvas; marks outside it are hidden. */
|
|
11
|
+
scrollTarget: HTMLElement;
|
|
12
|
+
/** Changes whenever the layout may move text (zoom, view mode, page setup), so the marks are measured again. */
|
|
13
|
+
layoutKey: string;
|
|
14
|
+
}
|
|
15
|
+
declare const __VLS_export: import("vue").DefineComponent<Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
16
|
+
declare const _default: typeof __VLS_export;
|
|
17
|
+
export default _default;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { DocumentComment } from '../core/comments';
|
|
2
|
+
interface Props {
|
|
3
|
+
/** Comments in document order. */
|
|
4
|
+
comments: ReadonlyArray<DocumentComment>;
|
|
5
|
+
/** Ids of the comments whose text is still in the document. */
|
|
6
|
+
anchored: ReadonlySet<string>;
|
|
7
|
+
/** Comment at the caret, highlighted in the list. */
|
|
8
|
+
activeId: string | null;
|
|
9
|
+
/** Whether the form of a new comment is shown. */
|
|
10
|
+
drafting: boolean;
|
|
11
|
+
/** Read-only document: comments can be read but not changed. */
|
|
12
|
+
readonly: boolean | undefined;
|
|
13
|
+
}
|
|
14
|
+
declare const __VLS_export: import("vue").DefineComponent<Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
15
|
+
select: (id: string) => any;
|
|
16
|
+
reply: (id: string, text: string) => any;
|
|
17
|
+
close: () => any;
|
|
18
|
+
remove: (id: string) => any;
|
|
19
|
+
resolve: (id: string, resolved: boolean) => any;
|
|
20
|
+
add: (text: string) => any;
|
|
21
|
+
cancelDraft: () => any;
|
|
22
|
+
}, string, import("vue").PublicProps, Readonly<Props> & Readonly<{
|
|
23
|
+
onSelect?: ((id: string) => any) | undefined;
|
|
24
|
+
onReply?: ((id: string, text: string) => any) | undefined;
|
|
25
|
+
onClose?: (() => any) | undefined;
|
|
26
|
+
onRemove?: ((id: string) => any) | undefined;
|
|
27
|
+
onResolve?: ((id: string, resolved: boolean) => any) | undefined;
|
|
28
|
+
onAdd?: ((text: string) => any) | undefined;
|
|
29
|
+
onCancelDraft?: (() => any) | undefined;
|
|
30
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
31
|
+
declare const _default: typeof __VLS_export;
|
|
32
|
+
export default _default;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
/** The footnote reference being edited. */
|
|
3
|
+
reference: HTMLElement;
|
|
4
|
+
/** Element the form is positioned in; it must be positioned itself. */
|
|
5
|
+
container: HTMLElement;
|
|
6
|
+
/** Read-only document: the note is shown but cannot be changed. */
|
|
7
|
+
readonly: boolean | undefined;
|
|
8
|
+
}
|
|
9
|
+
declare const __VLS_export: import("vue").DefineComponent<Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
10
|
+
cancel: () => any;
|
|
11
|
+
remove: () => any;
|
|
12
|
+
save: (text: string) => any;
|
|
13
|
+
}, string, import("vue").PublicProps, Readonly<Props> & Readonly<{
|
|
14
|
+
onCancel?: (() => any) | undefined;
|
|
15
|
+
onRemove?: (() => any) | undefined;
|
|
16
|
+
onSave?: ((text: string) => any) | undefined;
|
|
17
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
18
|
+
declare const _default: typeof __VLS_export;
|
|
19
|
+
export default _default;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { OutlineHeading, OutlineView } from '../core/outline';
|
|
2
|
+
import type { PageMetrics } from '../core/page';
|
|
3
|
+
import type { SheetGeometry } from '../core/pagination';
|
|
4
|
+
interface Props {
|
|
5
|
+
/** Headings of the document with the page each starts on, `null` outside the page view. */
|
|
6
|
+
headings: ReadonlyArray<OutlineHeading & {
|
|
7
|
+
page: number | null;
|
|
8
|
+
}>;
|
|
9
|
+
/** Clean HTML of every sheet, shown as page thumbnails; empty outside the page view. */
|
|
10
|
+
pages: readonly string[];
|
|
11
|
+
/** Page geometry the thumbnails are scaled from. */
|
|
12
|
+
metrics: PageMetrics;
|
|
13
|
+
/** Size of every sheet; a sheet turned by a section break gets a turned thumbnail. */
|
|
14
|
+
sheets: readonly SheetGeometry[];
|
|
15
|
+
/** Number printed on the first sheet. */
|
|
16
|
+
firstPageNumber: number;
|
|
17
|
+
}
|
|
18
|
+
type __VLS_Props = Props;
|
|
19
|
+
type __VLS_ModelProps = {
|
|
20
|
+
/** Which list is shown; the editor reads it to lay out thumbnails only while they are visible. */
|
|
21
|
+
'view'?: OutlineView;
|
|
22
|
+
};
|
|
23
|
+
type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
|
|
24
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
25
|
+
select: (heading: HTMLElement) => any;
|
|
26
|
+
close: () => any;
|
|
27
|
+
selectPage: (index: number) => any;
|
|
28
|
+
"update:view": (value: OutlineView) => any;
|
|
29
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
|
|
30
|
+
onSelect?: ((heading: HTMLElement) => any) | undefined;
|
|
31
|
+
onClose?: (() => any) | undefined;
|
|
32
|
+
onSelectPage?: ((index: number) => any) | undefined;
|
|
33
|
+
"onUpdate:view"?: ((value: OutlineView) => any) | undefined;
|
|
34
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
35
|
+
declare const _default: typeof __VLS_export;
|
|
36
|
+
export default _default;
|