asciidoc-editor 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +134 -0
- package/asciidoc_editor.d.ts +160 -0
- package/asciidoc_editor.js +1428 -0
- package/asciidoc_editor_bg.wasm +0 -0
- package/asciidoc_editor_bg.wasm.d.ts +42 -0
- package/package.json +43 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Alexander Thaller
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALING IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# AsciiDoc editor
|
|
2
|
+
|
|
3
|
+
A WYSIWYG AsciiDoc editor for the web, written in Rust and compiled to
|
|
4
|
+
WebAssembly. You edit the rendered document — click a heading and type in it,
|
|
5
|
+
press Tab in a list to indent — and the AsciiDoc source stays the thing being
|
|
6
|
+
edited underneath.
|
|
7
|
+
|
|
8
|
+
Parsing and rendering are [`asciidoc-parser`] and [`asciidoc-html5`]; the
|
|
9
|
+
interface is [Leptos].
|
|
10
|
+
|
|
11
|
+
## Using it in a page
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
npm install asciidoc-editor
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Or build it yourself, which is what `npm publish ./pkg` publishes:
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
./build-package.sh
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Either way you get an ES module, its wasm, and TypeScript types — and nothing
|
|
24
|
+
else to serve, because the stylesheets are inside the wasm.
|
|
25
|
+
|
|
26
|
+
```html
|
|
27
|
+
<div id="editor" style="height: 34rem"></div>
|
|
28
|
+
|
|
29
|
+
<script type="module">
|
|
30
|
+
import init, { mount } from "./pkg/asciidoc_editor.js";
|
|
31
|
+
|
|
32
|
+
await init();
|
|
33
|
+
const editor = mount("#editor", { value: "= Title\n\nWords.\n" });
|
|
34
|
+
|
|
35
|
+
editor.onChange((document) => save(document));
|
|
36
|
+
</script>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The editor fills the element it is given, so give that element a height.
|
|
40
|
+
|
|
41
|
+
Loading the module from a bundler works the same way. The wasm is fetched
|
|
42
|
+
relative to the module (`new URL(..., import.meta.url)`), which Vite, webpack 5
|
|
43
|
+
and esbuild all understand without configuration.
|
|
44
|
+
|
|
45
|
+
### TypeScript
|
|
46
|
+
|
|
47
|
+
The types ship with the package. wasm-bindgen writes a `[Symbol.dispose]()` on
|
|
48
|
+
its classes, so a project compiling the package's own declarations needs either
|
|
49
|
+
`"skipLibCheck": true` — which most templates set already — or `"lib"` at
|
|
50
|
+
`ESNext`.
|
|
51
|
+
|
|
52
|
+
### `mount(target, options?)`
|
|
53
|
+
|
|
54
|
+
`target` is a CSS selector or an element (`MountTarget`). The element keeps
|
|
55
|
+
whatever classes the page put on it.
|
|
56
|
+
|
|
57
|
+
| Option | Default | |
|
|
58
|
+
|---|---|---|
|
|
59
|
+
| `value` | the sample document | the document to open with |
|
|
60
|
+
| `autosave` | none | a `localStorage` key to keep the document under |
|
|
61
|
+
| `richText` | `true` | start in rich text rather than source |
|
|
62
|
+
|
|
63
|
+
With `autosave` set and no `value`, the editor opens whatever was last saved
|
|
64
|
+
under that key.
|
|
65
|
+
|
|
66
|
+
### The handle
|
|
67
|
+
|
|
68
|
+
| | |
|
|
69
|
+
|---|---|
|
|
70
|
+
| `editor.value()` | the document, as AsciiDoc |
|
|
71
|
+
| `editor.setValue(source)` | replace the document |
|
|
72
|
+
| `editor.onChange(callback)` | called with the document whenever it changes |
|
|
73
|
+
| `editor.destroy()` | take the editor off the page |
|
|
74
|
+
|
|
75
|
+
Hold on to the handle. Dropping it unmounts the editor, which is what
|
|
76
|
+
`destroy()` does deliberately; after it the element is as it was found, and can
|
|
77
|
+
be mounted into again.
|
|
78
|
+
|
|
79
|
+
`demo/index.html` is a page doing all of this. Serve the repository root and
|
|
80
|
+
open `/demo/`:
|
|
81
|
+
|
|
82
|
+
```sh
|
|
83
|
+
python3 -m http.server 8000
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Publishing
|
|
87
|
+
|
|
88
|
+
```sh
|
|
89
|
+
./build-package.sh
|
|
90
|
+
npm publish ./pkg
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
The leading `./` matters: `npm publish pkg` reads `pkg` as the name of a
|
|
94
|
+
package on the registry and tries to publish that one.
|
|
95
|
+
|
|
96
|
+
## Licence
|
|
97
|
+
|
|
98
|
+
MIT. See `LICENSE`.
|
|
99
|
+
|
|
100
|
+
## Developing
|
|
101
|
+
|
|
102
|
+
```sh
|
|
103
|
+
trunk serve # the editor on its own at http://localhost:8080
|
|
104
|
+
cargo test # the source-editing and parsing logic
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
`trunk serve` runs `src/main.rs`, which is the same library mounted onto a bare
|
|
108
|
+
page.
|
|
109
|
+
|
|
110
|
+
### How it works
|
|
111
|
+
|
|
112
|
+
The AsciiDoc source is the only state. It is rendered to HTML with source
|
|
113
|
+
locations turned on, which puts a `data-source-line` on every block, and that
|
|
114
|
+
HTML goes into an iframe. Blocks in the iframe are made `contenteditable`; when
|
|
115
|
+
one changes, its DOM is written back out as AsciiDoc and spliced over the lines
|
|
116
|
+
it came from.
|
|
117
|
+
|
|
118
|
+
A block is only made editable if writing its DOM back reproduces its source
|
|
119
|
+
exactly. Anything that fails that check is left alone and can still be edited
|
|
120
|
+
in source mode — better a block that refuses to be edited in place than one
|
|
121
|
+
that quietly rewrites itself.
|
|
122
|
+
|
|
123
|
+
The block being edited is never re-rendered, or the caret would be lost; the
|
|
124
|
+
line numbers of the blocks below it are shifted instead.
|
|
125
|
+
|
|
126
|
+
### What stays source-only
|
|
127
|
+
|
|
128
|
+
Cell specifiers (`2+|`, `a|`, `^|`), explicitly numbered ordered lists, image
|
|
129
|
+
sizing attributes, and anything else that would not survive the round trip.
|
|
130
|
+
Strikethrough is not implemented.
|
|
131
|
+
|
|
132
|
+
[`asciidoc-parser`]: https://crates.io/crates/asciidoc-parser
|
|
133
|
+
[`asciidoc-html5`]: https://crates.io/crates/asciidoc-html5
|
|
134
|
+
[Leptos]: https://leptos.dev
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/* tslint:disable */
|
|
2
|
+
/* eslint-disable */
|
|
3
|
+
/**
|
|
4
|
+
* The `ReadableStreamType` enum.
|
|
5
|
+
*
|
|
6
|
+
* *This API requires the following crate features to be activated: `ReadableStreamType`*
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export type ReadableStreamType = "bytes";
|
|
10
|
+
|
|
11
|
+
/** What the editor may be told when it is mounted. */
|
|
12
|
+
export interface MountOptions {
|
|
13
|
+
/** The document to open with. Defaults to a sample document. */
|
|
14
|
+
value?: string;
|
|
15
|
+
/** A `localStorage` key to keep the document under. */
|
|
16
|
+
autosave?: string;
|
|
17
|
+
/** Start in rich text rather than source. Defaults to `true`. */
|
|
18
|
+
richText?: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** The element to mount into, or a selector naming it. */
|
|
22
|
+
export type MountTarget = Element | string;
|
|
23
|
+
|
|
24
|
+
/** Called with the document, as AsciiDoc, whenever it changes. */
|
|
25
|
+
export type ChangeCallback = (document: string) => void;
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* A mounted editor.
|
|
31
|
+
*
|
|
32
|
+
* Hold on to it: letting it go takes the editor off the page, which is what
|
|
33
|
+
* [`Editor::destroy`] does deliberately.
|
|
34
|
+
*/
|
|
35
|
+
export class Editor {
|
|
36
|
+
private constructor();
|
|
37
|
+
free(): void;
|
|
38
|
+
[Symbol.dispose](): void;
|
|
39
|
+
/**
|
|
40
|
+
* Takes the editor off the page, leaving the element as it was found.
|
|
41
|
+
*/
|
|
42
|
+
destroy(): void;
|
|
43
|
+
/**
|
|
44
|
+
* Calls `callback` with the document whenever it changes.
|
|
45
|
+
*/
|
|
46
|
+
onChange(callback: ChangeCallback): void;
|
|
47
|
+
/**
|
|
48
|
+
* Replaces the document.
|
|
49
|
+
*/
|
|
50
|
+
setValue(source: string): void;
|
|
51
|
+
/**
|
|
52
|
+
* The document, as AsciiDoc.
|
|
53
|
+
*/
|
|
54
|
+
value(): string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export class IntoUnderlyingByteSource {
|
|
58
|
+
private constructor();
|
|
59
|
+
free(): void;
|
|
60
|
+
[Symbol.dispose](): void;
|
|
61
|
+
cancel(): void;
|
|
62
|
+
pull(controller: ReadableByteStreamController): Promise<any>;
|
|
63
|
+
start(controller: ReadableByteStreamController): void;
|
|
64
|
+
readonly autoAllocateChunkSize: number;
|
|
65
|
+
readonly type: ReadableStreamType;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export class IntoUnderlyingSink {
|
|
69
|
+
private constructor();
|
|
70
|
+
free(): void;
|
|
71
|
+
[Symbol.dispose](): void;
|
|
72
|
+
abort(reason: any): Promise<any>;
|
|
73
|
+
close(): Promise<any>;
|
|
74
|
+
write(chunk: any): Promise<any>;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export class IntoUnderlyingSource {
|
|
78
|
+
private constructor();
|
|
79
|
+
free(): void;
|
|
80
|
+
[Symbol.dispose](): void;
|
|
81
|
+
cancel(): void;
|
|
82
|
+
pull(controller: ReadableStreamDefaultController): Promise<any>;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Puts an editor inside the element named by `target`, a CSS selector or the
|
|
87
|
+
* element itself.
|
|
88
|
+
*
|
|
89
|
+
* Options, all of them optional: `value` to open with, `autosave` naming a
|
|
90
|
+
* `localStorage` key to keep the document under, and `richText` to start in
|
|
91
|
+
* rich text rather than source (the default).
|
|
92
|
+
*/
|
|
93
|
+
export function mount(target: MountTarget, options?: MountOptions | null): Editor;
|
|
94
|
+
|
|
95
|
+
export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
|
|
96
|
+
|
|
97
|
+
export interface InitOutput {
|
|
98
|
+
readonly memory: WebAssembly.Memory;
|
|
99
|
+
readonly __wbg_editor_free: (a: number, b: number) => void;
|
|
100
|
+
readonly __wbg_intounderlyingbytesource_free: (a: number, b: number) => void;
|
|
101
|
+
readonly __wbg_intounderlyingsink_free: (a: number, b: number) => void;
|
|
102
|
+
readonly __wbg_intounderlyingsource_free: (a: number, b: number) => void;
|
|
103
|
+
readonly editor_destroy: (a: number) => void;
|
|
104
|
+
readonly editor_onChange: (a: number, b: any) => void;
|
|
105
|
+
readonly editor_setValue: (a: number, b: number, c: number) => void;
|
|
106
|
+
readonly editor_value: (a: number) => [number, number];
|
|
107
|
+
readonly intounderlyingbytesource_autoAllocateChunkSize: (a: number) => number;
|
|
108
|
+
readonly intounderlyingbytesource_cancel: (a: number) => void;
|
|
109
|
+
readonly intounderlyingbytesource_pull: (a: number, b: any) => any;
|
|
110
|
+
readonly intounderlyingbytesource_start: (a: number, b: any) => void;
|
|
111
|
+
readonly intounderlyingbytesource_type: (a: number) => number;
|
|
112
|
+
readonly intounderlyingsink_abort: (a: number, b: any) => any;
|
|
113
|
+
readonly intounderlyingsink_close: (a: number) => any;
|
|
114
|
+
readonly intounderlyingsink_write: (a: number, b: any) => any;
|
|
115
|
+
readonly intounderlyingsource_cancel: (a: number) => void;
|
|
116
|
+
readonly intounderlyingsource_pull: (a: number, b: any) => any;
|
|
117
|
+
readonly mount: (a: any, b: number) => [number, number, number];
|
|
118
|
+
readonly wasm_bindgen_56bfa123189e575a___convert__closures_____invoke___js_sys_b5492ab1ec297925___Function_fn_wasm_bindgen_56bfa123189e575a___JsValue_____wasm_bindgen_56bfa123189e575a___sys__Undefined___js_sys_b5492ab1ec297925___Function_fn_wasm_bindgen_56bfa123189e575a___JsValue_____wasm_bindgen_56bfa123189e575a___sys__Undefined_______true_: (a: number, b: number, c: any, d: any) => void;
|
|
119
|
+
readonly wasm_bindgen_56bfa123189e575a___convert__closures_____invoke___wasm_bindgen_56bfa123189e575a___JsValue__core_dcdb5b26ee14507c___result__Result_____wasm_bindgen_56bfa123189e575a___JsError___true_: (a: number, b: number, c: any) => [number, number];
|
|
120
|
+
readonly wasm_bindgen_56bfa123189e575a___convert__closures_____invoke___wasm_bindgen_56bfa123189e575a___JsValue______true_: (a: number, b: number, c: any) => void;
|
|
121
|
+
readonly wasm_bindgen_56bfa123189e575a___convert__closures_____invoke___wasm_bindgen_56bfa123189e575a___JsValue______true__20: (a: number, b: number, c: any) => void;
|
|
122
|
+
readonly wasm_bindgen_56bfa123189e575a___convert__closures_____invoke___web_sys_7d5907b0b8adcf95___features__gen_CloseEvent__CloseEvent______true_: (a: number, b: number, c: any) => void;
|
|
123
|
+
readonly wasm_bindgen_56bfa123189e575a___convert__closures_____invoke___web_sys_7d5907b0b8adcf95___features__gen_DragEvent__DragEvent______true_: (a: number, b: number, c: any) => void;
|
|
124
|
+
readonly wasm_bindgen_56bfa123189e575a___convert__closures_____invoke___web_sys_7d5907b0b8adcf95___features__gen_DragEvent__DragEvent______true__19: (a: number, b: number, c: any) => void;
|
|
125
|
+
readonly wasm_bindgen_56bfa123189e575a___convert__closures_____invoke___web_sys_7d5907b0b8adcf95___features__gen_DragEvent__DragEvent______true__22: (a: number, b: number, c: any) => void;
|
|
126
|
+
readonly wasm_bindgen_56bfa123189e575a___convert__closures_____invoke___web_sys_7d5907b0b8adcf95___features__gen_DragEvent__DragEvent______true__23: (a: number, b: number, c: any) => void;
|
|
127
|
+
readonly wasm_bindgen_56bfa123189e575a___convert__closures_____invoke_______true_: (a: number, b: number) => void;
|
|
128
|
+
readonly wasm_bindgen_56bfa123189e575a___convert__closures_____invoke_______true__1_: (a: number, b: number) => void;
|
|
129
|
+
readonly __wbindgen_malloc: (a: number, b: number) => number;
|
|
130
|
+
readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
|
|
131
|
+
readonly __externref_table_alloc: () => number;
|
|
132
|
+
readonly __wbindgen_externrefs: WebAssembly.Table;
|
|
133
|
+
readonly __wbindgen_exn_store: (a: number) => void;
|
|
134
|
+
readonly __wbindgen_free: (a: number, b: number, c: number) => void;
|
|
135
|
+
readonly __wbindgen_destroy_closure: (a: number, b: number) => void;
|
|
136
|
+
readonly __externref_table_dealloc: (a: number) => void;
|
|
137
|
+
readonly __wbindgen_start: () => void;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export type SyncInitInput = BufferSource | WebAssembly.Module;
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Instantiates the given `module`, which can either be bytes or
|
|
144
|
+
* a precompiled `WebAssembly.Module`.
|
|
145
|
+
*
|
|
146
|
+
* @param {{ module: SyncInitInput }} module - Passing `SyncInitInput` directly is deprecated.
|
|
147
|
+
*
|
|
148
|
+
* @returns {InitOutput}
|
|
149
|
+
*/
|
|
150
|
+
export function initSync(module: { module: SyncInitInput } | SyncInitInput): InitOutput;
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* If `module_or_path` is {RequestInfo} or {URL}, makes a request and
|
|
154
|
+
* for everything else, calls `WebAssembly.instantiate` directly.
|
|
155
|
+
*
|
|
156
|
+
* @param {{ module_or_path: InitInput | Promise<InitInput> }} module_or_path - Passing `InitInput` directly is deprecated.
|
|
157
|
+
*
|
|
158
|
+
* @returns {Promise<InitOutput>}
|
|
159
|
+
*/
|
|
160
|
+
export default function __wbg_init (module_or_path?: { module_or_path: InitInput | Promise<InitInput> } | InitInput | Promise<InitInput>): Promise<InitOutput>;
|