@kubohiroya/turbowarp-document-data 0.0.0-stage → 0.1.1
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 +373 -0
- package/README.ja.md +35 -0
- package/README.md +126 -2
- package/THIRD_PARTY_NOTICES.md +9 -0
- package/dist/document-data.js +19457 -0
- package/dist/extension-manifest.json +117 -0
- package/docs/architecture.ja.md +7 -0
- package/docs/architecture.md +7 -0
- package/docs/fixed-input-editors.ja.md +17 -0
- package/docs/index.html +54 -0
- package/package.json +85 -4
- package/schemas/block-editor-fixed-inputs.schema.json +22 -0
- package/src/block-definitions.json +14 -0
- package/src/config.ts +21 -0
- package/src/document-provider.ts +92 -0
- package/src/document.ts +254 -0
- package/src/extension.ts +136 -0
- package/src/fixed-input-editors.ts +116 -0
- package/src/fixed-input-schema.ts +15 -0
- package/src/globals.d.ts +28 -0
- package/src/index.ts +8 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
{
|
|
2
|
+
"formatVersion": 1,
|
|
3
|
+
"id": "kubohiroyadocumentdata",
|
|
4
|
+
"blocks": [
|
|
5
|
+
{
|
|
6
|
+
"opcode": "deleteDocument",
|
|
7
|
+
"blockType": "COMMAND",
|
|
8
|
+
"arguments": [
|
|
9
|
+
{
|
|
10
|
+
"id": "NAME",
|
|
11
|
+
"type": "STRING"
|
|
12
|
+
}
|
|
13
|
+
]
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"opcode": "documentKind",
|
|
17
|
+
"blockType": "REPORTER",
|
|
18
|
+
"arguments": [
|
|
19
|
+
{
|
|
20
|
+
"id": "NAME",
|
|
21
|
+
"type": "STRING"
|
|
22
|
+
}
|
|
23
|
+
]
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"opcode": "getDocumentText",
|
|
27
|
+
"blockType": "REPORTER",
|
|
28
|
+
"arguments": [
|
|
29
|
+
{
|
|
30
|
+
"id": "LOCATION",
|
|
31
|
+
"type": "STRING"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"id": "NAME",
|
|
35
|
+
"type": "STRING"
|
|
36
|
+
}
|
|
37
|
+
]
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"opcode": "hasDocument",
|
|
41
|
+
"blockType": "BOOLEAN",
|
|
42
|
+
"arguments": [
|
|
43
|
+
{
|
|
44
|
+
"id": "NAME",
|
|
45
|
+
"type": "STRING"
|
|
46
|
+
}
|
|
47
|
+
]
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"opcode": "parseHtml",
|
|
51
|
+
"blockType": "COMMAND",
|
|
52
|
+
"arguments": [
|
|
53
|
+
{
|
|
54
|
+
"id": "NAME",
|
|
55
|
+
"type": "STRING"
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"id": "TEXT",
|
|
59
|
+
"type": "STRING"
|
|
60
|
+
}
|
|
61
|
+
]
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"opcode": "parseMarkdown",
|
|
65
|
+
"blockType": "COMMAND",
|
|
66
|
+
"arguments": [
|
|
67
|
+
{
|
|
68
|
+
"id": "NAME",
|
|
69
|
+
"type": "STRING"
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"id": "TEXT",
|
|
73
|
+
"type": "STRING"
|
|
74
|
+
}
|
|
75
|
+
]
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"opcode": "replaceDocumentText",
|
|
79
|
+
"blockType": "COMMAND",
|
|
80
|
+
"arguments": [
|
|
81
|
+
{
|
|
82
|
+
"id": "LOCATION",
|
|
83
|
+
"type": "STRING"
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"id": "NAME",
|
|
87
|
+
"type": "STRING"
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"id": "TEXT",
|
|
91
|
+
"type": "STRING"
|
|
92
|
+
}
|
|
93
|
+
]
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"opcode": "serializeDocument",
|
|
97
|
+
"blockType": "REPORTER",
|
|
98
|
+
"arguments": [
|
|
99
|
+
{
|
|
100
|
+
"id": "FORMAT",
|
|
101
|
+
"type": "STRING",
|
|
102
|
+
"menu": "documentFormat"
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
"id": "NAME",
|
|
106
|
+
"type": "STRING"
|
|
107
|
+
}
|
|
108
|
+
]
|
|
109
|
+
}
|
|
110
|
+
],
|
|
111
|
+
"menus": [
|
|
112
|
+
{
|
|
113
|
+
"id": "documentFormat",
|
|
114
|
+
"acceptReporters": true
|
|
115
|
+
}
|
|
116
|
+
]
|
|
117
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# アーキテクチャ
|
|
2
|
+
|
|
3
|
+
名前付きbindingは`WeakMap<Target, Map<Name, Binding>>`に保持し、更新時はtree全体を検証してからimmutable replacementします。HTMLはparse5から正規化したtyped tree、Markdownは位置情報を除いたmdastとして保持します。
|
|
4
|
+
|
|
5
|
+
block APIはdocumentを操作し、Named Data providerは同じstoreをread-onlyで参照してHTTP向けのreplayable UTF-8 bodyを生成します。canonical `@kubohiroya/turbowarp-named-data` composition契約を実装し、persistent lifetimeでruntime共通registryへ登録します。project stopではprovider登録を維持し、bindingとsession handleだけをclearします。
|
|
6
|
+
|
|
7
|
+
HTMLとMarkdownの変換はlossyになり得るため暗黙に実行しません。また、parse済みHTMLをsafeまたはsanitizedとは表示しません。
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Architecture
|
|
2
|
+
|
|
3
|
+
The extension stores immutable replacement bindings in `WeakMap<Target, Map<Name, Binding>>`. HTML input becomes a small typed tree normalized from parse5; Markdown input becomes a position-free mdast tree. Revisions increase whenever a binding is replaced.
|
|
4
|
+
|
|
5
|
+
The block API operates on documents. The Named Data provider is a read-only adapter over the same binding store and produces replayable UTF-8 bytes for HTTP consumers. It implements the canonical `@kubohiroya/turbowarp-named-data` composition contract and registers in the runtime registry with persistent lifetime. Project stop clears bindings and session handles without unregistering the provider.
|
|
6
|
+
|
|
7
|
+
Cross-format serialization is rejected because HTML and Markdown conversion may be lossy. This package never describes parsed HTML as safe or sanitized.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# 固定入力の編集
|
|
2
|
+
|
|
3
|
+
S3-4のローカル実装です。対応GUIの固定入力機能は既定OFFです。旧GUI・API無し・固定入力機能OFFではブロックの従来動作を保ちます。`getInfo()` に存在する対象opcodeだけ登録し、再取得・APIの置換・拡張の`dispose()`・runtimeの破棄に対応します。既存SOURCEエディターとopcodeは分離しています。
|
|
4
|
+
|
|
5
|
+
|ブロックopcode|引数|UI|
|
|
6
|
+
|---|---|---|
|
|
7
|
+
|`serializeDocument`|`FORMAT`|既定値からの選択:`html`, `markdown`|
|
|
8
|
+
|
|
9
|
+
数値の新規入力は、空白を除いた完全な有限JSON数値トークンを受け付け、指数表記・負のゼロ・表記を保持します。空文字、16進表記、先頭の`+`、先頭ゼロや未完成トークンは新規保存しません。既存値は開いただけで書き換えません。既存の有限数値別表記は参照用に投影し、未編集の生値を保ちます。未知の旧enum値も無操作では保存し直しません。
|
|
10
|
+
|
|
11
|
+
Document Data MVP の既定OFFは変更しません。MVPが無効で `getInfo()` に対象opcodeが無い場合、登録しません。
|
|
12
|
+
|
|
13
|
+
reporter接続は読取専用で、実行・置換しません。新しい選択や数値を保存するときだけ純粋codecとSchemaで検証します。検証失敗時は全変更を拒否し、ブロックや接続を部分的に書き換えません。
|
|
14
|
+
|
|
15
|
+
正本は本ownerの `schemas/block-editor-fixed-inputs.schema.json`、TSモジュールは選択・参照用、`dist`は生成物です。外部候補取得や実行時Schema生成は行いません。
|
|
16
|
+
|
|
17
|
+
切戻しはGUIの固定入力機能OFFで行えます。既存のプロジェクト形式・opcode・メニュー・defaultValue・実行時の処理は維持します。
|
package/docs/index.html
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
6
|
+
<title>Document Data documentation</title>
|
|
7
|
+
<style>
|
|
8
|
+
body{font-family:system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;max-width:900px;margin:0 auto;padding:2rem;line-height:1.6;color:#222}
|
|
9
|
+
code{background:#f3f3f3;padding:.1em .3em;border-radius:.25em}
|
|
10
|
+
pre{background:#f6f8fa;padding:1rem;overflow:auto;border-radius:.5rem}
|
|
11
|
+
</style>
|
|
12
|
+
<style id="block-reference-style">
|
|
13
|
+
#extension-block-reference{max-width:72rem;margin:2rem auto;padding:1rem;line-height:1.6;overflow-wrap:anywhere}
|
|
14
|
+
#extension-block-reference .block-reference-entry{display:block;scroll-margin-top:6rem;margin:2rem 0;padding:1rem;border:1px solid currentColor;border-radius:.5rem}
|
|
15
|
+
#extension-block-reference .block-reference-table{overflow-x:auto}
|
|
16
|
+
#extension-block-reference table{border-collapse:collapse;width:100%}
|
|
17
|
+
#extension-block-reference th,#extension-block-reference td{text-align:left;vertical-align:top;border:1px solid currentColor;padding:.5rem}
|
|
18
|
+
#extension-block-reference code{white-space:pre-wrap}
|
|
19
|
+
#extension-block-reference a:focus-visible{outline:3px solid currentColor;outline-offset:3px}
|
|
20
|
+
</style>
|
|
21
|
+
</head>
|
|
22
|
+
<body><p lang="en" class="complete-block-reference-link"><a href="#extension-block-reference">Complete block reference (8 blocks)</a></p>
|
|
23
|
+
<h1>Document Data</h1>
|
|
24
|
+
<p>Parse HTML and Markdown into target-local named document trees, edit text, and expose deterministic UTF-8 HTTP response bodies.</p>
|
|
25
|
+
|
|
26
|
+
<h2>Blocks</h2>
|
|
27
|
+
<p>Use <code>parse HTML ... as document ...</code> or <code>parse Markdown ... as document ...</code>, then inspect, replace, or serialize its text. Tree locations use <code>$</code> or numeric paths such as <code>$[0][1]</code>. HTML also supports one simple tag, <code>#id</code>, or <code>.class</code> selector.</p>
|
|
28
|
+
|
|
29
|
+
<h2>Safety boundary</h2>
|
|
30
|
+
<p>Parsing does not sanitize HTML, execute scripts, or mount a DOM. HTML/Markdown conversion is rejected because it may be lossy. The extension is unsandboxed and its <code>DOCUMENT_DATA_MVP</code> flag defaults to off.</p>
|
|
31
|
+
|
|
32
|
+
<h2>Development</h2>
|
|
33
|
+
<p>Run <code>pnpm run check</code>. See the repository README and architecture documents for limits and the Named Data provider contract.</p>
|
|
34
|
+
<!-- BEGIN COMPLETE BLOCK REFERENCE -->
|
|
35
|
+
<section lang="en" id="extension-block-reference" aria-labelledby="block-reference-title">
|
|
36
|
+
<h2 id="block-reference-title">Document Data: complete block reference</h2>
|
|
37
|
+
<p>All 8 blocks are documented below, including compatibility blocks and blocks available through optional features. Select a block to read its behavior, arguments, default values, and menu choices. Each block has a permanent link.</p>
|
|
38
|
+
<nav aria-label="Block reference"><ul><li><a href="#block-parseHtml"><code>parse HTML [TEXT] as document [NAME]</code></a></li>
|
|
39
|
+
<li><a href="#block-parseMarkdown"><code>parse Markdown [TEXT] as document [NAME]</code></a></li>
|
|
40
|
+
<li><a href="#block-hasDocument"><code>document [NAME] exists?</code></a></li>
|
|
41
|
+
<li><a href="#block-deleteDocument"><code>delete document [NAME]</code></a></li>
|
|
42
|
+
<li><a href="#block-documentKind"><code>kind of document [NAME]</code></a></li>
|
|
43
|
+
<li><a href="#block-getDocumentText"><code>text in document [NAME] at [LOCATION]</code></a></li>
|
|
44
|
+
<li><a href="#block-replaceDocumentText"><code>replace text in document [NAME] at [LOCATION] with [TEXT]</code></a></li>
|
|
45
|
+
<li><a href="#block-serializeDocument"><code>serialize document [NAME] as [FORMAT]</code></a></li>
|
|
46
|
+
</ul></nav>
|
|
47
|
+
<article id="block-parseHtml" class="block-reference-entry"><h3><code>parse HTML [TEXT] as document [NAME]</code></h3><p>Parse HTML into a target-local named document tree. This does not sanitize HTML.</p><p>Block type: COMMAND. Opcode: <code>parseHtml</code>. <a href="#block-parseHtml">Permanent link</a></p><div class="block-reference-table"><table><thead><tr><th>Argument</th><th>Type</th><th>Default value</th><th>Menu choices</th></tr></thead><tbody><tr><th scope="row"><code>TEXT</code></th><td>STRING</td><td><code><p>Hello</p></code></td><td></td></tr><tr><th scope="row"><code>NAME</code></th><td>STRING</td><td><code>page</code></td><td></td></tr></tbody></table></div></article><article id="block-parseMarkdown" class="block-reference-entry"><h3><code>parse Markdown [TEXT] as document [NAME]</code></h3><p>Parse Markdown into a target-local named syntax tree.</p><p>Block type: COMMAND. Opcode: <code>parseMarkdown</code>. <a href="#block-parseMarkdown">Permanent link</a></p><div class="block-reference-table"><table><thead><tr><th>Argument</th><th>Type</th><th>Default value</th><th>Menu choices</th></tr></thead><tbody><tr><th scope="row"><code>TEXT</code></th><td>STRING</td><td><code># Hello</code></td><td></td></tr><tr><th scope="row"><code>NAME</code></th><td>STRING</td><td><code>page</code></td><td></td></tr></tbody></table></div></article><article id="block-hasDocument" class="block-reference-entry"><h3><code>document [NAME] exists?</code></h3><p>Report whether the current target has the named document.</p><p>Block type: BOOLEAN. Opcode: <code>hasDocument</code>. <a href="#block-hasDocument">Permanent link</a></p><div class="block-reference-table"><table><thead><tr><th>Argument</th><th>Type</th><th>Default value</th><th>Menu choices</th></tr></thead><tbody><tr><th scope="row"><code>NAME</code></th><td>STRING</td><td><code>page</code></td><td></td></tr></tbody></table></div></article><article id="block-deleteDocument" class="block-reference-entry"><h3><code>delete document [NAME]</code></h3><p>Delete the named document from the current target.</p><p>Block type: COMMAND. Opcode: <code>deleteDocument</code>. <a href="#block-deleteDocument">Permanent link</a></p><div class="block-reference-table"><table><thead><tr><th>Argument</th><th>Type</th><th>Default value</th><th>Menu choices</th></tr></thead><tbody><tr><th scope="row"><code>NAME</code></th><td>STRING</td><td><code>page</code></td><td></td></tr></tbody></table></div></article><article id="block-documentKind" class="block-reference-entry"><h3><code>kind of document [NAME]</code></h3><p>Return html or markdown.</p><p>Block type: REPORTER. Opcode: <code>documentKind</code>. <a href="#block-documentKind">Permanent link</a></p><div class="block-reference-table"><table><thead><tr><th>Argument</th><th>Type</th><th>Default value</th><th>Menu choices</th></tr></thead><tbody><tr><th scope="row"><code>NAME</code></th><td>STRING</td><td><code>page</code></td><td></td></tr></tbody></table></div></article><article id="block-getDocumentText" class="block-reference-entry"><h3><code>text in document [NAME] at [LOCATION]</code></h3><p>Get text at the root, a numeric tree path, or a simple HTML selector.</p><p>Block type: REPORTER. Opcode: <code>getDocumentText</code>. <a href="#block-getDocumentText">Permanent link</a></p><div class="block-reference-table"><table><thead><tr><th>Argument</th><th>Type</th><th>Default value</th><th>Menu choices</th></tr></thead><tbody><tr><th scope="row"><code>NAME</code></th><td>STRING</td><td><code>page</code></td><td></td></tr><tr><th scope="row"><code>LOCATION</code></th><td>STRING</td><td><code>$</code></td><td></td></tr></tbody></table></div></article><article id="block-replaceDocumentText" class="block-reference-entry"><h3><code>replace text in document [NAME] at [LOCATION] with [TEXT]</code></h3><p>Immutably replace text at the selected node.</p><p>Block type: COMMAND. Opcode: <code>replaceDocumentText</code>. <a href="#block-replaceDocumentText">Permanent link</a></p><div class="block-reference-table"><table><thead><tr><th>Argument</th><th>Type</th><th>Default value</th><th>Menu choices</th></tr></thead><tbody><tr><th scope="row"><code>NAME</code></th><td>STRING</td><td><code>page</code></td><td></td></tr><tr><th scope="row"><code>LOCATION</code></th><td>STRING</td><td><code>$</code></td><td></td></tr><tr><th scope="row"><code>TEXT</code></th><td>STRING</td><td><code>Hello</code></td><td></td></tr></tbody></table></div></article><article id="block-serializeDocument" class="block-reference-entry"><h3><code>serialize document [NAME] as [FORMAT]</code></h3><p>Deterministically serialize a document in its native format. Cross-format conversion is rejected as lossy.</p><p>Block type: REPORTER. Opcode: <code>serializeDocument</code>. <a href="#block-serializeDocument">Permanent link</a></p><div class="block-reference-table"><table><thead><tr><th>Argument</th><th>Type</th><th>Default value</th><th>Menu choices</th></tr></thead><tbody><tr><th scope="row"><code>NAME</code></th><td>STRING</td><td><code>page</code></td><td></td></tr><tr><th scope="row"><code>FORMAT</code></th><td>STRING</td><td><code>html</code></td><td><ul><li><code>html</code></li><li><code>markdown</code></li></ul></td></tr></tbody></table></div><p lang="ja" class="fixed-input-help"><a href="#fixed-input-editors">このブロックの固定入力編集と互換性</a></p>
|
|
48
|
+
</article>
|
|
49
|
+
<p><a href="https://github.com/kubohiroya/turbowarp-document-data/blob/main/README.md">Repository guide and examples</a></p>
|
|
50
|
+
</section>
|
|
51
|
+
<!-- END COMPLETE BLOCK REFERENCE -->
|
|
52
|
+
<section lang="ja" id="fixed-input-editors"><h2>固定入力の編集</h2><p>対応GUIの固定入力機能は既定OFFです。既存値は開いただけでは変更せず、reporter接続は評価・置換しません。新しい選択と有限JSON数値入力のみを検証して保存し、既存の補正・丸め処理を維持します。機能OFFで従来の編集へ戻せます。</p><p><a href="fixed-input-editors.ja.md">対象入力・Schemaの正本・互換性・切戻し</a></p></section>
|
|
53
|
+
</body>
|
|
54
|
+
</html>
|
package/package.json
CHANGED
|
@@ -1,6 +1,87 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kubohiroya/turbowarp-document-data",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Named HTML and Markdown document trees for TurboWarp and HTTP response bodies.",
|
|
5
|
+
"author": "Hiroya Kubo <hiroya@cuc.ac.jp>",
|
|
6
|
+
"license": "MPL-2.0",
|
|
7
|
+
"homepage": "https://github.com/kubohiroya/turbowarp-document-data#readme",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/kubohiroya/turbowarp-document-data.git"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/kubohiroya/turbowarp-document-data/issues"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"turbowarp",
|
|
17
|
+
"scratch",
|
|
18
|
+
"extension",
|
|
19
|
+
"document",
|
|
20
|
+
"html",
|
|
21
|
+
"markdown",
|
|
22
|
+
"vite",
|
|
23
|
+
"typescript"
|
|
24
|
+
],
|
|
25
|
+
"type": "module",
|
|
26
|
+
"files": [
|
|
27
|
+
"dist/",
|
|
28
|
+
"docs/",
|
|
29
|
+
"src/",
|
|
30
|
+
"README.md",
|
|
31
|
+
"README.ja.md",
|
|
32
|
+
"LICENSE",
|
|
33
|
+
"THIRD_PARTY_NOTICES.md",
|
|
34
|
+
"schemas/"
|
|
35
|
+
],
|
|
36
|
+
"publishConfig": {
|
|
37
|
+
"access": "public",
|
|
38
|
+
"registry": "https://registry.npmjs.org/"
|
|
39
|
+
},
|
|
40
|
+
"peerDependencies": {
|
|
41
|
+
"@kubohiroya/turbowarp-named-data": "0.3.1"
|
|
42
|
+
},
|
|
43
|
+
"packageManager": "pnpm@11.11.0",
|
|
44
|
+
"engines": {
|
|
45
|
+
"node": ">=22.18.0"
|
|
46
|
+
},
|
|
47
|
+
"scripts": {
|
|
48
|
+
"dev": "vite build --watch",
|
|
49
|
+
"typecheck": "tsc --noEmit",
|
|
50
|
+
"build": "vite build",
|
|
51
|
+
"lint": "eslint src tests scripts vite.config.ts eslint.config.ts",
|
|
52
|
+
"test": "vitest run",
|
|
53
|
+
"docs": "node scripts/generate-readme.ts",
|
|
54
|
+
"docs:check": "node scripts/check-docs.ts",
|
|
55
|
+
"template:readme": "node scripts/apply-readme-template.ts",
|
|
56
|
+
"repo:check": "node scripts/check-repo.ts",
|
|
57
|
+
"pack:check": "npm pack --dry-run --ignore-scripts",
|
|
58
|
+
"check": "pnpm run typecheck && pnpm run lint && pnpm run test && pnpm run docs:check && pnpm run check:dist && pnpm run repo:check && pnpm run pack:check",
|
|
59
|
+
"check:dist": "pnpm run build && node scripts/check-dist.ts"
|
|
60
|
+
},
|
|
61
|
+
"devDependencies": {
|
|
62
|
+
"@eslint/js": "^9.39.5",
|
|
63
|
+
"@kubohiroya/turbowarp-extension-manifest": "0.3.2",
|
|
64
|
+
"@kubohiroya/turbowarp-named-data": "0.3.1",
|
|
65
|
+
"@kubohiroya/vite-plugin-turbowarp-extension": "0.6.1",
|
|
66
|
+
"@types/mdast": "^4.0.4",
|
|
67
|
+
"@types/node": "^24.13.3",
|
|
68
|
+
"eslint": "^9.39.5",
|
|
69
|
+
"jiti": "^2.7.0",
|
|
70
|
+
"typescript": "^5.9.3",
|
|
71
|
+
"typescript-eslint": "^8.66.0",
|
|
72
|
+
"vite": "^8.2.2",
|
|
73
|
+
"vitest": "^5.0.0",
|
|
74
|
+
"@kubohiroya/turbowarp-extension-data-editor": "0.2.1"
|
|
75
|
+
},
|
|
76
|
+
"dependencies": {
|
|
77
|
+
"mdast-util-from-markdown": "^2.0.3",
|
|
78
|
+
"mdast-util-to-markdown": "^2.1.2",
|
|
79
|
+
"parse5": "^8.0.1"
|
|
80
|
+
},
|
|
81
|
+
"gitHead": "d1b34cb87df3ee0630f5fea7ff7fbe8f7f3f7ae3",
|
|
82
|
+
"x-turbowarp-source": {
|
|
83
|
+
"revision": "d1b34cb87df3ee0630f5fea7ff7fbe8f7f3f7ae3",
|
|
84
|
+
"revisionRole": "base-commit",
|
|
85
|
+
"sourceState": "local-release-overlay"
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://kubohiroya.github.io/turbowarp-document-data/schemas/block-editor-fixed-inputs.schema.json",
|
|
4
|
+
"$defs": {
|
|
5
|
+
"serializeDocument": {
|
|
6
|
+
"type": "object",
|
|
7
|
+
"properties": {
|
|
8
|
+
"FORMAT": {
|
|
9
|
+
"type": "string",
|
|
10
|
+
"enum": [
|
|
11
|
+
"html",
|
|
12
|
+
"markdown"
|
|
13
|
+
]
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"required": [
|
|
17
|
+
"FORMAT"
|
|
18
|
+
],
|
|
19
|
+
"additionalProperties": true
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extensionName": "Document Data",
|
|
3
|
+
"menus": {"documentFormat": {"acceptReporters": true, "items": ["html", "markdown"]}},
|
|
4
|
+
"blocks": [
|
|
5
|
+
{"opcode":"parseHtml","blockType":"COMMAND","text":"parse HTML [TEXT] as document [NAME]","description":"Parse HTML into a target-local named document tree. This does not sanitize HTML.","arguments":{"TEXT":{"type":"STRING","defaultValue":"<p>Hello</p>"},"NAME":{"type":"STRING","defaultValue":"page"}}},
|
|
6
|
+
{"opcode":"parseMarkdown","blockType":"COMMAND","text":"parse Markdown [TEXT] as document [NAME]","description":"Parse Markdown into a target-local named syntax tree.","arguments":{"TEXT":{"type":"STRING","defaultValue":"# Hello"},"NAME":{"type":"STRING","defaultValue":"page"}}},
|
|
7
|
+
{"opcode":"hasDocument","blockType":"BOOLEAN","text":"document [NAME] exists?","description":"Report whether the current target has the named document.","arguments":{"NAME":{"type":"STRING","defaultValue":"page"}}},
|
|
8
|
+
{"opcode":"deleteDocument","blockType":"COMMAND","text":"delete document [NAME]","description":"Delete the named document from the current target.","arguments":{"NAME":{"type":"STRING","defaultValue":"page"}}},
|
|
9
|
+
{"opcode":"documentKind","blockType":"REPORTER","text":"kind of document [NAME]","description":"Return html or markdown.","arguments":{"NAME":{"type":"STRING","defaultValue":"page"}}},
|
|
10
|
+
{"opcode":"getDocumentText","blockType":"REPORTER","text":"text in document [NAME] at [LOCATION]","description":"Get text at the root, a numeric tree path, or a simple HTML selector.","arguments":{"NAME":{"type":"STRING","defaultValue":"page"},"LOCATION":{"type":"STRING","defaultValue":"$"}}},
|
|
11
|
+
{"opcode":"replaceDocumentText","blockType":"COMMAND","text":"replace text in document [NAME] at [LOCATION] with [TEXT]","description":"Immutably replace text at the selected node.","arguments":{"NAME":{"type":"STRING","defaultValue":"page"},"LOCATION":{"type":"STRING","defaultValue":"$"},"TEXT":{"type":"STRING","defaultValue":"Hello"}}},
|
|
12
|
+
{"opcode":"serializeDocument","blockType":"REPORTER","text":"serialize document [NAME] as [FORMAT]","description":"Deterministically serialize a document in its native format. Cross-format conversion is rejected as lossy.","arguments":{"NAME":{"type":"STRING","defaultValue":"page"},"FORMAT":{"type":"STRING","defaultValue":"html","menu":"documentFormat"}}}
|
|
13
|
+
]
|
|
14
|
+
}
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export const DOCUMENT_DATA_MVP_DEFAULT = false;
|
|
2
|
+
|
|
3
|
+
export function isDocumentDataMvpEnabled(
|
|
4
|
+
source: {DOCUMENT_DATA_MVP?: unknown} = globalThis as {DOCUMENT_DATA_MVP?: unknown}
|
|
5
|
+
): boolean {
|
|
6
|
+
const value = source.DOCUMENT_DATA_MVP;
|
|
7
|
+
return value === undefined ? DOCUMENT_DATA_MVP_DEFAULT : value === true || value === 'true';
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const extensionConfig = {
|
|
11
|
+
id: 'kubohiroyadocumentdata',
|
|
12
|
+
slug: 'document-data',
|
|
13
|
+
name: 'Document Data',
|
|
14
|
+
description: 'Named HTML and Markdown document trees for TurboWarp and HTTP bodies.',
|
|
15
|
+
author: 'Hiroya Kubo',
|
|
16
|
+
license: 'MPL-2.0',
|
|
17
|
+
unsandboxed: true,
|
|
18
|
+
docsURI: 'https://kubohiroya.github.io/turbowarp-document-data/',
|
|
19
|
+
blockIconURI:
|
|
20
|
+
'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA0OCA0OCI+PHJlY3QgeD0iNCIgeT0iOCIgd2lkdGg9IjE4IiBoZWlnaHQ9IjE0IiByeD0iMyIgZmlsbD0iIzRDOTdGRiIvPjxyZWN0IHg9IjI2IiB5PSI4IiB3aWR0aD0iMTgiIGhlaWdodD0iMTQiIHJ4PSIzIiBmaWxsPSIjNTlDMDU5Ii8+PHJlY3QgeD0iMTUiIHk9IjI2IiB3aWR0aD0iMTgiIGhlaWdodD0iMTQiIHJ4PSIzIiBmaWxsPSIjRkZBQjE5Ii8+PC9zdmc+'
|
|
21
|
+
} as const;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import {DOCUMENT_MAX_OUTPUT_BYTES, serializeDocument, type DocumentTree} from './document.js';
|
|
2
|
+
import {
|
|
3
|
+
NamedDataError,
|
|
4
|
+
type NamedDataBody,
|
|
5
|
+
type NamedDataMetadata,
|
|
6
|
+
type NamedDataProvider,
|
|
7
|
+
type NamedDataReference,
|
|
8
|
+
type NamedDataReleaseReason,
|
|
9
|
+
type NamedDataRepresentation,
|
|
10
|
+
type NamedDataResolveContext
|
|
11
|
+
} from '@kubohiroya/turbowarp-named-data/composition';
|
|
12
|
+
|
|
13
|
+
export const DOCUMENT_DATA_NAMESPACE = 'document';
|
|
14
|
+
export interface DocumentBinding {tree: DocumentTree; revision: number}
|
|
15
|
+
export interface DocumentBindingStore {get(target: object, name: string): DocumentBinding | undefined}
|
|
16
|
+
|
|
17
|
+
export class DocumentNamedDataProvider implements NamedDataProvider {
|
|
18
|
+
public readonly namespace = DOCUMENT_DATA_NAMESPACE;
|
|
19
|
+
public readonly kind = 'document' as const;
|
|
20
|
+
private readonly handles = new Set<symbol>();
|
|
21
|
+
private released = false;
|
|
22
|
+
|
|
23
|
+
public constructor(private readonly store: DocumentBindingStore) {}
|
|
24
|
+
|
|
25
|
+
public canResolve(
|
|
26
|
+
reference: NamedDataReference,
|
|
27
|
+
representation: NamedDataRepresentation
|
|
28
|
+
): boolean {
|
|
29
|
+
return (
|
|
30
|
+
!this.released &&
|
|
31
|
+
reference.namespace === this.namespace &&
|
|
32
|
+
reference.name.trim().length > 0 &&
|
|
33
|
+
reference.kind === this.kind &&
|
|
34
|
+
reference.scope === 'target' &&
|
|
35
|
+
(representation === 'html' || representation === 'markdown')
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
public stat(reference: NamedDataReference, representation: NamedDataRepresentation, context: NamedDataResolveContext): NamedDataMetadata {
|
|
40
|
+
const result = this.resolve(reference, representation, context);
|
|
41
|
+
return this.metadata(reference, representation, result.bytes.byteLength, result.binding.revision);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
public openBody(reference: NamedDataReference, representation: NamedDataRepresentation, context: NamedDataResolveContext): NamedDataBody {
|
|
45
|
+
const result = this.resolve(reference, representation, context);
|
|
46
|
+
const handle = Symbol('document-body');
|
|
47
|
+
this.handles.add(handle);
|
|
48
|
+
let released = false;
|
|
49
|
+
return {
|
|
50
|
+
...this.metadata(reference, representation, result.bytes.byteLength, result.binding.revision),
|
|
51
|
+
body: result.bytes,
|
|
52
|
+
release: (reason?: NamedDataReleaseReason) => {
|
|
53
|
+
void reason;
|
|
54
|
+
if (!released) {
|
|
55
|
+
released = true;
|
|
56
|
+
this.handles.delete(handle);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
public clearSession(): void {this.handles.clear();}
|
|
63
|
+
public release(reason?: NamedDataReleaseReason): void {
|
|
64
|
+
void reason;
|
|
65
|
+
this.released = true;
|
|
66
|
+
this.handles.clear();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
private resolve(reference: NamedDataReference, representation: NamedDataRepresentation, context: NamedDataResolveContext): {binding: DocumentBinding; bytes: Uint8Array} {
|
|
70
|
+
if (this.released) throw new NamedDataError('NAMED_DATA_PROVIDER_RELEASED', 'The document provider was released.');
|
|
71
|
+
if (context.signal?.aborted) throw new NamedDataError('NAMED_DATA_ABORTED', 'Body resolution was aborted.');
|
|
72
|
+
if (reference.namespace !== this.namespace || reference.name.trim().length === 0) throw new NamedDataError('NAMED_DATA_INVALID_REF', 'Invalid document reference.');
|
|
73
|
+
if (reference.kind !== this.kind) throw new NamedDataError('NAMED_DATA_KIND_MISMATCH', `Expected document, received ${reference.kind}.`);
|
|
74
|
+
if (reference.scope !== 'target' || !context.target) throw new NamedDataError('NAMED_DATA_SCOPE_MISMATCH', 'Document data requires target scope and target context.');
|
|
75
|
+
if (representation !== 'html' && representation !== 'markdown') throw new NamedDataError('NAMED_DATA_REPRESENTATION_UNSUPPORTED', `Documents cannot be rendered as ${representation}.`);
|
|
76
|
+
const binding = this.store.get(context.target, reference.name);
|
|
77
|
+
if (!binding) throw new NamedDataError('NAMED_DATA_NOT_FOUND', `Document does not exist: ${reference.name}`);
|
|
78
|
+
if (binding.tree.kind !== representation) throw new NamedDataError('NAMED_DATA_REPRESENTATION_UNSUPPORTED', `Cross-format ${binding.tree.kind} to ${representation} conversion may be lossy.`);
|
|
79
|
+
const bytes = new TextEncoder().encode(serializeDocument(binding.tree));
|
|
80
|
+
if (bytes.byteLength > DOCUMENT_MAX_OUTPUT_BYTES) throw new NamedDataError('NAMED_DATA_BODY_TOO_LARGE', 'Serialized document is too large.');
|
|
81
|
+
if (context.signal?.aborted) throw new NamedDataError('NAMED_DATA_ABORTED', 'Body resolution was aborted.');
|
|
82
|
+
return {binding, bytes};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
private metadata(reference: NamedDataReference, representation: NamedDataRepresentation, byteLength: number, revision: number): NamedDataMetadata {
|
|
86
|
+
return {
|
|
87
|
+
reference: {...reference}, nativeRepresentation: representation, representation,
|
|
88
|
+
mediaType: representation === 'html' ? 'text/html; charset=utf-8' : 'text/markdown; charset=utf-8',
|
|
89
|
+
byteLength, revision: String(revision), replayable: true
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
}
|