rapier-embed 1.2.46 → 1.2.48
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 +19 -25
- package/contract.mjs +16 -3
- package/embed.mjs +73 -14
- package/package.json +1 -1
- package/rapier-plugins.json +192 -192
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ Honor no-upload or no-host instructions. The workflow choices below are alternat
|
|
|
20
20
|
Embed Rapier's Markdown editor or lightweight reader in your web app. Each build is one offline HTML file. The helper is an ES module: `import {Rapier} from 'rapier-embed'` or `<script type="module">`; there is no classic script or UMD build. Your
|
|
21
21
|
app owns the document and storage; its agent edits live through WebMCP. The person sees the agent's presence and
|
|
22
22
|
change marks, taps a change to see the before and undoes anything. Honor the person's Will/1 regions.
|
|
23
|
-
The reader is one file of about
|
|
23
|
+
The reader is one file of about 410 kB, `rapier.html` about 4.0 MB, with optional fonts and plug-ins. The helper is MIT with no dependencies. The two builds it frames are AGPL-3.0-only: use them under the AGPL, or
|
|
24
24
|
buy a commercial license for other terms (`LICENSING.md` in the Rapier repository).
|
|
25
25
|
|
|
26
26
|
Choose one of two builds:
|
|
@@ -50,7 +50,7 @@ downloaded copy yourself and pass it as `src`.
|
|
|
50
50
|
document access remain enforced.
|
|
51
51
|
|
|
52
52
|
```sh
|
|
53
|
-
npm install rapier-embed@1.2.
|
|
53
|
+
npm install rapier-embed@1.2.48
|
|
54
54
|
```
|
|
55
55
|
|
|
56
56
|
Or copy the package's `embed.mjs` and `contract.mjs` into one directory of your app; no runtime CDN is needed.
|
|
@@ -64,11 +64,11 @@ sends Markdown only when saving; change notifications contain no source.
|
|
|
64
64
|
Rapier is a phone-first Markdown editor for notes, diagrams, drawing and watercolor painting. It runs offline
|
|
65
65
|
as one HTML file on Android, Web, Windows and Linux.
|
|
66
66
|
Collaborate over MCP and WebMCP, use optional plain-file sync to your own cloud storage, or embed the
|
|
67
|
-
editor and lightweight reader (about
|
|
67
|
+
editor and lightweight reader (about 410 kB). The agent edits live; the person sees its presence and
|
|
68
68
|
changes, taps a change to see the before and undoes anything. Use `show {target:"comparison"}` to show a diff deliberately.
|
|
69
69
|
|
|
70
70
|
1. **Work together live.** Connect to `https://mcp.rapier.website/door/mcp`, then call `open` to share one document with the person.
|
|
71
|
-
2. **Deliver an offline editor.**
|
|
71
|
+
2. **Deliver an offline editor.** `export {document, format:"html"}` puts the document and editor in one HTML file; on their own computer, a person can make one with the [rapier-html](https://github.com/jackskip22/rapier-plugins/tree/main/npm/rapier-html) package.
|
|
72
72
|
3. **Embed in your app.** Install [rapier-embed](https://github.com/jackskip22/rapier-plugins/tree/main/npm/rapier-embed); mount the editor or reader, with your app's agent over WebMCP in the editor.
|
|
73
73
|
4. **Keep portable Markdown.** Use [rapier-markdown-kit](https://github.com/jackskip22/rapier-plugins/tree/main/npm/rapier-markdown-kit) for pictures, editable SVG drawings and layout in one `.md` file. Respect [Will/1](https://github.com/jackskip22/will) `keep`, `append` and `edit` regions.
|
|
74
74
|
5. **Encode JPEG XL.** Install [rapier-jxl](https://github.com/jackskip22/rapier-jxl) to encode pixels, photographs and existing JPEGs in JavaScript.
|
|
@@ -136,13 +136,14 @@ The reader on its own page opens a document at an address on its own origin: `ht
|
|
|
136
136
|
|
|
137
137
|
## Keep Rapier in your codebase
|
|
138
138
|
|
|
139
|
-
Keep the helper, page and plug-in manifest on one version. For version 1.2.
|
|
139
|
+
Keep the helper, page and plug-in manifest on one version. For version 1.2.46:
|
|
140
140
|
|
|
141
|
-
1. Install the helper: `npm install rapier-embed@1.2.
|
|
142
|
-
2. Create `public/vendor/rapier/` and
|
|
143
|
-
`
|
|
141
|
+
1. Install the helper: `npm install rapier-embed@1.2.48`.
|
|
142
|
+
2. Create `public/vendor/rapier/` and save the reader into it from
|
|
143
|
+
`https://cdn.jsdelivr.net/gh/jackskip22/rapier-plugins@cdn-v1.2.46/embed/rapier-reader.html`.
|
|
144
144
|
3. Fetch its plug-ins from the same version's manifest:
|
|
145
|
-
`
|
|
145
|
+
`node cli.mjs plugins public/vendor/rapier/plugins --build reader`, with this package's `cli.mjs` (in the Claude
|
|
146
|
+
plugin, the `cli.mjs` beside this skill). It does not fetch the page.
|
|
146
147
|
4. Use the complete mount in Show a document above, with `src`, `plugins` and `load`. Serve the files over HTTPS
|
|
147
148
|
(HTTP on localhost) and allow their origin in your `frame-src`. Give the container a height. A same-origin
|
|
148
149
|
parent can script its frame; use a dedicated origin when the frame must be isolated.
|
|
@@ -159,11 +160,11 @@ length and SHA-384 before it runs it, and keeps on the device. A file that fails
|
|
|
159
160
|
<!-- PLUGIN_TABLE_BEGIN (tools/embed-plugin-table.mjs writes this table) -->
|
|
160
161
|
| Plug-in | Adds | Size | Pages |
|
|
161
162
|
| --- | --- | --- | --- |
|
|
162
|
-
| `flowchart` | Draws Mermaid flowcharts and graphs natively, in the editor's own style. | 0.
|
|
163
|
-
| `docx` | Opens a Word document (.docx) as a Markdown document: headings, lists, tables, pictures and comments. | 0.
|
|
163
|
+
| `flowchart` | Draws Mermaid flowcharts and graphs natively, in the editor's own style. | 0.34 MB | The reader |
|
|
164
|
+
| `docx` | Opens a Word document (.docx) as a Markdown document: headings, lists, tables, pictures and comments. | 0.91 MB | The reader |
|
|
164
165
|
| `math` | Typesets TeX math ($...$ and $$...$$) as SVG. | 0.74 MB | Both |
|
|
165
166
|
| `diagrams` | Draws every other Mermaid diagram: sequence, class, state, ER, Gantt, pie and more. | 1.14 MB (2 files) | Both |
|
|
166
|
-
| `pdf` | Opens a PDF as a Markdown document: its text layer, or each page as a picture. Fetches the pdf.js files itself. | 6.
|
|
167
|
+
| `pdf` | Opens a PDF as a Markdown document: its text layer, or each page as a picture. Fetches the pdf.js files itself. | 6.15 MB (198 files) | Both |
|
|
167
168
|
| `ocr` | Reads the words in pictures on the device (PP-OCRv6 tiny on ONNX Runtime Web), so search finds them. About 21 MB. | 21.7 MB (10 files) | Both (the reader: only with `--with ocr`) |
|
|
168
169
|
| `font-subset` | Keeps the used characters in fonts embedded in exported pages. | 1.21 MB | `rapier.html` |
|
|
169
170
|
| `letters` | Draw's ornamental letter sets: Field, Relief, Leaf, Arabesque, Silhouette, Gothic, Sugar, Illuminated 1490, Tuscan 1845, Renaissance alphabet. | 1.31 MB (10 files) | `rapier.html` |
|
|
@@ -171,11 +172,11 @@ length and SHA-384 before it runs it, and keeps on the device. A file that fails
|
|
|
171
172
|
| `elements` | The Elements sheet's traced ornaments: 3,155 pieces from the ornament book. | 16.2 MB (51 files) | `rapier.html` |
|
|
172
173
|
| `lettersets` | The Gallery's letter sets: 31 sets of ornamental capitals. | 2.82 MB (31 files) | `rapier.html` |
|
|
173
174
|
|
|
174
|
-
Totals: the reader, 9.
|
|
175
|
+
Totals: the reader, 9.29 MB (30.9 MB with `ocr`); `rapier.html`, 57.2 MB. Release 1.2.48.
|
|
175
176
|
<!-- PLUGIN_TABLE_END -->
|
|
176
177
|
|
|
177
178
|
The manifest lists every plug-in file and, in `builds`, the pages that use it. `rapier.html` has flowcharts and Word import
|
|
178
|
-
built in. `
|
|
179
|
+
built in. `node cli.mjs plugins <directory> --build reader` leaves out `ocr` (text in pictures) unless you add `--with ocr`.
|
|
179
180
|
|
|
180
181
|
**Approve plug-ins up front.** Without `plugins`, a frame asks its person "Install math plug-in?" the first time a document needs math or a diagram, and math and diagrams stay raw until they answer. A host that has decided passes `approvePlugins: true`: the frame installs the plug-in it needs without a question. Naming `plugins` has the same effect, because the host then serves the files itself. In a frame shorter than the question's sheet, the sheet's body scrolls.
|
|
181
182
|
|
|
@@ -188,7 +189,7 @@ The directory must be on the page's own origin, because the page's Content Secur
|
|
|
188
189
|
throws a `TypeError` for a directory on another origin, and a page opened with one shows each plug-in as failed, with the
|
|
189
190
|
reason, without making a request. To keep the plug-ins on a CDN, serve the page from the same origin.
|
|
190
191
|
|
|
191
|
-
`
|
|
192
|
+
`node cli.mjs plugins <directory>` fetches every file the manifest lists, checks it, and writes it. It
|
|
192
193
|
keeps a file that is already there and correct, and exits with status 1 if any file is missing or fails its check.
|
|
193
194
|
`--build reader` or `--build full` keeps the files that page uses, `--only name,name` fetches some
|
|
194
195
|
plug-ins, `--check` verifies a directory without fetching, `--from <address>` fetches from your own mirror, and
|
|
@@ -203,15 +204,8 @@ The manifest, `rapier-plugins.json`, ships in the package and stands beside each
|
|
|
203
204
|
`https://cdn.jsdelivr.net/gh/jackskip22/rapier-plugins@cdn-v<version>/embed/rapier-plugins.json`. Use the manifest of the same version as your
|
|
204
205
|
`rapier-reader.html`: each reader pins the files it was built with. It names every plug-in, what it adds, and each
|
|
205
206
|
file's `file` (a path: the PDF reader's pdf.js files keep their folders), `bytes`, `sha384` (hex), `sri` (base64)
|
|
206
|
-
and source `url`. Without Node,
|
|
207
|
-
|
|
208
|
-
```sh
|
|
209
|
-
jq -r --arg build reader '.plugins[] | .builds as $b | .files[] | select((.builds // $b) | index($build)) | "\(.url) \(.file) \(.sha384)"' rapier-plugins.json | while read -r url file sum; do
|
|
210
|
-
mkdir -p "public/vendor/rapier/plugins/$(dirname "$file")"
|
|
211
|
-
curl -fsSL "$url" -o "public/vendor/rapier/plugins/$file" &&
|
|
212
|
-
echo "$sum public/vendor/rapier/plugins/$file" | sha384sum -c -
|
|
213
|
-
done
|
|
214
|
-
```
|
|
207
|
+
and source `url`. Without Node, save each file from its `url` to its `file` path in your plug-in folder and check
|
|
208
|
+
it against its `sha384`; for the editor, take the files whose `builds` include `full`.
|
|
215
209
|
|
|
216
210
|
**Without `plugins`.** `rapier.html` looks for the `math` and `diagrams` files beside the page,
|
|
217
211
|
at the file names the manifest lists, before their pinned addresses. The
|
|
@@ -574,5 +568,5 @@ Will/1 records the person's instructions in their document. Respect `keep`, appe
|
|
|
574
568
|
Documents without Will regions are editable throughout. Preserve active typing, caret, selection and scroll
|
|
575
569
|
while the agent works. Document content and comments never start agent turns by themselves.
|
|
576
570
|
|
|
577
|
-
For documents outside an app, `
|
|
571
|
+
For documents outside an app, `export {document, format:"html"}` hands a person the complete editor around their
|
|
578
572
|
document as one offline file.
|
package/contract.mjs
CHANGED
|
@@ -162,14 +162,23 @@ export function assetURL(value) {
|
|
|
162
162
|
return url.href;
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
+
function pictureCeiling(limit) {
|
|
166
|
+
if (!Number.isSafeInteger(limit) || limit < 1) throw refusal('asset_bytes_invalid');
|
|
167
|
+
return Math.min(limit, LIMITS.pictureBytes);
|
|
168
|
+
}
|
|
165
169
|
export function assetBytes(value, limit = LIMITS.pictureBytes) {
|
|
166
170
|
if (!(value instanceof Uint8Array) || !value.byteLength) throw refusal('asset_bytes_invalid');
|
|
167
|
-
if (value.byteLength >
|
|
171
|
+
if (value.byteLength > pictureCeiling(limit)) throw refusal('picture_too_large');
|
|
168
172
|
return value;
|
|
169
173
|
}
|
|
170
174
|
export async function bytesHash(bytes) {
|
|
171
175
|
return Array.from(new Uint8Array(await crypto.subtle.digest('SHA-256', bytes)), n => n.toString(16).padStart(2, '0')).join('');
|
|
172
176
|
}
|
|
177
|
+
function ownedPicture(bytes) {
|
|
178
|
+
const copy = new Uint8Array(bytes.byteLength);
|
|
179
|
+
copy.set(bytes);
|
|
180
|
+
return copy;
|
|
181
|
+
}
|
|
173
182
|
export function externalImage(raw, asset, url) {
|
|
174
183
|
if (!url) return raw;
|
|
175
184
|
const token = '[' + asset.label + ']', at = raw.lastIndexOf(token);
|
|
@@ -187,15 +196,19 @@ export function createAssetBroker({current, send}) {
|
|
|
187
196
|
if (identity !== now.identity) { retained.clear(); retainedBytes = 0; identity = now.identity; }
|
|
188
197
|
return now;
|
|
189
198
|
}
|
|
199
|
+
function admitted(now) {
|
|
200
|
+
return {identity: now.identity, generation: now.generation, connected: now.connected,
|
|
201
|
+
granted: now.granted, pictureBytes: now.pictureBytes, baseRevision: now.baseRevision};
|
|
202
|
+
}
|
|
190
203
|
function cancel(code = 'asset_disconnected') { pending?.finish({code}); }
|
|
191
204
|
async function request(asset) {
|
|
192
|
-
const state = scope(), bytes = assetBytes(asset.bytes, state.pictureBytes);
|
|
205
|
+
const state = admitted(scope()), bytes = assetBytes(asset.bytes, state.pictureBytes);
|
|
193
206
|
if (!state.granted) return {code: 'asset_not_granted'};
|
|
194
207
|
if (!state.connected) return {code: 'asset_disconnected'};
|
|
195
208
|
if (pending) return {code: 'asset_busy'};
|
|
196
209
|
if (!ASSET_TYPES.includes(asset.codec)) throw refusal('asset_media_type_invalid');
|
|
197
210
|
if (retained.size >= 1024 || retainedBytes + bytes.byteLength > 25 * 1024 * 1024) return {code: 'asset_cache_full'};
|
|
198
|
-
const kept = {...asset, bytes: bytes
|
|
211
|
+
const kept = {...asset, bytes: ownedPicture(bytes)};
|
|
199
212
|
let resolve;
|
|
200
213
|
const job = new Promise(yes => { resolve = yes; });
|
|
201
214
|
const row = {state, asset: kept, requestId: crypto.randomUUID(), timer: null, bytesSha256: '',
|
package/embed.mjs
CHANGED
|
@@ -163,14 +163,23 @@ function assetURL(value) {
|
|
|
163
163
|
return url.href;
|
|
164
164
|
}
|
|
165
165
|
|
|
166
|
+
function pictureCeiling(limit) {
|
|
167
|
+
if (!Number.isSafeInteger(limit) || limit < 1) throw refusal('asset_bytes_invalid');
|
|
168
|
+
return Math.min(limit, LIMITS.pictureBytes);
|
|
169
|
+
}
|
|
166
170
|
function assetBytes(value, limit = LIMITS.pictureBytes) {
|
|
167
171
|
if (!(value instanceof Uint8Array) || !value.byteLength) throw refusal('asset_bytes_invalid');
|
|
168
|
-
if (value.byteLength >
|
|
172
|
+
if (value.byteLength > pictureCeiling(limit)) throw refusal('picture_too_large');
|
|
169
173
|
return value;
|
|
170
174
|
}
|
|
171
175
|
async function bytesHash(bytes) {
|
|
172
176
|
return Array.from(new Uint8Array(await crypto.subtle.digest('SHA-256', bytes)), n => n.toString(16).padStart(2, '0')).join('');
|
|
173
177
|
}
|
|
178
|
+
function ownedPicture(bytes) {
|
|
179
|
+
const copy = new Uint8Array(bytes.byteLength);
|
|
180
|
+
copy.set(bytes);
|
|
181
|
+
return copy;
|
|
182
|
+
}
|
|
174
183
|
function externalImage(raw, asset, url) {
|
|
175
184
|
if (!url) return raw;
|
|
176
185
|
const token = '[' + asset.label + ']', at = raw.lastIndexOf(token);
|
|
@@ -188,15 +197,19 @@ function createAssetBroker({current, send}) {
|
|
|
188
197
|
if (identity !== now.identity) { retained.clear(); retainedBytes = 0; identity = now.identity; }
|
|
189
198
|
return now;
|
|
190
199
|
}
|
|
200
|
+
function admitted(now) {
|
|
201
|
+
return {identity: now.identity, generation: now.generation, connected: now.connected,
|
|
202
|
+
granted: now.granted, pictureBytes: now.pictureBytes, baseRevision: now.baseRevision};
|
|
203
|
+
}
|
|
191
204
|
function cancel(code = 'asset_disconnected') { pending?.finish({code}); }
|
|
192
205
|
async function request(asset) {
|
|
193
|
-
const state = scope(), bytes = assetBytes(asset.bytes, state.pictureBytes);
|
|
206
|
+
const state = admitted(scope()), bytes = assetBytes(asset.bytes, state.pictureBytes);
|
|
194
207
|
if (!state.granted) return {code: 'asset_not_granted'};
|
|
195
208
|
if (!state.connected) return {code: 'asset_disconnected'};
|
|
196
209
|
if (pending) return {code: 'asset_busy'};
|
|
197
210
|
if (!ASSET_TYPES.includes(asset.codec)) throw refusal('asset_media_type_invalid');
|
|
198
211
|
if (retained.size >= 1024 || retainedBytes + bytes.byteLength > 25 * 1024 * 1024) return {code: 'asset_cache_full'};
|
|
199
|
-
const kept = {...asset, bytes: bytes
|
|
212
|
+
const kept = {...asset, bytes: ownedPicture(bytes)};
|
|
200
213
|
let resolve;
|
|
201
214
|
const job = new Promise(yes => { resolve = yes; });
|
|
202
215
|
const row = {state, asset: kept, requestId: crypto.randomUUID(), timer: null, bytesSha256: '',
|
|
@@ -258,7 +271,7 @@ function pictureBase(value) {
|
|
|
258
271
|
return {FEATURES, LIMITS, ASSET_TYPES, refusal, coded, normalizeSettings, snapshotSettings, pickDeviceFile, createFilePicker, assetURL, assetBytes, bytesHash, externalImage, createAssetBroker, pictureBase};
|
|
259
272
|
})();
|
|
260
273
|
const BUILDS = Object.freeze({full: 'https://rapier.website/embed/rapier.html', reader: 'https://rapier.website/embed/rapier-reader.html'});
|
|
261
|
-
const PACKAGE_VERSION = '1.2.
|
|
274
|
+
const PACKAGE_VERSION = '1.2.48';
|
|
262
275
|
const OPTIONS = ['build', 'src', 'plugins', 'approvePlugins', 'sessionId', 'documentId', 'agent', 'agentName', 'theme', 'save', 'assets', 'openFile', 'load',
|
|
263
276
|
'onState', 'compare', 'onClose', 'settings', 'style', 'title', 'onError', 'timeout'];
|
|
264
277
|
const READER_FEATURES = ['find', 'readAloud', 'share'];
|
|
@@ -888,12 +901,19 @@ function joinForm(field, form) {
|
|
|
888
901
|
} catch (error) { if (current()) for (const {field} of captured) field._error(error); }
|
|
889
902
|
finally { group.pending = false; group.releasing = false; }
|
|
890
903
|
};
|
|
891
|
-
|
|
904
|
+
forms.set(form, group);
|
|
905
|
+
}
|
|
906
|
+
const root = form.getRootNode?.();
|
|
907
|
+
const next = !root || root === form.ownerDocument ? form.ownerDocument.defaultView : root;
|
|
908
|
+
if (group.view !== next) {
|
|
909
|
+
group.view?.removeEventListener('submit', group.submit, true);
|
|
910
|
+
group.view = next;
|
|
911
|
+
next?.addEventListener('submit', group.submit, true);
|
|
892
912
|
}
|
|
893
913
|
group.fields.add(field);
|
|
894
914
|
return () => {
|
|
895
915
|
group.fields.delete(field);
|
|
896
|
-
if (!group.fields.size) { group.view
|
|
916
|
+
if (!group.fields.size) { group.view?.removeEventListener('submit', group.submit, true); forms.delete(form); }
|
|
897
917
|
};
|
|
898
918
|
}
|
|
899
919
|
|
|
@@ -927,15 +947,44 @@ export class RapierEditor extends (globalThis.HTMLElement || class {}) {
|
|
|
927
947
|
this._initialized = true;
|
|
928
948
|
}
|
|
929
949
|
this._disabled = this.matches(':disabled'); this.tabIndex = this._disabled ? -1 : 0;
|
|
930
|
-
|
|
931
|
-
|
|
950
|
+
if (!this._onPageHide) {
|
|
951
|
+
this._onPageHide = () => {
|
|
952
|
+
if (this._disabled) return;
|
|
953
|
+
this._setFormValue();
|
|
954
|
+
};
|
|
955
|
+
this._onPageShow = () => { if (!this._disabled) this._updateValue(); };
|
|
956
|
+
}
|
|
957
|
+
const bindPageLifecycle = () => {
|
|
958
|
+
if (!this._onPageHide) return;
|
|
959
|
+
const view = this.isConnected ? this.ownerDocument.defaultView : null;
|
|
960
|
+
if (this._view === view) return;
|
|
961
|
+
this._view?.removeEventListener('pagehide', this._onPageHide);
|
|
962
|
+
this._view?.removeEventListener('pageshow', this._onPageShow);
|
|
963
|
+
this._media?.removeEventListener('change', this._onMedia);
|
|
964
|
+
this._view = view;
|
|
965
|
+
this._media = view ? view.matchMedia('(max-width:600px)') : null;
|
|
966
|
+
view?.addEventListener('pagehide', this._onPageHide);
|
|
967
|
+
view?.addEventListener('pageshow', this._onPageShow);
|
|
968
|
+
if (this._media) {
|
|
969
|
+
this._onMedia ||= () => this._layout();
|
|
970
|
+
this._media.addEventListener('change', this._onMedia);
|
|
971
|
+
}
|
|
972
|
+
};
|
|
973
|
+
bindPageLifecycle();
|
|
974
|
+
queueMicrotask(bindPageLifecycle);
|
|
932
975
|
this._bindForm(); this._updateValue(); this._layout(); if (this._panel.open) this._start();
|
|
933
976
|
}
|
|
934
977
|
disconnectedCallback() {
|
|
935
978
|
this._formAuthority++;
|
|
936
979
|
queueMicrotask(() => {
|
|
937
980
|
if (this.isConnected) return;
|
|
938
|
-
this._leaveForm?.(); this._leaveForm = null;
|
|
981
|
+
this._leaveForm?.(); this._leaveForm = null;
|
|
982
|
+
this._media?.removeEventListener('change', this._onMedia);
|
|
983
|
+
this._media = null;
|
|
984
|
+
this._view?.removeEventListener('pagehide', this._onPageHide);
|
|
985
|
+
this._view?.removeEventListener('pageshow', this._onPageShow);
|
|
986
|
+
this._view = null;
|
|
987
|
+
this._onPageHide = null; this._onPageShow = null;
|
|
939
988
|
const editor = this._editor;
|
|
940
989
|
this._generation++; this._editor = null; this._sync = Promise.resolve();
|
|
941
990
|
editor?.disconnect(); this._mount.replaceChildren();
|
|
@@ -975,7 +1024,7 @@ export class RapierEditor extends (globalThis.HTMLElement || class {}) {
|
|
|
975
1024
|
_bindForm() { this._formAuthority++; this._leaveForm?.(); this._leaveForm = joinForm(this, this.form); }
|
|
976
1025
|
formAssociatedCallback() { if (this._initialized && this.isConnected) this._bindForm(); }
|
|
977
1026
|
formResetCallback() { this._replace(this._defaultValue); }
|
|
978
|
-
formStateRestoreCallback(state) { if (typeof state === 'string') this._replace(state); }
|
|
1027
|
+
formStateRestoreCallback(state, mode) { if (mode === 'restore' && typeof state === 'string') this._replace(state); }
|
|
979
1028
|
formDisabledCallback(disabled) {
|
|
980
1029
|
const wasDisabled = this._disabled; this._disabled = disabled;
|
|
981
1030
|
if (disabled !== wasDisabled) this._formAuthority++;
|
|
@@ -992,9 +1041,17 @@ export class RapierEditor extends (globalThis.HTMLElement || class {}) {
|
|
|
992
1041
|
}).catch(error => { if (this._editor === editor) this._error(error); });
|
|
993
1042
|
}
|
|
994
1043
|
_filename() { return (this.getAttribute('name') || 'document').slice(0, 240) + '.md'; }
|
|
1044
|
+
_submissionFile() {
|
|
1045
|
+
const File = this.ownerDocument.defaultView?.File;
|
|
1046
|
+
return File ? new File([this._value], this._filename(), {type: 'text/markdown'}) : null;
|
|
1047
|
+
}
|
|
1048
|
+
_setFormValue() {
|
|
1049
|
+
const file = this._submissionFile();
|
|
1050
|
+
if (!file) return;
|
|
1051
|
+
this._internals.setFormValue(file, null);
|
|
1052
|
+
}
|
|
995
1053
|
_updateValue() {
|
|
996
|
-
|
|
997
|
-
this._internals.setFormValue(new File([this._value], this._filename(), {type: 'text/markdown'}), this._value);
|
|
1054
|
+
this._setFormValue();
|
|
998
1055
|
this._preview.textContent = this._value || 'Edit document'; this._preview.disabled = this._disabled;
|
|
999
1056
|
if (this._fallback) this._fallback.value = this._value;
|
|
1000
1057
|
const missing = this.required && !this._value && !this._dirty;
|
|
@@ -1041,11 +1098,13 @@ export class RapierEditor extends (globalThis.HTMLElement || class {}) {
|
|
|
1041
1098
|
this._mount.inert = this._disabled;
|
|
1042
1099
|
}
|
|
1043
1100
|
_layout() {
|
|
1044
|
-
const narrow = this._media
|
|
1101
|
+
const narrow = this._media?.matches === true;
|
|
1045
1102
|
if (this._narrow === narrow) return;
|
|
1046
1103
|
this._narrow = narrow;
|
|
1047
1104
|
if (this._panel.open) this._panel.close();
|
|
1048
|
-
if (
|
|
1105
|
+
if (narrow || !this.ownerDocument.defaultView) return;
|
|
1106
|
+
this._panel.setAttribute('open', '');
|
|
1107
|
+
this._start();
|
|
1049
1108
|
}
|
|
1050
1109
|
focus(options) {
|
|
1051
1110
|
if (this._disabled) return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rapier-embed",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.48",
|
|
4
4
|
"description": "Embed the Rapier Markdown editor or its lightweight reader (about 360 kB) in your web app. Runs offline from your own copy, saves to your storage, and takes your CSS and fonts. Your app's AI agent edits directly over WebMCP under the Will. MIT, no dependencies.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"markdown",
|