petari 0.3.0 → 0.4.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/README.md +30 -6
- package/dist/cli.js +4 -1
- package/dist/commands/show.js +239 -40
- package/dist/core/diff-html.js +272 -0
- package/dist/core/diff.js +177 -0
- package/dist/core/edit.js +83 -0
- package/dist/core/editor-html.js +301 -0
- package/dist/infra/browser.js +24 -0
- package/dist/infra/diff-server.js +437 -0
- package/package.json +7 -4
- package/vendor/monaco/LICENSE.txt +21 -0
- package/vendor/monaco/ThirdPartyNotices.txt +448 -0
- package/vendor/monaco/vs/abap-D-t0cyap.js +1 -0
- package/vendor/monaco/vs/apex-CcIm7xu6.js +1 -0
- package/vendor/monaco/vs/assets/css.worker-URu8fCFR.js +97 -0
- package/vendor/monaco/vs/assets/editor.worker-lj3bdIIn.js +30 -0
- package/vendor/monaco/vs/assets/editorWebWorkerMain-CA_vMoUU.js +30 -0
- package/vendor/monaco/vs/assets/html.worker-D1SL3iM8.js +474 -0
- package/vendor/monaco/vs/assets/json.worker-CoJx_OPf.js +62 -0
- package/vendor/monaco/vs/assets/ts.worker-BWKtMYOk.js +67722 -0
- package/vendor/monaco/vs/azcli-BA0tQDCg.js +1 -0
- package/vendor/monaco/vs/basic-languages/monaco.contribution.js +1 -0
- package/vendor/monaco/vs/bat-C397hTD6.js +1 -0
- package/vendor/monaco/vs/bicep-DF5aW17k.js +2 -0
- package/vendor/monaco/vs/cameligo-plsz8qhj.js +1 -0
- package/vendor/monaco/vs/clojure-Y2auQMzK.js +1 -0
- package/vendor/monaco/vs/coffee-Bu45yuWE.js +1 -0
- package/vendor/monaco/vs/cpp-CkKPQIni.js +1 -0
- package/vendor/monaco/vs/csharp-CX28MZyh.js +1 -0
- package/vendor/monaco/vs/csp-D8uWnyxW.js +1 -0
- package/vendor/monaco/vs/css-CaeNmE3S.js +3 -0
- package/vendor/monaco/vs/css.worker-CyhWkhHo.js +1 -0
- package/vendor/monaco/vs/cssMode-CV6Ay48H.js +1 -0
- package/vendor/monaco/vs/cypher-DVThT8BS.js +1 -0
- package/vendor/monaco/vs/dart-CmGfCvrO.js +1 -0
- package/vendor/monaco/vs/dockerfile-CZqqYdch.js +1 -0
- package/vendor/monaco/vs/ecl-30fUercY.js +1 -0
- package/vendor/monaco/vs/editor/editor.main.css +1 -0
- package/vendor/monaco/vs/editor/editor.main.js +1 -0
- package/vendor/monaco/vs/editor/editor.worker.js +1 -0
- package/vendor/monaco/vs/editor-KLE6jdfb.js +831 -0
- package/vendor/monaco/vs/editor.js +1 -0
- package/vendor/monaco/vs/editorWorkerHost-fVE1cjcC.js +35 -0
- package/vendor/monaco/vs/elixir-xjPaIfzF.js +1 -0
- package/vendor/monaco/vs/flow9-DqtmStfK.js +1 -0
- package/vendor/monaco/vs/freemarker2-FWreY7v7.js +3 -0
- package/vendor/monaco/vs/fsharp-BOMdg4U1.js +1 -0
- package/vendor/monaco/vs/go-D_hbi-Jt.js +1 -0
- package/vendor/monaco/vs/graphql-CKUU4kLG.js +1 -0
- package/vendor/monaco/vs/handlebars-CZi4pHCH.js +1 -0
- package/vendor/monaco/vs/hcl-DTaboeZW.js +1 -0
- package/vendor/monaco/vs/html-j_7ZTNRU.js +1 -0
- package/vendor/monaco/vs/html.worker-CA3iAimZ.js +1 -0
- package/vendor/monaco/vs/htmlMode-27by_KM4.js +1 -0
- package/vendor/monaco/vs/index-049xrXrp.js +1 -0
- package/vendor/monaco/vs/index-CBVt3dzv.js +5 -0
- package/vendor/monaco/vs/index.js +1 -0
- package/vendor/monaco/vs/ini-CsNwO04R.js +1 -0
- package/vendor/monaco/vs/initialize-DL0l1TGY.js +1 -0
- package/vendor/monaco/vs/java-CI4ZMsH9.js +1 -0
- package/vendor/monaco/vs/javascript-DTJl4Jn1.js +1 -0
- package/vendor/monaco/vs/json.worker-BizpAl9O.js +1 -0
- package/vendor/monaco/vs/jsonMode-DLUiMUrI.js +1 -0
- package/vendor/monaco/vs/julia-BwzEvaQw.js +1 -0
- package/vendor/monaco/vs/kotlin-IUYPiTV8.js +1 -0
- package/vendor/monaco/vs/language/css/css.worker.js +66 -0
- package/vendor/monaco/vs/language/css/monaco.contribution.js +1 -0
- package/vendor/monaco/vs/language/html/html.worker.js +443 -0
- package/vendor/monaco/vs/language/html/monaco.contribution.js +1 -0
- package/vendor/monaco/vs/language/json/json.worker.js +17 -0
- package/vendor/monaco/vs/language/json/monaco.contribution.js +1 -0
- package/vendor/monaco/vs/language/typescript/monaco.contribution.js +1 -0
- package/vendor/monaco/vs/language/typescript/ts.worker.js +67693 -0
- package/vendor/monaco/vs/less-C0eDYdqa.js +2 -0
- package/vendor/monaco/vs/lexon-iON-Kj97.js +1 -0
- package/vendor/monaco/vs/liquid-CLJYelW6.js +1 -0
- package/vendor/monaco/vs/loader.js +1368 -0
- package/vendor/monaco/vs/lspLanguageFeatures-BIkJOWLw.js +2 -0
- package/vendor/monaco/vs/lua-DtygF91M.js +1 -0
- package/vendor/monaco/vs/m3-CsR4AuFi.js +1 -0
- package/vendor/monaco/vs/main-BEx-Fmlo.js +15 -0
- package/vendor/monaco/vs/main-DsK8pnKg.js +3 -0
- package/vendor/monaco/vs/markdown-C_rD0bIw.js +1 -0
- package/vendor/monaco/vs/mdx-CpzZIPHF.js +1 -0
- package/vendor/monaco/vs/mips-CiYP61RB.js +1 -0
- package/vendor/monaco/vs/monaco.contribution-9cKT3C7t.js +1 -0
- package/vendor/monaco/vs/monaco.contribution-BE88ZNGY.js +1 -0
- package/vendor/monaco/vs/monaco.contribution-BPhsneLd.js +1 -0
- package/vendor/monaco/vs/monaco.contribution-BgRy6xDf.js +1 -0
- package/vendor/monaco/vs/msdax-C38-sJlp.js +1 -0
- package/vendor/monaco/vs/mysql-CdtbpvbG.js +1 -0
- package/vendor/monaco/vs/nls/lang/cs.d.ts +1 -0
- package/vendor/monaco/vs/nls/lang/cs.js +9 -0
- package/vendor/monaco/vs/nls/lang/de.d.ts +1 -0
- package/vendor/monaco/vs/nls/lang/de.js +9 -0
- package/vendor/monaco/vs/nls/lang/es.d.ts +1 -0
- package/vendor/monaco/vs/nls/lang/es.js +9 -0
- package/vendor/monaco/vs/nls/lang/fr.d.ts +1 -0
- package/vendor/monaco/vs/nls/lang/fr.js +9 -0
- package/vendor/monaco/vs/nls/lang/it.d.ts +1 -0
- package/vendor/monaco/vs/nls/lang/it.js +9 -0
- package/vendor/monaco/vs/nls/lang/ja.d.ts +1 -0
- package/vendor/monaco/vs/nls/lang/ja.js +9 -0
- package/vendor/monaco/vs/nls/lang/ko.d.ts +1 -0
- package/vendor/monaco/vs/nls/lang/ko.js +9 -0
- package/vendor/monaco/vs/nls/lang/pl.d.ts +1 -0
- package/vendor/monaco/vs/nls/lang/pl.js +9 -0
- package/vendor/monaco/vs/nls/lang/pt-br.d.ts +1 -0
- package/vendor/monaco/vs/nls/lang/pt-br.js +9 -0
- package/vendor/monaco/vs/nls/lang/ru.d.ts +1 -0
- package/vendor/monaco/vs/nls/lang/ru.js +9 -0
- package/vendor/monaco/vs/nls/lang/tr.d.ts +1 -0
- package/vendor/monaco/vs/nls/lang/tr.js +9 -0
- package/vendor/monaco/vs/nls/lang/zh-cn.d.ts +1 -0
- package/vendor/monaco/vs/nls/lang/zh-cn.js +9 -0
- package/vendor/monaco/vs/nls/lang/zh-tw.d.ts +1 -0
- package/vendor/monaco/vs/nls/lang/zh-tw.js +9 -0
- package/vendor/monaco/vs/nls.messages-loader.js +1 -0
- package/vendor/monaco/vs/objective-c-CntZFaHX.js +1 -0
- package/vendor/monaco/vs/pascal-r6kuqfl_.js +1 -0
- package/vendor/monaco/vs/pascaligo-BiXoTmXh.js +1 -0
- package/vendor/monaco/vs/perl-DABw_TcH.js +1 -0
- package/vendor/monaco/vs/pgsql-me_jFXeX.js +1 -0
- package/vendor/monaco/vs/php-D_kh-9LK.js +1 -0
- package/vendor/monaco/vs/pla-VfZjczW0.js +1 -0
- package/vendor/monaco/vs/postiats-BBSzz8Pk.js +1 -0
- package/vendor/monaco/vs/powerquery-Dt-g_2cc.js +1 -0
- package/vendor/monaco/vs/powershell-B-7ap1zc.js +1 -0
- package/vendor/monaco/vs/protobuf-BmtuEB1A.js +2 -0
- package/vendor/monaco/vs/pug-BRpRNeEb.js +1 -0
- package/vendor/monaco/vs/python-CqWUUgfu.js +1 -0
- package/vendor/monaco/vs/qsharp-BzsFaUU9.js +1 -0
- package/vendor/monaco/vs/r-f8dDdrp4.js +1 -0
- package/vendor/monaco/vs/razor-hok5y9Km.js +1 -0
- package/vendor/monaco/vs/redis-fvZQY4PI.js +1 -0
- package/vendor/monaco/vs/redshift-45Et0LQi.js +1 -0
- package/vendor/monaco/vs/restructuredtext-C7UUFKFD.js +1 -0
- package/vendor/monaco/vs/ruby-CZO8zYTz.js +1 -0
- package/vendor/monaco/vs/rust-Bfetafyc.js +1 -0
- package/vendor/monaco/vs/sb-3GYllVck.js +1 -0
- package/vendor/monaco/vs/scala-foMgrKo1.js +1 -0
- package/vendor/monaco/vs/scheme-CHdMtr7p.js +1 -0
- package/vendor/monaco/vs/scss-C1cmLt9V.js +3 -0
- package/vendor/monaco/vs/shell-ClXCKCEW.js +1 -0
- package/vendor/monaco/vs/solidity-MZ6ExpPy.js +1 -0
- package/vendor/monaco/vs/sophia-DWkuSsPQ.js +1 -0
- package/vendor/monaco/vs/sparql-AUGFYSyk.js +1 -0
- package/vendor/monaco/vs/sql-32GpJSV2.js +1 -0
- package/vendor/monaco/vs/st-CuDFIVZ_.js +1 -0
- package/vendor/monaco/vs/swift-t-PfMj7W.js +1 -0
- package/vendor/monaco/vs/systemverilog-Ch4vA8Yt.js +1 -0
- package/vendor/monaco/vs/tcl-D74tq1nH.js +1 -0
- package/vendor/monaco/vs/toggleHighContrast-qGX7E9o7.js +61 -0
- package/vendor/monaco/vs/ts.worker-2QLmBukE.js +1 -0
- package/vendor/monaco/vs/tsMode-B0S_kp2q.js +11 -0
- package/vendor/monaco/vs/twig-C6taOxMV.js +1 -0
- package/vendor/monaco/vs/typescript-_2t_511t.js +1 -0
- package/vendor/monaco/vs/typespec-8OfoLt6R.js +1 -0
- package/vendor/monaco/vs/vb-Dyb2648j.js +1 -0
- package/vendor/monaco/vs/wgsl-BhLXMOR0.js +298 -0
- package/vendor/monaco/vs/workers-BBttULjf.js +1 -0
- package/vendor/monaco/vs/xml-cZjNDqDT.js +1 -0
- package/vendor/monaco/vs/yaml-A1fOIdH6.js +1 -0
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Monaco Editor (VS Code のエディタ実装) によるブラウザ差分エディタページ (§4.3 --edit)。
|
|
3
|
+
* 純粋な文字列生成のみ。
|
|
4
|
+
*
|
|
5
|
+
* セキュリティ方針 (2026-08-12 Monaco 採用で更新):
|
|
6
|
+
* - スクリプトは「ビルド時に同梱した monaco-editor (script-src 'self')」と
|
|
7
|
+
* 「このファイルに埋め込んだ自前ブートストラップ (nonce)」のみ。
|
|
8
|
+
* 外部 CDN・外部リソースへの参照はゼロ (CSP が遮断する)
|
|
9
|
+
* - 埋め込みデータ (JSON) は JSON.stringify 後に < を < へ置換し、
|
|
10
|
+
* </script> による脱出を封じる (type="application/json" の不活性ブロック)
|
|
11
|
+
* - トークンを HTML に埋め込まない: monaco 資産のパスは実行時に location.pathname
|
|
12
|
+
* から組み立てる。フォーム action・リンクは相対のみ
|
|
13
|
+
* - JS が無効でも動く: <noscript> 内の素のフォーム (textarea) で保存できる。
|
|
14
|
+
* textarea の値はエスケープ以外の加工禁止・開きタグ直後の改行必須 (diff-html.ts と同じ理由)
|
|
15
|
+
*
|
|
16
|
+
* 実装メモ: monaco.editor.createDiffEditor が VS Code と同じ差分 UI
|
|
17
|
+
* (view zone による完全な行整列・右ペイン編集・シンタックスハイライト) を提供する。
|
|
18
|
+
* モデルの改行は monaco が単一 EOL に正規化するが、保存は §8 の mergeEditedText が
|
|
19
|
+
* 行テキスト単位で raw バイトを保全するため、無変更行の実バイトは壊れない。
|
|
20
|
+
*/
|
|
21
|
+
import { escapeHtml, CSP } from "./diff-html.js";
|
|
22
|
+
/**
|
|
23
|
+
* エディタページ用 CSP。同梱 monaco ('self') + 自前ブートストラップ (nonce) のみ許可。
|
|
24
|
+
* font-src data: は monaco の codicon フォント (CSS 内 data: URI)、
|
|
25
|
+
* worker-src は monaco のエディタ/差分計算ワーカー用。
|
|
26
|
+
*/
|
|
27
|
+
export function editorCsp(nonce) {
|
|
28
|
+
return CSP.replace("default-src 'none'; style-src 'unsafe-inline'", `default-src 'none'; script-src 'self' 'nonce-${nonce}'; style-src 'unsafe-inline' 'self'; ` +
|
|
29
|
+
"font-src data:; img-src 'self' data:; worker-src 'self' blob:; connect-src 'self'");
|
|
30
|
+
}
|
|
31
|
+
const STYLE = `
|
|
32
|
+
:root{
|
|
33
|
+
--bg:#ffffff;--panel:#f3f3f3;--border:#e5e5e5;--fg:#1f1f1f;--muted:#6e7681;
|
|
34
|
+
--accent:#005fb8;--accent-fg:#ffffff;--status-bg:#005fb8;--status-fg:#ffffff;
|
|
35
|
+
--ok:#1a7f37;--err:#c72e2e;
|
|
36
|
+
}
|
|
37
|
+
@media (prefers-color-scheme: dark){:root{
|
|
38
|
+
--bg:#1e1e1e;--panel:#252526;--border:#3c3c3c;--fg:#d4d4d4;--muted:#858585;
|
|
39
|
+
--accent:#0e639c;--accent-fg:#ffffff;--status-bg:#007acc;--status-fg:#ffffff;
|
|
40
|
+
--ok:#89d185;--err:#f48771;
|
|
41
|
+
}}
|
|
42
|
+
*{box-sizing:border-box;margin:0}
|
|
43
|
+
html,body{height:100%}
|
|
44
|
+
body{background:var(--bg);color:var(--fg);overflow:hidden;
|
|
45
|
+
font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","Hiragino Sans","Noto Sans JP",sans-serif;font-size:13px}
|
|
46
|
+
.app{display:flex;flex-direction:column;height:100vh}
|
|
47
|
+
.titlebar{display:flex;align-items:center;gap:.8rem;background:var(--panel);
|
|
48
|
+
border-bottom:1px solid var(--border);padding:0 .6rem 0 0;flex-shrink:0}
|
|
49
|
+
.tab{display:flex;align-items:center;gap:.45em;background:var(--bg);border-right:1px solid var(--border);
|
|
50
|
+
padding:.45em .95em;font-size:12.5px}
|
|
51
|
+
.tab code{font-family:ui-monospace,Menlo,Consolas,monospace}
|
|
52
|
+
#dirty{color:var(--fg);visibility:hidden}
|
|
53
|
+
.tb-sp{flex:1}
|
|
54
|
+
#msg{font-size:12px;color:var(--muted)}
|
|
55
|
+
#msg.ok{color:var(--ok)} #msg.err{color:var(--err)}
|
|
56
|
+
.btn{font-size:12.5px;padding:.34em 1.1em;border-radius:3px;border:1px solid transparent;cursor:pointer;
|
|
57
|
+
background:var(--accent);color:var(--accent-fg)}
|
|
58
|
+
.btn:hover{filter:brightness(1.15)}
|
|
59
|
+
.btn.ghost{background:transparent;color:var(--fg);border-color:var(--border);text-decoration:none;display:inline-block}
|
|
60
|
+
.inline{display:inline}
|
|
61
|
+
.banner{padding:.4rem .8rem;font-size:12.5px;border-bottom:1px solid var(--border)}
|
|
62
|
+
.banner.ok{color:var(--ok)} .banner.err{color:var(--err)}
|
|
63
|
+
#ed{flex:1;min-height:0}
|
|
64
|
+
.note{color:var(--muted);padding:.8rem}
|
|
65
|
+
noscript textarea{width:100%;height:60vh;font-family:ui-monospace,Menlo,Consolas,monospace;
|
|
66
|
+
font-size:12.5px;background:var(--bg);color:var(--fg);border:1px solid var(--border)}
|
|
67
|
+
noscript .nsform{padding:.8rem}
|
|
68
|
+
.statusbar{display:flex;align-items:center;gap:1.2em;background:var(--status-bg);color:var(--status-fg);
|
|
69
|
+
font-size:12px;padding:.18em .9em;flex-shrink:0}
|
|
70
|
+
.statusbar .sp{flex:1}
|
|
71
|
+
`.trim();
|
|
72
|
+
/** 埋め込み JSON: </script> 脱出と行分離子を封じる */
|
|
73
|
+
function embedJson(value) {
|
|
74
|
+
return JSON.stringify(value)
|
|
75
|
+
.replaceAll("<", "\\u003c")
|
|
76
|
+
.replaceAll("\u2028", "\\u2028")
|
|
77
|
+
.replaceAll("\u2029", "\\u2029");
|
|
78
|
+
}
|
|
79
|
+
// 埋め込みブートストラップ JS。テンプレートリテラル内のため ` と ${ は使わない
|
|
80
|
+
const BOOT_JS = String.raw `
|
|
81
|
+
(function () {
|
|
82
|
+
"use strict";
|
|
83
|
+
var data = JSON.parse(document.getElementById("pd").textContent);
|
|
84
|
+
var msgEl = document.getElementById("msg");
|
|
85
|
+
function setMsg(text, cls) {
|
|
86
|
+
msgEl.textContent = text;
|
|
87
|
+
msgEl.className = cls || "";
|
|
88
|
+
}
|
|
89
|
+
if (data.blocked) return;
|
|
90
|
+
var saveBtn = document.getElementById("savebtn");
|
|
91
|
+
var dirtyEl = document.getElementById("dirty");
|
|
92
|
+
var statEl = document.getElementById("diffstat");
|
|
93
|
+
var caretEl = document.getElementById("caret");
|
|
94
|
+
// monaco 資産のベース URL。トークンは HTML に埋めず実行時に URL から得る
|
|
95
|
+
var tokenBase = "/" + window.location.pathname.split("/").filter(function (s) { return s !== ""; })[0];
|
|
96
|
+
var baseSha = data.baseSha256;
|
|
97
|
+
var saving = false, dirty = false;
|
|
98
|
+
var modified = null;
|
|
99
|
+
var msgTimer = null;
|
|
100
|
+
|
|
101
|
+
var css = document.createElement("link");
|
|
102
|
+
css.rel = "stylesheet";
|
|
103
|
+
css.href = tokenBase + "/vs/editor/editor.main.css";
|
|
104
|
+
document.head.appendChild(css);
|
|
105
|
+
// 読み込みが黙って止まった場合の見張り (依存欠落は AMD ではエラーにならないことがある)
|
|
106
|
+
var watchdog = setTimeout(function () {
|
|
107
|
+
setMsg("エディタの読み込みに時間がかかっています (コンソールを確認してください)", "err");
|
|
108
|
+
}, 20000);
|
|
109
|
+
// 日本語 NLS: monaco 0.5x では AMD モジュールではなく素のスクリプトが
|
|
110
|
+
// globalThis._VSCODE_NLS_MESSAGES を設定する方式。editor.main より先に読み込む。
|
|
111
|
+
// (旧 availableLanguages 設定で読むと define されないモジュールを待ってハングする)
|
|
112
|
+
var nls = document.createElement("script");
|
|
113
|
+
nls.src = tokenBase + "/vs/nls/lang/ja.js";
|
|
114
|
+
nls.onload = loadLoader;
|
|
115
|
+
nls.onerror = loadLoader; // NLS がなくても英語 UI で続行
|
|
116
|
+
document.head.appendChild(nls);
|
|
117
|
+
|
|
118
|
+
function loadLoader() {
|
|
119
|
+
var s = document.createElement("script");
|
|
120
|
+
s.src = tokenBase + "/vs/loader.js";
|
|
121
|
+
s.onload = boot;
|
|
122
|
+
s.onerror = function () { setMsg("エディタ資産を読み込めませんでした", "err"); };
|
|
123
|
+
document.head.appendChild(s);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function boot() {
|
|
127
|
+
window.require.config({ paths: { vs: tokenBase + "/vs" } });
|
|
128
|
+
window.require(["vs/editor/editor.main"], init, function () {
|
|
129
|
+
setMsg("エディタを初期化できませんでした", "err");
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function init() {
|
|
134
|
+
var monaco = window.monaco;
|
|
135
|
+
var dark = window.matchMedia("(prefers-color-scheme: dark)");
|
|
136
|
+
var original = monaco.editor.createModel(
|
|
137
|
+
data.snapshotText === null ? "" : data.snapshotText,
|
|
138
|
+
undefined,
|
|
139
|
+
monaco.Uri.file("/original/" + data.path)
|
|
140
|
+
);
|
|
141
|
+
var mod = monaco.editor.createModel(
|
|
142
|
+
data.currentText,
|
|
143
|
+
undefined,
|
|
144
|
+
monaco.Uri.file("/modified/" + data.path)
|
|
145
|
+
);
|
|
146
|
+
modified = mod;
|
|
147
|
+
var diffEditor = monaco.editor.createDiffEditor(document.getElementById("ed"), {
|
|
148
|
+
automaticLayout: true,
|
|
149
|
+
originalEditable: false,
|
|
150
|
+
renderSideBySide: true,
|
|
151
|
+
ignoreTrimWhitespace: false,
|
|
152
|
+
theme: dark.matches ? "vs-dark" : "vs",
|
|
153
|
+
});
|
|
154
|
+
diffEditor.setModel({ original: original, modified: mod });
|
|
155
|
+
if (dark.addEventListener) {
|
|
156
|
+
dark.addEventListener("change", function (e) {
|
|
157
|
+
monaco.editor.setTheme(e.matches ? "vs-dark" : "vs");
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
mod.onDidChangeContent(function () {
|
|
161
|
+
if (!dirty) { dirty = true; dirtyEl.style.visibility = "visible"; }
|
|
162
|
+
});
|
|
163
|
+
diffEditor.onDidUpdateDiff(function () {
|
|
164
|
+
var changes = diffEditor.getLineChanges() || [];
|
|
165
|
+
var add = 0, del = 0;
|
|
166
|
+
for (var i = 0; i < changes.length; i++) {
|
|
167
|
+
var c = changes[i];
|
|
168
|
+
if (c.modifiedEndLineNumber >= c.modifiedStartLineNumber && c.modifiedEndLineNumber > 0) {
|
|
169
|
+
add += c.modifiedEndLineNumber - c.modifiedStartLineNumber + 1;
|
|
170
|
+
}
|
|
171
|
+
if (c.originalEndLineNumber >= c.originalStartLineNumber && c.originalEndLineNumber > 0) {
|
|
172
|
+
del += c.originalEndLineNumber - c.originalStartLineNumber + 1;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
statEl.textContent = "+" + add + " −" + del;
|
|
176
|
+
});
|
|
177
|
+
var me = diffEditor.getModifiedEditor();
|
|
178
|
+
me.onDidChangeCursorPosition(function (e) {
|
|
179
|
+
caretEl.textContent = "行 " + e.position.lineNumber + "、列 " + e.position.column;
|
|
180
|
+
});
|
|
181
|
+
me.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, doSave);
|
|
182
|
+
saveBtn.hidden = false;
|
|
183
|
+
saveBtn.addEventListener("click", doSave);
|
|
184
|
+
document.addEventListener("keydown", function (e) {
|
|
185
|
+
if ((e.metaKey || e.ctrlKey) && (e.key === "s" || e.key === "S")) {
|
|
186
|
+
e.preventDefault();
|
|
187
|
+
doSave();
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
window.addEventListener("beforeunload", function (e) {
|
|
191
|
+
if (dirty) e.preventDefault();
|
|
192
|
+
});
|
|
193
|
+
clearTimeout(watchdog);
|
|
194
|
+
setMsg("", "");
|
|
195
|
+
me.focus();
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function doSave() {
|
|
199
|
+
if (saving || modified === null) return;
|
|
200
|
+
saving = true;
|
|
201
|
+
setMsg("保存中…", "");
|
|
202
|
+
var body = new URLSearchParams();
|
|
203
|
+
body.set("baseSha256", baseSha);
|
|
204
|
+
body.set("text", modified.getValue());
|
|
205
|
+
body.set("ajax", "1");
|
|
206
|
+
fetch(window.location.pathname, {
|
|
207
|
+
method: "POST",
|
|
208
|
+
headers: { "content-type": "application/x-www-form-urlencoded;charset=UTF-8" },
|
|
209
|
+
body: body.toString(),
|
|
210
|
+
}).then(function (res) {
|
|
211
|
+
return res.json().then(function (j) { return { status: res.status, j: j }; });
|
|
212
|
+
}).then(function (r) {
|
|
213
|
+
saving = false;
|
|
214
|
+
if (r.j.ok === true) {
|
|
215
|
+
baseSha = r.j.baseSha256;
|
|
216
|
+
dirty = false;
|
|
217
|
+
dirtyEl.style.visibility = "hidden";
|
|
218
|
+
setMsg("保存しました ✓", "ok");
|
|
219
|
+
if (msgTimer !== null) clearTimeout(msgTimer);
|
|
220
|
+
msgTimer = setTimeout(function () { setMsg("", ""); }, 3000);
|
|
221
|
+
} else {
|
|
222
|
+
setMsg(r.j.error || "保存に失敗しました (" + r.status + ")", "err");
|
|
223
|
+
}
|
|
224
|
+
}).catch(function () {
|
|
225
|
+
saving = false;
|
|
226
|
+
setMsg("保存に失敗しました (接続エラー)", "err");
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
})();
|
|
230
|
+
`;
|
|
231
|
+
export function buildEditorPage(d) {
|
|
232
|
+
const banners = [];
|
|
233
|
+
if (d.saved)
|
|
234
|
+
banners.push(`<div class="banner ok">保存しました</div>`);
|
|
235
|
+
if (d.errorBanner !== null)
|
|
236
|
+
banners.push(`<div class="banner err">${escapeHtml(d.errorBanner)}</div>`);
|
|
237
|
+
if (d.blockReason !== null)
|
|
238
|
+
banners.push(`<div class="banner err">${escapeHtml(d.blockReason)}</div>`);
|
|
239
|
+
if (d.snapshotNote !== null)
|
|
240
|
+
banners.push(`<div class="banner">${escapeHtml(d.snapshotNote)}</div>`);
|
|
241
|
+
// JS 無効時のフォールバック: 素のフォーム POST (§8 の保存経路は同じ)。
|
|
242
|
+
// <textarea> 開きタグ直後の改行はブラウザに 1 個食われるため必ず入れる
|
|
243
|
+
const noscriptForm = d.blockReason !== null
|
|
244
|
+
? ""
|
|
245
|
+
: `<noscript><div class="nsform">
|
|
246
|
+
<p class="note">JS が無効のため Monaco エディタは使えません。以下で直接編集して保存できます (${escapeHtml(d.leftLabel)}との差分表示はありません)</p>
|
|
247
|
+
<form method="post" action="">
|
|
248
|
+
<input type="hidden" name="baseSha256" value="${escapeHtml(d.baseSha256)}">
|
|
249
|
+
<textarea name="text" spellcheck="false" autocomplete="off">
|
|
250
|
+
${escapeHtml(d.currentText)}</textarea>
|
|
251
|
+
<p><button class="btn">保存</button></p>
|
|
252
|
+
</form>
|
|
253
|
+
</div></noscript>`;
|
|
254
|
+
const editorArea = d.blockReason !== null
|
|
255
|
+
? `<div class="note">${escapeHtml(d.blockReason)}</div>`
|
|
256
|
+
: `<div id="ed"></div>\n${noscriptForm}`;
|
|
257
|
+
const data = {
|
|
258
|
+
blocked: d.blockReason !== null,
|
|
259
|
+
path: d.path,
|
|
260
|
+
snapshotText: d.snapshotText,
|
|
261
|
+
currentText: d.currentText,
|
|
262
|
+
baseSha256: d.baseSha256,
|
|
263
|
+
};
|
|
264
|
+
const body = `<div class="app">
|
|
265
|
+
<div class="titlebar">
|
|
266
|
+
<div class="tab"><span id="dirty">●</span><code>${escapeHtml(d.path)}</code></div>
|
|
267
|
+
<span id="msg">エディタを読み込み中…</span>
|
|
268
|
+
<div class="tb-sp"></div>
|
|
269
|
+
${d.blockReason === null ? `<button class="btn" id="savebtn" hidden>保存</button>` : ""}
|
|
270
|
+
<a class="btn ghost" href="../">一覧</a>
|
|
271
|
+
<form class="inline" method="post" action="../quit"><button class="btn ghost">終了</button></form>
|
|
272
|
+
</div>
|
|
273
|
+
${banners.join("\n")}
|
|
274
|
+
${editorArea}
|
|
275
|
+
<div class="statusbar">
|
|
276
|
+
<span>${escapeHtml(d.leftLabel)} ⇔ 現在のファイル</span>
|
|
277
|
+
<span id="diffstat"></span>
|
|
278
|
+
<span class="sp"></span>
|
|
279
|
+
<span>無操作 ${d.idleMinutes} 分で自動終了</span>
|
|
280
|
+
<span>${escapeHtml(d.encodingLabel)}</span>
|
|
281
|
+
<span>${escapeHtml(d.eolLabel)}</span>
|
|
282
|
+
<span id="caret"></span>
|
|
283
|
+
</div>
|
|
284
|
+
</div>
|
|
285
|
+
<script type="application/json" id="pd">${embedJson(data)}</script>
|
|
286
|
+
<script nonce="${escapeHtml(d.nonce)}">${BOOT_JS}</script>`;
|
|
287
|
+
return `<!doctype html>
|
|
288
|
+
<html lang="ja">
|
|
289
|
+
<head>
|
|
290
|
+
<meta charset="utf-8">
|
|
291
|
+
<meta http-equiv="Content-Security-Policy" content="${editorCsp(d.nonce)}">
|
|
292
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
293
|
+
<title>${escapeHtml(d.path)} — petari</title>
|
|
294
|
+
<style>${STYLE}</style>
|
|
295
|
+
</head>
|
|
296
|
+
<body>
|
|
297
|
+
${body}
|
|
298
|
+
</body>
|
|
299
|
+
</html>
|
|
300
|
+
`;
|
|
301
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OS 既定ブラウザで URL / ファイルを開く (§4.3 ブラウザ差分ビュー)。
|
|
3
|
+
* 依存ゼロ方針のため OS コマンドを呼び出す (常に execFile の配列引数・シェル非経由)。
|
|
4
|
+
* 対象は自前で生成した URL (http://127.0.0.1:...) か一時ファイルのパスのみで
|
|
5
|
+
* 外部入力は渡さないが、Windows の引用は clipboard.ts と同じ単一引用符方式で行う。
|
|
6
|
+
* 自動テストなし (実機確認のみ — clipboard.ts と同方針)。
|
|
7
|
+
*/
|
|
8
|
+
import { execFile } from "node:child_process";
|
|
9
|
+
import { promisify } from "node:util";
|
|
10
|
+
const execFileP = promisify(execFile);
|
|
11
|
+
/** 既定ブラウザで開く。失敗時は throw (呼び出し側がパス/URL を案内して続行する) */
|
|
12
|
+
export async function openInBrowser(target) {
|
|
13
|
+
if (process.platform === "darwin") {
|
|
14
|
+
await execFileP("open", [target]);
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
if (process.platform === "win32") {
|
|
18
|
+
// ' は '' にエスケープした単一引用符リテラルで渡す (展開・注入が起きない)
|
|
19
|
+
const quoted = `'${target.replaceAll("'", "''")}'`;
|
|
20
|
+
await execFileP("powershell", ["-NoProfile", "-Command", `Start-Process ${quoted}`]);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
await execFileP("xdg-open", [target]);
|
|
24
|
+
}
|