weml-monaco 0.5.2 → 0.7.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/CHANGELOG.md CHANGED
@@ -4,9 +4,62 @@ All notable changes to the **Monaco** contribution (`weml-monaco`) are documente
4
4
  here. The format is based on [Keep a Changelog](https://keepachangelog.com/), and
5
5
  this project adheres to [Semantic Versioning](https://semver.org/).
6
6
 
7
- This package mirrors the language logic of the VS Code extension; entries below
8
- track the port catching up with the VS Code provider changes (see the root
9
- `CHANGELOG.md` for the extension).
7
+ This package shares the language logic of the VS Code extension (`weml-support`): older
8
+ entries below track the port catching up with its provider changes; since the shared language
9
+ features, both take the same code.
10
+
11
+ ## [0.7.0] — 2026-10-08
12
+
13
+ ### Added
14
+ - The WEML panel, `weml-monaco/panel` (+ `weml-monaco/panel.css`): the Tools and Search
15
+ Suggestions of the WEML Tools VS Code extension as a framework-free DOM component
16
+ (`createWemlPanel`), the same look and behaviour, theme from Monaco's `--vscode-*` variables.
17
+ - `createMonacoPanelHost(editor, options)`: the panel's host for a Monaco editor — tools on the
18
+ editor, EGW search with the host application's access token, layout in `localStorage`.
19
+ - `createEgwWebAuth(options)`: the EGW sign-in for a web page without one of its own — OIDC
20
+ code flow + PKCE in a popup, tokens in `sessionStorage` (or `localStorage`), refreshed; spread
21
+ into `createMonacoPanelHost`'s `search`. The panel shows why a sign-in failed under its button
22
+ (`signIn` may return a promise).
23
+ - The WEML preview, `weml-monaco/preview` (`createWemlPreview`): a live preview of the editor's
24
+ document rendered with weml-stylesheet — now an optional peer dependency (`^4.0.0`), needed
25
+ only by this entry.
26
+ - The preview's scroll sync with the editor, both ways (`scrollSync` option, `setScrollSync`),
27
+ sharing its line mapping with the VS Code preview; a `transform` may return
28
+ `{ markup, firstLine }`; double-click on a block goes to its line (`doubleClickToEditor`).
29
+ It looks like the open file next to it: the editor's background, text and scrollbars (10px,
30
+ the theme's slider) through `monaco-component` on the pane (`monacoTheme: false` to leave it
31
+ off), not the page's background and the browser's scrollbars.
32
+ - `TOOL_ITEMS` / `TOOL_GROUPS` / `filterToolItems` / `toolItemsFor(mode)`: everything the Tools
33
+ offer as one list, grouped Containers / Blocks / Inlines, for a host that builds its own picker.
34
+ - Completion in `<body>` offers a new section with any container — *div + w-para*,
35
+ *div + w-toc* and *div + w-page* join *div + w-heading* and *div + w-para-group*.
36
+
37
+ ### Changed
38
+ - The WEML panel's Tools: the wrap `<select>` and the Insert buttons are one searchable tag picker
39
+ drawn as VS Code's quick pick (Enter or a click chooses, the button in the field wraps or
40
+ inserts; the choice is remembered). It offers every tag of the docs' containers, blocks and
41
+ inlines — new: `w-para-group`, `w-toc`, a new `div` section (with `w-para`, `w-heading`,
42
+ `w-para-group`, `w-toc`, or a `w-page` page break), `w-text-block`, `hr`, `w-page`, `br`,
43
+ the `a id` anchor, and `w-sent` to wrap in. Inputs and buttons are 26px, as in VS Code,
44
+ whatever the host page's line height; the panes' borders show under Monaco themes too.
45
+ - `createWemlPanel`'s `renumber` option is `mode: 'file' | 'paragraph'`: a paragraph has no
46
+ Renumber and no `div` sections to insert (`toolItemsFor(mode)`).
47
+ - Diagnostics, hover, completion and the outline come from the language logic shared with the
48
+ VS Code extension instead of a copy of it — the same problems, tooltips and suggestions in
49
+ both editors; no change in behaviour or API.
50
+
51
+ ### Deprecated
52
+ - `createWemlToolbar`: use the WEML panel (and `registerWemlEditorActions` for the hotkeys alone).
53
+
54
+ ## [0.6.0] — 2026-10-06
55
+
56
+ ### Added
57
+ - `registerWemlWorker` (`weml-monaco/worker`) and the worker module
58
+ (`weml-monaco/weml.worker`): the same language with completion, hover, outline
59
+ and diagnostics computed in a Web Worker, so typing in a large document no
60
+ longer waits for a full parse per keystroke. The worker mirrors models from
61
+ their edits and answers only the current version of a model. `registerWeml`
62
+ and the global build are unchanged.
10
63
 
11
64
  ## [0.5.2] — 2026-09-26
12
65
 
package/README.md CHANGED
@@ -23,7 +23,8 @@ extension. It provides, for models with language id `weml`:
23
23
  HTML ↔ canonical format cycle.
24
24
 
25
25
  This package does **not** modify the original VS Code extension — it is a
26
- standalone re-implementation living entirely under `monaco-editor/`.
26
+ standalone re-implementation living entirely in this package (`packages/weml-monaco` of the
27
+ monorepo).
27
28
 
28
29
  ## Install & build
29
30
 
@@ -31,9 +32,9 @@ standalone re-implementation living entirely under `monaco-editor/`.
31
32
  npm install weml-monaco
32
33
  ```
33
34
 
34
- The package has no runtime dependencies (`node-html-parser` and
35
- `vscode-html-languageservice` are bundled in) and `monaco-editor` is a peer
36
- dependency you already have.
35
+ The package has no runtime dependencies (`node-html-parser`,
36
+ `vscode-html-languageservice` and the shared normalizer are bundled in) and
37
+ `monaco-editor` is a peer dependency you already have.
37
38
 
38
39
  | entry point | file | for |
39
40
  | ---------------------- | ----------------------- | -------------------------------------- |
@@ -44,8 +45,8 @@ dependency you already have.
44
45
  Working on the package itself:
45
46
 
46
47
  ```bash
47
- cd monaco-editor
48
- npm install
48
+ npm ci # at the repository root (npm workspaces)
49
+ cd packages/weml-monaco
49
50
  npm run build # tsup → dist/ (ESM + CJS + IIFE + declarations)
50
51
  npm run typecheck # tsc --noEmit
51
52
  npm test # build + Node smoke test
@@ -99,31 +100,146 @@ formatter directly, call `formatWemlText(source)`.
99
100
  Structural validation failures are also available through
100
101
  `isWemlStructureValidationFailure(error)` and include a code, line and column.
101
102
 
102
- ## Toolbar & editor commands
103
+ ## Analysis in a Web Worker
103
104
 
104
- Monaco has no webview panels, so the VS Code toolbar is ported as a **DOM top
105
- panel** you mount yourself, plus per-editor actions/keybindings. Button
106
- behaviour is identical to the VS Code toolbar.
105
+ `registerWeml` runs completion, hover, outline and diagnostics on the main
106
+ thread, and each of them parses the whole document for every new model version.
107
+ On a large file — a whole book, a megabyte and more — that is tens of
108
+ milliseconds per keystroke on the thread that handles the keystrokes, and typing
109
+ falls behind. `registerWemlWorker` registers the same language with that work in
110
+ a Web Worker:
107
111
 
108
112
  ```ts
109
- import { registerWeml, createWemlToolbar } from 'weml-monaco';
113
+ import * as monaco from 'monaco-editor/editor/editor.api.js';
114
+ import { registerWemlWorker } from 'weml-monaco/worker';
115
+ import WemlWorker from 'weml-monaco/weml.worker?worker'; // Vite
116
+
117
+ const disposable = registerWemlWorker(monaco, { worker: () => new WemlWorker() });
118
+ // Without Vite's `?worker`:
119
+ // registerWemlWorker(monaco, {
120
+ // worker: () => new Worker(new URL('weml-monaco/weml.worker', import.meta.url), { type: 'module' }),
121
+ // });
122
+ ```
123
+
124
+ It is an addition, not a replacement: `registerWeml` is unchanged, and an
125
+ application chooses one of the two (registering both doubles every provider).
126
+ The worker runs the very providers `registerWeml` registers, so suggestions,
127
+ hovers, outline and markers are the same; only where they are computed changes.
128
+
129
+ - The worker mirrors each `weml` model's text and keeps it current from the
130
+ edits Monaco reports — the main thread never sends the whole text again.
131
+ - A request is answered only for the model's current version: one overtaken by
132
+ further typing is answered empty without parsing, so holding a key down does
133
+ not build up a queue of parses.
134
+ - Markers computed for a version the model has since left are dropped.
135
+ - The tokenizer, language configuration and formatting stay on the main thread,
136
+ as with `registerWeml` — they are cheap or run on demand.
137
+ - `weml-monaco/weml.worker` imports nothing of Monaco at runtime; it needs a
138
+ bundler (or a module worker) to load, and is ESM only.
139
+
140
+ `registerWemlWorker(monaco, options)` takes the options of `registerWeml` plus
141
+ `worker`, a function that starts the worker (called once; the worker is
142
+ terminated on `dispose()`). The global `<script>` build (`index.global.js`) has
143
+ no worker variant.
144
+
145
+ ## The WEML panel
146
+
147
+ The side panel of the WEML Tools VS Code extension — **Tools** (wrap the
148
+ selection, insert snippets, renumber `<div>` ids) and **Search Suggestions**
149
+ (EGW search with ParaId / BookCode and `egw://` links to copy or insert) — as a
150
+ framework-free DOM component: the same panel, look and behaviour as in VS Code
151
+ (the extension's webview bundles this very code). It follows Monaco's theme
152
+ through the `--vscode-*` variables Monaco defines on `.monaco-component`.
153
+
154
+ ```ts
155
+ import { createMonacoPanelHost, registerWeml, registerWemlEditorActions } from 'weml-monaco';
156
+ import { createWemlPanel } from 'weml-monaco/panel';
110
157
 
111
158
  registerWeml(monaco);
112
159
  const editor = monaco.editor.create(container, { model });
160
+ registerWemlEditorActions(monaco, editor); // the hotkeys (below)
161
+
162
+ const panel = createWemlPanel(
163
+ createMonacoPanelHost(editor, {
164
+ search: {
165
+ apiUrl: 'https://…', // the EGW API
166
+ getAccessToken: () => auth.token, // your app's sign-in — the only part you supply
167
+ signIn: () => auth.login(),
168
+ onDidChangeSession: (listener) => auth.subscribe(listener),
169
+ },
170
+ }),
171
+ { container: document.getElementById('weml-panel') },
172
+ );
173
+ // panel.dispose() removes it.
174
+ ```
113
175
 
114
- const toolbar = createWemlToolbar(monaco, editor, {
115
- container: document.getElementById('weml-toolbar'), // where to mount the panel
116
- onStatus: (msg) => console.log(msg), // optional status sink
117
- // includeSearchRow: true, // stub search row (as in VS Code); default true
118
- // onSearch: (query, lang) => { ... },
119
- });
176
+ - `createWemlPanel` (`weml-monaco/panel`, light: UI only) takes any
177
+ `WemlPanelHost` — tools, an optional search backend, optional storage.
178
+ `createMonacoPanelHost` (main entry) is that host for a Monaco editor: the
179
+ tools edit the editor, the search sends your token as a bearer (nothing is
180
+ sent while signed out), and the layout is remembered in `localStorage`
181
+ (`storage: false` to opt out). Without `search` the pane says the search is
182
+ not configured; `search: false` leaves the pane out.
183
+ - An app without a sign-in of its own takes `createEgwWebAuth` — the EGW
184
+ sign-in (OIDC code flow + PKCE) in a popup, tokens in `sessionStorage`,
185
+ refreshed before they expire:
186
+
187
+ ```ts
188
+ import { createEgwWebAuth } from 'weml-monaco';
189
+
190
+ const auth = createEgwWebAuth({ authority, clientId, scopes: 'openid offline_access search' });
191
+ createMonacoPanelHost(editor, { search: { apiUrl, ...auth } });
192
+ auth.signOut(); // from the app's own account menu
193
+ ```
194
+
195
+ The popup comes back to `<origin>/auth-callback` (`redirectUri` to change it),
196
+ which the client must allow; a blank page there is enough. `storage: 'local'`
197
+ shares the session between tabs. A failed sign-in (popup blocked or closed,
198
+ refused) shows its message under the Sign in button.
199
+ - The stylesheet is injected into the page by default; it is also published as
200
+ `weml-monaco/panel.css` (`injectStyles: false` to link it yourself).
201
+ - `mode: 'paragraph'` for an editor of one paragraph (the content of one `<div>`): no Renumber
202
+ div ids, and no `div + …` sections among the tags to insert. Default: `'file'`.
203
+
204
+ ## The WEML preview
205
+
206
+ A live preview of the editor's document, rendered with
207
+ [`weml-stylesheet`](../weml-stylesheet) — an **optional peer dependency**:
208
+ install it (and load its CSS) to use this entry; nothing else needs it.
209
+
210
+ ```ts
211
+ import { createWemlPreview } from 'weml-monaco/preview';
212
+ import 'weml-stylesheet/css/weml.scoped.css';
120
213
 
121
- // toolbar.element → the toolbar DOM node
122
- // toolbar.dispose() → removes the panel and its actions
214
+ const preview = createWemlPreview(editor, {
215
+ container: document.getElementById('weml-preview'),
216
+ theme: 'dark', // data-weml-theme; omit to follow prefers-color-scheme
217
+ });
218
+ preview.setTheme('light');
219
+ preview.dispose();
123
220
  ```
124
221
 
125
- `createWemlToolbar` also registers the editor actions (so they work without the
126
- panel too, via keybindings and the command palette):
222
+ It renders at once, then after a pause in typing (`delay`, 300 ms), and at once
223
+ again when the editor gets another model; an unchanged text is not rendered
224
+ twice (a whole book is not cheap). A whole document keeps its `dir` / `lang`
225
+ (right-to-left books render right to left). Blank text shows `emptyText`;
226
+ `transform(text)` picks what to render (e.g. without a wrapper your editor adds).
227
+
228
+ **Scroll sync**, both ways, as in the VS Code preview (and with the same code,
229
+ weml-language-core's): the rendered blocks carry their source line, so the
230
+ preview follows the editor's top line and the editor follows the preview. Turn
231
+ it off, or one way only, with `scrollSync` — `true` (default), `false`, or
232
+ `{ previewWithEditor, editorWithPreview }` — and later with
233
+ `preview.setScrollSync(...)`; there is no button of its own, the host decides.
234
+ A `transform` that drops lines from the top returns `{ markup, firstLine }` (the
235
+ model line its markup starts on) so the lines still match. A double-click on a
236
+ block puts the editor's cursor on its line (`doubleClickToEditor: false` to
237
+ turn it off).
238
+
239
+ ## Editor commands
240
+
241
+ `registerWemlEditorActions(monaco, editor, options)` registers the editor
242
+ actions (keybindings and the command palette):
127
243
 
128
244
  | action / command | keybinding | effect |
129
245
  | ------------------------- | ------------- | -------------------------------------------------- |
@@ -133,38 +249,36 @@ panel too, via keybindings and the command palette):
133
249
  | `weml.cycleFormat` | `Alt+Shift+F` | cycle document: HTML ↔ canonical WEML formatting |
134
250
  | `weml.renumberDivIds` | — | renumber every `<div>` id to a sequential 1…n |
135
251
 
136
- To register the actions without a toolbar, call
137
- `registerWemlEditorActions(monaco, editor, options)`. The underlying operations
138
- (`wrapSelection`, `insertSnippetById`, `renumberDivIds`, `cycleFormat`) and the
139
- pure helpers (`planDivIdRenumber`, `buildWrapSnippet`) are exported too.
252
+ The underlying operations (`wrapSelection`, `insertSnippetById`,
253
+ `renumberDivIds`, `cycleFormat`) and the pure helpers (`planDivIdRenumber`,
254
+ `buildWrapSnippet`) are exported too. Snippet insertion uses Monaco's
255
+ `snippetController2`, so `$1`/`$2` tab stops behave like `editor.insertSnippet`
256
+ in VS Code.
140
257
 
141
- Snippet insertion uses Monaco's `snippetController2`, so `$1`/`$2` tab stops
142
- behave like `editor.insertSnippet` in VS Code.
258
+ ### `createWemlToolbar` (deprecated)
143
259
 
144
- ## Demo
260
+ The earlier flat DOM toolbar (`createWemlToolbar(monaco, editor, { container,
261
+ onStatus })`) still works and still registers the editor actions, but it is
262
+ deprecated in favour of the WEML panel and gets no new features.
145
263
 
146
- ```bash
147
- npm run demo # builds dist/ and copies the IIFE bundle → demo/weml-monaco.js
148
- # then open demo/index.html in a browser
149
- ```
264
+ ## Trying it out
150
265
 
151
- `demo/index.html` loads Monaco from a CDN (AMD loader) and the WEML bundle as a
152
- global, registers the language and opens a sample document. Try Ctrl+Space for
153
- completions, hover a tag, "Format Document", Ctrl+Shift+O for the outline, or
154
- introduce an error to see diagnostics.
266
+ The monorepo's dev app, [`weml-storybook`](../weml-storybook)
267
+ (`npm start -w weml-storybook` from the repository root), runs this package from
268
+ its sources in a full editor: completions (Ctrl+Space), hover, "Format
269
+ Document", the outline, diagnostics and the toolbar.
155
270
 
156
271
  ## How it relates to the VS Code extension
157
272
 
158
- The schema and rules are reused verbatim from the extension:
273
+ The language logic is shared with the VS Code extensions, not copied — two workspace packages,
274
+ bundled into every build (neither is a runtime dependency):
159
275
 
160
- | reused as-is | adapted for Monaco |
276
+ | shared (workspace package) | adapted for Monaco |
161
277
  | ------------------------------------ | ---------------------------------------- |
162
- | `generated/schema.ts` | `schema/docs.ts` (plain `IMarkdownString`) |
163
- | `schema/structureRules.ts` | `wemlDocument.ts` (model ↔ LSP positions) |
164
- | `schema/snippets.ts` | `providers/*` (Monaco provider APIs) |
165
- | `wemlContentNormalizeV2.js` | `index.ts` (registration + diagnostics) |
166
-
167
- Parsing still uses `vscode-html-languageservice` (it is editor-agnostic and
168
- runs in the browser); the only real work was swapping the `vscode.*` provider
169
- APIs for `monaco.languages.*` and converting between Monaco's 1-based
170
- line/column positions and the language service's 0-based LSP positions.
278
+ | `weml-language-core`: schema, structure rules, snippets, entities, markdown text, the language features (diagnostics, hover, completion, outline), tool model, wrap / renumber | `schema/docs.ts` (plain `IMarkdownString`) |
279
+ | `weml-normalizer`: the canonical normalizer | `wemlDocument.ts` (parse cache, offsets ↔ Monaco positions), `providers/*` (the features' data → Monaco provider types), `index.ts` (registration + diagnostics) |
280
+
281
+ The diagnostics, tooltips, suggestions and outline are computed once, in the shared package, as
282
+ plain data with offsets — the VS Code extension and this package only map them to their editor's
283
+ types, so both editors report the same problems and offer the same items. Parsing uses
284
+ `vscode-html-languageservice` (editor-agnostic, runs in the browser and in the worker).