@youtyan/code-viewer 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 +37 -17
- package/dist/code-viewer.js +988 -408
- package/package.json +1 -1
- package/web/app.js +2323 -528
- package/web/index.html +13 -0
- package/web/style.css +521 -37
package/web/app.js
CHANGED
|
@@ -6645,10 +6645,48 @@
|
|
|
6645
6645
|
md.core.ruler.after("inline", "footnote_tail", footnote_tail);
|
|
6646
6646
|
}
|
|
6647
6647
|
|
|
6648
|
-
// web-src/core/
|
|
6648
|
+
// web-src/core/mermaid-loader.ts
|
|
6649
|
+
var DEFAULT_CONFIG = {
|
|
6650
|
+
startOnLoad: false,
|
|
6651
|
+
securityLevel: "strict",
|
|
6652
|
+
theme: "default",
|
|
6653
|
+
er: { useMaxWidth: false }
|
|
6654
|
+
};
|
|
6649
6655
|
var mermaidPromise = null;
|
|
6650
|
-
var
|
|
6651
|
-
|
|
6656
|
+
var initialized = false;
|
|
6657
|
+
function loadMermaid() {
|
|
6658
|
+
if (!mermaidPromise) {
|
|
6659
|
+
mermaidPromise = import("/mermaid.js").then((mod) => {
|
|
6660
|
+
const mermaid = mod.default;
|
|
6661
|
+
if (!initialized) {
|
|
6662
|
+
mermaid.initialize(DEFAULT_CONFIG);
|
|
6663
|
+
initialized = true;
|
|
6664
|
+
}
|
|
6665
|
+
return mermaid;
|
|
6666
|
+
}).catch(() => null);
|
|
6667
|
+
}
|
|
6668
|
+
return mermaidPromise;
|
|
6669
|
+
}
|
|
6670
|
+
|
|
6671
|
+
// web-src/core/shiki-loader.ts
|
|
6672
|
+
var cache = new Map;
|
|
6673
|
+
function loadShikiHighlighter(options) {
|
|
6674
|
+
const key = JSON.stringify({
|
|
6675
|
+
themes: [...options.themes].sort(),
|
|
6676
|
+
langs: [...options.langs].sort()
|
|
6677
|
+
});
|
|
6678
|
+
const cached = cache.get(key);
|
|
6679
|
+
if (cached)
|
|
6680
|
+
return cached;
|
|
6681
|
+
const promise = import("/shiki.js").then((mod) => mod.createHighlighter({
|
|
6682
|
+
themes: options.themes,
|
|
6683
|
+
langs: options.langs
|
|
6684
|
+
})).catch(() => null);
|
|
6685
|
+
cache.set(key, promise);
|
|
6686
|
+
return promise;
|
|
6687
|
+
}
|
|
6688
|
+
|
|
6689
|
+
// web-src/core/markdown-preview.ts
|
|
6652
6690
|
var MARKDOWN_FENCE_LANG_ALIASES = {
|
|
6653
6691
|
sh: "bash",
|
|
6654
6692
|
zsh: "bash",
|
|
@@ -6884,17 +6922,11 @@ ${frontmatter.yaml}
|
|
|
6884
6922
|
body: textValue.slice(closing + newline2.length + 3 + newline2.length)
|
|
6885
6923
|
};
|
|
6886
6924
|
}
|
|
6887
|
-
|
|
6888
|
-
|
|
6889
|
-
|
|
6890
|
-
|
|
6891
|
-
|
|
6892
|
-
themes: ["github-light", "github-dark"],
|
|
6893
|
-
langs: MARKDOWN_SHIKI_LANGS
|
|
6894
|
-
});
|
|
6895
|
-
}).catch(() => null);
|
|
6896
|
-
}
|
|
6897
|
-
return shikiPromise;
|
|
6925
|
+
function loadMarkdownHighlighter() {
|
|
6926
|
+
return loadShikiHighlighter({
|
|
6927
|
+
themes: ["github-light", "github-dark"],
|
|
6928
|
+
langs: MARKDOWN_SHIKI_LANGS
|
|
6929
|
+
});
|
|
6898
6930
|
}
|
|
6899
6931
|
function enhanceTaskLists(root) {
|
|
6900
6932
|
root.querySelectorAll("[data-gdp-task]").forEach((inline2) => {
|
|
@@ -7091,24 +7123,6 @@ ${frontmatter.yaml}
|
|
|
7091
7123
|
await renderMermaidError(node, mermaid);
|
|
7092
7124
|
}
|
|
7093
7125
|
}
|
|
7094
|
-
async function loadMermaid() {
|
|
7095
|
-
if (!mermaidPromise) {
|
|
7096
|
-
mermaidPromise = import("/mermaid.js").then((mod) => {
|
|
7097
|
-
const typed = mod;
|
|
7098
|
-
const mermaid = typed.default;
|
|
7099
|
-
if (!mermaidInitialized) {
|
|
7100
|
-
mermaid.initialize({
|
|
7101
|
-
startOnLoad: false,
|
|
7102
|
-
securityLevel: "strict",
|
|
7103
|
-
theme: "default"
|
|
7104
|
-
});
|
|
7105
|
-
mermaidInitialized = true;
|
|
7106
|
-
}
|
|
7107
|
-
return mermaid;
|
|
7108
|
-
}).catch(() => null);
|
|
7109
|
-
}
|
|
7110
|
-
return mermaidPromise;
|
|
7111
|
-
}
|
|
7112
7126
|
function isMermaidErrorSvg(svg) {
|
|
7113
7127
|
return !!svg && /Syntax error/i.test(svg.textContent || "");
|
|
7114
7128
|
}
|
|
@@ -7210,12 +7224,12 @@ ${frontmatter.yaml}
|
|
|
7210
7224
|
overlay.remove();
|
|
7211
7225
|
};
|
|
7212
7226
|
button("+", "zoom in", () => zoomCentered(1.25));
|
|
7213
|
-
button("-", "zoom out", () => zoomCentered(
|
|
7227
|
+
button("-", "zoom out", () => zoomCentered(1 / 1.25));
|
|
7214
7228
|
button("fit", "fit", fit);
|
|
7215
7229
|
button("x", "close", close);
|
|
7216
7230
|
overlay.addEventListener("wheel", (e2) => {
|
|
7217
7231
|
e2.preventDefault();
|
|
7218
|
-
zoomAt(e2.clientX, e2.clientY, e2.deltaY < 0 ? 1.12 :
|
|
7232
|
+
zoomAt(e2.clientX, e2.clientY, e2.deltaY < 0 ? 1.12 : 1 / 1.12);
|
|
7219
7233
|
}, { passive: false });
|
|
7220
7234
|
let dragging = false;
|
|
7221
7235
|
let lastX = 0;
|
|
@@ -7251,7 +7265,7 @@ ${frontmatter.yaml}
|
|
|
7251
7265
|
else if (e2.key === "+" || e2.key === "=")
|
|
7252
7266
|
zoomCentered(1.25);
|
|
7253
7267
|
else if (e2.key === "-")
|
|
7254
|
-
zoomCentered(
|
|
7268
|
+
zoomCentered(1 / 1.25);
|
|
7255
7269
|
};
|
|
7256
7270
|
window.addEventListener("mousemove", onMove);
|
|
7257
7271
|
window.addEventListener("mouseup", onUp);
|
|
@@ -7281,6 +7295,235 @@ ${frontmatter.yaml}
|
|
|
7281
7295
|
return { width: rect.width || 800, height: rect.height || 600 };
|
|
7282
7296
|
}
|
|
7283
7297
|
|
|
7298
|
+
// web-src/views/ui-dialog.ts
|
|
7299
|
+
var BACKDROP_CLASS = "gdp-dialog-backdrop";
|
|
7300
|
+
var DIALOG_CLASS = "gdp-dialog";
|
|
7301
|
+
function closeOpenDialog() {
|
|
7302
|
+
document.querySelector(`.${BACKDROP_CLASS}`)?.remove();
|
|
7303
|
+
}
|
|
7304
|
+
function createDialogShell(titleText, bodyText, actions) {
|
|
7305
|
+
closeOpenDialog();
|
|
7306
|
+
const backdrop = document.createElement("div");
|
|
7307
|
+
backdrop.className = BACKDROP_CLASS;
|
|
7308
|
+
const dialog = document.createElement("div");
|
|
7309
|
+
dialog.className = DIALOG_CLASS;
|
|
7310
|
+
dialog.setAttribute("role", "dialog");
|
|
7311
|
+
dialog.setAttribute("aria-modal", "true");
|
|
7312
|
+
if (titleText) {
|
|
7313
|
+
const titleId = "gdp-dialog-title";
|
|
7314
|
+
dialog.setAttribute("aria-labelledby", titleId);
|
|
7315
|
+
const heading2 = document.createElement("div");
|
|
7316
|
+
heading2.id = titleId;
|
|
7317
|
+
heading2.className = "gdp-dialog-title";
|
|
7318
|
+
heading2.textContent = titleText;
|
|
7319
|
+
dialog.appendChild(heading2);
|
|
7320
|
+
}
|
|
7321
|
+
const bodyId = "gdp-dialog-body";
|
|
7322
|
+
dialog.setAttribute("aria-describedby", bodyId);
|
|
7323
|
+
const body = document.createElement("div");
|
|
7324
|
+
body.id = bodyId;
|
|
7325
|
+
body.className = "gdp-dialog-body";
|
|
7326
|
+
if (bodyText)
|
|
7327
|
+
body.textContent = bodyText;
|
|
7328
|
+
dialog.appendChild(body);
|
|
7329
|
+
const actionRow = document.createElement("div");
|
|
7330
|
+
actionRow.className = "gdp-dialog-actions";
|
|
7331
|
+
actionRow.append(...actions);
|
|
7332
|
+
dialog.appendChild(actionRow);
|
|
7333
|
+
backdrop.appendChild(dialog);
|
|
7334
|
+
document.body.appendChild(backdrop);
|
|
7335
|
+
return { backdrop, body };
|
|
7336
|
+
}
|
|
7337
|
+
function trapTabKey(event, focusables) {
|
|
7338
|
+
if (event.key !== "Tab")
|
|
7339
|
+
return false;
|
|
7340
|
+
const index = focusables.indexOf(document.activeElement);
|
|
7341
|
+
if (index < 0) {
|
|
7342
|
+
event.preventDefault();
|
|
7343
|
+
focusables[0]?.focus();
|
|
7344
|
+
return true;
|
|
7345
|
+
}
|
|
7346
|
+
if (event.shiftKey && index <= 0) {
|
|
7347
|
+
event.preventDefault();
|
|
7348
|
+
focusables[focusables.length - 1]?.focus();
|
|
7349
|
+
return true;
|
|
7350
|
+
}
|
|
7351
|
+
if (!event.shiftKey && index === focusables.length - 1) {
|
|
7352
|
+
event.preventDefault();
|
|
7353
|
+
focusables[0]?.focus();
|
|
7354
|
+
return true;
|
|
7355
|
+
}
|
|
7356
|
+
return false;
|
|
7357
|
+
}
|
|
7358
|
+
function showConfirmDialog(opts) {
|
|
7359
|
+
return new Promise((resolve) => {
|
|
7360
|
+
const previousFocus = opts.focusReturnTarget ?? document.activeElement;
|
|
7361
|
+
const cancel = document.createElement("button");
|
|
7362
|
+
cancel.type = "button";
|
|
7363
|
+
cancel.className = "gdp-btn gdp-btn-sm";
|
|
7364
|
+
cancel.textContent = opts.cancelLabel ?? "Cancel";
|
|
7365
|
+
const confirm = document.createElement("button");
|
|
7366
|
+
confirm.type = "button";
|
|
7367
|
+
confirm.className = "gdp-btn gdp-btn-sm";
|
|
7368
|
+
if (opts.danger)
|
|
7369
|
+
confirm.classList.add("gdp-dialog-danger");
|
|
7370
|
+
confirm.textContent = opts.confirmLabel ?? "OK";
|
|
7371
|
+
const done = (ok) => {
|
|
7372
|
+
document.removeEventListener("keydown", onKeydown);
|
|
7373
|
+
closeOpenDialog();
|
|
7374
|
+
previousFocus?.focus?.();
|
|
7375
|
+
resolve(ok);
|
|
7376
|
+
};
|
|
7377
|
+
const onKeydown = (event) => {
|
|
7378
|
+
if (isImeComposing(event))
|
|
7379
|
+
return;
|
|
7380
|
+
if (event.key === "Escape") {
|
|
7381
|
+
event.preventDefault();
|
|
7382
|
+
event.stopPropagation();
|
|
7383
|
+
done(false);
|
|
7384
|
+
return;
|
|
7385
|
+
}
|
|
7386
|
+
if (event.key === "Enter" && document.activeElement !== cancel) {
|
|
7387
|
+
event.preventDefault();
|
|
7388
|
+
done(true);
|
|
7389
|
+
return;
|
|
7390
|
+
}
|
|
7391
|
+
trapTabKey(event, [cancel, confirm]);
|
|
7392
|
+
};
|
|
7393
|
+
cancel.addEventListener("click", () => done(false));
|
|
7394
|
+
confirm.addEventListener("click", () => done(true));
|
|
7395
|
+
const { backdrop } = createDialogShell(opts.title, opts.body, [
|
|
7396
|
+
cancel,
|
|
7397
|
+
confirm
|
|
7398
|
+
]);
|
|
7399
|
+
backdrop.addEventListener("pointerdown", (event) => {
|
|
7400
|
+
if (event.target === backdrop)
|
|
7401
|
+
done(false);
|
|
7402
|
+
});
|
|
7403
|
+
document.addEventListener("keydown", onKeydown);
|
|
7404
|
+
confirm.focus();
|
|
7405
|
+
});
|
|
7406
|
+
}
|
|
7407
|
+
function showAlertDialog(opts) {
|
|
7408
|
+
return new Promise((resolve) => {
|
|
7409
|
+
const previousFocus = opts.focusReturnTarget ?? document.activeElement;
|
|
7410
|
+
const ok = document.createElement("button");
|
|
7411
|
+
ok.type = "button";
|
|
7412
|
+
ok.className = "gdp-btn gdp-btn-sm";
|
|
7413
|
+
if (opts.danger)
|
|
7414
|
+
ok.classList.add("gdp-dialog-danger");
|
|
7415
|
+
ok.textContent = opts.confirmLabel ?? "OK";
|
|
7416
|
+
const done = () => {
|
|
7417
|
+
document.removeEventListener("keydown", onKeydown);
|
|
7418
|
+
closeOpenDialog();
|
|
7419
|
+
previousFocus?.focus?.();
|
|
7420
|
+
resolve();
|
|
7421
|
+
};
|
|
7422
|
+
const onKeydown = (event) => {
|
|
7423
|
+
if (isImeComposing(event))
|
|
7424
|
+
return;
|
|
7425
|
+
if (event.key === "Escape" || event.key === "Enter") {
|
|
7426
|
+
event.preventDefault();
|
|
7427
|
+
event.stopPropagation();
|
|
7428
|
+
done();
|
|
7429
|
+
return;
|
|
7430
|
+
}
|
|
7431
|
+
trapTabKey(event, [ok]);
|
|
7432
|
+
};
|
|
7433
|
+
ok.addEventListener("click", done);
|
|
7434
|
+
const { backdrop } = createDialogShell(opts.title, opts.body, [ok]);
|
|
7435
|
+
backdrop.addEventListener("pointerdown", (event) => {
|
|
7436
|
+
if (event.target === backdrop)
|
|
7437
|
+
done();
|
|
7438
|
+
});
|
|
7439
|
+
document.addEventListener("keydown", onKeydown);
|
|
7440
|
+
ok.focus();
|
|
7441
|
+
});
|
|
7442
|
+
}
|
|
7443
|
+
function showPromptDialog(opts) {
|
|
7444
|
+
return new Promise((resolve) => {
|
|
7445
|
+
const previousFocus = opts.focusReturnTarget ?? document.activeElement;
|
|
7446
|
+
const cancel = document.createElement("button");
|
|
7447
|
+
cancel.type = "button";
|
|
7448
|
+
cancel.className = "gdp-btn gdp-btn-sm";
|
|
7449
|
+
cancel.textContent = opts.cancelLabel ?? "Cancel";
|
|
7450
|
+
const submit = document.createElement("button");
|
|
7451
|
+
submit.type = "button";
|
|
7452
|
+
submit.className = "gdp-btn gdp-btn-sm";
|
|
7453
|
+
submit.textContent = opts.confirmLabel ?? "OK";
|
|
7454
|
+
const input = document.createElement("input");
|
|
7455
|
+
input.className = "gdp-dialog-input";
|
|
7456
|
+
input.type = "text";
|
|
7457
|
+
input.autocomplete = "off";
|
|
7458
|
+
if (opts.placeholder)
|
|
7459
|
+
input.placeholder = opts.placeholder;
|
|
7460
|
+
if (opts.ariaLabel)
|
|
7461
|
+
input.setAttribute("aria-label", opts.ariaLabel);
|
|
7462
|
+
input.value = opts.defaultValue ?? "";
|
|
7463
|
+
const error2 = document.createElement("div");
|
|
7464
|
+
error2.className = "gdp-dialog-error";
|
|
7465
|
+
error2.setAttribute("role", "alert");
|
|
7466
|
+
const computeValid = () => {
|
|
7467
|
+
if (opts.validate)
|
|
7468
|
+
return opts.validate(input.value);
|
|
7469
|
+
return input.value;
|
|
7470
|
+
};
|
|
7471
|
+
const syncValidity = () => {
|
|
7472
|
+
const normalized = computeValid();
|
|
7473
|
+
const valid = normalized !== null && normalized !== "";
|
|
7474
|
+
submit.disabled = !valid;
|
|
7475
|
+
error2.textContent = !valid && input.value && opts.invalidMessage ? opts.invalidMessage : "";
|
|
7476
|
+
return valid;
|
|
7477
|
+
};
|
|
7478
|
+
const done = (value) => {
|
|
7479
|
+
document.removeEventListener("keydown", onKeydown);
|
|
7480
|
+
closeOpenDialog();
|
|
7481
|
+
previousFocus?.focus?.();
|
|
7482
|
+
resolve(value);
|
|
7483
|
+
};
|
|
7484
|
+
const trySubmit = () => {
|
|
7485
|
+
const normalized = computeValid();
|
|
7486
|
+
if (normalized === null || normalized === "") {
|
|
7487
|
+
syncValidity();
|
|
7488
|
+
input.focus();
|
|
7489
|
+
return;
|
|
7490
|
+
}
|
|
7491
|
+
done(normalized);
|
|
7492
|
+
};
|
|
7493
|
+
const onKeydown = (event) => {
|
|
7494
|
+
if (isImeComposing(event))
|
|
7495
|
+
return;
|
|
7496
|
+
if (event.key === "Escape") {
|
|
7497
|
+
event.preventDefault();
|
|
7498
|
+
event.stopPropagation();
|
|
7499
|
+
done(null);
|
|
7500
|
+
return;
|
|
7501
|
+
}
|
|
7502
|
+
if (event.key === "Enter") {
|
|
7503
|
+
event.preventDefault();
|
|
7504
|
+
trySubmit();
|
|
7505
|
+
return;
|
|
7506
|
+
}
|
|
7507
|
+
trapTabKey(event, [input, cancel, submit]);
|
|
7508
|
+
};
|
|
7509
|
+
cancel.addEventListener("click", () => done(null));
|
|
7510
|
+
submit.addEventListener("click", trySubmit);
|
|
7511
|
+
input.addEventListener("input", syncValidity);
|
|
7512
|
+
const { body } = createDialogShell(opts.title, opts.body, [cancel, submit]);
|
|
7513
|
+
body.append(input, error2);
|
|
7514
|
+
body.parentElement?.parentElement?.addEventListener("pointerdown", (event) => {
|
|
7515
|
+
const target = event.target;
|
|
7516
|
+
if (target?.classList.contains(BACKDROP_CLASS))
|
|
7517
|
+
done(null);
|
|
7518
|
+
});
|
|
7519
|
+
document.addEventListener("keydown", onKeydown);
|
|
7520
|
+
syncValidity();
|
|
7521
|
+
input.focus();
|
|
7522
|
+
if (input.value)
|
|
7523
|
+
input.select();
|
|
7524
|
+
});
|
|
7525
|
+
}
|
|
7526
|
+
|
|
7284
7527
|
// web-src/views/annotations-ui.ts
|
|
7285
7528
|
var ANNOTATION_SESSION_PARAM = "annotationSession";
|
|
7286
7529
|
function createAnnotationsUi(deps) {
|
|
@@ -7830,9 +8073,18 @@ ${frontmatter.yaml}
|
|
|
7830
8073
|
setActiveSession(session.id === activeSessionId ? null : session.id);
|
|
7831
8074
|
});
|
|
7832
8075
|
const rename = annotationIconButton("octicon-pencil", PENCIL_16_PATH, `rename session ${session.title}`);
|
|
7833
|
-
rename.addEventListener("click", () => {
|
|
7834
|
-
const next =
|
|
7835
|
-
|
|
8076
|
+
rename.addEventListener("click", async () => {
|
|
8077
|
+
const next = await showPromptDialog({
|
|
8078
|
+
title: "Rename session",
|
|
8079
|
+
defaultValue: session.title,
|
|
8080
|
+
ariaLabel: "Session name",
|
|
8081
|
+
confirmLabel: "Rename",
|
|
8082
|
+
validate: (v) => {
|
|
8083
|
+
const trimmed = v.trim();
|
|
8084
|
+
return trimmed ? trimmed : null;
|
|
8085
|
+
}
|
|
8086
|
+
});
|
|
8087
|
+
if (next === null)
|
|
7836
8088
|
return;
|
|
7837
8089
|
postAnnotationAction({
|
|
7838
8090
|
action: "rename",
|
|
@@ -7841,8 +8093,14 @@ ${frontmatter.yaml}
|
|
|
7841
8093
|
});
|
|
7842
8094
|
});
|
|
7843
8095
|
const del = annotationIconButton("octicon-trash", TRASH_16_PATH, `delete session ${session.title}`);
|
|
7844
|
-
del.addEventListener("click", () => {
|
|
7845
|
-
|
|
8096
|
+
del.addEventListener("click", async () => {
|
|
8097
|
+
const ok = await showConfirmDialog({
|
|
8098
|
+
title: "Delete session?",
|
|
8099
|
+
body: `Delete annotation session "${session.title}"?`,
|
|
8100
|
+
confirmLabel: "Delete",
|
|
8101
|
+
danger: true
|
|
8102
|
+
});
|
|
8103
|
+
if (!ok)
|
|
7846
8104
|
return;
|
|
7847
8105
|
postAnnotationAction({ action: "delete", id: session.id });
|
|
7848
8106
|
});
|
|
@@ -7868,8 +8126,14 @@ ${frontmatter.yaml}
|
|
|
7868
8126
|
openAnnotationEntry(entry.id);
|
|
7869
8127
|
});
|
|
7870
8128
|
const remove = annotationIconButton("octicon-trash", TRASH_16_PATH, `delete annotation for ${annotationLocationLabel(entry)}`);
|
|
7871
|
-
remove.addEventListener("click", () => {
|
|
7872
|
-
|
|
8129
|
+
remove.addEventListener("click", async () => {
|
|
8130
|
+
const ok = await showConfirmDialog({
|
|
8131
|
+
title: "Delete annotation?",
|
|
8132
|
+
body: `Delete annotation for ${annotationLocationLabel(entry)}?`,
|
|
8133
|
+
confirmLabel: "Delete",
|
|
8134
|
+
danger: true
|
|
8135
|
+
});
|
|
8136
|
+
if (!ok)
|
|
7873
8137
|
return;
|
|
7874
8138
|
postAnnotationAction({ action: "delete", id: entry.id });
|
|
7875
8139
|
});
|
|
@@ -8131,8 +8395,14 @@ ${frontmatter.yaml}
|
|
|
8131
8395
|
annotationFollow = followCheckbox.checked;
|
|
8132
8396
|
deps.setAnnotationFollow(annotationFollow);
|
|
8133
8397
|
});
|
|
8134
|
-
$("#annotation-clear").addEventListener("click", () => {
|
|
8135
|
-
|
|
8398
|
+
$("#annotation-clear").addEventListener("click", async () => {
|
|
8399
|
+
const ok = await showConfirmDialog({
|
|
8400
|
+
title: "Delete all annotations?",
|
|
8401
|
+
body: "Delete all annotations?",
|
|
8402
|
+
confirmLabel: "Delete all",
|
|
8403
|
+
danger: true
|
|
8404
|
+
});
|
|
8405
|
+
if (!ok)
|
|
8136
8406
|
return;
|
|
8137
8407
|
hideAnnotationDetail();
|
|
8138
8408
|
postAnnotationAction({ action: "clear" });
|
|
@@ -9171,13 +9441,17 @@ ${frontmatter.yaml}
|
|
|
9171
9441
|
searchTitle: "Search across tables",
|
|
9172
9442
|
snapshot: "Snapshot",
|
|
9173
9443
|
snapshotTitle: "Snapshot & Diff",
|
|
9174
|
-
queryHistory: "
|
|
9175
|
-
queryHistoryTitle: "
|
|
9444
|
+
queryHistory: "Close",
|
|
9445
|
+
queryHistoryTitle: "Close panel",
|
|
9176
9446
|
newTab: "New tab",
|
|
9177
9447
|
closeTab: (label) => `Close ${label}`,
|
|
9178
9448
|
loadingSchema: "Loading schema…",
|
|
9179
9449
|
noDatastores: "No datastores found",
|
|
9180
|
-
dockerLimitReached: "Docker discovery reached the service limit; some compose services may be hidden."
|
|
9450
|
+
dockerLimitReached: "Docker discovery reached the service limit; some compose services may be hidden.",
|
|
9451
|
+
inferFkLabel: "Rails FK inference",
|
|
9452
|
+
inferFkTitle: "Infer FK from Rails-style <name>_id → <names>.id",
|
|
9453
|
+
inferredBadge: "inferred",
|
|
9454
|
+
inferredBadgeTitle: "Inferred from Rails-style naming, not declared in DB"
|
|
9181
9455
|
},
|
|
9182
9456
|
grid: {
|
|
9183
9457
|
searchPlaceholder: "Search all columns…",
|
|
@@ -9189,6 +9463,22 @@ ${frontmatter.yaml}
|
|
|
9189
9463
|
statusSort: (column, dir) => `Sort: ${column} ${dir}`,
|
|
9190
9464
|
statusFilters: (n2) => `${n2} filter(s)`
|
|
9191
9465
|
},
|
|
9466
|
+
edit: {
|
|
9467
|
+
editMode: "Edit",
|
|
9468
|
+
editModeTitle: "Toggle edit mode",
|
|
9469
|
+
newRow: "New row",
|
|
9470
|
+
commit: "Commit",
|
|
9471
|
+
discard: "Discard",
|
|
9472
|
+
deleteRow: "Mark row for deletion",
|
|
9473
|
+
undoDelete: "Undo deletion",
|
|
9474
|
+
setNull: "Set NULL",
|
|
9475
|
+
dblclickToEdit: "Double-click to edit",
|
|
9476
|
+
pending: (n2) => `${n2} change(s)`,
|
|
9477
|
+
committing: "Saving…",
|
|
9478
|
+
commitError: (message) => `Save failed: ${message}`,
|
|
9479
|
+
confirmDiscard: "Discard all pending changes?",
|
|
9480
|
+
noPrimaryKey: "This table has no primary key. Existing rows cannot be edited or deleted (you can still add new rows)."
|
|
9481
|
+
},
|
|
9192
9482
|
schema: {
|
|
9193
9483
|
columns: "Columns",
|
|
9194
9484
|
foreignKeys: "Foreign Keys",
|
|
@@ -9246,6 +9536,24 @@ ${frontmatter.yaml}
|
|
|
9246
9536
|
confirmClear: "Confirm clear",
|
|
9247
9537
|
truncatedRows: (saved, total) => `Showing ${saved} of ${total} rows`
|
|
9248
9538
|
},
|
|
9539
|
+
sessionLog: {
|
|
9540
|
+
tabLabel: "Log",
|
|
9541
|
+
historyTabLabel: "Query history",
|
|
9542
|
+
clear: "Clear",
|
|
9543
|
+
clearTitle: "Clear the session log",
|
|
9544
|
+
empty: "No entries yet. SQL executions and commits will appear here.",
|
|
9545
|
+
selectPlaceholder: "Select an entry to view details.",
|
|
9546
|
+
statusOk: "ok",
|
|
9547
|
+
statusError: "error",
|
|
9548
|
+
kindQuery: "query",
|
|
9549
|
+
kindMutate: "commit",
|
|
9550
|
+
useInEditor: "Open in editor",
|
|
9551
|
+
copy: "Copy",
|
|
9552
|
+
copied: "Copied",
|
|
9553
|
+
autoFollow: "Auto-follow",
|
|
9554
|
+
autoFollowTitle: "Automatically scroll to the newest entry. Scroll up to pause.",
|
|
9555
|
+
commitLabel: (changes) => `Commit (${changes} ${changes === 1 ? "change" : "changes"})`
|
|
9556
|
+
},
|
|
9249
9557
|
er: {
|
|
9250
9558
|
zoomIn: "Zoom in",
|
|
9251
9559
|
zoomOut: "Zoom out",
|
|
@@ -9310,7 +9618,16 @@ ${frontmatter.yaml}
|
|
|
9310
9618
|
label: (date, tables, note) => `${date} (${tables} tables)${note}`
|
|
9311
9619
|
},
|
|
9312
9620
|
explorer: {
|
|
9313
|
-
common: {
|
|
9621
|
+
common: {
|
|
9622
|
+
loadMore: "Load more",
|
|
9623
|
+
search: "Search",
|
|
9624
|
+
edit: "Edit",
|
|
9625
|
+
save: "Save",
|
|
9626
|
+
cancel: "Cancel",
|
|
9627
|
+
delete: "Delete",
|
|
9628
|
+
saving: "Saving…",
|
|
9629
|
+
saveError: (message) => `Save failed: ${message}`
|
|
9630
|
+
},
|
|
9314
9631
|
redis: {
|
|
9315
9632
|
databases: "Databases",
|
|
9316
9633
|
keys: "Keys",
|
|
@@ -9322,7 +9639,12 @@ ${frontmatter.yaml}
|
|
|
9322
9639
|
emptyHash: "(empty hash)",
|
|
9323
9640
|
emptyList: "(empty list)",
|
|
9324
9641
|
noKeys: "(no keys)",
|
|
9325
|
-
keyCount: (n2) => `${n2} keys
|
|
9642
|
+
keyCount: (n2) => `${n2} keys`,
|
|
9643
|
+
newKey: "New key",
|
|
9644
|
+
newKeyNamePlaceholder: "key name",
|
|
9645
|
+
newKeyValuePlaceholder: "value",
|
|
9646
|
+
confirmDeleteKey: (key) => `Delete key "${key}"?`,
|
|
9647
|
+
create: "Create"
|
|
9326
9648
|
},
|
|
9327
9649
|
es: {
|
|
9328
9650
|
indices: "Indices",
|
|
@@ -9338,7 +9660,15 @@ ${frontmatter.yaml}
|
|
|
9338
9660
|
noMappedFields: "(no mapped fields)",
|
|
9339
9661
|
noDocs: "(no docs)",
|
|
9340
9662
|
docNotFound: "(doc not found)",
|
|
9341
|
-
loadingIndices: "Loading indices..."
|
|
9663
|
+
loadingIndices: "Loading indices...",
|
|
9664
|
+
newDoc: "New document",
|
|
9665
|
+
newDocIdPlaceholder: "document id (optional — auto-generated if blank)",
|
|
9666
|
+
sourceJsonPlaceholder: `{
|
|
9667
|
+
"field": "value"
|
|
9668
|
+
}`,
|
|
9669
|
+
confirmDeleteDoc: (id) => `Delete document "${id}"?`,
|
|
9670
|
+
invalidJson: "Invalid JSON",
|
|
9671
|
+
create: "Create"
|
|
9342
9672
|
},
|
|
9343
9673
|
s3: {
|
|
9344
9674
|
bucket: "Bucket",
|
|
@@ -9358,7 +9688,13 @@ ${frontmatter.yaml}
|
|
|
9358
9688
|
copied: "Copied",
|
|
9359
9689
|
copyFailed: "Copy failed",
|
|
9360
9690
|
unsupported: "This object type cannot be previewed safely in the browser.",
|
|
9361
|
-
truncatedNotice: (size) => `Showing first ${size}
|
|
9691
|
+
truncatedNotice: (size) => `Showing first ${size}.`,
|
|
9692
|
+
newObject: "New object",
|
|
9693
|
+
newObjectKeyPlaceholder: "object key, e.g. folder/file.txt",
|
|
9694
|
+
contentPlaceholder: "object content (text)",
|
|
9695
|
+
confirmDeleteObject: (key) => `Delete object "${key}"?`,
|
|
9696
|
+
editTextHint: "Only text objects can be edited in the browser.",
|
|
9697
|
+
create: "Create"
|
|
9362
9698
|
}
|
|
9363
9699
|
}
|
|
9364
9700
|
};
|
|
@@ -9377,13 +9713,17 @@ ${frontmatter.yaml}
|
|
|
9377
9713
|
searchTitle: "テーブル横断検索",
|
|
9378
9714
|
snapshot: "スナップショット",
|
|
9379
9715
|
snapshotTitle: "スナップショットと差分",
|
|
9380
|
-
queryHistory: "
|
|
9381
|
-
queryHistoryTitle: "
|
|
9716
|
+
queryHistory: "閉じる",
|
|
9717
|
+
queryHistoryTitle: "パネルを閉じる",
|
|
9382
9718
|
newTab: "新しいタブ",
|
|
9383
9719
|
closeTab: (label) => `${label} を閉じる`,
|
|
9384
9720
|
loadingSchema: "スキーマを読み込み中…",
|
|
9385
9721
|
noDatastores: "データストアが見つかりません",
|
|
9386
|
-
dockerLimitReached: "Docker のサービス数が上限に達しました。一部の compose サービスは表示されていない可能性があります。"
|
|
9722
|
+
dockerLimitReached: "Docker のサービス数が上限に達しました。一部の compose サービスは表示されていない可能性があります。",
|
|
9723
|
+
inferFkLabel: "Rails FK 推測",
|
|
9724
|
+
inferFkTitle: "Rails 命名規約 (<name>_id → <names>.id) から FK を推測",
|
|
9725
|
+
inferredBadge: "推測",
|
|
9726
|
+
inferredBadgeTitle: "Rails 命名規約から推測した FK (DB の宣言ではありません)"
|
|
9387
9727
|
},
|
|
9388
9728
|
grid: {
|
|
9389
9729
|
searchPlaceholder: "全カラムを検索…",
|
|
@@ -9395,6 +9735,22 @@ ${frontmatter.yaml}
|
|
|
9395
9735
|
statusSort: (column, dir) => `並び替え: ${column} ${dir}`,
|
|
9396
9736
|
statusFilters: (n2) => `フィルタ ${n2} 件`
|
|
9397
9737
|
},
|
|
9738
|
+
edit: {
|
|
9739
|
+
editMode: "編集",
|
|
9740
|
+
editModeTitle: "編集モードの切り替え",
|
|
9741
|
+
newRow: "新規行",
|
|
9742
|
+
commit: "コミット",
|
|
9743
|
+
discard: "破棄",
|
|
9744
|
+
deleteRow: "行を削除対象にする",
|
|
9745
|
+
undoDelete: "削除を取り消す",
|
|
9746
|
+
setNull: "NULL にする",
|
|
9747
|
+
dblclickToEdit: "ダブルクリックで編集",
|
|
9748
|
+
pending: (n2) => `変更 ${n2} 件`,
|
|
9749
|
+
committing: "保存中…",
|
|
9750
|
+
commitError: (message) => `保存に失敗しました: ${message}`,
|
|
9751
|
+
confirmDiscard: "保留中の変更をすべて破棄しますか?",
|
|
9752
|
+
noPrimaryKey: "このテーブルには主キーがありません。既存行の編集・削除はできません(新規行の追加は可能です)。"
|
|
9753
|
+
},
|
|
9398
9754
|
schema: {
|
|
9399
9755
|
columns: "カラム",
|
|
9400
9756
|
foreignKeys: "外部キー",
|
|
@@ -9452,6 +9808,24 @@ ${frontmatter.yaml}
|
|
|
9452
9808
|
confirmClear: "全削除を確認",
|
|
9453
9809
|
truncatedRows: (saved, total) => `全 ${total} 行中 ${saved} 行を表示`
|
|
9454
9810
|
},
|
|
9811
|
+
sessionLog: {
|
|
9812
|
+
tabLabel: "ログ",
|
|
9813
|
+
historyTabLabel: "クエリ履歴",
|
|
9814
|
+
clear: "クリア",
|
|
9815
|
+
clearTitle: "セッションログを消去",
|
|
9816
|
+
empty: "まだログはありません。SQL の実行や編集コミットがここに出ます。",
|
|
9817
|
+
selectPlaceholder: "エントリを選ぶと詳細を表示します。",
|
|
9818
|
+
statusOk: "成功",
|
|
9819
|
+
statusError: "エラー",
|
|
9820
|
+
kindQuery: "クエリ",
|
|
9821
|
+
kindMutate: "コミット",
|
|
9822
|
+
useInEditor: "エディタに開く",
|
|
9823
|
+
copy: "コピー",
|
|
9824
|
+
copied: "コピーしました",
|
|
9825
|
+
autoFollow: "自動追従",
|
|
9826
|
+
autoFollowTitle: "新しいエントリを常に表示。下に手動スクロールすると自動解除されます。",
|
|
9827
|
+
commitLabel: (changes) => `コミット (${changes} 件)`
|
|
9828
|
+
},
|
|
9455
9829
|
er: {
|
|
9456
9830
|
zoomIn: "拡大",
|
|
9457
9831
|
zoomOut: "縮小",
|
|
@@ -9516,7 +9890,16 @@ ${frontmatter.yaml}
|
|
|
9516
9890
|
label: (date, tables, note) => `${date} (${tables}テーブル)${note}`
|
|
9517
9891
|
},
|
|
9518
9892
|
explorer: {
|
|
9519
|
-
common: {
|
|
9893
|
+
common: {
|
|
9894
|
+
loadMore: "さらに読み込む",
|
|
9895
|
+
search: "検索",
|
|
9896
|
+
edit: "編集",
|
|
9897
|
+
save: "保存",
|
|
9898
|
+
cancel: "キャンセル",
|
|
9899
|
+
delete: "削除",
|
|
9900
|
+
saving: "保存中…",
|
|
9901
|
+
saveError: (message) => `保存に失敗しました: ${message}`
|
|
9902
|
+
},
|
|
9520
9903
|
redis: {
|
|
9521
9904
|
databases: "データベース",
|
|
9522
9905
|
keys: "キー",
|
|
@@ -9528,7 +9911,12 @@ ${frontmatter.yaml}
|
|
|
9528
9911
|
emptyHash: "(空のハッシュ)",
|
|
9529
9912
|
emptyList: "(空のリスト)",
|
|
9530
9913
|
noKeys: "(キーがありません)",
|
|
9531
|
-
keyCount: (n2) => `${n2}
|
|
9914
|
+
keyCount: (n2) => `${n2} キー`,
|
|
9915
|
+
newKey: "新規キー",
|
|
9916
|
+
newKeyNamePlaceholder: "キー名",
|
|
9917
|
+
newKeyValuePlaceholder: "値",
|
|
9918
|
+
confirmDeleteKey: (key) => `キー "${key}" を削除しますか?`,
|
|
9919
|
+
create: "作成"
|
|
9532
9920
|
},
|
|
9533
9921
|
es: {
|
|
9534
9922
|
indices: "インデックス",
|
|
@@ -9544,7 +9932,15 @@ ${frontmatter.yaml}
|
|
|
9544
9932
|
noMappedFields: "(マッピング済みフィールドがありません)",
|
|
9545
9933
|
noDocs: "(ドキュメントがありません)",
|
|
9546
9934
|
docNotFound: "(ドキュメントが見つかりません)",
|
|
9547
|
-
loadingIndices: "インデックスを読み込み中..."
|
|
9935
|
+
loadingIndices: "インデックスを読み込み中...",
|
|
9936
|
+
newDoc: "新規ドキュメント",
|
|
9937
|
+
newDocIdPlaceholder: "ドキュメント ID (省略時は自動採番)",
|
|
9938
|
+
sourceJsonPlaceholder: `{
|
|
9939
|
+
"field": "value"
|
|
9940
|
+
}`,
|
|
9941
|
+
confirmDeleteDoc: (id) => `ドキュメント "${id}" を削除しますか?`,
|
|
9942
|
+
invalidJson: "JSON が不正です",
|
|
9943
|
+
create: "作成"
|
|
9548
9944
|
},
|
|
9549
9945
|
s3: {
|
|
9550
9946
|
bucket: "バケット",
|
|
@@ -9564,7 +9960,13 @@ ${frontmatter.yaml}
|
|
|
9564
9960
|
copied: "コピーしました",
|
|
9565
9961
|
copyFailed: "コピーに失敗しました",
|
|
9566
9962
|
unsupported: "この種類のオブジェクトはブラウザで安全にプレビューできません。",
|
|
9567
|
-
truncatedNotice: (size) => `先頭 ${size}
|
|
9963
|
+
truncatedNotice: (size) => `先頭 ${size} を表示中。`,
|
|
9964
|
+
newObject: "新規オブジェクト",
|
|
9965
|
+
newObjectKeyPlaceholder: "オブジェクトキー 例: folder/file.txt",
|
|
9966
|
+
contentPlaceholder: "オブジェクトの内容 (テキスト)",
|
|
9967
|
+
confirmDeleteObject: (key) => `オブジェクト "${key}" を削除しますか?`,
|
|
9968
|
+
editTextHint: "ブラウザで編集できるのはテキストオブジェクトのみです。",
|
|
9969
|
+
create: "作成"
|
|
9568
9970
|
}
|
|
9569
9971
|
}
|
|
9570
9972
|
};
|
|
@@ -9619,8 +10021,16 @@ ${frontmatter.yaml}
|
|
|
9619
10021
|
const docListPane = document.createElement("div");
|
|
9620
10022
|
docListPane.className = "es-doc-list-pane";
|
|
9621
10023
|
const docListHeader = document.createElement("div");
|
|
9622
|
-
docListHeader.className = "db-explorer-pane-header";
|
|
9623
|
-
|
|
10024
|
+
docListHeader.className = "db-explorer-pane-header es-doc-list-header";
|
|
10025
|
+
const docListTitle = document.createElement("span");
|
|
10026
|
+
docListTitle.textContent = text2().es.docs;
|
|
10027
|
+
const newDocBtn = document.createElement("button");
|
|
10028
|
+
newDocBtn.type = "button";
|
|
10029
|
+
newDocBtn.className = "db-btn db-btn-sm es-new-doc-btn";
|
|
10030
|
+
newDocBtn.textContent = `+ ${text2().es.newDoc}`;
|
|
10031
|
+
newDocBtn.title = text2().es.newDoc;
|
|
10032
|
+
newDocBtn.hidden = true;
|
|
10033
|
+
docListHeader.append(docListTitle, newDocBtn);
|
|
9624
10034
|
docListPane.appendChild(docListHeader);
|
|
9625
10035
|
const searchBar = document.createElement("div");
|
|
9626
10036
|
searchBar.className = "es-search-bar";
|
|
@@ -9839,12 +10249,176 @@ ${frontmatter.yaml}
|
|
|
9839
10249
|
table2.appendChild(tbody);
|
|
9840
10250
|
mappingBody.appendChild(table2);
|
|
9841
10251
|
}
|
|
10252
|
+
function mkBtn(label, cls) {
|
|
10253
|
+
const b2 = document.createElement("button");
|
|
10254
|
+
b2.type = "button";
|
|
10255
|
+
b2.className = cls;
|
|
10256
|
+
b2.textContent = label;
|
|
10257
|
+
return b2;
|
|
10258
|
+
}
|
|
10259
|
+
async function postEsWrite(body) {
|
|
10260
|
+
const doFetch = fetch("/_db/elasticsearch/write", {
|
|
10261
|
+
method: "POST",
|
|
10262
|
+
headers: {
|
|
10263
|
+
"Content-Type": "application/json",
|
|
10264
|
+
"X-Code-Viewer-Action": "1"
|
|
10265
|
+
},
|
|
10266
|
+
body: JSON.stringify(body)
|
|
10267
|
+
});
|
|
10268
|
+
const res = await (callbacks.trackLoad ? callbacks.trackLoad(doFetch) : doFetch);
|
|
10269
|
+
if (!res.ok)
|
|
10270
|
+
throw new Error(await res.text() || res.statusText);
|
|
10271
|
+
}
|
|
10272
|
+
function startDocEdit(resp) {
|
|
10273
|
+
docBody.innerHTML = "";
|
|
10274
|
+
const bar = document.createElement("div");
|
|
10275
|
+
bar.className = "es-doc-edit-bar";
|
|
10276
|
+
const save = mkBtn(text2().common.save, "db-btn db-btn-primary db-btn-sm");
|
|
10277
|
+
const cancel = mkBtn(text2().common.cancel, "db-btn db-btn-sm");
|
|
10278
|
+
const status = document.createElement("span");
|
|
10279
|
+
status.className = "es-doc-edit-status";
|
|
10280
|
+
bar.append(save, cancel, status);
|
|
10281
|
+
const ta = document.createElement("textarea");
|
|
10282
|
+
ta.className = "es-doc-edit-textarea";
|
|
10283
|
+
ta.value = JSON.stringify(resp.source, null, 2);
|
|
10284
|
+
docBody.append(bar, ta);
|
|
10285
|
+
cancel.addEventListener("click", () => renderDoc(resp));
|
|
10286
|
+
save.addEventListener("click", async () => {
|
|
10287
|
+
let parsed;
|
|
10288
|
+
try {
|
|
10289
|
+
parsed = JSON.parse(ta.value);
|
|
10290
|
+
} catch {
|
|
10291
|
+
status.textContent = text2().es.invalidJson;
|
|
10292
|
+
return;
|
|
10293
|
+
}
|
|
10294
|
+
if (!currentDbId || !currentIndex)
|
|
10295
|
+
return;
|
|
10296
|
+
save.disabled = true;
|
|
10297
|
+
cancel.disabled = true;
|
|
10298
|
+
status.textContent = text2().common.saving;
|
|
10299
|
+
try {
|
|
10300
|
+
await postEsWrite({
|
|
10301
|
+
db: currentDbId,
|
|
10302
|
+
index: currentIndex,
|
|
10303
|
+
id: resp.id,
|
|
10304
|
+
source: parsed,
|
|
10305
|
+
seqNo: resp.seqNo,
|
|
10306
|
+
primaryTerm: resp.primaryTerm
|
|
10307
|
+
});
|
|
10308
|
+
await selectDoc(resp.id);
|
|
10309
|
+
} catch (err) {
|
|
10310
|
+
save.disabled = false;
|
|
10311
|
+
cancel.disabled = false;
|
|
10312
|
+
status.textContent = text2().common.saveError(err instanceof Error ? err.message : String(err));
|
|
10313
|
+
}
|
|
10314
|
+
});
|
|
10315
|
+
ta.focus();
|
|
10316
|
+
}
|
|
10317
|
+
async function deleteDoc(resp) {
|
|
10318
|
+
if (!currentDbId || !currentIndex)
|
|
10319
|
+
return;
|
|
10320
|
+
const ok = await showConfirmDialog({
|
|
10321
|
+
body: text2().es.confirmDeleteDoc(resp.id),
|
|
10322
|
+
confirmLabel: text2().common.delete,
|
|
10323
|
+
danger: true
|
|
10324
|
+
});
|
|
10325
|
+
if (!ok)
|
|
10326
|
+
return;
|
|
10327
|
+
try {
|
|
10328
|
+
await postEsWrite({
|
|
10329
|
+
db: currentDbId,
|
|
10330
|
+
index: currentIndex,
|
|
10331
|
+
id: resp.id,
|
|
10332
|
+
op: "delete"
|
|
10333
|
+
});
|
|
10334
|
+
setPaneEmpty(docBody, text2().es.selectDoc);
|
|
10335
|
+
lastDoc = null;
|
|
10336
|
+
loadDocs(false);
|
|
10337
|
+
} catch (err) {
|
|
10338
|
+
setPaneStatus(docBody, text2().common.saveError(err instanceof Error ? err.message : String(err)), { error: true });
|
|
10339
|
+
}
|
|
10340
|
+
}
|
|
10341
|
+
function showNewDocForm() {
|
|
10342
|
+
if (!currentIndex) {
|
|
10343
|
+
setPaneStatus(docBody, text2().es.selectIndex);
|
|
10344
|
+
return;
|
|
10345
|
+
}
|
|
10346
|
+
setDetailTab("doc");
|
|
10347
|
+
docBody.innerHTML = "";
|
|
10348
|
+
const form = document.createElement("form");
|
|
10349
|
+
form.className = "es-new-doc-form";
|
|
10350
|
+
const idInput = document.createElement("input");
|
|
10351
|
+
idInput.type = "text";
|
|
10352
|
+
idInput.className = "es-new-doc-id";
|
|
10353
|
+
idInput.placeholder = text2().es.newDocIdPlaceholder;
|
|
10354
|
+
idInput.autocomplete = "off";
|
|
10355
|
+
const ta = document.createElement("textarea");
|
|
10356
|
+
ta.className = "es-doc-edit-textarea";
|
|
10357
|
+
ta.placeholder = text2().es.sourceJsonPlaceholder;
|
|
10358
|
+
const bar = document.createElement("div");
|
|
10359
|
+
bar.className = "es-doc-edit-bar";
|
|
10360
|
+
const create = mkBtn(text2().es.create, "db-btn db-btn-primary db-btn-sm");
|
|
10361
|
+
create.type = "submit";
|
|
10362
|
+
const cancel = mkBtn(text2().common.cancel, "db-btn db-btn-sm");
|
|
10363
|
+
const status = document.createElement("span");
|
|
10364
|
+
status.className = "es-doc-edit-status";
|
|
10365
|
+
bar.append(create, cancel, status);
|
|
10366
|
+
form.append(bar, idInput, ta);
|
|
10367
|
+
docBody.append(form);
|
|
10368
|
+
idInput.focus();
|
|
10369
|
+
cancel.addEventListener("click", () => setPaneEmpty(docBody, text2().es.selectDoc));
|
|
10370
|
+
form.addEventListener("submit", async (e2) => {
|
|
10371
|
+
e2.preventDefault();
|
|
10372
|
+
if (!currentDbId || !currentIndex)
|
|
10373
|
+
return;
|
|
10374
|
+
let parsed;
|
|
10375
|
+
try {
|
|
10376
|
+
parsed = JSON.parse(ta.value);
|
|
10377
|
+
} catch {
|
|
10378
|
+
status.textContent = text2().es.invalidJson;
|
|
10379
|
+
return;
|
|
10380
|
+
}
|
|
10381
|
+
const id = idInput.value.trim();
|
|
10382
|
+
create.disabled = true;
|
|
10383
|
+
status.textContent = text2().common.saving;
|
|
10384
|
+
try {
|
|
10385
|
+
await postEsWrite({
|
|
10386
|
+
db: currentDbId,
|
|
10387
|
+
index: currentIndex,
|
|
10388
|
+
...id ? { id } : {},
|
|
10389
|
+
source: parsed,
|
|
10390
|
+
op: "create"
|
|
10391
|
+
});
|
|
10392
|
+
loadDocs(false);
|
|
10393
|
+
if (id)
|
|
10394
|
+
await selectDoc(id);
|
|
10395
|
+
else
|
|
10396
|
+
setPaneEmpty(docBody, text2().es.selectDoc);
|
|
10397
|
+
} catch (err) {
|
|
10398
|
+
create.disabled = false;
|
|
10399
|
+
status.textContent = text2().common.saveError(err instanceof Error ? err.message : String(err));
|
|
10400
|
+
}
|
|
10401
|
+
});
|
|
10402
|
+
}
|
|
10403
|
+
newDocBtn.addEventListener("click", () => showNewDocForm());
|
|
9842
10404
|
function renderDoc(resp) {
|
|
9843
10405
|
lastDoc = resp;
|
|
9844
10406
|
docBody.innerHTML = "";
|
|
9845
10407
|
const header = document.createElement("div");
|
|
9846
10408
|
header.className = "es-doc-detail-header";
|
|
9847
|
-
|
|
10409
|
+
const title = document.createElement("span");
|
|
10410
|
+
title.textContent = `${resp.index} / ${resp.id}`;
|
|
10411
|
+
header.appendChild(title);
|
|
10412
|
+
if (resp.found) {
|
|
10413
|
+
const actions = document.createElement("div");
|
|
10414
|
+
actions.className = "es-doc-actions";
|
|
10415
|
+
const editBtn = mkBtn(text2().common.edit, "db-btn db-btn-sm");
|
|
10416
|
+
editBtn.addEventListener("click", () => startDocEdit(resp));
|
|
10417
|
+
const delBtn = mkBtn(text2().common.delete, "db-btn db-btn-sm");
|
|
10418
|
+
delBtn.addEventListener("click", () => void deleteDoc(resp));
|
|
10419
|
+
actions.append(editBtn, delBtn);
|
|
10420
|
+
header.appendChild(actions);
|
|
10421
|
+
}
|
|
9848
10422
|
docBody.appendChild(header);
|
|
9849
10423
|
if (!resp.found) {
|
|
9850
10424
|
setPaneStatus(docBody, text2().es.docNotFound, {
|
|
@@ -9975,6 +10549,7 @@ ${frontmatter.yaml}
|
|
|
9975
10549
|
docBody.innerHTML = "";
|
|
9976
10550
|
setPaneEmpty(docBody, text2().es.selectDoc);
|
|
9977
10551
|
setDetailTab("mapping");
|
|
10552
|
+
newDocBtn.hidden = false;
|
|
9978
10553
|
await Promise.all([fetchMapping(name), loadDocs(false)]);
|
|
9979
10554
|
}
|
|
9980
10555
|
async function selectDoc(id) {
|
|
@@ -10171,7 +10746,9 @@ ${frontmatter.yaml}
|
|
|
10171
10746
|
function localize() {
|
|
10172
10747
|
const t2 = text2();
|
|
10173
10748
|
indexListHeader.textContent = t2.es.indices;
|
|
10174
|
-
|
|
10749
|
+
docListTitle.textContent = t2.es.docs;
|
|
10750
|
+
newDocBtn.textContent = `+ ${t2.es.newDoc}`;
|
|
10751
|
+
newDocBtn.title = t2.es.newDoc;
|
|
10175
10752
|
searchInput.placeholder = t2.es.queryPlaceholder;
|
|
10176
10753
|
searchBtn.textContent = t2.common.search;
|
|
10177
10754
|
docMoreBtn.textContent = t2.common.loadMore;
|
|
@@ -10200,27 +10777,6 @@ ${frontmatter.yaml}
|
|
|
10200
10777
|
}
|
|
10201
10778
|
|
|
10202
10779
|
// web-src/views/database/er-diagram.ts
|
|
10203
|
-
var mermaidPromise2 = null;
|
|
10204
|
-
var mermaidInitialized2 = false;
|
|
10205
|
-
async function loadMermaid2() {
|
|
10206
|
-
if (!mermaidPromise2) {
|
|
10207
|
-
mermaidPromise2 = import("/mermaid.js").then((mod) => {
|
|
10208
|
-
const typed = mod;
|
|
10209
|
-
const mermaid = typed.default;
|
|
10210
|
-
if (!mermaidInitialized2) {
|
|
10211
|
-
mermaid.initialize({
|
|
10212
|
-
startOnLoad: false,
|
|
10213
|
-
securityLevel: "strict",
|
|
10214
|
-
theme: "default",
|
|
10215
|
-
er: { useMaxWidth: false }
|
|
10216
|
-
});
|
|
10217
|
-
mermaidInitialized2 = true;
|
|
10218
|
-
}
|
|
10219
|
-
return mermaid;
|
|
10220
|
-
}).catch(() => null);
|
|
10221
|
-
}
|
|
10222
|
-
return mermaidPromise2;
|
|
10223
|
-
}
|
|
10224
10780
|
function mermaidType(sqlType) {
|
|
10225
10781
|
const upper = sqlType.toUpperCase();
|
|
10226
10782
|
if (upper.includes("INT"))
|
|
@@ -10391,7 +10947,7 @@ ${frontmatter.yaml}
|
|
|
10391
10947
|
}
|
|
10392
10948
|
const markup = buildErMarkup(schema, columnsMap);
|
|
10393
10949
|
lastMarkup = markup;
|
|
10394
|
-
const mermaid = await
|
|
10950
|
+
const mermaid = await loadMermaid();
|
|
10395
10951
|
if (!mermaid) {
|
|
10396
10952
|
svgWrap.textContent = text2().er.loadError;
|
|
10397
10953
|
return;
|
|
@@ -10661,6 +11217,12 @@ ${frontmatter.yaml}
|
|
|
10661
11217
|
}
|
|
10662
11218
|
|
|
10663
11219
|
// web-src/views/database/pref-toggle.ts
|
|
11220
|
+
function localizePrefToggle(btn, label, title) {
|
|
11221
|
+
btn.title = title;
|
|
11222
|
+
const labelEl = btn.querySelector(".db-pref-toggle-label");
|
|
11223
|
+
if (labelEl)
|
|
11224
|
+
labelEl.textContent = label;
|
|
11225
|
+
}
|
|
10664
11226
|
function makePrefToggle(opts) {
|
|
10665
11227
|
const btn = document.createElement("button");
|
|
10666
11228
|
btn.type = "button";
|
|
@@ -10683,20 +11245,22 @@ ${frontmatter.yaml}
|
|
|
10683
11245
|
return btn;
|
|
10684
11246
|
}
|
|
10685
11247
|
|
|
10686
|
-
// web-src/views/database/
|
|
10687
|
-
|
|
10688
|
-
|
|
10689
|
-
|
|
10690
|
-
|
|
10691
|
-
|
|
10692
|
-
|
|
10693
|
-
|
|
10694
|
-
|
|
10695
|
-
|
|
10696
|
-
|
|
10697
|
-
|
|
10698
|
-
return
|
|
11248
|
+
// web-src/views/database/shiki-sql.ts
|
|
11249
|
+
function highlightSqlToInnerHtml(code2, highlighter) {
|
|
11250
|
+
if (!highlighter || !code2)
|
|
11251
|
+
return "";
|
|
11252
|
+
const html = highlighter.codeToHtml(code2, {
|
|
11253
|
+
lang: "sql",
|
|
11254
|
+
themes: { light: "github-light", dark: "github-dark" },
|
|
11255
|
+
defaultColor: false
|
|
11256
|
+
});
|
|
11257
|
+
const template = document.createElement("template");
|
|
11258
|
+
template.innerHTML = html;
|
|
11259
|
+
const pre = template.content.querySelector("pre");
|
|
11260
|
+
return pre ? pre.innerHTML : "";
|
|
10699
11261
|
}
|
|
11262
|
+
|
|
11263
|
+
// web-src/views/database/query-editor.ts
|
|
10700
11264
|
var MAX_HISTORY = 50;
|
|
10701
11265
|
function createQueryEditor(callbacks) {
|
|
10702
11266
|
const text2 = () => callbacks.getText?.() ?? dbText("en");
|
|
@@ -10716,7 +11280,10 @@ ${frontmatter.yaml}
|
|
|
10716
11280
|
textarea.rows = 3;
|
|
10717
11281
|
editorWrap.append(highlight, textarea);
|
|
10718
11282
|
let shiki = null;
|
|
10719
|
-
|
|
11283
|
+
loadShikiHighlighter({
|
|
11284
|
+
themes: ["github-light", "github-dark"],
|
|
11285
|
+
langs: ["sql"]
|
|
11286
|
+
}).then((h) => {
|
|
10720
11287
|
shiki = h;
|
|
10721
11288
|
syncHighlight();
|
|
10722
11289
|
});
|
|
@@ -10733,21 +11300,9 @@ ${frontmatter.yaml}
|
|
|
10733
11300
|
syncEditorHeight();
|
|
10734
11301
|
return;
|
|
10735
11302
|
}
|
|
10736
|
-
|
|
10737
|
-
|
|
10738
|
-
|
|
10739
|
-
return;
|
|
10740
|
-
}
|
|
10741
|
-
const html = shiki.codeToHtml(code2, {
|
|
10742
|
-
lang: "sql",
|
|
10743
|
-
themes: { light: "github-light", dark: "github-dark" },
|
|
10744
|
-
defaultColor: false
|
|
10745
|
-
});
|
|
10746
|
-
const template = document.createElement("template");
|
|
10747
|
-
template.innerHTML = html;
|
|
10748
|
-
const pre = template.content.querySelector("pre");
|
|
10749
|
-
if (pre) {
|
|
10750
|
-
highlight.innerHTML = pre.innerHTML;
|
|
11303
|
+
const inner = highlightSqlToInnerHtml(code2, shiki);
|
|
11304
|
+
if (inner) {
|
|
11305
|
+
highlight.innerHTML = inner;
|
|
10751
11306
|
} else {
|
|
10752
11307
|
highlight.textContent = code2;
|
|
10753
11308
|
}
|
|
@@ -11462,8 +12017,15 @@ ${frontmatter.yaml}
|
|
|
11462
12017
|
const keyListPane = document.createElement("div");
|
|
11463
12018
|
keyListPane.className = "redis-key-list-pane";
|
|
11464
12019
|
const keyListHeader = document.createElement("div");
|
|
11465
|
-
keyListHeader.className = "db-explorer-pane-header";
|
|
11466
|
-
|
|
12020
|
+
keyListHeader.className = "db-explorer-pane-header redis-key-list-header";
|
|
12021
|
+
const keyListTitle = document.createElement("span");
|
|
12022
|
+
keyListTitle.textContent = text2().redis.keys;
|
|
12023
|
+
const newKeyBtn = document.createElement("button");
|
|
12024
|
+
newKeyBtn.type = "button";
|
|
12025
|
+
newKeyBtn.className = "db-btn db-btn-sm redis-new-key-btn";
|
|
12026
|
+
newKeyBtn.textContent = `+ ${text2().redis.newKey}`;
|
|
12027
|
+
newKeyBtn.title = text2().redis.newKey;
|
|
12028
|
+
keyListHeader.append(keyListTitle, newKeyBtn);
|
|
11467
12029
|
keyListPane.appendChild(keyListHeader);
|
|
11468
12030
|
const keyFilterForm = document.createElement("form");
|
|
11469
12031
|
keyFilterForm.className = "redis-key-filter-form";
|
|
@@ -11586,6 +12148,152 @@ ${frontmatter.yaml}
|
|
|
11586
12148
|
div.textContent = message;
|
|
11587
12149
|
return div;
|
|
11588
12150
|
}
|
|
12151
|
+
function mkBtn(label, cls) {
|
|
12152
|
+
const b2 = document.createElement("button");
|
|
12153
|
+
b2.type = "button";
|
|
12154
|
+
b2.className = cls;
|
|
12155
|
+
b2.textContent = label;
|
|
12156
|
+
return b2;
|
|
12157
|
+
}
|
|
12158
|
+
function writeBase() {
|
|
12159
|
+
if (currentDbId === null || currentDbIndex === null)
|
|
12160
|
+
return null;
|
|
12161
|
+
return { db: currentDbId, dbIndex: currentDbIndex };
|
|
12162
|
+
}
|
|
12163
|
+
async function postRedisWrite(body) {
|
|
12164
|
+
const doFetch = fetch("/_db/redis/write", {
|
|
12165
|
+
method: "POST",
|
|
12166
|
+
headers: {
|
|
12167
|
+
"Content-Type": "application/json",
|
|
12168
|
+
"X-Code-Viewer-Action": "1"
|
|
12169
|
+
},
|
|
12170
|
+
body: JSON.stringify(body)
|
|
12171
|
+
});
|
|
12172
|
+
const res = await (callbacks.trackLoad ? callbacks.trackLoad(doFetch) : doFetch);
|
|
12173
|
+
if (!res.ok)
|
|
12174
|
+
throw new Error(await res.text() || res.statusText);
|
|
12175
|
+
}
|
|
12176
|
+
async function deleteCurrentKey(key) {
|
|
12177
|
+
const base2 = writeBase();
|
|
12178
|
+
if (!base2)
|
|
12179
|
+
return;
|
|
12180
|
+
const ok = await showConfirmDialog({
|
|
12181
|
+
body: text2().redis.confirmDeleteKey(key),
|
|
12182
|
+
confirmLabel: text2().common.delete,
|
|
12183
|
+
danger: true
|
|
12184
|
+
});
|
|
12185
|
+
if (!ok)
|
|
12186
|
+
return;
|
|
12187
|
+
try {
|
|
12188
|
+
await postRedisWrite({ ...base2, key, op: "delete" });
|
|
12189
|
+
currentKey = null;
|
|
12190
|
+
setPaneEmpty(mainPane, text2().redis.selectKey);
|
|
12191
|
+
await loadKeys(false);
|
|
12192
|
+
} catch (err) {
|
|
12193
|
+
setPaneStatus(mainPane, text2().common.saveError(err instanceof Error ? err.message : String(err)), { error: true });
|
|
12194
|
+
}
|
|
12195
|
+
}
|
|
12196
|
+
function startStringEdit(key, current) {
|
|
12197
|
+
const body = mainPane.querySelector(".redis-value-body");
|
|
12198
|
+
if (!body)
|
|
12199
|
+
return;
|
|
12200
|
+
body.innerHTML = "";
|
|
12201
|
+
const bar = document.createElement("div");
|
|
12202
|
+
bar.className = "redis-value-edit-bar";
|
|
12203
|
+
const save = mkBtn(text2().common.save, "db-btn db-btn-primary db-btn-sm");
|
|
12204
|
+
const cancel = mkBtn(text2().common.cancel, "db-btn db-btn-sm");
|
|
12205
|
+
const status = document.createElement("span");
|
|
12206
|
+
status.className = "redis-value-edit-status";
|
|
12207
|
+
bar.append(save, cancel, status);
|
|
12208
|
+
const ta = document.createElement("textarea");
|
|
12209
|
+
ta.className = "redis-value-edit-textarea";
|
|
12210
|
+
ta.value = current;
|
|
12211
|
+
body.append(bar, ta);
|
|
12212
|
+
cancel.addEventListener("click", () => void selectKey(key));
|
|
12213
|
+
save.addEventListener("click", async () => {
|
|
12214
|
+
const base2 = writeBase();
|
|
12215
|
+
if (!base2)
|
|
12216
|
+
return;
|
|
12217
|
+
save.disabled = true;
|
|
12218
|
+
cancel.disabled = true;
|
|
12219
|
+
status.textContent = text2().common.saving;
|
|
12220
|
+
try {
|
|
12221
|
+
await postRedisWrite({
|
|
12222
|
+
...base2,
|
|
12223
|
+
key,
|
|
12224
|
+
op: "setString",
|
|
12225
|
+
value: ta.value
|
|
12226
|
+
});
|
|
12227
|
+
await selectKey(key);
|
|
12228
|
+
} catch (err) {
|
|
12229
|
+
save.disabled = false;
|
|
12230
|
+
cancel.disabled = false;
|
|
12231
|
+
status.textContent = text2().common.saveError(err instanceof Error ? err.message : String(err));
|
|
12232
|
+
}
|
|
12233
|
+
});
|
|
12234
|
+
ta.focus();
|
|
12235
|
+
}
|
|
12236
|
+
function showNewKeyForm() {
|
|
12237
|
+
if (currentDbIndex === null) {
|
|
12238
|
+
setPaneStatus(mainPane, text2().redis.selectDatabase);
|
|
12239
|
+
return;
|
|
12240
|
+
}
|
|
12241
|
+
mainPane.innerHTML = "";
|
|
12242
|
+
const form = document.createElement("form");
|
|
12243
|
+
form.className = "redis-new-key-form";
|
|
12244
|
+
const nameInput = document.createElement("input");
|
|
12245
|
+
nameInput.type = "text";
|
|
12246
|
+
nameInput.className = "redis-new-key-name";
|
|
12247
|
+
nameInput.placeholder = text2().redis.newKeyNamePlaceholder;
|
|
12248
|
+
nameInput.autocomplete = "off";
|
|
12249
|
+
const valueInput = document.createElement("textarea");
|
|
12250
|
+
valueInput.className = "redis-new-key-value";
|
|
12251
|
+
valueInput.placeholder = text2().redis.newKeyValuePlaceholder;
|
|
12252
|
+
const bar = document.createElement("div");
|
|
12253
|
+
bar.className = "redis-value-edit-bar";
|
|
12254
|
+
const create = mkBtn(text2().redis.create, "db-btn db-btn-primary db-btn-sm");
|
|
12255
|
+
create.type = "submit";
|
|
12256
|
+
const cancel = mkBtn(text2().common.cancel, "db-btn db-btn-sm");
|
|
12257
|
+
const status = document.createElement("span");
|
|
12258
|
+
status.className = "redis-value-edit-status";
|
|
12259
|
+
bar.append(create, cancel, status);
|
|
12260
|
+
form.append(bar, nameInput, valueInput);
|
|
12261
|
+
mainPane.append(form);
|
|
12262
|
+
nameInput.focus();
|
|
12263
|
+
cancel.addEventListener("click", () => {
|
|
12264
|
+
if (currentKey)
|
|
12265
|
+
selectKey(currentKey);
|
|
12266
|
+
else
|
|
12267
|
+
setPaneEmpty(mainPane, text2().redis.selectKey);
|
|
12268
|
+
});
|
|
12269
|
+
form.addEventListener("submit", async (e2) => {
|
|
12270
|
+
e2.preventDefault();
|
|
12271
|
+
const base2 = writeBase();
|
|
12272
|
+
if (!base2)
|
|
12273
|
+
return;
|
|
12274
|
+
const name = nameInput.value.trim();
|
|
12275
|
+
if (!name) {
|
|
12276
|
+
nameInput.focus();
|
|
12277
|
+
return;
|
|
12278
|
+
}
|
|
12279
|
+
create.disabled = true;
|
|
12280
|
+
status.textContent = text2().common.saving;
|
|
12281
|
+
try {
|
|
12282
|
+
await postRedisWrite({
|
|
12283
|
+
...base2,
|
|
12284
|
+
key: name,
|
|
12285
|
+
op: "createString",
|
|
12286
|
+
value: valueInput.value
|
|
12287
|
+
});
|
|
12288
|
+
await loadKeys(false);
|
|
12289
|
+
await selectKey(name);
|
|
12290
|
+
} catch (err) {
|
|
12291
|
+
create.disabled = false;
|
|
12292
|
+
status.textContent = text2().common.saveError(err instanceof Error ? err.message : String(err));
|
|
12293
|
+
}
|
|
12294
|
+
});
|
|
12295
|
+
}
|
|
12296
|
+
newKeyBtn.addEventListener("click", () => showNewKeyForm());
|
|
11589
12297
|
function renderValue(key, value) {
|
|
11590
12298
|
mainPane.innerHTML = "";
|
|
11591
12299
|
const header = document.createElement("div");
|
|
@@ -11597,6 +12305,20 @@ ${frontmatter.yaml}
|
|
|
11597
12305
|
keyEl.className = "redis-value-key-name";
|
|
11598
12306
|
keyEl.textContent = key;
|
|
11599
12307
|
header.append(typeBadge, keyEl);
|
|
12308
|
+
const actions = document.createElement("div");
|
|
12309
|
+
actions.className = "redis-value-actions";
|
|
12310
|
+
if (value.type === "string" && value.binaryBase64 === undefined && !value.truncated) {
|
|
12311
|
+
const editBtn = mkBtn(text2().common.edit, "db-btn db-btn-sm");
|
|
12312
|
+
const stringValue = value.value;
|
|
12313
|
+
editBtn.addEventListener("click", () => startStringEdit(key, stringValue));
|
|
12314
|
+
actions.appendChild(editBtn);
|
|
12315
|
+
}
|
|
12316
|
+
if (value.type !== "none") {
|
|
12317
|
+
const delBtn = mkBtn(text2().common.delete, "db-btn db-btn-sm");
|
|
12318
|
+
delBtn.addEventListener("click", () => void deleteCurrentKey(key));
|
|
12319
|
+
actions.appendChild(delBtn);
|
|
12320
|
+
}
|
|
12321
|
+
header.appendChild(actions);
|
|
11600
12322
|
mainPane.appendChild(header);
|
|
11601
12323
|
const body = document.createElement("div");
|
|
11602
12324
|
body.className = "redis-value-body";
|
|
@@ -11935,7 +12657,9 @@ ${frontmatter.yaml}
|
|
|
11935
12657
|
function localize() {
|
|
11936
12658
|
const t2 = text2();
|
|
11937
12659
|
dbListHeader.textContent = t2.redis.databases;
|
|
11938
|
-
|
|
12660
|
+
keyListTitle.textContent = t2.redis.keys;
|
|
12661
|
+
newKeyBtn.textContent = `+ ${t2.redis.newKey}`;
|
|
12662
|
+
newKeyBtn.title = t2.redis.newKey;
|
|
11939
12663
|
keyFilterInput.placeholder = t2.redis.keyFilterPlaceholder;
|
|
11940
12664
|
keyFilterBtn.textContent = t2.common.search;
|
|
11941
12665
|
keyMoreBtn.textContent = t2.common.loadMore;
|
|
@@ -12085,13 +12809,6 @@ ${frontmatter.yaml}
|
|
|
12085
12809
|
explorerViewBtn.textContent = "Explorer";
|
|
12086
12810
|
viewSeg.append(listViewBtn, explorerViewBtn);
|
|
12087
12811
|
bucketRow.appendChild(viewSeg);
|
|
12088
|
-
const tooltipToggle = makePrefToggle({
|
|
12089
|
-
title: "Toggle hover preview tooltip",
|
|
12090
|
-
label: "Hover preview",
|
|
12091
|
-
pathD: "M8 3.5C4.5 3.5 1.7 5.7 0 8c1.7 2.3 4.5 4.5 8 4.5s6.3-2.2 8-4.5C14.3 5.7 11.5 3.5 8 3.5Zm0 7.5a3 3 0 1 1 0-6 3 3 0 0 1 0 6Zm0-4.5a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3Z",
|
|
12092
|
-
extraClass: "s3-tooltip-toggle"
|
|
12093
|
-
});
|
|
12094
|
-
bucketRow.appendChild(tooltipToggle);
|
|
12095
12812
|
sidebarSlot.appendChild(bucketRow);
|
|
12096
12813
|
const searchRow = document.createElement("form");
|
|
12097
12814
|
searchRow.className = "s3-search-row";
|
|
@@ -12126,7 +12843,13 @@ ${frontmatter.yaml}
|
|
|
12126
12843
|
sortKey.value = "key-asc";
|
|
12127
12844
|
sortKey.textContent = text2().sortKey;
|
|
12128
12845
|
sortSelect.append(sortUpdated, sortKey);
|
|
12129
|
-
|
|
12846
|
+
const newObjectBtn = document.createElement("button");
|
|
12847
|
+
newObjectBtn.type = "button";
|
|
12848
|
+
newObjectBtn.className = "db-btn db-btn-sm s3-new-object-btn";
|
|
12849
|
+
newObjectBtn.textContent = `+ ${text2().newObject}`;
|
|
12850
|
+
newObjectBtn.title = text2().newObject;
|
|
12851
|
+
newObjectBtn.hidden = true;
|
|
12852
|
+
optionRow.append(modeSeg, sortSelect, newObjectBtn);
|
|
12130
12853
|
sidebarSlot.appendChild(optionRow);
|
|
12131
12854
|
const objectStatus = document.createElement("div");
|
|
12132
12855
|
objectStatus.className = "s3-object-status";
|
|
@@ -12179,19 +12902,9 @@ ${frontmatter.yaml}
|
|
|
12179
12902
|
return;
|
|
12180
12903
|
callbacks.onSelectionChange?.(getSelection());
|
|
12181
12904
|
}
|
|
12182
|
-
|
|
12183
|
-
|
|
12184
|
-
|
|
12185
|
-
tooltipToggle.setAttribute("aria-pressed", String(tooltipEnabled));
|
|
12186
|
-
}
|
|
12187
|
-
applyTooltipToggleState();
|
|
12188
|
-
tooltipToggle.addEventListener("click", () => {
|
|
12189
|
-
tooltipEnabled = !tooltipEnabled;
|
|
12190
|
-
callbacks.setTooltipEnabled?.(tooltipEnabled);
|
|
12191
|
-
applyTooltipToggleState();
|
|
12192
|
-
if (!tooltipEnabled)
|
|
12193
|
-
hideKeyTooltip();
|
|
12194
|
-
});
|
|
12905
|
+
function tooltipEnabled() {
|
|
12906
|
+
return callbacks.getTooltipEnabled?.() ?? true;
|
|
12907
|
+
}
|
|
12195
12908
|
const keyTooltip = document.createElement("div");
|
|
12196
12909
|
keyTooltip.className = "s3-key-tooltip";
|
|
12197
12910
|
keyTooltip.hidden = true;
|
|
@@ -12208,7 +12921,7 @@ ${frontmatter.yaml}
|
|
|
12208
12921
|
keyTooltip.style.top = `${top}px`;
|
|
12209
12922
|
}
|
|
12210
12923
|
function showKeyTooltip(row, object) {
|
|
12211
|
-
if (!tooltipEnabled)
|
|
12924
|
+
if (!tooltipEnabled())
|
|
12212
12925
|
return;
|
|
12213
12926
|
keyTooltip.innerHTML = "";
|
|
12214
12927
|
if (currentDbId && currentBucket) {
|
|
@@ -12440,6 +13153,171 @@ ${frontmatter.yaml}
|
|
|
12440
13153
|
moreBtn.disabled = false;
|
|
12441
13154
|
}
|
|
12442
13155
|
}
|
|
13156
|
+
function mkBtn(label, cls) {
|
|
13157
|
+
const b2 = document.createElement("button");
|
|
13158
|
+
b2.type = "button";
|
|
13159
|
+
b2.className = cls;
|
|
13160
|
+
b2.textContent = label;
|
|
13161
|
+
return b2;
|
|
13162
|
+
}
|
|
13163
|
+
async function postS3Write(body) {
|
|
13164
|
+
const doFetch = fetch("/_db/s3/write", {
|
|
13165
|
+
method: "POST",
|
|
13166
|
+
headers: {
|
|
13167
|
+
"Content-Type": "application/json",
|
|
13168
|
+
"X-Code-Viewer-Action": "1"
|
|
13169
|
+
},
|
|
13170
|
+
body: JSON.stringify(body)
|
|
13171
|
+
});
|
|
13172
|
+
const res = await (callbacks.trackLoad ? callbacks.trackLoad(doFetch) : doFetch);
|
|
13173
|
+
if (!res.ok)
|
|
13174
|
+
throw new Error(await res.text() || res.statusText);
|
|
13175
|
+
}
|
|
13176
|
+
function refreshObjectList() {
|
|
13177
|
+
if (currentView === "explorer")
|
|
13178
|
+
loadExplorerRoot();
|
|
13179
|
+
else
|
|
13180
|
+
loadObjects(false);
|
|
13181
|
+
}
|
|
13182
|
+
async function startObjectEdit(object) {
|
|
13183
|
+
if (!currentDbId || !currentBucket)
|
|
13184
|
+
return;
|
|
13185
|
+
const bodyEl = previewPane.querySelector(".s3-preview-body");
|
|
13186
|
+
if (!bodyEl)
|
|
13187
|
+
return;
|
|
13188
|
+
setPaneStatus(bodyEl, tCommon().saving);
|
|
13189
|
+
let current = "";
|
|
13190
|
+
try {
|
|
13191
|
+
const params = new URLSearchParams({
|
|
13192
|
+
db: currentDbId,
|
|
13193
|
+
bucket: currentBucket,
|
|
13194
|
+
key: object.key
|
|
13195
|
+
});
|
|
13196
|
+
const res = await fetch(`/_db/s3/text?${params}`);
|
|
13197
|
+
if (!res.ok)
|
|
13198
|
+
throw new Error(await res.text() || res.statusText);
|
|
13199
|
+
current = (await res.json()).text;
|
|
13200
|
+
} catch (err) {
|
|
13201
|
+
setPaneStatus(bodyEl, tCommon().saveError(err instanceof Error ? err.message : String(err)), { error: true });
|
|
13202
|
+
return;
|
|
13203
|
+
}
|
|
13204
|
+
bodyEl.innerHTML = "";
|
|
13205
|
+
const bar = document.createElement("div");
|
|
13206
|
+
bar.className = "s3-edit-bar";
|
|
13207
|
+
const save = mkBtn(tCommon().save, "db-btn db-btn-primary db-btn-sm");
|
|
13208
|
+
const cancel = mkBtn(tCommon().cancel, "db-btn db-btn-sm");
|
|
13209
|
+
const status = document.createElement("span");
|
|
13210
|
+
status.className = "s3-edit-status";
|
|
13211
|
+
bar.append(save, cancel, status);
|
|
13212
|
+
const ta = document.createElement("textarea");
|
|
13213
|
+
ta.className = "s3-edit-textarea";
|
|
13214
|
+
ta.value = current;
|
|
13215
|
+
bodyEl.append(bar, ta);
|
|
13216
|
+
cancel.addEventListener("click", () => void selectObject(object));
|
|
13217
|
+
save.addEventListener("click", async () => {
|
|
13218
|
+
if (!currentDbId || !currentBucket)
|
|
13219
|
+
return;
|
|
13220
|
+
save.disabled = true;
|
|
13221
|
+
cancel.disabled = true;
|
|
13222
|
+
status.textContent = tCommon().saving;
|
|
13223
|
+
try {
|
|
13224
|
+
await postS3Write({
|
|
13225
|
+
db: currentDbId,
|
|
13226
|
+
bucket: currentBucket,
|
|
13227
|
+
key: object.key,
|
|
13228
|
+
content: ta.value,
|
|
13229
|
+
contentType: object.contentType || "text/plain"
|
|
13230
|
+
});
|
|
13231
|
+
await selectObject(object);
|
|
13232
|
+
} catch (err) {
|
|
13233
|
+
save.disabled = false;
|
|
13234
|
+
cancel.disabled = false;
|
|
13235
|
+
status.textContent = tCommon().saveError(err instanceof Error ? err.message : String(err));
|
|
13236
|
+
}
|
|
13237
|
+
});
|
|
13238
|
+
ta.focus();
|
|
13239
|
+
}
|
|
13240
|
+
async function deleteObject(object) {
|
|
13241
|
+
if (!currentDbId || !currentBucket)
|
|
13242
|
+
return;
|
|
13243
|
+
const ok = await showConfirmDialog({
|
|
13244
|
+
body: text2().confirmDeleteObject(object.key),
|
|
13245
|
+
confirmLabel: tCommon().delete,
|
|
13246
|
+
danger: true
|
|
13247
|
+
});
|
|
13248
|
+
if (!ok)
|
|
13249
|
+
return;
|
|
13250
|
+
try {
|
|
13251
|
+
await postS3Write({
|
|
13252
|
+
db: currentDbId,
|
|
13253
|
+
bucket: currentBucket,
|
|
13254
|
+
key: object.key,
|
|
13255
|
+
op: "delete"
|
|
13256
|
+
});
|
|
13257
|
+
currentKey = null;
|
|
13258
|
+
setPaneEmpty(previewPane, text2().selectObject);
|
|
13259
|
+
refreshObjectList();
|
|
13260
|
+
} catch (err) {
|
|
13261
|
+
setPaneStatus(previewPane, tCommon().saveError(err instanceof Error ? err.message : String(err)), { error: true });
|
|
13262
|
+
}
|
|
13263
|
+
}
|
|
13264
|
+
function showNewObjectForm() {
|
|
13265
|
+
if (!currentBucket) {
|
|
13266
|
+
setPaneStatus(previewPane, text2().selectObject);
|
|
13267
|
+
return;
|
|
13268
|
+
}
|
|
13269
|
+
previewPane.innerHTML = "";
|
|
13270
|
+
const form = document.createElement("form");
|
|
13271
|
+
form.className = "s3-new-object-form";
|
|
13272
|
+
const keyInput = document.createElement("input");
|
|
13273
|
+
keyInput.type = "text";
|
|
13274
|
+
keyInput.className = "s3-new-object-key";
|
|
13275
|
+
keyInput.placeholder = text2().newObjectKeyPlaceholder;
|
|
13276
|
+
keyInput.autocomplete = "off";
|
|
13277
|
+
const ta = document.createElement("textarea");
|
|
13278
|
+
ta.className = "s3-edit-textarea";
|
|
13279
|
+
ta.placeholder = text2().contentPlaceholder;
|
|
13280
|
+
const bar = document.createElement("div");
|
|
13281
|
+
bar.className = "s3-edit-bar";
|
|
13282
|
+
const create = mkBtn(text2().create, "db-btn db-btn-primary db-btn-sm");
|
|
13283
|
+
create.type = "submit";
|
|
13284
|
+
const cancel = mkBtn(tCommon().cancel, "db-btn db-btn-sm");
|
|
13285
|
+
const status = document.createElement("span");
|
|
13286
|
+
status.className = "s3-edit-status";
|
|
13287
|
+
bar.append(create, cancel, status);
|
|
13288
|
+
form.append(bar, keyInput, ta);
|
|
13289
|
+
previewPane.appendChild(form);
|
|
13290
|
+
keyInput.focus();
|
|
13291
|
+
cancel.addEventListener("click", () => setPaneEmpty(previewPane, text2().selectObject));
|
|
13292
|
+
form.addEventListener("submit", async (e2) => {
|
|
13293
|
+
e2.preventDefault();
|
|
13294
|
+
if (!currentDbId || !currentBucket)
|
|
13295
|
+
return;
|
|
13296
|
+
const key = keyInput.value.trim();
|
|
13297
|
+
if (!key) {
|
|
13298
|
+
keyInput.focus();
|
|
13299
|
+
return;
|
|
13300
|
+
}
|
|
13301
|
+
create.disabled = true;
|
|
13302
|
+
status.textContent = tCommon().saving;
|
|
13303
|
+
try {
|
|
13304
|
+
await postS3Write({
|
|
13305
|
+
db: currentDbId,
|
|
13306
|
+
bucket: currentBucket,
|
|
13307
|
+
key,
|
|
13308
|
+
content: ta.value,
|
|
13309
|
+
contentType: "text/plain",
|
|
13310
|
+
op: "create"
|
|
13311
|
+
});
|
|
13312
|
+
refreshObjectList();
|
|
13313
|
+
setPaneEmpty(previewPane, text2().selectObject);
|
|
13314
|
+
} catch (err) {
|
|
13315
|
+
create.disabled = false;
|
|
13316
|
+
status.textContent = tCommon().saveError(err instanceof Error ? err.message : String(err));
|
|
13317
|
+
}
|
|
13318
|
+
});
|
|
13319
|
+
}
|
|
13320
|
+
newObjectBtn.addEventListener("click", () => showNewObjectForm());
|
|
12443
13321
|
function renderPreviewHeader(object) {
|
|
12444
13322
|
const header = document.createElement("div");
|
|
12445
13323
|
header.className = "s3-preview-header";
|
|
@@ -12485,6 +13363,14 @@ ${frontmatter.yaml}
|
|
|
12485
13363
|
}
|
|
12486
13364
|
});
|
|
12487
13365
|
actions.append(open, download, copy);
|
|
13366
|
+
if (sourceDisplayKind(object.key) === "text") {
|
|
13367
|
+
const editBtn = mkBtn(tCommon().edit, "db-btn db-btn-sm");
|
|
13368
|
+
editBtn.addEventListener("click", () => void startObjectEdit(object));
|
|
13369
|
+
actions.appendChild(editBtn);
|
|
13370
|
+
}
|
|
13371
|
+
const delBtn = mkBtn(tCommon().delete, "db-btn db-btn-sm");
|
|
13372
|
+
delBtn.addEventListener("click", () => void deleteObject(object));
|
|
13373
|
+
actions.appendChild(delBtn);
|
|
12488
13374
|
}
|
|
12489
13375
|
header.append(title, meta, actions);
|
|
12490
13376
|
return header;
|
|
@@ -12876,6 +13762,7 @@ ${frontmatter.yaml}
|
|
|
12876
13762
|
currentNextToken = undefined;
|
|
12877
13763
|
listLoaded = false;
|
|
12878
13764
|
resetExplorer();
|
|
13765
|
+
newObjectBtn.hidden = false;
|
|
12879
13766
|
notifySelectionChange();
|
|
12880
13767
|
if (currentView === "explorer")
|
|
12881
13768
|
await loadExplorerRoot();
|
|
@@ -12994,6 +13881,7 @@ ${frontmatter.yaml}
|
|
|
12994
13881
|
return;
|
|
12995
13882
|
}
|
|
12996
13883
|
bucketSelect.value = selected;
|
|
13884
|
+
newObjectBtn.hidden = false;
|
|
12997
13885
|
suppressNotify = true;
|
|
12998
13886
|
try {
|
|
12999
13887
|
currentBucket = selected;
|
|
@@ -13090,6 +13978,8 @@ ${frontmatter.yaml}
|
|
|
13090
13978
|
containsModeBtn.textContent = t2.containsMode;
|
|
13091
13979
|
sortUpdated.textContent = t2.sortUpdated;
|
|
13092
13980
|
sortKey.textContent = t2.sortKey;
|
|
13981
|
+
newObjectBtn.textContent = `+ ${t2.newObject}`;
|
|
13982
|
+
newObjectBtn.title = t2.newObject;
|
|
13093
13983
|
moreBtn.textContent = tCommon().loadMore;
|
|
13094
13984
|
searchInput.placeholder = currentMode === "prefix" ? t2.prefixPlaceholder : t2.containsPlaceholder;
|
|
13095
13985
|
if (!currentKey)
|
|
@@ -13292,6 +14182,276 @@ ${frontmatter.yaml}
|
|
|
13292
14182
|
return { el, render, clear, localize };
|
|
13293
14183
|
}
|
|
13294
14184
|
|
|
14185
|
+
// web-src/views/database/session-log.ts
|
|
14186
|
+
var DEFAULT_MAX_ENTRIES = 500;
|
|
14187
|
+
function createSessionLog(options = {}) {
|
|
14188
|
+
const max = options.maxEntries ?? DEFAULT_MAX_ENTRIES;
|
|
14189
|
+
const entries = [];
|
|
14190
|
+
const listeners = new Set;
|
|
14191
|
+
let seq = 0;
|
|
14192
|
+
function emit() {
|
|
14193
|
+
for (const fn of listeners)
|
|
14194
|
+
fn();
|
|
14195
|
+
}
|
|
14196
|
+
function add2(entry) {
|
|
14197
|
+
seq += 1;
|
|
14198
|
+
const next = {
|
|
14199
|
+
...entry,
|
|
14200
|
+
id: `sl-${seq}`,
|
|
14201
|
+
timestamp: Date.now()
|
|
14202
|
+
};
|
|
14203
|
+
entries.push(next);
|
|
14204
|
+
while (entries.length > max)
|
|
14205
|
+
entries.shift();
|
|
14206
|
+
emit();
|
|
14207
|
+
return next;
|
|
14208
|
+
}
|
|
14209
|
+
return {
|
|
14210
|
+
add: add2,
|
|
14211
|
+
list: () => entries,
|
|
14212
|
+
clear() {
|
|
14213
|
+
if (entries.length === 0)
|
|
14214
|
+
return;
|
|
14215
|
+
entries.length = 0;
|
|
14216
|
+
emit();
|
|
14217
|
+
},
|
|
14218
|
+
subscribe(listener) {
|
|
14219
|
+
listeners.add(listener);
|
|
14220
|
+
return () => listeners.delete(listener);
|
|
14221
|
+
}
|
|
14222
|
+
};
|
|
14223
|
+
}
|
|
14224
|
+
|
|
14225
|
+
// web-src/views/database/session-log-view.ts
|
|
14226
|
+
function createSessionLogView(callbacks) {
|
|
14227
|
+
const text2 = () => callbacks.getText?.() ?? dbText("en");
|
|
14228
|
+
const el = document.createElement("div");
|
|
14229
|
+
el.className = "db-session-log";
|
|
14230
|
+
const toolbar = document.createElement("div");
|
|
14231
|
+
toolbar.className = "db-session-log-toolbar";
|
|
14232
|
+
const followBtn = document.createElement("button");
|
|
14233
|
+
followBtn.className = "db-session-log-action active";
|
|
14234
|
+
followBtn.type = "button";
|
|
14235
|
+
followBtn.textContent = text2().sessionLog.autoFollow;
|
|
14236
|
+
followBtn.title = text2().sessionLog.autoFollowTitle;
|
|
14237
|
+
followBtn.setAttribute("aria-pressed", "true");
|
|
14238
|
+
const clearBtn = document.createElement("button");
|
|
14239
|
+
clearBtn.className = "db-session-log-action db-session-log-danger";
|
|
14240
|
+
clearBtn.type = "button";
|
|
14241
|
+
clearBtn.textContent = text2().sessionLog.clear;
|
|
14242
|
+
clearBtn.title = text2().sessionLog.clearTitle;
|
|
14243
|
+
toolbar.append(followBtn, clearBtn);
|
|
14244
|
+
const body = document.createElement("div");
|
|
14245
|
+
body.className = "db-session-log-body-split";
|
|
14246
|
+
const listCol = document.createElement("div");
|
|
14247
|
+
listCol.className = "db-session-log-list-col";
|
|
14248
|
+
const listEl = document.createElement("div");
|
|
14249
|
+
listEl.className = "db-session-log-list";
|
|
14250
|
+
listCol.appendChild(listEl);
|
|
14251
|
+
const detailCol = document.createElement("div");
|
|
14252
|
+
detailCol.className = "db-session-log-detail-col";
|
|
14253
|
+
const placeholder = document.createElement("div");
|
|
14254
|
+
placeholder.className = "db-session-log-detail-placeholder";
|
|
14255
|
+
placeholder.textContent = text2().sessionLog.selectPlaceholder;
|
|
14256
|
+
detailCol.appendChild(placeholder);
|
|
14257
|
+
body.append(listCol, detailCol);
|
|
14258
|
+
el.append(toolbar, body);
|
|
14259
|
+
let selectedId = null;
|
|
14260
|
+
const rowById = new Map;
|
|
14261
|
+
let autoFollow = true;
|
|
14262
|
+
let suppressScrollEvent = false;
|
|
14263
|
+
const AUTO_FOLLOW_THRESHOLD_PX = 4;
|
|
14264
|
+
function refreshFollowBtn() {
|
|
14265
|
+
followBtn.classList.toggle("active", autoFollow);
|
|
14266
|
+
followBtn.setAttribute("aria-pressed", String(autoFollow));
|
|
14267
|
+
}
|
|
14268
|
+
followBtn.addEventListener("click", () => {
|
|
14269
|
+
autoFollow = !autoFollow;
|
|
14270
|
+
refreshFollowBtn();
|
|
14271
|
+
if (autoFollow) {
|
|
14272
|
+
suppressScrollEvent = true;
|
|
14273
|
+
listEl.scrollTop = 0;
|
|
14274
|
+
}
|
|
14275
|
+
});
|
|
14276
|
+
listEl.addEventListener("scroll", () => {
|
|
14277
|
+
if (suppressScrollEvent) {
|
|
14278
|
+
suppressScrollEvent = false;
|
|
14279
|
+
return;
|
|
14280
|
+
}
|
|
14281
|
+
if (autoFollow && listEl.scrollTop > AUTO_FOLLOW_THRESHOLD_PX) {
|
|
14282
|
+
autoFollow = false;
|
|
14283
|
+
refreshFollowBtn();
|
|
14284
|
+
}
|
|
14285
|
+
});
|
|
14286
|
+
let shiki = null;
|
|
14287
|
+
loadShikiHighlighter({
|
|
14288
|
+
themes: ["github-light", "github-dark"],
|
|
14289
|
+
langs: ["sql"]
|
|
14290
|
+
}).then((h) => {
|
|
14291
|
+
shiki = h;
|
|
14292
|
+
if (selectedId) {
|
|
14293
|
+
const cur = callbacks.store.list().find((e2) => e2.id === selectedId);
|
|
14294
|
+
if (cur)
|
|
14295
|
+
renderDetail(cur);
|
|
14296
|
+
}
|
|
14297
|
+
});
|
|
14298
|
+
function render() {
|
|
14299
|
+
listEl.innerHTML = "";
|
|
14300
|
+
rowById.clear();
|
|
14301
|
+
const entries = callbacks.store.list();
|
|
14302
|
+
if (entries.length === 0) {
|
|
14303
|
+
const empty = document.createElement("div");
|
|
14304
|
+
empty.className = "db-session-log-empty";
|
|
14305
|
+
empty.textContent = text2().sessionLog.empty;
|
|
14306
|
+
listEl.appendChild(empty);
|
|
14307
|
+
detailCol.innerHTML = "";
|
|
14308
|
+
detailCol.appendChild(placeholder);
|
|
14309
|
+
selectedId = null;
|
|
14310
|
+
return;
|
|
14311
|
+
}
|
|
14312
|
+
const fragment = document.createDocumentFragment();
|
|
14313
|
+
for (let i2 = entries.length - 1;i2 >= 0; i2--) {
|
|
14314
|
+
fragment.appendChild(renderEntry(entries[i2]));
|
|
14315
|
+
}
|
|
14316
|
+
listEl.appendChild(fragment);
|
|
14317
|
+
if (autoFollow) {
|
|
14318
|
+
suppressScrollEvent = true;
|
|
14319
|
+
listEl.scrollTop = 0;
|
|
14320
|
+
const latest = entries[entries.length - 1];
|
|
14321
|
+
selectedId = latest.id;
|
|
14322
|
+
renderDetail(latest);
|
|
14323
|
+
rowById.get(latest.id)?.classList.add("selected");
|
|
14324
|
+
} else if (selectedId) {
|
|
14325
|
+
const cur = entries.find((e2) => e2.id === selectedId);
|
|
14326
|
+
if (cur) {
|
|
14327
|
+
renderDetail(cur);
|
|
14328
|
+
rowById.get(cur.id)?.classList.add("selected");
|
|
14329
|
+
} else {
|
|
14330
|
+
detailCol.innerHTML = "";
|
|
14331
|
+
detailCol.appendChild(placeholder);
|
|
14332
|
+
selectedId = null;
|
|
14333
|
+
}
|
|
14334
|
+
}
|
|
14335
|
+
}
|
|
14336
|
+
function renderEntry(entry) {
|
|
14337
|
+
const row = document.createElement("div");
|
|
14338
|
+
row.className = "db-session-log-entry";
|
|
14339
|
+
row.dataset.id = entry.id;
|
|
14340
|
+
row.dataset.status = entry.status;
|
|
14341
|
+
row.dataset.kind = entry.kind;
|
|
14342
|
+
rowById.set(entry.id, row);
|
|
14343
|
+
const meta = document.createElement("div");
|
|
14344
|
+
meta.className = "db-session-log-entry-meta";
|
|
14345
|
+
const badge = document.createElement("span");
|
|
14346
|
+
badge.className = `db-session-log-badge db-session-log-badge-${entry.status}`;
|
|
14347
|
+
badge.textContent = entry.status === "error" ? text2().sessionLog.statusError : text2().sessionLog.statusOk;
|
|
14348
|
+
const kind = document.createElement("span");
|
|
14349
|
+
kind.className = "db-session-log-kind";
|
|
14350
|
+
kind.textContent = entry.kind === "query" ? text2().sessionLog.kindQuery : text2().sessionLog.kindMutate;
|
|
14351
|
+
const time = document.createElement("span");
|
|
14352
|
+
time.className = "db-session-log-time";
|
|
14353
|
+
time.textContent = formatTime2(entry.timestamp);
|
|
14354
|
+
const stats = document.createElement("span");
|
|
14355
|
+
stats.className = "db-session-log-stats";
|
|
14356
|
+
const parts = [];
|
|
14357
|
+
if (entry.rowCount !== undefined)
|
|
14358
|
+
parts.push(`${entry.rowCount} rows`);
|
|
14359
|
+
if (entry.elapsedMs !== undefined)
|
|
14360
|
+
parts.push(`${entry.elapsedMs}ms`);
|
|
14361
|
+
stats.textContent = parts.join(", ");
|
|
14362
|
+
meta.append(badge, kind, time, stats);
|
|
14363
|
+
const title = document.createElement("div");
|
|
14364
|
+
title.className = "db-session-log-entry-title";
|
|
14365
|
+
title.textContent = entry.label.length > 100 ? `${entry.label.slice(0, 100)}...` : entry.label;
|
|
14366
|
+
row.append(meta, title);
|
|
14367
|
+
row.addEventListener("click", () => selectEntry(entry));
|
|
14368
|
+
return row;
|
|
14369
|
+
}
|
|
14370
|
+
function selectEntry(entry) {
|
|
14371
|
+
selectedId = entry.id;
|
|
14372
|
+
for (const r2 of rowById.values())
|
|
14373
|
+
r2.classList.remove("selected");
|
|
14374
|
+
rowById.get(entry.id)?.classList.add("selected");
|
|
14375
|
+
renderDetail(entry);
|
|
14376
|
+
const entries = callbacks.store.list();
|
|
14377
|
+
const isLatest = entries[entries.length - 1]?.id === entry.id;
|
|
14378
|
+
if (autoFollow && !isLatest) {
|
|
14379
|
+
autoFollow = false;
|
|
14380
|
+
refreshFollowBtn();
|
|
14381
|
+
}
|
|
14382
|
+
}
|
|
14383
|
+
function renderDetail(entry) {
|
|
14384
|
+
detailCol.innerHTML = "";
|
|
14385
|
+
if (callbacks.copySqlToQuery && entry.detail) {
|
|
14386
|
+
const actions = document.createElement("div");
|
|
14387
|
+
actions.className = "db-session-log-detail-actions";
|
|
14388
|
+
const useBtn = document.createElement("button");
|
|
14389
|
+
useBtn.className = "db-btn db-btn-primary";
|
|
14390
|
+
useBtn.type = "button";
|
|
14391
|
+
useBtn.textContent = text2().sessionLog.useInEditor;
|
|
14392
|
+
useBtn.addEventListener("click", () => {
|
|
14393
|
+
callbacks.copySqlToQuery?.(entry.detail ?? "");
|
|
14394
|
+
});
|
|
14395
|
+
const copyBtn = document.createElement("button");
|
|
14396
|
+
copyBtn.className = "db-btn";
|
|
14397
|
+
copyBtn.type = "button";
|
|
14398
|
+
copyBtn.textContent = text2().sessionLog.copy;
|
|
14399
|
+
copyBtn.addEventListener("click", () => {
|
|
14400
|
+
const txt = entry.message && entry.detail ? `${entry.detail}
|
|
14401
|
+
|
|
14402
|
+
${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
14403
|
+
navigator.clipboard.writeText(txt).then(() => {
|
|
14404
|
+
copyBtn.textContent = text2().sessionLog.copied;
|
|
14405
|
+
setTimeout(() => {
|
|
14406
|
+
copyBtn.textContent = text2().sessionLog.copy;
|
|
14407
|
+
}, 1200);
|
|
14408
|
+
}, () => {});
|
|
14409
|
+
});
|
|
14410
|
+
actions.append(useBtn, copyBtn);
|
|
14411
|
+
detailCol.appendChild(actions);
|
|
14412
|
+
}
|
|
14413
|
+
if (entry.detail) {
|
|
14414
|
+
const sql = document.createElement("pre");
|
|
14415
|
+
sql.className = "db-session-log-sql";
|
|
14416
|
+
const inner = highlightSqlToInnerHtml(entry.detail, shiki);
|
|
14417
|
+
if (inner) {
|
|
14418
|
+
sql.innerHTML = inner;
|
|
14419
|
+
} else {
|
|
14420
|
+
sql.textContent = entry.detail;
|
|
14421
|
+
}
|
|
14422
|
+
detailCol.appendChild(sql);
|
|
14423
|
+
}
|
|
14424
|
+
if (entry.message) {
|
|
14425
|
+
const msg = document.createElement("pre");
|
|
14426
|
+
msg.className = `db-session-log-message db-session-log-message-${entry.status}`;
|
|
14427
|
+
msg.textContent = entry.message;
|
|
14428
|
+
detailCol.appendChild(msg);
|
|
14429
|
+
}
|
|
14430
|
+
}
|
|
14431
|
+
clearBtn.addEventListener("click", () => {
|
|
14432
|
+
callbacks.store.clear();
|
|
14433
|
+
});
|
|
14434
|
+
callbacks.store.subscribe(render);
|
|
14435
|
+
render();
|
|
14436
|
+
function clear() {
|
|
14437
|
+
callbacks.store.clear();
|
|
14438
|
+
}
|
|
14439
|
+
function localize() {
|
|
14440
|
+
clearBtn.textContent = text2().sessionLog.clear;
|
|
14441
|
+
clearBtn.title = text2().sessionLog.clearTitle;
|
|
14442
|
+
followBtn.textContent = text2().sessionLog.autoFollow;
|
|
14443
|
+
followBtn.title = text2().sessionLog.autoFollowTitle;
|
|
14444
|
+
placeholder.textContent = text2().sessionLog.selectPlaceholder;
|
|
14445
|
+
render();
|
|
14446
|
+
}
|
|
14447
|
+
return { el, clear, localize };
|
|
14448
|
+
}
|
|
14449
|
+
function formatTime2(timestamp) {
|
|
14450
|
+
const d2 = new Date(timestamp);
|
|
14451
|
+
const pad = (n2) => String(n2).padStart(2, "0");
|
|
14452
|
+
return `${pad(d2.getHours())}:${pad(d2.getMinutes())}:${pad(d2.getSeconds())}`;
|
|
14453
|
+
}
|
|
14454
|
+
|
|
13295
14455
|
// web-src/views/database/snapshot-view.ts
|
|
13296
14456
|
function arraysEqual(a2, b2) {
|
|
13297
14457
|
if (a2.length !== b2.length)
|
|
@@ -14022,7 +15182,28 @@ ${frontmatter.yaml}
|
|
|
14022
15182
|
closeExportMenu();
|
|
14023
15183
|
triggerExport("json");
|
|
14024
15184
|
});
|
|
14025
|
-
|
|
15185
|
+
const editWrap = document.createElement("div");
|
|
15186
|
+
editWrap.className = "db-grid-edit-controls";
|
|
15187
|
+
editWrap.hidden = true;
|
|
15188
|
+
const newRowBtn = document.createElement("button");
|
|
15189
|
+
newRowBtn.type = "button";
|
|
15190
|
+
newRowBtn.className = "db-btn db-grid-edit-newrow";
|
|
15191
|
+
newRowBtn.textContent = text2().edit.newRow;
|
|
15192
|
+
newRowBtn.style.visibility = "hidden";
|
|
15193
|
+
const commitBtn = document.createElement("button");
|
|
15194
|
+
commitBtn.type = "button";
|
|
15195
|
+
commitBtn.className = "db-btn db-btn-primary db-grid-edit-commit";
|
|
15196
|
+
commitBtn.textContent = text2().edit.commit;
|
|
15197
|
+
commitBtn.style.visibility = "hidden";
|
|
15198
|
+
const discardBtn = document.createElement("button");
|
|
15199
|
+
discardBtn.type = "button";
|
|
15200
|
+
discardBtn.className = "db-btn db-grid-edit-discard";
|
|
15201
|
+
discardBtn.textContent = text2().edit.discard;
|
|
15202
|
+
discardBtn.style.visibility = "hidden";
|
|
15203
|
+
const editStatus = document.createElement("span");
|
|
15204
|
+
editStatus.className = "db-grid-edit-status";
|
|
15205
|
+
editWrap.append(newRowBtn, commitBtn, discardBtn, editStatus);
|
|
15206
|
+
filterBar.append(filterIcon, filterInput, filterClear, editWrap, exportWrap);
|
|
14026
15207
|
const headerWrap = document.createElement("div");
|
|
14027
15208
|
headerWrap.className = "db-grid-header-wrap";
|
|
14028
15209
|
const headerRow = document.createElement("div");
|
|
@@ -14130,6 +15311,13 @@ ${frontmatter.yaml}
|
|
|
14130
15311
|
let activeCellColIndex = -1;
|
|
14131
15312
|
let renderStartRow = 0;
|
|
14132
15313
|
const fkColumns = new Set;
|
|
15314
|
+
let editMode = false;
|
|
15315
|
+
let isComposing = false;
|
|
15316
|
+
const pendingEdits = new Map;
|
|
15317
|
+
const pendingDeletes = new Map;
|
|
15318
|
+
let draftRows = [];
|
|
15319
|
+
let editingCellRow = -1;
|
|
15320
|
+
let editingCellCol = -1;
|
|
14133
15321
|
let relatedPanel = null;
|
|
14134
15322
|
let relatedListEl = null;
|
|
14135
15323
|
let relatedGridHost = null;
|
|
@@ -14275,6 +15463,9 @@ ${frontmatter.yaml}
|
|
|
14275
15463
|
detailPanel.hidden = true;
|
|
14276
15464
|
clearDetailContent();
|
|
14277
15465
|
colWidths.clear();
|
|
15466
|
+
clearPending();
|
|
15467
|
+
setEditStatus("");
|
|
15468
|
+
refreshEditButtons();
|
|
14278
15469
|
}
|
|
14279
15470
|
let relatedFirstPageController = null;
|
|
14280
15471
|
function relatedLookupValue(value) {
|
|
@@ -14524,8 +15715,8 @@ ${frontmatter.yaml}
|
|
|
14524
15715
|
if (target.fk.inferred) {
|
|
14525
15716
|
const badge = document.createElement("span");
|
|
14526
15717
|
badge.className = "db-related-list-inferred-badge";
|
|
14527
|
-
badge.textContent =
|
|
14528
|
-
badge.title =
|
|
15718
|
+
badge.textContent = text2().nav.inferredBadge;
|
|
15719
|
+
badge.title = text2().nav.inferredBadgeTitle;
|
|
14529
15720
|
name.appendChild(badge);
|
|
14530
15721
|
}
|
|
14531
15722
|
const via = document.createElement("span");
|
|
@@ -14795,7 +15986,7 @@ ${frontmatter.yaml}
|
|
|
14795
15986
|
return;
|
|
14796
15987
|
pageCache.set(pageStart, data.rows);
|
|
14797
15988
|
totalRows = data.totalRows;
|
|
14798
|
-
|
|
15989
|
+
syncSpacer();
|
|
14799
15990
|
updateStatus();
|
|
14800
15991
|
renderViewport();
|
|
14801
15992
|
}).catch((err) => {
|
|
@@ -14813,98 +16004,334 @@ ${frontmatter.yaml}
|
|
|
14813
16004
|
pendingPages.set(pageStart, tracked);
|
|
14814
16005
|
return tracked;
|
|
14815
16006
|
}
|
|
16007
|
+
function buildEditableCell(colIndex, value, opts) {
|
|
16008
|
+
const cell = document.createElement("div");
|
|
16009
|
+
cell.className = "db-grid-cell db-grid-cell-edit";
|
|
16010
|
+
cell.style.width = `${getColWidth(columnNames[colIndex])}px`;
|
|
16011
|
+
if (!opts.editing) {
|
|
16012
|
+
cell.classList.add("db-grid-cell-display");
|
|
16013
|
+
cell.textContent = value === null ? "NULL" : value === "" ? "" : value;
|
|
16014
|
+
if (value === null)
|
|
16015
|
+
cell.classList.add("null");
|
|
16016
|
+
else if (value === "")
|
|
16017
|
+
cell.classList.add("empty");
|
|
16018
|
+
if (opts.readonly) {
|
|
16019
|
+
cell.classList.add("readonly");
|
|
16020
|
+
}
|
|
16021
|
+
cell.style.cursor = "pointer";
|
|
16022
|
+
cell.addEventListener("click", (e2) => {
|
|
16023
|
+
e2.stopPropagation();
|
|
16024
|
+
opts.onActivate?.();
|
|
16025
|
+
});
|
|
16026
|
+
if (!opts.readonly) {
|
|
16027
|
+
cell.title = text2().edit.dblclickToEdit;
|
|
16028
|
+
cell.addEventListener("dblclick", (e2) => {
|
|
16029
|
+
e2.stopPropagation();
|
|
16030
|
+
opts.onEnterEdit?.();
|
|
16031
|
+
});
|
|
16032
|
+
}
|
|
16033
|
+
return cell;
|
|
16034
|
+
}
|
|
16035
|
+
const input = document.createElement("input");
|
|
16036
|
+
input.type = "text";
|
|
16037
|
+
input.className = "db-grid-cell-input";
|
|
16038
|
+
input.dataset.editRow = String(opts.rowIndex);
|
|
16039
|
+
input.dataset.editCol = String(colIndex);
|
|
16040
|
+
const applyNullVisual = (isNull) => {
|
|
16041
|
+
input.classList.toggle("is-null", isNull);
|
|
16042
|
+
input.placeholder = isNull ? "NULL" : "";
|
|
16043
|
+
};
|
|
16044
|
+
input.value = value === null ? "" : value;
|
|
16045
|
+
applyNullVisual(value === null);
|
|
16046
|
+
if (opts.readonly) {
|
|
16047
|
+
input.readOnly = true;
|
|
16048
|
+
input.classList.add("readonly");
|
|
16049
|
+
}
|
|
16050
|
+
input.addEventListener("input", () => {
|
|
16051
|
+
applyNullVisual(false);
|
|
16052
|
+
opts.onChange(input.value);
|
|
16053
|
+
});
|
|
16054
|
+
input.addEventListener("click", (e2) => e2.stopPropagation());
|
|
16055
|
+
input.addEventListener("blur", (e2) => {
|
|
16056
|
+
const next = e2.relatedTarget;
|
|
16057
|
+
if (next && cell.contains(next))
|
|
16058
|
+
return;
|
|
16059
|
+
opts.onExitEdit?.();
|
|
16060
|
+
});
|
|
16061
|
+
input.addEventListener("keydown", (e2) => {
|
|
16062
|
+
if (e2.key === "Enter" || e2.key === "Escape") {
|
|
16063
|
+
e2.preventDefault();
|
|
16064
|
+
opts.onExitEdit?.();
|
|
16065
|
+
}
|
|
16066
|
+
});
|
|
16067
|
+
cell.appendChild(input);
|
|
16068
|
+
if (opts.nullable && !opts.readonly) {
|
|
16069
|
+
const nullBtn = document.createElement("button");
|
|
16070
|
+
nullBtn.type = "button";
|
|
16071
|
+
nullBtn.className = "db-grid-cell-null-btn";
|
|
16072
|
+
nullBtn.textContent = "∅";
|
|
16073
|
+
nullBtn.title = text2().edit.setNull;
|
|
16074
|
+
nullBtn.addEventListener("mousedown", (e2) => e2.preventDefault());
|
|
16075
|
+
nullBtn.addEventListener("click", (e2) => {
|
|
16076
|
+
e2.stopPropagation();
|
|
16077
|
+
input.value = "";
|
|
16078
|
+
applyNullVisual(true);
|
|
16079
|
+
opts.onChange(null);
|
|
16080
|
+
input.focus();
|
|
16081
|
+
});
|
|
16082
|
+
cell.appendChild(nullBtn);
|
|
16083
|
+
}
|
|
16084
|
+
return cell;
|
|
16085
|
+
}
|
|
16086
|
+
function buildDataRow(i2) {
|
|
16087
|
+
const pageStart = Math.floor(i2 / PAGE_SIZE) * PAGE_SIZE;
|
|
16088
|
+
const pageRows = pageCache.get(pageStart);
|
|
16089
|
+
const rowData = pageRows ? pageRows[i2 - pageStart] : null;
|
|
16090
|
+
const row = document.createElement("div");
|
|
16091
|
+
row.className = "db-grid-row";
|
|
16092
|
+
if (i2 % 2 === 1)
|
|
16093
|
+
row.classList.add("alt");
|
|
16094
|
+
if (i2 === selectedRowIndex)
|
|
16095
|
+
row.classList.add("selected");
|
|
16096
|
+
const rowIndex = i2;
|
|
16097
|
+
const rowNum = document.createElement("div");
|
|
16098
|
+
rowNum.className = "db-grid-cell db-grid-rownum";
|
|
16099
|
+
rowNum.textContent = String(i2 + 1);
|
|
16100
|
+
row.appendChild(rowNum);
|
|
16101
|
+
if (!rowData) {
|
|
16102
|
+
for (let c2 = 0;c2 < columnNames.length; c2++) {
|
|
16103
|
+
const cell = document.createElement("div");
|
|
16104
|
+
cell.className = "db-grid-cell loading";
|
|
16105
|
+
cell.style.width = `${getColWidth(columnNames[c2])}px`;
|
|
16106
|
+
cell.textContent = "…";
|
|
16107
|
+
row.appendChild(cell);
|
|
16108
|
+
}
|
|
16109
|
+
return row;
|
|
16110
|
+
}
|
|
16111
|
+
if (editMode) {
|
|
16112
|
+
const key = rowKeyFor(rowData);
|
|
16113
|
+
const editableRow = key !== null;
|
|
16114
|
+
if (key !== null && pendingDeletes.has(key)) {
|
|
16115
|
+
row.classList.add("db-grid-row-deleted");
|
|
16116
|
+
}
|
|
16117
|
+
if (editableRow) {
|
|
16118
|
+
rowNum.classList.add("db-grid-rownum-deletable");
|
|
16119
|
+
rowNum.title = text2().edit.deleteRow;
|
|
16120
|
+
rowNum.style.cursor = "pointer";
|
|
16121
|
+
rowNum.addEventListener("click", (e2) => {
|
|
16122
|
+
e2.stopPropagation();
|
|
16123
|
+
const deleted = toggleDelete(rowData);
|
|
16124
|
+
row.classList.toggle("db-grid-row-deleted", deleted);
|
|
16125
|
+
});
|
|
16126
|
+
}
|
|
16127
|
+
const pendingForRow = key !== null ? pendingEdits.get(key) : undefined;
|
|
16128
|
+
if ((pendingForRow?.edits.size ?? 0) > 0) {
|
|
16129
|
+
row.classList.add("db-grid-row-edited");
|
|
16130
|
+
}
|
|
16131
|
+
for (let c2 = 0;c2 < columnNames.length; c2++) {
|
|
16132
|
+
const original = rowData[c2];
|
|
16133
|
+
const originalShown = original === null ? null : original instanceof Uint8Array ? formatValue3(original) : String(original);
|
|
16134
|
+
const hasOverride = pendingForRow?.edits.has(c2) ?? false;
|
|
16135
|
+
const shown = hasOverride ? pendingForRow?.edits.get(c2) ?? null : originalShown;
|
|
16136
|
+
const ro = !editableRow || columns[c2]?.primaryKey === true || original instanceof Uint8Array;
|
|
16137
|
+
const cellColIndex = c2;
|
|
16138
|
+
const cellColName = columnNames[c2];
|
|
16139
|
+
const cellValue = original;
|
|
16140
|
+
const rowValues = rowData;
|
|
16141
|
+
const fkClickable = fkColumns.has(cellColName) && (!embedded || !!callbacks.onForeignKeyCellClick);
|
|
16142
|
+
const isEditingThisCell = !ro && i2 === editingCellRow && c2 === editingCellCol;
|
|
16143
|
+
const activate = () => {
|
|
16144
|
+
selectedRowIndex = rowIndex;
|
|
16145
|
+
body.querySelectorAll(".db-grid-row.selected").forEach((r2) => {
|
|
16146
|
+
r2.classList.remove("selected");
|
|
16147
|
+
});
|
|
16148
|
+
row.classList.add("selected");
|
|
16149
|
+
setActiveCell(rowIndex, cellColIndex);
|
|
16150
|
+
if (fkClickable) {
|
|
16151
|
+
if (embedded) {
|
|
16152
|
+
callbacks.onForeignKeyCellClick?.(currentTable, columnNames, rowValues, cellColName);
|
|
16153
|
+
} else {
|
|
16154
|
+
openRelatedForRow(currentTable, columnNames, rowValues, cellColName);
|
|
16155
|
+
}
|
|
16156
|
+
} else {
|
|
16157
|
+
showCellDetail(cellColIndex, cellValue);
|
|
16158
|
+
}
|
|
16159
|
+
};
|
|
16160
|
+
const enterEdit = () => {
|
|
16161
|
+
editingCellRow = i2;
|
|
16162
|
+
editingCellCol = cellColIndex;
|
|
16163
|
+
renderViewport();
|
|
16164
|
+
requestAnimationFrame(() => {
|
|
16165
|
+
const inp = body.querySelector(`.db-grid-cell-input[data-edit-row="${i2}"][data-edit-col="${cellColIndex}"]`);
|
|
16166
|
+
if (!inp)
|
|
16167
|
+
return;
|
|
16168
|
+
inp.focus?.();
|
|
16169
|
+
try {
|
|
16170
|
+
inp.setSelectionRange?.(inp.value.length, inp.value.length);
|
|
16171
|
+
} catch {}
|
|
16172
|
+
});
|
|
16173
|
+
};
|
|
16174
|
+
const exitEdit = () => {
|
|
16175
|
+
if (editingCellRow === i2 && editingCellCol === cellColIndex) {
|
|
16176
|
+
editingCellRow = -1;
|
|
16177
|
+
editingCellCol = -1;
|
|
16178
|
+
renderViewport();
|
|
16179
|
+
}
|
|
16180
|
+
};
|
|
16181
|
+
const cellEl = buildEditableCell(c2, shown, {
|
|
16182
|
+
readonly: ro,
|
|
16183
|
+
nullable: columns[c2]?.nullable === true,
|
|
16184
|
+
editing: isEditingThisCell,
|
|
16185
|
+
rowIndex: i2,
|
|
16186
|
+
onChange: (v) => recordEdit(rowData, c2, v),
|
|
16187
|
+
onActivate: activate,
|
|
16188
|
+
onEnterEdit: enterEdit,
|
|
16189
|
+
onExitEdit: exitEdit
|
|
16190
|
+
});
|
|
16191
|
+
if (fkClickable) {
|
|
16192
|
+
cellEl.classList.add("db-grid-cell-fk");
|
|
16193
|
+
}
|
|
16194
|
+
if (hasOverride) {
|
|
16195
|
+
cellEl.classList.add("db-grid-cell-edited");
|
|
16196
|
+
}
|
|
16197
|
+
if (!isEditingThisCell && i2 === activeCellRowIndex && c2 === activeCellColIndex) {
|
|
16198
|
+
cellEl.classList.add("db-grid-cell-active");
|
|
16199
|
+
}
|
|
16200
|
+
row.appendChild(cellEl);
|
|
16201
|
+
}
|
|
16202
|
+
return row;
|
|
16203
|
+
}
|
|
16204
|
+
row.addEventListener("click", () => {
|
|
16205
|
+
selectedRowIndex = rowIndex;
|
|
16206
|
+
body.querySelectorAll(".db-grid-row.selected").forEach((r2) => {
|
|
16207
|
+
r2.classList.remove("selected");
|
|
16208
|
+
});
|
|
16209
|
+
row.classList.add("selected");
|
|
16210
|
+
});
|
|
16211
|
+
for (let c2 = 0;c2 < columnNames.length; c2++) {
|
|
16212
|
+
const cell = document.createElement("div");
|
|
16213
|
+
cell.className = "db-grid-cell";
|
|
16214
|
+
cell.style.width = `${getColWidth(columnNames[c2])}px`;
|
|
16215
|
+
const val = rowData[c2];
|
|
16216
|
+
cell.textContent = formatValue3(val);
|
|
16217
|
+
if (val === null)
|
|
16218
|
+
cell.classList.add("null");
|
|
16219
|
+
else if (val instanceof Uint8Array)
|
|
16220
|
+
cell.classList.add("blob");
|
|
16221
|
+
else if (typeof val === "string" && val === "")
|
|
16222
|
+
cell.classList.add("empty");
|
|
16223
|
+
cell.style.cursor = "pointer";
|
|
16224
|
+
const cellValue = val;
|
|
16225
|
+
const cellColIndex = c2;
|
|
16226
|
+
const cellColName = columnNames[c2];
|
|
16227
|
+
const rowValues = rowData;
|
|
16228
|
+
const fkClickable = fkColumns.has(cellColName) && (!embedded || !!callbacks.onForeignKeyCellClick);
|
|
16229
|
+
if (fkClickable) {
|
|
16230
|
+
cell.classList.add("db-grid-cell-fk");
|
|
16231
|
+
}
|
|
16232
|
+
cell.addEventListener("click", (e2) => {
|
|
16233
|
+
e2.stopPropagation();
|
|
16234
|
+
selectedRowIndex = rowIndex;
|
|
16235
|
+
body.querySelectorAll(".db-grid-row.selected").forEach((r2) => {
|
|
16236
|
+
r2.classList.remove("selected");
|
|
16237
|
+
});
|
|
16238
|
+
row.classList.add("selected");
|
|
16239
|
+
setActiveCell(rowIndex, cellColIndex);
|
|
16240
|
+
if (fkClickable) {
|
|
16241
|
+
if (embedded) {
|
|
16242
|
+
callbacks.onForeignKeyCellClick?.(currentTable, columnNames, rowValues, cellColName);
|
|
16243
|
+
} else {
|
|
16244
|
+
openRelatedForRow(currentTable, columnNames, rowValues, cellColName);
|
|
16245
|
+
}
|
|
16246
|
+
} else {
|
|
16247
|
+
showCellDetail(cellColIndex, cellValue);
|
|
16248
|
+
}
|
|
16249
|
+
});
|
|
16250
|
+
if (i2 === activeCellRowIndex && c2 === activeCellColIndex) {
|
|
16251
|
+
cell.classList.add("db-grid-cell-active");
|
|
16252
|
+
}
|
|
16253
|
+
row.appendChild(cell);
|
|
16254
|
+
}
|
|
16255
|
+
return row;
|
|
16256
|
+
}
|
|
16257
|
+
function buildDraftRow(draftIndex) {
|
|
16258
|
+
const draft = draftRows[draftIndex];
|
|
16259
|
+
const row = document.createElement("div");
|
|
16260
|
+
row.className = "db-grid-row db-grid-row-draft";
|
|
16261
|
+
const rowNum = document.createElement("div");
|
|
16262
|
+
rowNum.className = "db-grid-cell db-grid-rownum db-grid-rownum-draft";
|
|
16263
|
+
rowNum.textContent = "+";
|
|
16264
|
+
rowNum.title = text2().edit.newRow;
|
|
16265
|
+
rowNum.style.cursor = "pointer";
|
|
16266
|
+
rowNum.addEventListener("click", (e2) => {
|
|
16267
|
+
e2.stopPropagation();
|
|
16268
|
+
draftRows.splice(draftIndex, 1);
|
|
16269
|
+
syncSpacer();
|
|
16270
|
+
refreshEditButtons();
|
|
16271
|
+
showPendingStatus();
|
|
16272
|
+
renderViewport();
|
|
16273
|
+
});
|
|
16274
|
+
row.appendChild(rowNum);
|
|
16275
|
+
for (let c2 = 0;c2 < columnNames.length; c2++) {
|
|
16276
|
+
const dv = draft.has(c2) ? draft.get(c2) : "";
|
|
16277
|
+
row.appendChild(buildEditableCell(c2, dv, {
|
|
16278
|
+
readonly: false,
|
|
16279
|
+
nullable: columns[c2]?.nullable === true,
|
|
16280
|
+
editing: true,
|
|
16281
|
+
rowIndex: totalRows + draftIndex,
|
|
16282
|
+
onChange: (v) => {
|
|
16283
|
+
if (v === "")
|
|
16284
|
+
draft.delete(c2);
|
|
16285
|
+
else
|
|
16286
|
+
draft.set(c2, v);
|
|
16287
|
+
refreshEditButtons();
|
|
16288
|
+
showPendingStatus();
|
|
16289
|
+
}
|
|
16290
|
+
}));
|
|
16291
|
+
}
|
|
16292
|
+
return row;
|
|
16293
|
+
}
|
|
14816
16294
|
function renderViewport() {
|
|
14817
16295
|
cancelAnimationFrame(rafId);
|
|
14818
16296
|
rafId = requestAnimationFrame(() => {
|
|
16297
|
+
if (isComposing)
|
|
16298
|
+
return;
|
|
14819
16299
|
const scrollTop = viewport.scrollTop;
|
|
14820
16300
|
const viewHeight = viewport.clientHeight;
|
|
14821
16301
|
const startRow = Math.max(0, Math.floor(scrollTop / ROW_HEIGHT) - OVERSCAN);
|
|
14822
16302
|
renderStartRow = startRow;
|
|
14823
|
-
const endRow = Math.min(
|
|
14824
|
-
const
|
|
14825
|
-
|
|
14826
|
-
|
|
14827
|
-
|
|
16303
|
+
const endRow = Math.min(displayRowCount(), Math.ceil((scrollTop + viewHeight) / ROW_HEIGHT) + OVERSCAN);
|
|
16304
|
+
const dataEnd = Math.min(endRow, totalRows);
|
|
16305
|
+
if (startRow < totalRows && dataEnd > startRow) {
|
|
16306
|
+
const neededPageStart = Math.floor(startRow / PAGE_SIZE) * PAGE_SIZE;
|
|
16307
|
+
const neededPageEnd = Math.floor((dataEnd - 1) / PAGE_SIZE) * PAGE_SIZE;
|
|
16308
|
+
for (let p2 = neededPageStart;p2 <= neededPageEnd; p2 += PAGE_SIZE) {
|
|
16309
|
+
ensurePage(p2);
|
|
16310
|
+
}
|
|
16311
|
+
}
|
|
16312
|
+
const active = document.activeElement;
|
|
16313
|
+
let focusRestore = null;
|
|
16314
|
+
if (active?.classList?.contains("db-grid-cell-input") && body.contains(active)) {
|
|
16315
|
+
focusRestore = {
|
|
16316
|
+
row: active.dataset?.editRow ?? "",
|
|
16317
|
+
col: active.dataset?.editCol ?? "",
|
|
16318
|
+
start: active.selectionStart ?? null,
|
|
16319
|
+
end: active.selectionEnd ?? null
|
|
16320
|
+
};
|
|
14828
16321
|
}
|
|
14829
16322
|
body.innerHTML = "";
|
|
14830
16323
|
body.style.transform = `translateY(${startRow * ROW_HEIGHT}px)`;
|
|
14831
16324
|
for (let i2 = startRow;i2 < endRow; i2++) {
|
|
14832
|
-
|
|
14833
|
-
|
|
14834
|
-
|
|
14835
|
-
const
|
|
14836
|
-
|
|
14837
|
-
|
|
14838
|
-
|
|
14839
|
-
|
|
14840
|
-
|
|
14841
|
-
const rowIndex = i2;
|
|
14842
|
-
row.addEventListener("click", () => {
|
|
14843
|
-
selectedRowIndex = rowIndex;
|
|
14844
|
-
body.querySelectorAll(".db-grid-row.selected").forEach((r2) => {
|
|
14845
|
-
r2.classList.remove("selected");
|
|
14846
|
-
});
|
|
14847
|
-
row.classList.add("selected");
|
|
14848
|
-
});
|
|
14849
|
-
const rowNum = document.createElement("div");
|
|
14850
|
-
rowNum.className = "db-grid-cell db-grid-rownum";
|
|
14851
|
-
rowNum.textContent = String(i2 + 1);
|
|
14852
|
-
row.appendChild(rowNum);
|
|
14853
|
-
if (rowData) {
|
|
14854
|
-
for (let c2 = 0;c2 < columnNames.length; c2++) {
|
|
14855
|
-
const cell = document.createElement("div");
|
|
14856
|
-
cell.className = "db-grid-cell";
|
|
14857
|
-
cell.style.width = `${getColWidth(columnNames[c2])}px`;
|
|
14858
|
-
const val = rowData[c2];
|
|
14859
|
-
cell.textContent = formatValue3(val);
|
|
14860
|
-
if (val === null)
|
|
14861
|
-
cell.classList.add("null");
|
|
14862
|
-
else if (val instanceof Uint8Array)
|
|
14863
|
-
cell.classList.add("blob");
|
|
14864
|
-
else if (typeof val === "string" && val === "")
|
|
14865
|
-
cell.classList.add("empty");
|
|
14866
|
-
cell.style.cursor = "pointer";
|
|
14867
|
-
const cellValue = val;
|
|
14868
|
-
const cellColIndex = c2;
|
|
14869
|
-
const cellColName = columnNames[c2];
|
|
14870
|
-
const rowValues = rowData;
|
|
14871
|
-
const fkClickable = fkColumns.has(cellColName) && (!embedded || !!callbacks.onForeignKeyCellClick);
|
|
14872
|
-
if (fkClickable) {
|
|
14873
|
-
cell.classList.add("db-grid-cell-fk");
|
|
14874
|
-
}
|
|
14875
|
-
cell.addEventListener("click", (e2) => {
|
|
14876
|
-
e2.stopPropagation();
|
|
14877
|
-
selectedRowIndex = rowIndex;
|
|
14878
|
-
body.querySelectorAll(".db-grid-row.selected").forEach((r2) => {
|
|
14879
|
-
r2.classList.remove("selected");
|
|
14880
|
-
});
|
|
14881
|
-
row.classList.add("selected");
|
|
14882
|
-
setActiveCell(rowIndex, cellColIndex);
|
|
14883
|
-
if (fkClickable) {
|
|
14884
|
-
if (embedded) {
|
|
14885
|
-
callbacks.onForeignKeyCellClick?.(currentTable, columnNames, rowValues, cellColName);
|
|
14886
|
-
} else {
|
|
14887
|
-
openRelatedForRow(currentTable, columnNames, rowValues, cellColName);
|
|
14888
|
-
}
|
|
14889
|
-
} else {
|
|
14890
|
-
showCellDetail(cellColIndex, cellValue);
|
|
14891
|
-
}
|
|
14892
|
-
});
|
|
14893
|
-
if (i2 === activeCellRowIndex && c2 === activeCellColIndex) {
|
|
14894
|
-
cell.classList.add("db-grid-cell-active");
|
|
14895
|
-
}
|
|
14896
|
-
row.appendChild(cell);
|
|
14897
|
-
}
|
|
14898
|
-
} else {
|
|
14899
|
-
for (let c2 = 0;c2 < columnNames.length; c2++) {
|
|
14900
|
-
const cell = document.createElement("div");
|
|
14901
|
-
cell.className = "db-grid-cell loading";
|
|
14902
|
-
cell.style.width = `${getColWidth(columnNames[c2])}px`;
|
|
14903
|
-
cell.textContent = "…";
|
|
14904
|
-
row.appendChild(cell);
|
|
14905
|
-
}
|
|
16325
|
+
body.appendChild(i2 < totalRows ? buildDataRow(i2) : buildDraftRow(i2 - totalRows));
|
|
16326
|
+
}
|
|
16327
|
+
if (focusRestore) {
|
|
16328
|
+
const next = body.querySelector(`.db-grid-cell-input[data-edit-row="${focusRestore.row}"][data-edit-col="${focusRestore.col}"]`);
|
|
16329
|
+
if (next) {
|
|
16330
|
+
next.focus?.();
|
|
16331
|
+
try {
|
|
16332
|
+
next.setSelectionRange?.(focusRestore.start ?? next.value.length, focusRestore.end ?? next.value.length);
|
|
16333
|
+
} catch {}
|
|
14906
16334
|
}
|
|
14907
|
-
body.appendChild(row);
|
|
14908
16335
|
}
|
|
14909
16336
|
});
|
|
14910
16337
|
}
|
|
@@ -14978,9 +16405,11 @@ ${frontmatter.yaml}
|
|
|
14978
16405
|
}
|
|
14979
16406
|
rebuildFkColumnsForCurrentTable();
|
|
14980
16407
|
loadColWidths();
|
|
14981
|
-
|
|
16408
|
+
syncSpacer();
|
|
14982
16409
|
renderHeader();
|
|
14983
16410
|
updateStatus();
|
|
16411
|
+
refreshEditButtons();
|
|
16412
|
+
showPendingStatus();
|
|
14984
16413
|
if (!initialData) {
|
|
14985
16414
|
ensurePage(0);
|
|
14986
16415
|
} else {
|
|
@@ -15045,11 +16474,22 @@ ${frontmatter.yaml}
|
|
|
15045
16474
|
renderViewport();
|
|
15046
16475
|
};
|
|
15047
16476
|
viewport.addEventListener("scroll", onViewportScroll, { passive: true });
|
|
16477
|
+
const onCompositionStart = () => {
|
|
16478
|
+
isComposing = true;
|
|
16479
|
+
};
|
|
16480
|
+
const onCompositionEnd = () => {
|
|
16481
|
+
isComposing = false;
|
|
16482
|
+
renderViewport();
|
|
16483
|
+
};
|
|
16484
|
+
body.addEventListener("compositionstart", onCompositionStart);
|
|
16485
|
+
body.addEventListener("compositionend", onCompositionEnd);
|
|
15048
16486
|
function destroy() {
|
|
15049
16487
|
clear();
|
|
15050
16488
|
embeddedGrid?.destroy();
|
|
15051
16489
|
embeddedGrid = null;
|
|
15052
16490
|
viewport.removeEventListener("scroll", onViewportScroll);
|
|
16491
|
+
body.removeEventListener("compositionstart", onCompositionStart);
|
|
16492
|
+
body.removeEventListener("compositionend", onCompositionEnd);
|
|
15053
16493
|
detailResizeCleanup?.();
|
|
15054
16494
|
}
|
|
15055
16495
|
function localize() {
|
|
@@ -15057,6 +16497,10 @@ ${frontmatter.yaml}
|
|
|
15057
16497
|
filterInput.placeholder = t2.grid.searchPlaceholder;
|
|
15058
16498
|
exportBtn.title = t2.grid.exportAction;
|
|
15059
16499
|
exportBtn.setAttribute("aria-label", t2.grid.exportAction);
|
|
16500
|
+
newRowBtn.textContent = t2.edit.newRow;
|
|
16501
|
+
commitBtn.textContent = t2.edit.commit;
|
|
16502
|
+
discardBtn.textContent = t2.edit.discard;
|
|
16503
|
+
showPendingStatus();
|
|
15060
16504
|
if (relatedEmptyEl)
|
|
15061
16505
|
relatedEmptyEl.textContent = t2.grid.relatedEmpty;
|
|
15062
16506
|
if (currentTable) {
|
|
@@ -15077,6 +16521,250 @@ ${frontmatter.yaml}
|
|
|
15077
16521
|
fkColumns.add(fk.toColumn);
|
|
15078
16522
|
}
|
|
15079
16523
|
}
|
|
16524
|
+
function isEditable() {
|
|
16525
|
+
return !embedded && callbacks.getEditable?.() === true && typeof callbacks.applyMutations === "function";
|
|
16526
|
+
}
|
|
16527
|
+
function pkColumnIndices() {
|
|
16528
|
+
const idx = [];
|
|
16529
|
+
for (let i2 = 0;i2 < columns.length; i2++) {
|
|
16530
|
+
if (columns[i2].primaryKey)
|
|
16531
|
+
idx.push(i2);
|
|
16532
|
+
}
|
|
16533
|
+
return idx;
|
|
16534
|
+
}
|
|
16535
|
+
function hasPrimaryKey() {
|
|
16536
|
+
return columns.some((c2) => c2.primaryKey);
|
|
16537
|
+
}
|
|
16538
|
+
function rowKeyFor(rowData) {
|
|
16539
|
+
const idx = pkColumnIndices();
|
|
16540
|
+
if (idx.length === 0)
|
|
16541
|
+
return null;
|
|
16542
|
+
const parts = [];
|
|
16543
|
+
for (const i2 of idx) {
|
|
16544
|
+
const v = rowData[i2];
|
|
16545
|
+
if (v === null || v instanceof Uint8Array)
|
|
16546
|
+
return null;
|
|
16547
|
+
parts.push(String(v));
|
|
16548
|
+
}
|
|
16549
|
+
return JSON.stringify(parts);
|
|
16550
|
+
}
|
|
16551
|
+
function pkCellsFor(rowData) {
|
|
16552
|
+
return pkColumnIndices().map((i2) => ({
|
|
16553
|
+
column: columnNames[i2],
|
|
16554
|
+
value: String(rowData[i2])
|
|
16555
|
+
}));
|
|
16556
|
+
}
|
|
16557
|
+
function nonEmptyDraftCount() {
|
|
16558
|
+
return draftRows.filter((d2) => Array.from(d2.values()).some((v) => v === null || v !== "")).length;
|
|
16559
|
+
}
|
|
16560
|
+
function pendingCount() {
|
|
16561
|
+
let updates = 0;
|
|
16562
|
+
for (const [key, entry] of pendingEdits) {
|
|
16563
|
+
if (pendingDeletes.has(key))
|
|
16564
|
+
continue;
|
|
16565
|
+
if (entry.edits.size > 0)
|
|
16566
|
+
updates++;
|
|
16567
|
+
}
|
|
16568
|
+
return updates + pendingDeletes.size + nonEmptyDraftCount();
|
|
16569
|
+
}
|
|
16570
|
+
function hasPending() {
|
|
16571
|
+
return pendingCount() > 0;
|
|
16572
|
+
}
|
|
16573
|
+
function clearPending() {
|
|
16574
|
+
pendingEdits.clear();
|
|
16575
|
+
pendingDeletes.clear();
|
|
16576
|
+
draftRows = [];
|
|
16577
|
+
}
|
|
16578
|
+
function displayRowCount() {
|
|
16579
|
+
return totalRows + (editMode ? draftRows.length : 0);
|
|
16580
|
+
}
|
|
16581
|
+
function syncSpacer() {
|
|
16582
|
+
spacer.style.height = `${displayRowCount() * ROW_HEIGHT}px`;
|
|
16583
|
+
}
|
|
16584
|
+
function setEditStatus(message) {
|
|
16585
|
+
editStatus.textContent = message;
|
|
16586
|
+
}
|
|
16587
|
+
function refreshEditButtons() {
|
|
16588
|
+
const editable = isEditable();
|
|
16589
|
+
editWrap.hidden = !editable;
|
|
16590
|
+
if (!editable) {
|
|
16591
|
+
notifyEditableIfChanged();
|
|
16592
|
+
return;
|
|
16593
|
+
}
|
|
16594
|
+
const inert = !editMode;
|
|
16595
|
+
setControlVisibility(newRowBtn, !inert);
|
|
16596
|
+
setControlVisibility(commitBtn, !inert);
|
|
16597
|
+
setControlVisibility(discardBtn, !inert);
|
|
16598
|
+
setControlVisibility(editStatus, !inert);
|
|
16599
|
+
const count = pendingCount();
|
|
16600
|
+
commitBtn.disabled = count === 0;
|
|
16601
|
+
discardBtn.disabled = count === 0;
|
|
16602
|
+
notifyEditableIfChanged();
|
|
16603
|
+
}
|
|
16604
|
+
function setControlVisibility(el2, visible) {
|
|
16605
|
+
el2.style.visibility = visible ? "" : "hidden";
|
|
16606
|
+
if ("tabIndex" in el2) {
|
|
16607
|
+
el2.tabIndex = visible ? 0 : -1;
|
|
16608
|
+
}
|
|
16609
|
+
el2.setAttribute("aria-hidden", visible ? "false" : "true");
|
|
16610
|
+
}
|
|
16611
|
+
const editableListeners = new Set;
|
|
16612
|
+
const editModeListeners = new Set;
|
|
16613
|
+
let lastEditableNotified = null;
|
|
16614
|
+
function notifyEditableIfChanged() {
|
|
16615
|
+
const v = isEditable();
|
|
16616
|
+
if (v === lastEditableNotified)
|
|
16617
|
+
return;
|
|
16618
|
+
lastEditableNotified = v;
|
|
16619
|
+
for (const fn of editableListeners)
|
|
16620
|
+
fn(v);
|
|
16621
|
+
}
|
|
16622
|
+
function notifyEditMode() {
|
|
16623
|
+
for (const fn of editModeListeners)
|
|
16624
|
+
fn(editMode);
|
|
16625
|
+
}
|
|
16626
|
+
function onEditableChange(listener) {
|
|
16627
|
+
editableListeners.add(listener);
|
|
16628
|
+
listener(isEditable());
|
|
16629
|
+
return () => editableListeners.delete(listener);
|
|
16630
|
+
}
|
|
16631
|
+
function onEditModeChange(listener) {
|
|
16632
|
+
editModeListeners.add(listener);
|
|
16633
|
+
listener(editMode);
|
|
16634
|
+
return () => editModeListeners.delete(listener);
|
|
16635
|
+
}
|
|
16636
|
+
function showPendingStatus() {
|
|
16637
|
+
const count = pendingCount();
|
|
16638
|
+
setEditStatus(editMode && count > 0 ? text2().edit.pending(count) : editMode && !hasPrimaryKey() ? text2().edit.noPrimaryKey : "");
|
|
16639
|
+
}
|
|
16640
|
+
function recordEdit(rowData, colIndex, value) {
|
|
16641
|
+
const key = rowKeyFor(rowData);
|
|
16642
|
+
if (key === null)
|
|
16643
|
+
return;
|
|
16644
|
+
let entry = pendingEdits.get(key);
|
|
16645
|
+
if (!entry) {
|
|
16646
|
+
entry = { pk: pkCellsFor(rowData), edits: new Map };
|
|
16647
|
+
pendingEdits.set(key, entry);
|
|
16648
|
+
}
|
|
16649
|
+
const original = rowData[colIndex];
|
|
16650
|
+
const originalStr = original === null || original instanceof Uint8Array ? null : String(original);
|
|
16651
|
+
if (value === originalStr) {
|
|
16652
|
+
entry.edits.delete(colIndex);
|
|
16653
|
+
if (entry.edits.size === 0)
|
|
16654
|
+
pendingEdits.delete(key);
|
|
16655
|
+
} else {
|
|
16656
|
+
entry.edits.set(colIndex, value);
|
|
16657
|
+
}
|
|
16658
|
+
refreshEditButtons();
|
|
16659
|
+
showPendingStatus();
|
|
16660
|
+
}
|
|
16661
|
+
function toggleDelete(rowData) {
|
|
16662
|
+
const key = rowKeyFor(rowData);
|
|
16663
|
+
if (key === null)
|
|
16664
|
+
return false;
|
|
16665
|
+
if (pendingDeletes.has(key))
|
|
16666
|
+
pendingDeletes.delete(key);
|
|
16667
|
+
else
|
|
16668
|
+
pendingDeletes.set(key, pkCellsFor(rowData));
|
|
16669
|
+
refreshEditButtons();
|
|
16670
|
+
showPendingStatus();
|
|
16671
|
+
return pendingDeletes.has(key);
|
|
16672
|
+
}
|
|
16673
|
+
function buildMutations() {
|
|
16674
|
+
const muts = [];
|
|
16675
|
+
for (const pk of pendingDeletes.values()) {
|
|
16676
|
+
muts.push({ kind: "delete", pk });
|
|
16677
|
+
}
|
|
16678
|
+
for (const [key, entry] of pendingEdits) {
|
|
16679
|
+
if (pendingDeletes.has(key))
|
|
16680
|
+
continue;
|
|
16681
|
+
if (entry.edits.size === 0)
|
|
16682
|
+
continue;
|
|
16683
|
+
const values = Array.from(entry.edits).map(([c2, v]) => ({
|
|
16684
|
+
column: columnNames[c2],
|
|
16685
|
+
value: v
|
|
16686
|
+
}));
|
|
16687
|
+
muts.push({ kind: "update", pk: entry.pk, values });
|
|
16688
|
+
}
|
|
16689
|
+
for (const draft of draftRows) {
|
|
16690
|
+
const values = Array.from(draft).filter(([, v]) => v === null || v !== "").map(([c2, v]) => ({ column: columnNames[c2], value: v }));
|
|
16691
|
+
if (values.length > 0)
|
|
16692
|
+
muts.push({ kind: "insert", values });
|
|
16693
|
+
}
|
|
16694
|
+
return muts;
|
|
16695
|
+
}
|
|
16696
|
+
async function setEditMode(on) {
|
|
16697
|
+
if (on === editMode)
|
|
16698
|
+
return true;
|
|
16699
|
+
if (!on && hasPending()) {
|
|
16700
|
+
const ok = await showConfirmDialog({
|
|
16701
|
+
body: text2().edit.confirmDiscard,
|
|
16702
|
+
confirmLabel: text2().edit.discard,
|
|
16703
|
+
danger: true
|
|
16704
|
+
});
|
|
16705
|
+
if (!ok)
|
|
16706
|
+
return false;
|
|
16707
|
+
}
|
|
16708
|
+
editMode = on;
|
|
16709
|
+
if (!on)
|
|
16710
|
+
clearPending();
|
|
16711
|
+
notifyEditMode();
|
|
16712
|
+
editingCellRow = -1;
|
|
16713
|
+
editingCellCol = -1;
|
|
16714
|
+
syncSpacer();
|
|
16715
|
+
refreshEditButtons();
|
|
16716
|
+
showPendingStatus();
|
|
16717
|
+
renderViewport();
|
|
16718
|
+
}
|
|
16719
|
+
async function commitPending() {
|
|
16720
|
+
if (!callbacks.applyMutations)
|
|
16721
|
+
return;
|
|
16722
|
+
const mutations = buildMutations();
|
|
16723
|
+
if (mutations.length === 0)
|
|
16724
|
+
return;
|
|
16725
|
+
setEditStatus(text2().edit.committing);
|
|
16726
|
+
commitBtn.disabled = true;
|
|
16727
|
+
discardBtn.disabled = true;
|
|
16728
|
+
try {
|
|
16729
|
+
await callbacks.applyMutations(mutations);
|
|
16730
|
+
clearPending();
|
|
16731
|
+
await invalidateData();
|
|
16732
|
+
syncSpacer();
|
|
16733
|
+
setEditStatus("");
|
|
16734
|
+
} catch (err) {
|
|
16735
|
+
setEditStatus(text2().edit.commitError(err instanceof Error ? err.message : String(err)));
|
|
16736
|
+
} finally {
|
|
16737
|
+
refreshEditButtons();
|
|
16738
|
+
}
|
|
16739
|
+
}
|
|
16740
|
+
newRowBtn.addEventListener("click", () => {
|
|
16741
|
+
draftRows.push(new Map);
|
|
16742
|
+
syncSpacer();
|
|
16743
|
+
refreshEditButtons();
|
|
16744
|
+
showPendingStatus();
|
|
16745
|
+
renderViewport();
|
|
16746
|
+
viewport.scrollTop = viewport.scrollHeight;
|
|
16747
|
+
renderViewport();
|
|
16748
|
+
});
|
|
16749
|
+
discardBtn.addEventListener("click", async () => {
|
|
16750
|
+
if (!hasPending())
|
|
16751
|
+
return;
|
|
16752
|
+
const ok = await showConfirmDialog({
|
|
16753
|
+
body: text2().edit.confirmDiscard,
|
|
16754
|
+
confirmLabel: text2().edit.discard,
|
|
16755
|
+
danger: true
|
|
16756
|
+
});
|
|
16757
|
+
if (!ok)
|
|
16758
|
+
return;
|
|
16759
|
+
clearPending();
|
|
16760
|
+
syncSpacer();
|
|
16761
|
+
refreshEditButtons();
|
|
16762
|
+
setEditStatus("");
|
|
16763
|
+
renderViewport();
|
|
16764
|
+
});
|
|
16765
|
+
commitBtn.addEventListener("click", () => {
|
|
16766
|
+
commitPending();
|
|
16767
|
+
});
|
|
15080
16768
|
function refreshForeignKeys() {
|
|
15081
16769
|
rebuildFkColumnsForCurrentTable();
|
|
15082
16770
|
if (currentTable) {
|
|
@@ -15094,7 +16782,12 @@ ${frontmatter.yaml}
|
|
|
15094
16782
|
clear,
|
|
15095
16783
|
destroy,
|
|
15096
16784
|
localize,
|
|
15097
|
-
refreshForeignKeys
|
|
16785
|
+
refreshForeignKeys,
|
|
16786
|
+
setEditMode,
|
|
16787
|
+
getEditMode: () => editMode,
|
|
16788
|
+
isEditable,
|
|
16789
|
+
onEditableChange,
|
|
16790
|
+
onEditModeChange
|
|
15098
16791
|
};
|
|
15099
16792
|
}
|
|
15100
16793
|
function formatValue3(value) {
|
|
@@ -15476,7 +17169,7 @@ ${frontmatter.yaml}
|
|
|
15476
17169
|
const historyVisible = sqlMode && userPrefersHistoryOpen;
|
|
15477
17170
|
return {
|
|
15478
17171
|
toolsHidden: !sqlMode,
|
|
15479
|
-
|
|
17172
|
+
historyTabStripHidden: !sqlMode,
|
|
15480
17173
|
historyPaneHidden: !historyVisible,
|
|
15481
17174
|
historyResizerHidden: !historyVisible,
|
|
15482
17175
|
tableListHidden: !sqlMode,
|
|
@@ -15651,25 +17344,7 @@ ${frontmatter.yaml}
|
|
|
15651
17344
|
snapshotView.refresh();
|
|
15652
17345
|
}
|
|
15653
17346
|
});
|
|
15654
|
-
const inferFkToggle = makePrefToggle({
|
|
15655
|
-
title: "Infer FK from Rails-style <name>_id → <names>.id",
|
|
15656
|
-
label: "Rails FK 推測",
|
|
15657
|
-
pathD: ICON_PATH_INFER_FK
|
|
15658
|
-
});
|
|
15659
|
-
inferFkToggle.addEventListener("click", () => {
|
|
15660
|
-
const next = !outerDeps.getDbUiPref("inferFkRails", false);
|
|
15661
|
-
outerDeps.setDbUiPref("inferFkRails", next);
|
|
15662
|
-
applyInferFkBtnState();
|
|
15663
|
-
grid.refreshForeignKeys();
|
|
15664
|
-
});
|
|
15665
|
-
function applyInferFkBtnState() {
|
|
15666
|
-
const on = outerDeps.getDbUiPref("inferFkRails", false);
|
|
15667
|
-
inferFkToggle.classList.toggle("active", on);
|
|
15668
|
-
inferFkToggle.setAttribute("aria-pressed", String(on));
|
|
15669
|
-
}
|
|
15670
|
-
applyInferFkBtnState();
|
|
15671
17347
|
const disposeInferFkPrefSub = outerDeps.onDbUiPrefChange(() => {
|
|
15672
|
-
applyInferFkBtnState();
|
|
15673
17348
|
grid.refreshForeignKeys();
|
|
15674
17349
|
});
|
|
15675
17350
|
reloc(() => {
|
|
@@ -15682,7 +17357,6 @@ ${frontmatter.yaml}
|
|
|
15682
17357
|
toolsSection.append(queryBtn, erBtn, searchBtn, snapshotBtn);
|
|
15683
17358
|
const prefsBar = document.createElement("div");
|
|
15684
17359
|
prefsBar.className = "db-prefs-bar";
|
|
15685
|
-
prefsBar.append(inferFkToggle);
|
|
15686
17360
|
const explorerSidebarHost = document.createElement("div");
|
|
15687
17361
|
explorerSidebarHost.className = "db-explorer-sidebar-host";
|
|
15688
17362
|
sidebar.append(toolsSection, dbToolbar, prefsBar, explorerSidebarHost, tableList.el);
|
|
@@ -15727,7 +17401,31 @@ ${frontmatter.yaml}
|
|
|
15727
17401
|
detailPanelHeightPx = h;
|
|
15728
17402
|
cb.onStateChange();
|
|
15729
17403
|
},
|
|
15730
|
-
getText: () => paneText()
|
|
17404
|
+
getText: () => paneText(),
|
|
17405
|
+
getEditable: () => isSqlKind(currentDbInfo?.kind),
|
|
17406
|
+
applyMutations: (mutations) => applyRowMutations(mutations)
|
|
17407
|
+
});
|
|
17408
|
+
const editModeToggle = makePrefToggle({
|
|
17409
|
+
title: paneText().edit.editModeTitle,
|
|
17410
|
+
label: paneText().edit.editMode,
|
|
17411
|
+
pathD: ICON_PATH_EDIT_MODE,
|
|
17412
|
+
extraClass: "db-edit-mode-toggle"
|
|
17413
|
+
});
|
|
17414
|
+
editModeToggle.hidden = true;
|
|
17415
|
+
editModeToggle.addEventListener("click", () => {
|
|
17416
|
+
grid.setEditMode(!grid.getEditMode());
|
|
17417
|
+
});
|
|
17418
|
+
prefsBar.append(editModeToggle);
|
|
17419
|
+
grid.onEditableChange((editable) => {
|
|
17420
|
+
editModeToggle.hidden = !editable;
|
|
17421
|
+
});
|
|
17422
|
+
grid.onEditModeChange((on) => {
|
|
17423
|
+
editModeToggle.classList.toggle("active", on);
|
|
17424
|
+
editModeToggle.setAttribute("aria-pressed", String(on));
|
|
17425
|
+
});
|
|
17426
|
+
reloc(() => {
|
|
17427
|
+
const e2 = paneText().edit;
|
|
17428
|
+
localizePrefToggle(editModeToggle, e2.editMode, e2.editModeTitle);
|
|
15731
17429
|
});
|
|
15732
17430
|
const queryEditor = createQueryEditor({
|
|
15733
17431
|
executeQuery: (sql) => executeQuery(sql),
|
|
@@ -15759,23 +17457,33 @@ ${frontmatter.yaml}
|
|
|
15759
17457
|
setActiveTab("query");
|
|
15760
17458
|
}
|
|
15761
17459
|
});
|
|
17460
|
+
const sessionLogView = createSessionLogView({
|
|
17461
|
+
store: outerDeps.sessionLog,
|
|
17462
|
+
getText: () => paneText(),
|
|
17463
|
+
copySqlToQuery: (sql) => {
|
|
17464
|
+
queryEditor.setSql(sql);
|
|
17465
|
+
setActiveTab("query");
|
|
17466
|
+
}
|
|
17467
|
+
});
|
|
15762
17468
|
const redisExplorer = createRedisExplorer({
|
|
15763
17469
|
onSelectionChange: () => cb.onStateChange(),
|
|
15764
|
-
getText: () => paneText()
|
|
17470
|
+
getText: () => paneText(),
|
|
17471
|
+
trackLoad: (p2) => deps.trackLoad(p2)
|
|
15765
17472
|
});
|
|
15766
17473
|
redisExplorer.el.hidden = true;
|
|
15767
17474
|
redisExplorer.sidebarSlot.hidden = true;
|
|
15768
17475
|
const esExplorer = createElasticsearchExplorer({
|
|
15769
17476
|
onSelectionChange: () => cb.onStateChange(),
|
|
15770
|
-
getText: () => paneText()
|
|
17477
|
+
getText: () => paneText(),
|
|
17478
|
+
trackLoad: (p2) => deps.trackLoad(p2)
|
|
15771
17479
|
});
|
|
15772
17480
|
esExplorer.el.hidden = true;
|
|
15773
17481
|
esExplorer.sidebarSlot.hidden = true;
|
|
15774
17482
|
const s3Explorer = createS3Explorer({
|
|
15775
17483
|
onSelectionChange: () => cb.onStateChange(),
|
|
15776
17484
|
getText: () => paneText(),
|
|
15777
|
-
|
|
15778
|
-
|
|
17485
|
+
trackLoad: (p2) => deps.trackLoad(p2),
|
|
17486
|
+
getTooltipEnabled: () => outerDeps.getDbUiPref("s3TooltipEnabled", true)
|
|
15779
17487
|
});
|
|
15780
17488
|
s3Explorer.el.hidden = true;
|
|
15781
17489
|
s3Explorer.sidebarSlot.hidden = true;
|
|
@@ -15795,7 +17503,76 @@ ${frontmatter.yaml}
|
|
|
15795
17503
|
historyPane.className = "db-history-pane";
|
|
15796
17504
|
if (initial.historyHeight)
|
|
15797
17505
|
historyPane.style.height = initial.historyHeight;
|
|
15798
|
-
|
|
17506
|
+
const historyTabContent = document.createElement("div");
|
|
17507
|
+
historyTabContent.className = "db-history-pane-content";
|
|
17508
|
+
historyTabContent.appendChild(historyView.el);
|
|
17509
|
+
const logTabContent = document.createElement("div");
|
|
17510
|
+
logTabContent.className = "db-history-pane-content";
|
|
17511
|
+
logTabContent.hidden = true;
|
|
17512
|
+
logTabContent.appendChild(sessionLogView.el);
|
|
17513
|
+
historyPane.append(historyTabContent, logTabContent);
|
|
17514
|
+
const historyDock = document.createElement("div");
|
|
17515
|
+
historyDock.className = "db-history-dock";
|
|
17516
|
+
historyDock.setAttribute("role", "tablist");
|
|
17517
|
+
const historyTabBtn = document.createElement("button");
|
|
17518
|
+
historyTabBtn.type = "button";
|
|
17519
|
+
historyTabBtn.className = "db-history-dock-tab";
|
|
17520
|
+
historyTabBtn.setAttribute("role", "tab");
|
|
17521
|
+
historyTabBtn.textContent = paneText().sessionLog.historyTabLabel;
|
|
17522
|
+
historyTabBtn.classList.add("active");
|
|
17523
|
+
historyTabBtn.setAttribute("aria-selected", "true");
|
|
17524
|
+
const logTabBtn = document.createElement("button");
|
|
17525
|
+
logTabBtn.type = "button";
|
|
17526
|
+
logTabBtn.className = "db-history-dock-tab";
|
|
17527
|
+
logTabBtn.setAttribute("role", "tab");
|
|
17528
|
+
logTabBtn.textContent = paneText().sessionLog.tabLabel;
|
|
17529
|
+
logTabBtn.setAttribute("aria-selected", "false");
|
|
17530
|
+
const historyDockSpacer = document.createElement("div");
|
|
17531
|
+
historyDockSpacer.className = "db-history-dock-spacer";
|
|
17532
|
+
const historyDockClose = document.createElement("button");
|
|
17533
|
+
historyDockClose.type = "button";
|
|
17534
|
+
historyDockClose.className = "db-history-dock-close";
|
|
17535
|
+
historyDockClose.setAttribute("aria-label", paneText().nav.queryHistory);
|
|
17536
|
+
historyDockClose.title = paneText().nav.queryHistoryTitle;
|
|
17537
|
+
historyDockClose.textContent = "×";
|
|
17538
|
+
historyDock.append(historyTabBtn, logTabBtn, historyDockSpacer, historyDockClose);
|
|
17539
|
+
let activeHistoryTab = initial.activeHistoryTab === "log" ? "log" : "history";
|
|
17540
|
+
function setActiveHistoryTab(which) {
|
|
17541
|
+
activeHistoryTab = which;
|
|
17542
|
+
const isHistory = which === "history";
|
|
17543
|
+
historyTabBtn.classList.toggle("active", isHistory);
|
|
17544
|
+
logTabBtn.classList.toggle("active", !isHistory);
|
|
17545
|
+
historyTabBtn.setAttribute("aria-selected", String(isHistory));
|
|
17546
|
+
logTabBtn.setAttribute("aria-selected", String(!isHistory));
|
|
17547
|
+
historyTabContent.hidden = !isHistory;
|
|
17548
|
+
logTabContent.hidden = isHistory;
|
|
17549
|
+
}
|
|
17550
|
+
setActiveHistoryTab(activeHistoryTab);
|
|
17551
|
+
function handleHistoryTabClick(which) {
|
|
17552
|
+
if (userPrefersHistoryOpen && activeHistoryTab === which) {
|
|
17553
|
+
userPrefersHistoryOpen = false;
|
|
17554
|
+
} else {
|
|
17555
|
+
setActiveHistoryTab(which);
|
|
17556
|
+
userPrefersHistoryOpen = true;
|
|
17557
|
+
}
|
|
17558
|
+
applyVisibility();
|
|
17559
|
+
cb.onStateChange();
|
|
17560
|
+
}
|
|
17561
|
+
historyTabBtn.addEventListener("click", () => handleHistoryTabClick("history"));
|
|
17562
|
+
logTabBtn.addEventListener("click", () => handleHistoryTabClick("log"));
|
|
17563
|
+
historyDockClose.addEventListener("click", () => {
|
|
17564
|
+
if (!userPrefersHistoryOpen)
|
|
17565
|
+
return;
|
|
17566
|
+
userPrefersHistoryOpen = false;
|
|
17567
|
+
applyVisibility();
|
|
17568
|
+
cb.onStateChange();
|
|
17569
|
+
});
|
|
17570
|
+
reloc(() => {
|
|
17571
|
+
historyTabBtn.textContent = paneText().sessionLog.historyTabLabel;
|
|
17572
|
+
logTabBtn.textContent = paneText().sessionLog.tabLabel;
|
|
17573
|
+
historyDockClose.setAttribute("aria-label", paneText().nav.queryHistory);
|
|
17574
|
+
historyDockClose.title = paneText().nav.queryHistoryTitle;
|
|
17575
|
+
});
|
|
15799
17576
|
let historyResizing = false;
|
|
15800
17577
|
historyResizer.addEventListener("mousedown", (e2) => {
|
|
15801
17578
|
e2.preventDefault();
|
|
@@ -15820,21 +17597,13 @@ ${frontmatter.yaml}
|
|
|
15820
17597
|
window.addEventListener("mousemove", onMove);
|
|
15821
17598
|
window.addEventListener("mouseup", onUp);
|
|
15822
17599
|
});
|
|
15823
|
-
const historyToggle = document.createElement("button");
|
|
15824
|
-
historyToggle.className = "db-history-toggle";
|
|
15825
|
-
historyToggle.type = "button";
|
|
15826
|
-
reloc(() => {
|
|
15827
|
-
historyToggle.textContent = paneText().nav.queryHistory;
|
|
15828
|
-
historyToggle.title = paneText().nav.queryHistoryTitle;
|
|
15829
|
-
});
|
|
15830
|
-
sidebar.appendChild(historyToggle);
|
|
15831
17600
|
let userPrefersHistoryOpen = initial.historyOpen ?? true;
|
|
15832
17601
|
function applyVisibility() {
|
|
15833
17602
|
const sqlMode = isSqlKind(currentDbInfo?.kind);
|
|
15834
17603
|
const visibility = computeVisibility(currentDbInfo?.kind, currentTab, userPrefersHistoryOpen);
|
|
15835
17604
|
toolsSection.hidden = visibility.toolsHidden;
|
|
15836
17605
|
prefsBar.hidden = visibility.toolsHidden;
|
|
15837
|
-
|
|
17606
|
+
historyDock.hidden = visibility.historyTabStripHidden;
|
|
15838
17607
|
historyResizer.hidden = visibility.historyResizerHidden;
|
|
15839
17608
|
historyPane.hidden = visibility.historyPaneHidden;
|
|
15840
17609
|
tableList.el.hidden = visibility.tableListHidden;
|
|
@@ -15858,19 +17627,14 @@ ${frontmatter.yaml}
|
|
|
15858
17627
|
searchBtn.classList.remove("active");
|
|
15859
17628
|
snapshotBtn.classList.remove("active");
|
|
15860
17629
|
}
|
|
15861
|
-
|
|
17630
|
+
historyDock.classList.toggle("open", userPrefersHistoryOpen);
|
|
15862
17631
|
if (!visibility.historyPaneHidden && cb.isActive())
|
|
15863
17632
|
historyView.refresh();
|
|
15864
17633
|
}
|
|
15865
17634
|
applyVisibility();
|
|
15866
|
-
historyToggle.addEventListener("click", () => {
|
|
15867
|
-
userPrefersHistoryOpen = !userPrefersHistoryOpen;
|
|
15868
|
-
applyVisibility();
|
|
15869
|
-
cb.onStateChange();
|
|
15870
|
-
});
|
|
15871
17635
|
const container = document.createElement("div");
|
|
15872
17636
|
container.className = "db-container";
|
|
15873
|
-
container.append(upperArea, historyResizer, historyPane);
|
|
17637
|
+
container.append(upperArea, historyResizer, historyPane, historyDock);
|
|
15874
17638
|
function createInnerTab(text2, active) {
|
|
15875
17639
|
const btn = document.createElement("button");
|
|
15876
17640
|
btn.className = `db-tab${active ? " active" : ""}`;
|
|
@@ -15975,11 +17739,12 @@ ${frontmatter.yaml}
|
|
|
15975
17739
|
const params = new URLSearchParams({ db: dbId });
|
|
15976
17740
|
if (preferredSchema)
|
|
15977
17741
|
params.set("schema", preferredSchema);
|
|
15978
|
-
|
|
15979
|
-
|
|
15980
|
-
|
|
15981
|
-
|
|
15982
|
-
|
|
17742
|
+
return logSqlFetch({
|
|
17743
|
+
url: `/_db/schemas?${params}`,
|
|
17744
|
+
kind: "query",
|
|
17745
|
+
label: `_db/schemas db=${dbId}`,
|
|
17746
|
+
errorPrefix: "failed to fetch schemas"
|
|
17747
|
+
});
|
|
15983
17748
|
}
|
|
15984
17749
|
function renderSchemaOptions(schemas, selected) {
|
|
15985
17750
|
schemaSelect.innerHTML = "";
|
|
@@ -15999,11 +17764,12 @@ ${frontmatter.yaml}
|
|
|
15999
17764
|
}
|
|
16000
17765
|
async function fetchSchema(dbId) {
|
|
16001
17766
|
const params = withCurrentSchema(new URLSearchParams({ db: dbId, includeColumns: "1" }));
|
|
16002
|
-
|
|
16003
|
-
|
|
16004
|
-
|
|
16005
|
-
|
|
16006
|
-
|
|
17767
|
+
return logSqlFetch({
|
|
17768
|
+
url: `/_db/schema?${params}`,
|
|
17769
|
+
kind: "query",
|
|
17770
|
+
label: `_db/schema db=${dbId}`,
|
|
17771
|
+
errorPrefix: "failed to fetch schema"
|
|
17772
|
+
});
|
|
16007
17773
|
}
|
|
16008
17774
|
async function fetchTablePage(table2, offset, limit, sort, filters, signal, eq = []) {
|
|
16009
17775
|
if (!currentDbInfo)
|
|
@@ -16025,38 +17791,128 @@ ${frontmatter.yaml}
|
|
|
16025
17791
|
if (eq.length > 0) {
|
|
16026
17792
|
params.set("eq", JSON.stringify(eq));
|
|
16027
17793
|
}
|
|
16028
|
-
|
|
16029
|
-
|
|
16030
|
-
|
|
16031
|
-
|
|
16032
|
-
|
|
17794
|
+
return logSqlFetch({
|
|
17795
|
+
url: `/_db/table?${params}`,
|
|
17796
|
+
init: signal ? { signal } : undefined,
|
|
17797
|
+
kind: "query",
|
|
17798
|
+
label: `SELECT FROM ${table2}`,
|
|
17799
|
+
trackLoad: false,
|
|
17800
|
+
errorPrefix: "failed to fetch table",
|
|
17801
|
+
rowCountOf: (r2) => r2.rows.length
|
|
17802
|
+
});
|
|
16033
17803
|
}
|
|
16034
17804
|
function fetchRelatedPage(table2, offset, limit, sort, filters, eq, signal) {
|
|
16035
17805
|
return fetchTablePage(table2, offset, limit, sort, filters, signal, eq);
|
|
16036
17806
|
}
|
|
17807
|
+
async function logSqlFetch(opts) {
|
|
17808
|
+
const startedAt = Date.now();
|
|
17809
|
+
const trackEnabled = opts.trackLoad !== false;
|
|
17810
|
+
try {
|
|
17811
|
+
const fetchPromise = fetch(opts.url, opts.init);
|
|
17812
|
+
const res = await (trackEnabled ? deps.trackLoad(fetchPromise) : fetchPromise);
|
|
17813
|
+
const elapsedMs = Date.now() - startedAt;
|
|
17814
|
+
if (!res.ok) {
|
|
17815
|
+
const message = await responseErrorMessage(res, opts.errorPrefix);
|
|
17816
|
+
outerDeps.sessionLog.add({
|
|
17817
|
+
kind: opts.kind,
|
|
17818
|
+
status: "error",
|
|
17819
|
+
label: opts.label,
|
|
17820
|
+
detail: opts.fallbackDetail,
|
|
17821
|
+
message,
|
|
17822
|
+
elapsedMs
|
|
17823
|
+
});
|
|
17824
|
+
const err = new Error(message);
|
|
17825
|
+
err.__sessionLogged = true;
|
|
17826
|
+
throw err;
|
|
17827
|
+
}
|
|
17828
|
+
const data = await res.json();
|
|
17829
|
+
const detail = data.executedSql && data.executedSql.length > 0 ? data.executedSql.join(`;
|
|
17830
|
+
`) : opts.fallbackDetail;
|
|
17831
|
+
outerDeps.sessionLog.add({
|
|
17832
|
+
kind: opts.kind,
|
|
17833
|
+
status: "ok",
|
|
17834
|
+
label: opts.label,
|
|
17835
|
+
detail,
|
|
17836
|
+
elapsedMs,
|
|
17837
|
+
rowCount: opts.rowCountOf?.(data)
|
|
17838
|
+
});
|
|
17839
|
+
return data;
|
|
17840
|
+
} catch (err) {
|
|
17841
|
+
if (!(err instanceof Error))
|
|
17842
|
+
throw err;
|
|
17843
|
+
if (err.__sessionLogged)
|
|
17844
|
+
throw err;
|
|
17845
|
+
if (err.name === "AbortError")
|
|
17846
|
+
throw err;
|
|
17847
|
+
outerDeps.sessionLog.add({
|
|
17848
|
+
kind: opts.kind,
|
|
17849
|
+
status: "error",
|
|
17850
|
+
label: opts.label,
|
|
17851
|
+
detail: opts.fallbackDetail,
|
|
17852
|
+
message: err.message,
|
|
17853
|
+
elapsedMs: Date.now() - startedAt
|
|
17854
|
+
});
|
|
17855
|
+
throw err;
|
|
17856
|
+
}
|
|
17857
|
+
}
|
|
16037
17858
|
async function executeQuery(sql) {
|
|
16038
17859
|
if (!currentDbInfo)
|
|
16039
17860
|
throw new Error("no database selected");
|
|
16040
|
-
const
|
|
16041
|
-
|
|
16042
|
-
|
|
16043
|
-
|
|
16044
|
-
|
|
17861
|
+
const label = sql.split(`
|
|
17862
|
+
`)[0]?.trim() || sql;
|
|
17863
|
+
return logSqlFetch({
|
|
17864
|
+
url: "/_db/query",
|
|
17865
|
+
init: {
|
|
17866
|
+
method: "POST",
|
|
17867
|
+
headers: {
|
|
17868
|
+
"Content-Type": "application/json",
|
|
17869
|
+
"X-Code-Viewer-Action": "1"
|
|
17870
|
+
},
|
|
17871
|
+
body: JSON.stringify({
|
|
17872
|
+
db: currentDbInfo.id,
|
|
17873
|
+
...currentSchema ? { schema: currentSchema } : {},
|
|
17874
|
+
sql,
|
|
17875
|
+
saveHistory: true,
|
|
17876
|
+
source: "browser",
|
|
17877
|
+
executedBy: "user"
|
|
17878
|
+
})
|
|
16045
17879
|
},
|
|
16046
|
-
|
|
16047
|
-
|
|
16048
|
-
|
|
16049
|
-
|
|
16050
|
-
|
|
16051
|
-
|
|
16052
|
-
|
|
16053
|
-
|
|
17880
|
+
kind: "query",
|
|
17881
|
+
label,
|
|
17882
|
+
fallbackDetail: sql,
|
|
17883
|
+
trackLoad: false,
|
|
17884
|
+
errorPrefix: "failed to execute query",
|
|
17885
|
+
rowCountOf: (r2) => r2.rowCount ?? r2.rows.length
|
|
17886
|
+
});
|
|
17887
|
+
}
|
|
17888
|
+
async function applyRowMutations(mutations) {
|
|
17889
|
+
if (!currentDbInfo)
|
|
17890
|
+
throw new Error("no database selected");
|
|
17891
|
+
if (!currentTable)
|
|
17892
|
+
throw new Error("no table selected");
|
|
17893
|
+
const label = paneText().sessionLog.commitLabel(mutations.length);
|
|
17894
|
+
const fallback = JSON.stringify({ table: currentTable, mutations }, null, 2);
|
|
17895
|
+
await logSqlFetch({
|
|
17896
|
+
url: "/_db/mutate",
|
|
17897
|
+
init: {
|
|
17898
|
+
method: "POST",
|
|
17899
|
+
headers: {
|
|
17900
|
+
"Content-Type": "application/json",
|
|
17901
|
+
"X-Code-Viewer-Action": "1"
|
|
17902
|
+
},
|
|
17903
|
+
body: JSON.stringify({
|
|
17904
|
+
db: currentDbInfo.id,
|
|
17905
|
+
...currentSchema ? { schema: currentSchema } : {},
|
|
17906
|
+
table: currentTable,
|
|
17907
|
+
mutations
|
|
17908
|
+
})
|
|
17909
|
+
},
|
|
17910
|
+
kind: "mutate",
|
|
17911
|
+
label,
|
|
17912
|
+
fallbackDetail: fallback,
|
|
17913
|
+
errorPrefix: "failed to save changes",
|
|
17914
|
+
rowCountOf: () => mutations.length
|
|
16054
17915
|
});
|
|
16055
|
-
if (!res.ok) {
|
|
16056
|
-
throw new Error(await responseErrorMessage(res, "failed to execute query"));
|
|
16057
|
-
}
|
|
16058
|
-
const result = await res.json();
|
|
16059
|
-
return result;
|
|
16060
17916
|
}
|
|
16061
17917
|
async function selectDb(dbId, explorerInitial, generation = loadGeneration, preferredSchema, preferredTable, targetView) {
|
|
16062
17918
|
if (generation !== loadGeneration || currentDbInfo?.id !== dbId)
|
|
@@ -16235,11 +18091,18 @@ ${frontmatter.yaml}
|
|
|
16235
18091
|
}
|
|
16236
18092
|
if (!currentDbInfo)
|
|
16237
18093
|
return [];
|
|
16238
|
-
|
|
16239
|
-
|
|
18094
|
+
try {
|
|
18095
|
+
const data = await logSqlFetch({
|
|
18096
|
+
url: `/_db/columns?${withCurrentSchema(new URLSearchParams({ db: currentDbInfo.id, table: table2 }))}`,
|
|
18097
|
+
kind: "query",
|
|
18098
|
+
label: `_db/columns ${table2}`,
|
|
18099
|
+
trackLoad: false,
|
|
18100
|
+
errorPrefix: "failed to fetch columns"
|
|
18101
|
+
});
|
|
18102
|
+
return data.columns;
|
|
18103
|
+
} catch {
|
|
16240
18104
|
return [];
|
|
16241
|
-
|
|
16242
|
-
return data.columns;
|
|
18105
|
+
}
|
|
16243
18106
|
}
|
|
16244
18107
|
async function showSchema(table2) {
|
|
16245
18108
|
setActiveTab("schema");
|
|
@@ -16253,10 +18116,13 @@ ${frontmatter.yaml}
|
|
|
16253
18116
|
return;
|
|
16254
18117
|
setActiveTab("schema");
|
|
16255
18118
|
try {
|
|
16256
|
-
const
|
|
16257
|
-
|
|
16258
|
-
|
|
16259
|
-
|
|
18119
|
+
const data = await logSqlFetch({
|
|
18120
|
+
url: `/_db/ddl?${withCurrentSchema(new URLSearchParams({ db: currentDbInfo.id, table: table2 }))}`,
|
|
18121
|
+
kind: "query",
|
|
18122
|
+
label: `_db/ddl ${table2}`,
|
|
18123
|
+
trackLoad: false,
|
|
18124
|
+
errorPrefix: "failed to fetch DDL"
|
|
18125
|
+
});
|
|
16260
18126
|
const columns = await fetchColumns(table2);
|
|
16261
18127
|
schemaView.render(table2, columns, schemaCache?.indexes || [], {
|
|
16262
18128
|
foreignKeys: schemaCache?.foreignKeys,
|
|
@@ -16503,6 +18369,8 @@ ${frontmatter.yaml}
|
|
|
16503
18369
|
state.historyOpen = false;
|
|
16504
18370
|
if (historyPane.style.height)
|
|
16505
18371
|
state.historyHeight = historyPane.style.height;
|
|
18372
|
+
if (activeHistoryTab === "log")
|
|
18373
|
+
state.activeHistoryTab = "log";
|
|
16506
18374
|
if (sidebar.style.width)
|
|
16507
18375
|
state.sidebarWidth = sidebar.style.width;
|
|
16508
18376
|
if (relatedPanelHeightPx != null)
|
|
@@ -16619,7 +18487,7 @@ ${frontmatter.yaml}
|
|
|
16619
18487
|
var ICON_PATH_ER = "M1.5 1.75A.75.75 0 0 1 2.25 1h4.5a.75.75 0 0 1 .75.75v3.5h2v-1.5A.75.75 0 0 1 10.25 3h3.5a.75.75 0 0 1 .75.75v3.5a.75.75 0 0 1-.75.75h-3.5a.75.75 0 0 1-.75-.75V6.5h-2v3h2v-.75A.75.75 0 0 1 10.25 8h3.5a.75.75 0 0 1 .75.75v3.5a.75.75 0 0 1-.75.75h-3.5a.75.75 0 0 1-.75-.75v-1.5h-2v3.5a.75.75 0 0 1-.75.75h-4.5a.75.75 0 0 1-.75-.75v-3.5A.75.75 0 0 1 2.25 9h4.5a.75.75 0 0 1 .75.75v.75h-.5v-1H3v3h3V11h.5v-.75a.75.75 0 0 0-.75-.75H3V6.5h2.5V2.5H3Z";
|
|
16620
18488
|
var ICON_PATH_SEARCH = "M10.68 11.74a6 6 0 0 1-7.917-8.984 6 6 0 0 1 8.997 7.905l3.05 3.05a.75.75 0 1 1-1.06 1.06l-3.07-3.03ZM11.5 7a4.499 4.499 0 1 1-8.997 0A4.499 4.499 0 0 1 11.5 7Z";
|
|
16621
18489
|
var ICON_PATH_SNAPSHOT = "M3.5 1.75A1.75 1.75 0 0 1 5.25 0h5.5A1.75 1.75 0 0 1 12.5 1.75v.5h1.75A1.75 1.75 0 0 1 16 4v9.25A1.75 1.75 0 0 1 14.25 15H1.75A1.75 1.75 0 0 1 0 13.25V4a1.75 1.75 0 0 1 1.75-1.75H3.5v-.5Zm1.5.5v.5h6v-.5a.25.25 0 0 0-.25-.25h-5.5a.25.25 0 0 0-.25.25Zm-3.25 2a.25.25 0 0 0-.25.25v9.25c0 .138.112.25.25.25h12.5a.25.25 0 0 0 .25-.25V4a.25.25 0 0 0-.25-.25H1.75ZM8 6a2.75 2.75 0 1 0 0 5.5 2.75 2.75 0 0 0 0-5.5Z";
|
|
16622
|
-
var
|
|
18490
|
+
var ICON_PATH_EDIT_MODE = "M11.013 1.427a1.75 1.75 0 0 1 2.474 0l1.086 1.086a1.75 1.75 0 0 1 0 2.474l-8.61 8.61c-.21.21-.47.364-.756.445l-3.251.93a.75.75 0 0 1-.927-.928l.929-3.25c.081-.286.235-.547.445-.758l8.61-8.61Zm.176 4.823L9.75 4.81l-6.286 6.287a.247.247 0 0 0-.064.108l-.558 1.953 1.953-.558a.249.249 0 0 0 .108-.064l6.286-6.286Zm1.238-3.763a.25.25 0 0 0-.354 0L10.811 3.75l1.439 1.44 1.263-1.263a.25.25 0 0 0 0-.354L12.427 2.49Z";
|
|
16623
18491
|
function makeIconButton(opts) {
|
|
16624
18492
|
const btn = document.createElement("button");
|
|
16625
18493
|
btn.type = "button";
|
|
@@ -16666,6 +18534,7 @@ ${frontmatter.yaml}
|
|
|
16666
18534
|
let dbFilesCache = null;
|
|
16667
18535
|
let dbUiState = { version: 1, columnWidths: {} };
|
|
16668
18536
|
let dbUiLoadPromise = null;
|
|
18537
|
+
const sessionLog = createSessionLog();
|
|
16669
18538
|
function isRestoring() {
|
|
16670
18539
|
return restoringDepth > 0;
|
|
16671
18540
|
}
|
|
@@ -17168,10 +19037,11 @@ ${frontmatter.yaml}
|
|
|
17168
19037
|
const closeBtn = document.createElement("button");
|
|
17169
19038
|
closeBtn.type = "button";
|
|
17170
19039
|
closeBtn.className = "db-tabs-chip-close";
|
|
17171
|
-
closeBtn.title = "閉じる";
|
|
17172
19040
|
closeBtn.tabIndex = -1;
|
|
17173
19041
|
closeBtn.textContent = "×";
|
|
17174
|
-
|
|
19042
|
+
const closeTabLabel = outerText().nav.closeTab(initialLabel);
|
|
19043
|
+
closeBtn.title = closeTabLabel;
|
|
19044
|
+
closeBtn.setAttribute("aria-label", closeTabLabel);
|
|
17175
19045
|
closeBtn.addEventListener("click", (e2) => {
|
|
17176
19046
|
e2.stopPropagation();
|
|
17177
19047
|
closeTab(id);
|
|
@@ -17204,7 +19074,8 @@ ${frontmatter.yaml}
|
|
|
17204
19074
|
getDbUiPref,
|
|
17205
19075
|
setDbUiPref,
|
|
17206
19076
|
onDbUiPrefChange,
|
|
17207
|
-
loadSqlHistory
|
|
19077
|
+
loadSqlHistory,
|
|
19078
|
+
sessionLog
|
|
17208
19079
|
}, {
|
|
17209
19080
|
tabId: id,
|
|
17210
19081
|
isActive: () => activeTabId === id,
|
|
@@ -17223,6 +19094,7 @@ ${frontmatter.yaml}
|
|
|
17223
19094
|
sqlDraft: initial?.sqlDraft,
|
|
17224
19095
|
historyOpen: initial?.historyOpen,
|
|
17225
19096
|
historyHeight: initial?.historyHeight,
|
|
19097
|
+
activeHistoryTab: initial?.activeHistoryTab,
|
|
17226
19098
|
sidebarWidth: initial?.sidebarWidth,
|
|
17227
19099
|
relatedPanelHeight: initial?.relatedPanelHeight,
|
|
17228
19100
|
detailPanelHeight: initial?.detailPanelHeight,
|
|
@@ -17588,7 +19460,10 @@ ${frontmatter.yaml}
|
|
|
17588
19460
|
suspend,
|
|
17589
19461
|
leave,
|
|
17590
19462
|
handleSse,
|
|
17591
|
-
localize
|
|
19463
|
+
localize,
|
|
19464
|
+
getDbUiPref,
|
|
19465
|
+
setDbUiPref,
|
|
19466
|
+
onDbUiPrefChange
|
|
17592
19467
|
};
|
|
17593
19468
|
}
|
|
17594
19469
|
function formatSize(bytes) {
|
|
@@ -19052,7 +20927,21 @@ ${frontmatter.yaml}
|
|
|
19052
20927
|
// web-src/views/history-view.ts
|
|
19053
20928
|
var HISTORY_BODY_COLLAPSE_LINES = 10;
|
|
19054
20929
|
var HISTORY_WORKTREE_COMMIT = "worktree";
|
|
19055
|
-
var
|
|
20930
|
+
var HISTORY_TEXT = {
|
|
20931
|
+
en: {
|
|
20932
|
+
worktreeLabel: "Uncommitted changes (Working tree)",
|
|
20933
|
+
bodyExpandClose: "Collapse",
|
|
20934
|
+
bodyExpandMore: (n2) => `Show more (${n2} lines)`
|
|
20935
|
+
},
|
|
20936
|
+
ja: {
|
|
20937
|
+
worktreeLabel: "未コミット変更 (Working tree)",
|
|
20938
|
+
bodyExpandClose: "閉じる",
|
|
20939
|
+
bodyExpandMore: (n2) => `もっと見る (${n2} 行)`
|
|
20940
|
+
}
|
|
20941
|
+
};
|
|
20942
|
+
function historyWorktreeLabel(lang) {
|
|
20943
|
+
return HISTORY_TEXT[lang].worktreeLabel;
|
|
20944
|
+
}
|
|
19056
20945
|
function buildHistoryPanelDom(options = {}) {
|
|
19057
20946
|
const variant = options.variant || "page";
|
|
19058
20947
|
const page = variant === "page";
|
|
@@ -19161,10 +21050,11 @@ ${frontmatter.yaml}
|
|
|
19161
21050
|
return 0;
|
|
19162
21051
|
return rawText.split(/\r?\n/).length;
|
|
19163
21052
|
}
|
|
19164
|
-
function historyBodyToggleLabel(expanded, remainingLines) {
|
|
19165
|
-
|
|
21053
|
+
function historyBodyToggleLabel(expanded, remainingLines, lang) {
|
|
21054
|
+
const t2 = HISTORY_TEXT[lang];
|
|
21055
|
+
return expanded ? t2.bodyExpandClose : t2.bodyExpandMore(remainingLines);
|
|
19166
21056
|
}
|
|
19167
|
-
function buildExpandableHistoryBody(rendered, rawText) {
|
|
21057
|
+
function buildExpandableHistoryBody(rendered, rawText, lang) {
|
|
19168
21058
|
const lineCount = historyBodyLineCount(rawText);
|
|
19169
21059
|
if (lineCount <= HISTORY_BODY_COLLAPSE_LINES)
|
|
19170
21060
|
return rendered;
|
|
@@ -19180,7 +21070,7 @@ ${frontmatter.yaml}
|
|
|
19180
21070
|
const remainingLines = lineCount - HISTORY_BODY_COLLAPSE_LINES;
|
|
19181
21071
|
const sync = () => {
|
|
19182
21072
|
const expanded = collapsible.classList.contains("expanded");
|
|
19183
|
-
button.textContent = historyBodyToggleLabel(expanded, remainingLines);
|
|
21073
|
+
button.textContent = historyBodyToggleLabel(expanded, remainingLines, lang);
|
|
19184
21074
|
button.setAttribute("aria-expanded", expanded ? "true" : "false");
|
|
19185
21075
|
};
|
|
19186
21076
|
const toggle = () => {
|
|
@@ -19339,7 +21229,7 @@ ${frontmatter.yaml}
|
|
|
19339
21229
|
}
|
|
19340
21230
|
function worktreeRow() {
|
|
19341
21231
|
const active = selectedSha === HISTORY_WORKTREE_COMMIT ? " active" : "";
|
|
19342
|
-
return `<li class="history-item history-item-worktree${active}" data-sha="${HISTORY_WORKTREE_COMMIT}">` + `<span class="subject" title="${deps.escapeHtml(
|
|
21232
|
+
return `<li class="history-item history-item-worktree${active}" data-sha="${HISTORY_WORKTREE_COMMIT}">` + `<span class="subject" title="${deps.escapeHtml(historyWorktreeLabel(deps.getLanguage()))}">${deps.escapeHtml(historyWorktreeLabel(deps.getLanguage()))}</span>` + `<span class="meta2">` + `<span class="sha">HEAD..worktree</span>` + `<span class="author">Working tree</span>` + `</span>` + `</li>`;
|
|
19343
21233
|
}
|
|
19344
21234
|
function renderList() {
|
|
19345
21235
|
const now = new Date;
|
|
@@ -19390,7 +21280,7 @@ ${frontmatter.yaml}
|
|
|
19390
21280
|
const rendered = await renderMarkdownPreview(commit.body, { path: "COMMIT_MSG", ref: commit.sha }, { syntaxHighlight: deps.getSyntaxHighlight() });
|
|
19391
21281
|
if (gen !== generation || selectedSha !== commit.sha)
|
|
19392
21282
|
return;
|
|
19393
|
-
body.replaceChildren(buildExpandableHistoryBody(rendered, commit.body));
|
|
21283
|
+
body.replaceChildren(buildExpandableHistoryBody(rendered, commit.body, deps.getLanguage()));
|
|
19394
21284
|
}
|
|
19395
21285
|
}
|
|
19396
21286
|
info.hidden = false;
|
|
@@ -19401,8 +21291,9 @@ ${frontmatter.yaml}
|
|
|
19401
21291
|
return;
|
|
19402
21292
|
info.querySelector(".hci-head")?.setAttribute("hidden", "");
|
|
19403
21293
|
const subject = info.querySelector(".hci-subject");
|
|
19404
|
-
if (subject)
|
|
19405
|
-
subject.textContent =
|
|
21294
|
+
if (subject) {
|
|
21295
|
+
subject.textContent = historyWorktreeLabel(deps.getLanguage());
|
|
21296
|
+
}
|
|
19406
21297
|
const body = info.querySelector(".hci-body");
|
|
19407
21298
|
if (body) {
|
|
19408
21299
|
body.hidden = true;
|
|
@@ -20141,7 +22032,7 @@ code-viewer annotate add-db --db app.db --tab query \\
|
|
|
20141
22032
|
database: {
|
|
20142
22033
|
nav: "Datastores",
|
|
20143
22034
|
title: "Datastore Viewer",
|
|
20144
|
-
intro: "Browse SQLite files, Docker-hosted databases, Redis, Elasticsearch, and S3-compatible object stores from one local viewer.",
|
|
22035
|
+
intro: "Browse and edit SQLite files, Docker-hosted databases, Redis, Elasticsearch, and S3-compatible object stores from one local viewer.",
|
|
20145
22036
|
groups: [
|
|
20146
22037
|
{
|
|
20147
22038
|
title: "Supported datastores",
|
|
@@ -20151,27 +22042,27 @@ code-viewer annotate add-db --db app.db --tab query \\
|
|
|
20151
22042
|
rows: [
|
|
20152
22043
|
[
|
|
20153
22044
|
"SQLite",
|
|
20154
|
-
"Automatically discovered from .db, .sqlite, .sqlite3, and .s3db files in the repository."
|
|
22045
|
+
"Automatically discovered from .db, .sqlite, .sqlite3, and .s3db files in the repository. Inline row edit / insert / delete via the grid Edit mode (atomic per-commit batch)."
|
|
20155
22046
|
],
|
|
20156
22047
|
[
|
|
20157
22048
|
"MySQL / MariaDB",
|
|
20158
|
-
"Detected from docker-compose.yml / compose.yml (or .yaml). Multiple databases per server are listed."
|
|
22049
|
+
"Detected from docker-compose.yml / compose.yml (or .yaml). Multiple databases per server are listed. Same inline row edit / insert / delete as SQLite."
|
|
20159
22050
|
],
|
|
20160
22051
|
[
|
|
20161
22052
|
"PostgreSQL",
|
|
20162
|
-
"Detected from compose files. Multiple databases per server, plus a schema selector for switching schemas without reopening."
|
|
22053
|
+
"Detected from compose files. Multiple databases per server, plus a schema selector for switching schemas without reopening. Same inline row edit / insert / delete as SQLite."
|
|
20163
22054
|
],
|
|
20164
22055
|
[
|
|
20165
22056
|
"Redis",
|
|
20166
|
-
"Detected from compose files.
|
|
22057
|
+
"Detected from compose files. Browse DB 0-15, SCAN keys, dedicated string/hash/list panes, JSON view for set/zset/stream. Edit values, delete keys, and create new keys (all types) via in-pane editors. Participates in snapshots and diffs."
|
|
20167
22058
|
],
|
|
20168
22059
|
[
|
|
20169
22060
|
"Elasticsearch",
|
|
20170
|
-
"Detected from compose files. List indices, view mappings, paginate with search_after, run lucene q= or DSL queries on an allowlist
|
|
22061
|
+
"Detected from compose files. List indices, view mappings, paginate with search_after, run lucene q= or DSL queries on an allowlist. Edit / create / delete documents with _seq_no / _primary_term optimistic concurrency. Take snapshots/diffs."
|
|
20171
22062
|
],
|
|
20172
22063
|
[
|
|
20173
22064
|
"S3 / MinIO / LocalStack",
|
|
20174
|
-
"Detected from compose files. Folder-tree browse, prefix/filename search, updated-time sort, and previews for images, video, audio, PDF, Markdown, HTML, and text. LocalStack falls back to `docker exec curl` when no host port is published; MinIO requires a published host port."
|
|
22065
|
+
"Detected from compose files. Folder-tree browse, prefix/filename search, updated-time sort, and previews for images, video, audio, PDF, Markdown, HTML, and text. Edit text/markdown/JSON object bodies inline, upload new objects, and delete existing ones. LocalStack falls back to `docker exec curl` when no host port is published; MinIO requires a published host port."
|
|
20175
22066
|
]
|
|
20176
22067
|
]
|
|
20177
22068
|
}
|
|
@@ -20193,7 +22084,7 @@ code-viewer annotate add-db --db app.db --tab query \\
|
|
|
20193
22084
|
],
|
|
20194
22085
|
[
|
|
20195
22086
|
"Data tab",
|
|
20196
|
-
"Paginated grid with sort, filter, cell copy, and CSV/JSON export (capped at 100k rows; export respects the current filter and sort)."
|
|
22087
|
+
"Paginated grid with sort, filter, cell copy, and CSV/JSON export (capped at 100k rows; export respects the current filter and sort). Toggle Edit mode in the prefs bar to enable inline editing — double-click a cell to edit, queue insert/delete via row actions, and commit the batch atomically. Edited rows/cells highlight in yellow until committed."
|
|
20197
22088
|
],
|
|
20198
22089
|
[
|
|
20199
22090
|
"Detail footer & related panel",
|
|
@@ -20205,7 +22096,7 @@ code-viewer annotate add-db --db app.db --tab query \\
|
|
|
20205
22096
|
],
|
|
20206
22097
|
[
|
|
20207
22098
|
"Query editor",
|
|
20208
|
-
"SQL syntax highlighting, Tab indent, auto-resize, Ctrl+Enter to run. Allowlist depends on the engine (SQLite: SELECT/PRAGMA/EXPLAIN/WITH; PostgreSQL and MySQL also accept SHOW/DESCRIBE)."
|
|
22099
|
+
"SQL syntax highlighting (shiki), Tab indent, auto-resize, Ctrl+Enter to run. Allowlist depends on the engine (SQLite: SELECT/PRAGMA/EXPLAIN/WITH; PostgreSQL and MySQL also accept SHOW/DESCRIBE)."
|
|
20209
22100
|
],
|
|
20210
22101
|
[
|
|
20211
22102
|
"ER Diagram",
|
|
@@ -20220,12 +22111,12 @@ code-viewer annotate add-db --db app.db --tab query \\
|
|
|
20220
22111
|
"Capture point-in-time snapshots of selected tables/indices/key spaces and diff any two to see inserts, updates, and deletes with full before/after values."
|
|
20221
22112
|
],
|
|
20222
22113
|
[
|
|
20223
|
-
"Query History",
|
|
20224
|
-
"
|
|
22114
|
+
"Footer dock (Query History / Session log)",
|
|
22115
|
+
"JetBrains-style bottom dock with two tabs. Query History: per-DB master/detail of saved queries, SSE-synced across tabs. Session log: every SQL the server runs in this session (read fetches, user queries, write commits) with timing, row count, and the executed SQL syntax-highlighted. Auto-follow keeps the newest entry pinned; scroll up or click a non-latest entry to pause. The active tab and open/closed state persist in tabs.json."
|
|
20225
22116
|
],
|
|
20226
22117
|
[
|
|
20227
22118
|
"Datastore explorers",
|
|
20228
|
-
"Redis / Elasticsearch / S3 keep the same Multi-DB tab UI but swap the table tree for a key-space / index-tree / folder-tree explorer."
|
|
22119
|
+
"Redis / Elasticsearch / S3 keep the same Multi-DB tab UI but swap the table tree for a key-space / index-tree / folder-tree explorer. Each supports value editing, document/key/object creation, and deletion via in-pane editors and confirmation dialogs."
|
|
20229
22120
|
]
|
|
20230
22121
|
]
|
|
20231
22122
|
}
|
|
@@ -20704,7 +22595,7 @@ code-viewer annotate add-db --db app.db --tab query \\
|
|
|
20704
22595
|
database: {
|
|
20705
22596
|
nav: "データストア",
|
|
20706
22597
|
title: "データストアビューア",
|
|
20707
|
-
intro: "SQLite ファイル、Docker 上のデータベース、Redis、Elasticsearch、S3
|
|
22598
|
+
intro: "SQLite ファイル、Docker 上のデータベース、Redis、Elasticsearch、S3 互換オブジェクトストアをローカルビューアで閲覧・編集できます。",
|
|
20708
22599
|
groups: [
|
|
20709
22600
|
{
|
|
20710
22601
|
title: "対応データストア",
|
|
@@ -20714,27 +22605,27 @@ code-viewer annotate add-db --db app.db --tab query \\
|
|
|
20714
22605
|
rows: [
|
|
20715
22606
|
[
|
|
20716
22607
|
"SQLite",
|
|
20717
|
-
"リポジトリ内の .db, .sqlite, .sqlite3, .s3db
|
|
22608
|
+
"リポジトリ内の .db, .sqlite, .sqlite3, .s3db ファイルを自動検出します。グリッドの Edit モードで行のインライン編集 / 追加 / 削除に対応 (コミット単位でアトミックに適用)。"
|
|
20718
22609
|
],
|
|
20719
22610
|
[
|
|
20720
22611
|
"MySQL / MariaDB",
|
|
20721
|
-
"docker-compose.yml / compose.yml (.yaml 含む) のサービスから検出。同一サーバー上の複数データベースを一覧表示します。"
|
|
22612
|
+
"docker-compose.yml / compose.yml (.yaml 含む) のサービスから検出。同一サーバー上の複数データベースを一覧表示します。SQLite と同じく Edit モードで行のインライン編集 / 追加 / 削除に対応。"
|
|
20722
22613
|
],
|
|
20723
22614
|
[
|
|
20724
22615
|
"PostgreSQL",
|
|
20725
|
-
"compose ファイルから検出。同一サーバー上の複数データベースに対応し、スキーマ切替セレクターで再オープンせずスキーマを切り替えられます。"
|
|
22616
|
+
"compose ファイルから検出。同一サーバー上の複数データベースに対応し、スキーマ切替セレクターで再オープンせずスキーマを切り替えられます。SQLite と同じく行のインライン編集 / 追加 / 削除に対応。"
|
|
20726
22617
|
],
|
|
20727
22618
|
[
|
|
20728
22619
|
"Redis",
|
|
20729
|
-
"compose ファイルから検出。DB 0-15 を
|
|
22620
|
+
"compose ファイルから検出。DB 0-15 を SCAN し、string/hash/list は専用ペイン、set/zset/stream は JSON ビュー。値の編集 / キー削除 / 新規キー作成 (全タイプ) に対応。スナップショット/差分にも参加します。"
|
|
20730
22621
|
],
|
|
20731
22622
|
[
|
|
20732
22623
|
"Elasticsearch",
|
|
20733
|
-
"compose ファイルから検出。インデックス一覧、マッピング、search_after ページング、lucene q= と許可リスト経由の DSL
|
|
22624
|
+
"compose ファイルから検出。インデックス一覧、マッピング、search_after ページング、lucene q= と許可リスト経由の DSL、スナップショット/差分に対応。_seq_no / _primary_term 楽観ロックでドキュメントの編集 / 新規作成 / 削除も可能。"
|
|
20734
22625
|
],
|
|
20735
22626
|
[
|
|
20736
22627
|
"S3 / MinIO / LocalStack",
|
|
20737
|
-
"compose ファイルから検出。フォルダツリー型ブラウザ、prefix/ファイル名検索、更新日時順表示、画像/動画/音声/PDF/Markdown/HTML
|
|
22628
|
+
"compose ファイルから検出。フォルダツリー型ブラウザ、prefix/ファイル名検索、更新日時順表示、画像/動画/音声/PDF/Markdown/HTML/テキストのプレビューに対応。テキスト/Markdown/JSON のインライン編集、新規オブジェクトアップロード、オブジェクト削除も可能。LocalStack はホストポート未公開時 `docker exec curl` にフォールバックしますが、MinIO はホストポート公開が必須です。"
|
|
20738
22629
|
]
|
|
20739
22630
|
]
|
|
20740
22631
|
}
|
|
@@ -20756,7 +22647,7 @@ code-viewer annotate add-db --db app.db --tab query \\
|
|
|
20756
22647
|
],
|
|
20757
22648
|
[
|
|
20758
22649
|
"Data タブ",
|
|
20759
|
-
"ページネーション付きグリッド。ソート、フィルター、セルコピー、CSV/JSON エクスポート(最大10
|
|
22650
|
+
"ページネーション付きグリッド。ソート、フィルター、セルコピー、CSV/JSON エクスポート(最大10万行、現在のフィルター/ソートを反映)。設定バーで Edit モードを ON にするとインライン編集が可能 — セルをダブルクリックで編集、行操作で挿入/削除を予約、コミット単位で一括適用。未コミットの編集行/セルは黄色でハイライト。"
|
|
20760
22651
|
],
|
|
20761
22652
|
[
|
|
20762
22653
|
"詳細フッタ・関連パネル",
|
|
@@ -20768,7 +22659,7 @@ code-viewer annotate add-db --db app.db --tab query \\
|
|
|
20768
22659
|
],
|
|
20769
22660
|
[
|
|
20770
22661
|
"クエリエディター",
|
|
20771
|
-
"SQL
|
|
22662
|
+
"SQL シンタックスハイライト (shiki)、Tab インデント、自動リサイズ、Ctrl+Enter で実行。許可文は DB 種別により異なります(SQLite: SELECT/PRAGMA/EXPLAIN/WITH。PostgreSQL と MySQL は SHOW/DESCRIBE も可)。"
|
|
20772
22663
|
],
|
|
20773
22664
|
[
|
|
20774
22665
|
"ER 図",
|
|
@@ -20783,12 +22674,12 @@ code-viewer annotate add-db --db app.db --tab query \\
|
|
|
20783
22674
|
"選んだテーブル / インデックス / キー空間の状態を保存し、任意の 2 つの差分(insert / update / delete + before/after)を表示します。"
|
|
20784
22675
|
],
|
|
20785
22676
|
[
|
|
20786
|
-
"クエリ履歴",
|
|
20787
|
-
"
|
|
22677
|
+
"フッタ Dock (クエリ履歴 / ログ)",
|
|
22678
|
+
"JetBrains 風の常駐 bottom dock に 2 タブ。「クエリ履歴」: DB ごとに保存されたクエリのマスター/ディテール、SSE で全タブにライブ同期。「ログ」: このセッションでサーバが実行した SQL すべて (テーブル読み込み / ユーザークエリ / 編集コミット) を所要時間・行数・実行 SQL (シンタックスハイライト) 付きで時系列表示。自動追従 ON で常に最新を表示、下スクロール or 最新以外をクリックすると追従解除。アクティブタブと開閉状態は tabs.json に永続化されます。"
|
|
20788
22679
|
],
|
|
20789
22680
|
[
|
|
20790
22681
|
"データストア専用エクスプローラ",
|
|
20791
|
-
"Redis / Elasticsearch / S3 はマルチ DB タブ UI を共有しつつ、テーブルツリーをキー空間ツリー / インデックスツリー /
|
|
22682
|
+
"Redis / Elasticsearch / S3 はマルチ DB タブ UI を共有しつつ、テーブルツリーをキー空間ツリー / インデックスツリー / フォルダツリーに差し替えます。それぞれインライン編集ペインと確認ダイアログによる値編集 / ドキュメント・キー・オブジェクト作成 / 削除に対応。"
|
|
20792
22683
|
]
|
|
20793
22684
|
]
|
|
20794
22685
|
}
|
|
@@ -23490,176 +25381,25 @@ code-viewer query diff rows --id diff-xyz789 --table users --type inserted --lim
|
|
|
23490
25381
|
function closeRepoContextMenu() {
|
|
23491
25382
|
document.querySelector(".gdp-context-menu")?.remove();
|
|
23492
25383
|
}
|
|
23493
|
-
function closeTrashDialog() {
|
|
23494
|
-
document.querySelector(".gdp-trash-dialog-backdrop")?.remove();
|
|
23495
|
-
}
|
|
23496
|
-
function createTrashDialog(title, body, actions) {
|
|
23497
|
-
closeTrashDialog();
|
|
23498
|
-
const backdrop = document.createElement("div");
|
|
23499
|
-
backdrop.className = "gdp-trash-dialog-backdrop";
|
|
23500
|
-
const dialog = document.createElement("div");
|
|
23501
|
-
dialog.className = "gdp-trash-dialog";
|
|
23502
|
-
const titleId = "gdp-trash-dialog-title";
|
|
23503
|
-
const bodyId = "gdp-trash-dialog-body";
|
|
23504
|
-
dialog.setAttribute("role", "dialog");
|
|
23505
|
-
dialog.setAttribute("aria-modal", "true");
|
|
23506
|
-
dialog.setAttribute("aria-labelledby", titleId);
|
|
23507
|
-
dialog.setAttribute("aria-describedby", bodyId);
|
|
23508
|
-
const heading2 = document.createElement("div");
|
|
23509
|
-
heading2.id = titleId;
|
|
23510
|
-
heading2.className = "gdp-trash-dialog-title";
|
|
23511
|
-
heading2.textContent = title;
|
|
23512
|
-
const message = document.createElement("div");
|
|
23513
|
-
message.id = bodyId;
|
|
23514
|
-
message.className = "gdp-trash-dialog-body";
|
|
23515
|
-
message.textContent = body;
|
|
23516
|
-
const actionRow = document.createElement("div");
|
|
23517
|
-
actionRow.className = "gdp-trash-dialog-actions";
|
|
23518
|
-
actionRow.append(...actions);
|
|
23519
|
-
dialog.append(heading2, message, actionRow);
|
|
23520
|
-
backdrop.appendChild(dialog);
|
|
23521
|
-
document.body.appendChild(backdrop);
|
|
23522
|
-
return backdrop;
|
|
23523
|
-
}
|
|
23524
25384
|
function confirmMoveToTrash(path, focusReturnTarget) {
|
|
23525
|
-
return
|
|
23526
|
-
|
|
23527
|
-
|
|
23528
|
-
|
|
23529
|
-
|
|
23530
|
-
|
|
23531
|
-
const move = document.createElement("button");
|
|
23532
|
-
move.type = "button";
|
|
23533
|
-
move.className = "gdp-btn gdp-btn-sm gdp-trash-dialog-danger";
|
|
23534
|
-
move.textContent = "Move to Trash";
|
|
23535
|
-
const done = (ok) => {
|
|
23536
|
-
document.removeEventListener("keydown", onKeydown);
|
|
23537
|
-
closeTrashDialog();
|
|
23538
|
-
previousFocus?.focus?.();
|
|
23539
|
-
resolve(ok);
|
|
23540
|
-
};
|
|
23541
|
-
const onKeydown = (event) => {
|
|
23542
|
-
if (isImeComposing(event))
|
|
23543
|
-
return;
|
|
23544
|
-
if (event.key === "Escape") {
|
|
23545
|
-
event.preventDefault();
|
|
23546
|
-
event.stopPropagation();
|
|
23547
|
-
done(false);
|
|
23548
|
-
return;
|
|
23549
|
-
}
|
|
23550
|
-
if (event.key !== "Tab")
|
|
23551
|
-
return;
|
|
23552
|
-
const focusables = [cancel, move];
|
|
23553
|
-
const index = focusables.indexOf(document.activeElement);
|
|
23554
|
-
if (index < 0) {
|
|
23555
|
-
event.preventDefault();
|
|
23556
|
-
focusables[0].focus();
|
|
23557
|
-
return;
|
|
23558
|
-
}
|
|
23559
|
-
if (event.shiftKey && index <= 0) {
|
|
23560
|
-
event.preventDefault();
|
|
23561
|
-
focusables[focusables.length - 1].focus();
|
|
23562
|
-
} else if (!event.shiftKey && index === focusables.length - 1) {
|
|
23563
|
-
event.preventDefault();
|
|
23564
|
-
focusables[0].focus();
|
|
23565
|
-
}
|
|
23566
|
-
};
|
|
23567
|
-
cancel.addEventListener("click", () => done(false));
|
|
23568
|
-
move.addEventListener("click", () => done(true));
|
|
23569
|
-
const backdrop = createTrashDialog("Move to Trash?", `Move "${path}" to Trash?`, [cancel, move]);
|
|
23570
|
-
backdrop.addEventListener("pointerdown", (event) => {
|
|
23571
|
-
if (event.target === backdrop)
|
|
23572
|
-
done(false);
|
|
23573
|
-
});
|
|
23574
|
-
document.addEventListener("keydown", onKeydown);
|
|
23575
|
-
cancel.focus();
|
|
25385
|
+
return showConfirmDialog({
|
|
25386
|
+
title: "Move to Trash?",
|
|
25387
|
+
body: `Move "${path}" to Trash?`,
|
|
25388
|
+
confirmLabel: "Move to Trash",
|
|
25389
|
+
danger: true,
|
|
25390
|
+
focusReturnTarget
|
|
23576
25391
|
});
|
|
23577
25392
|
}
|
|
23578
25393
|
function askNewDirectoryName(path, focusReturnTarget) {
|
|
23579
|
-
return
|
|
23580
|
-
|
|
23581
|
-
|
|
23582
|
-
|
|
23583
|
-
|
|
23584
|
-
|
|
23585
|
-
|
|
23586
|
-
|
|
23587
|
-
|
|
23588
|
-
create.textContent = "Create";
|
|
23589
|
-
const input = document.createElement("input");
|
|
23590
|
-
input.className = "gdp-create-dir-input";
|
|
23591
|
-
input.type = "text";
|
|
23592
|
-
input.autocomplete = "off";
|
|
23593
|
-
input.placeholder = "Folder name";
|
|
23594
|
-
input.setAttribute("aria-label", "Folder name");
|
|
23595
|
-
const error2 = document.createElement("div");
|
|
23596
|
-
error2.className = "gdp-create-dir-error";
|
|
23597
|
-
error2.setAttribute("role", "alert");
|
|
23598
|
-
const syncValidity = () => {
|
|
23599
|
-
const valid = !!normalizeNewDirectoryName(input.value);
|
|
23600
|
-
create.disabled = !valid;
|
|
23601
|
-
error2.textContent = input.value && !valid ? "Use a folder name without slashes, control characters, . or .." : "";
|
|
23602
|
-
return valid;
|
|
23603
|
-
};
|
|
23604
|
-
const done = (name) => {
|
|
23605
|
-
document.removeEventListener("keydown", onKeydown);
|
|
23606
|
-
closeTrashDialog();
|
|
23607
|
-
previousFocus?.focus?.();
|
|
23608
|
-
resolve(name);
|
|
23609
|
-
};
|
|
23610
|
-
const submit = () => {
|
|
23611
|
-
const name = normalizeNewDirectoryName(input.value);
|
|
23612
|
-
if (!name) {
|
|
23613
|
-
syncValidity();
|
|
23614
|
-
input.focus();
|
|
23615
|
-
return;
|
|
23616
|
-
}
|
|
23617
|
-
done(name);
|
|
23618
|
-
};
|
|
23619
|
-
const onKeydown = (event) => {
|
|
23620
|
-
if (isImeComposing(event))
|
|
23621
|
-
return;
|
|
23622
|
-
if (event.key === "Escape") {
|
|
23623
|
-
event.preventDefault();
|
|
23624
|
-
event.stopPropagation();
|
|
23625
|
-
done(null);
|
|
23626
|
-
return;
|
|
23627
|
-
}
|
|
23628
|
-
if (event.key === "Enter") {
|
|
23629
|
-
event.preventDefault();
|
|
23630
|
-
submit();
|
|
23631
|
-
return;
|
|
23632
|
-
}
|
|
23633
|
-
if (event.key !== "Tab")
|
|
23634
|
-
return;
|
|
23635
|
-
const focusables = [input, cancel, create];
|
|
23636
|
-
const index = focusables.indexOf(document.activeElement);
|
|
23637
|
-
if (index < 0) {
|
|
23638
|
-
event.preventDefault();
|
|
23639
|
-
focusables[0].focus();
|
|
23640
|
-
return;
|
|
23641
|
-
}
|
|
23642
|
-
if (event.shiftKey && index <= 0) {
|
|
23643
|
-
event.preventDefault();
|
|
23644
|
-
focusables[focusables.length - 1].focus();
|
|
23645
|
-
} else if (!event.shiftKey && index === focusables.length - 1) {
|
|
23646
|
-
event.preventDefault();
|
|
23647
|
-
focusables[0].focus();
|
|
23648
|
-
}
|
|
23649
|
-
};
|
|
23650
|
-
cancel.addEventListener("click", () => done(null));
|
|
23651
|
-
create.addEventListener("click", submit);
|
|
23652
|
-
input.addEventListener("input", syncValidity);
|
|
23653
|
-
create.disabled = true;
|
|
23654
|
-
const backdrop = createTrashDialog("New Folder", `Create a folder in "${path || getProjectName() || "repository"}".`, [cancel, create]);
|
|
23655
|
-
const body = backdrop.querySelector(".gdp-trash-dialog-body");
|
|
23656
|
-
body?.append(input, error2);
|
|
23657
|
-
backdrop.addEventListener("pointerdown", (event) => {
|
|
23658
|
-
if (event.target === backdrop)
|
|
23659
|
-
done(null);
|
|
23660
|
-
});
|
|
23661
|
-
document.addEventListener("keydown", onKeydown);
|
|
23662
|
-
input.focus();
|
|
25394
|
+
return showPromptDialog({
|
|
25395
|
+
title: "New Folder",
|
|
25396
|
+
body: `Create a folder in "${path || getProjectName() || "repository"}".`,
|
|
25397
|
+
placeholder: "Folder name",
|
|
25398
|
+
ariaLabel: "Folder name",
|
|
25399
|
+
confirmLabel: "Create",
|
|
25400
|
+
validate: (v) => normalizeNewDirectoryName(v),
|
|
25401
|
+
invalidMessage: "Use a folder name without slashes, control characters, . or ..",
|
|
25402
|
+
focusReturnTarget
|
|
23663
25403
|
});
|
|
23664
25404
|
}
|
|
23665
25405
|
async function requestCreateDirectory(path, onCreated, options = {}) {
|
|
@@ -23730,7 +25470,7 @@ code-viewer query diff rows --id diff-xyz789 --table users --type inserted --lim
|
|
|
23730
25470
|
return button;
|
|
23731
25471
|
}
|
|
23732
25472
|
function showRepoContextMenu(event, entry, ref, onChanged) {
|
|
23733
|
-
if (document.querySelector(".gdp-
|
|
25473
|
+
if (document.querySelector(".gdp-dialog-backdrop"))
|
|
23734
25474
|
return false;
|
|
23735
25475
|
if (!canTrashWorktreeRef(ref))
|
|
23736
25476
|
return false;
|
|
@@ -24364,22 +26104,10 @@ code-viewer query diff rows --id diff-xyz789 --table users --type inserted --lim
|
|
|
24364
26104
|
}
|
|
24365
26105
|
let creatingDirectory = false;
|
|
24366
26106
|
function showTrashError(message) {
|
|
24367
|
-
|
|
24368
|
-
ok.type = "button";
|
|
24369
|
-
ok.className = "gdp-btn gdp-btn-sm";
|
|
24370
|
-
ok.textContent = "OK";
|
|
24371
|
-
ok.addEventListener("click", closeTrashDialog);
|
|
24372
|
-
createTrashDialog("Trash failed", message, [ok]);
|
|
24373
|
-
ok.focus();
|
|
26107
|
+
showAlertDialog({ title: "Trash failed", body: message });
|
|
24374
26108
|
}
|
|
24375
26109
|
function showCreateDirectoryError(message) {
|
|
24376
|
-
|
|
24377
|
-
ok.type = "button";
|
|
24378
|
-
ok.className = "gdp-btn gdp-btn-sm";
|
|
24379
|
-
ok.textContent = "OK";
|
|
24380
|
-
ok.addEventListener("click", closeTrashDialog);
|
|
24381
|
-
createTrashDialog("New folder failed", message, [ok]);
|
|
24382
|
-
ok.focus();
|
|
26110
|
+
showAlertDialog({ title: "New folder failed", body: message });
|
|
24383
26111
|
}
|
|
24384
26112
|
async function moveRepoPathToTrash(path) {
|
|
24385
26113
|
const res = await fetch("/_trash_path", {
|
|
@@ -24404,7 +26132,12 @@ code-viewer query diff rows --id diff-xyz789 --table users --type inserted --lim
|
|
|
24404
26132
|
if (!list2.length)
|
|
24405
26133
|
return;
|
|
24406
26134
|
const label = path || getProjectName() || "repository root";
|
|
24407
|
-
|
|
26135
|
+
const ok = await showConfirmDialog({
|
|
26136
|
+
title: "Upload files?",
|
|
26137
|
+
body: `Upload ${list2.length} file${list2.length === 1 ? "" : "s"} into ${label}?`,
|
|
26138
|
+
confirmLabel: "Upload"
|
|
26139
|
+
});
|
|
26140
|
+
if (!ok)
|
|
24408
26141
|
return;
|
|
24409
26142
|
const form = new FormData;
|
|
24410
26143
|
form.set("dir", path);
|
|
@@ -27440,6 +29173,11 @@ code-viewer query diff rows --id diff-xyz789 --table users --type inserted --lim
|
|
|
27440
29173
|
uploadsTitle: "Uploads",
|
|
27441
29174
|
uploadEnabledLabel: "Allow file uploads into worktree folders",
|
|
27442
29175
|
uploadEnabledHelp: "Disable to make the worktree read-only for everyone using this server.",
|
|
29176
|
+
datastoreTitle: "Datastores",
|
|
29177
|
+
datastoreInferFkLabel: "Infer FK from Rails-style naming (<name>_id → <names>.id)",
|
|
29178
|
+
datastoreInferFkHelp: "Show inferred foreign-key links in the related-data panel for SQL tables.",
|
|
29179
|
+
datastoreS3TooltipLabel: "Show S3 object preview tooltip on hover",
|
|
29180
|
+
datastoreS3TooltipHelp: "Hovering an S3 object row shows the full key path and a content preview.",
|
|
27443
29181
|
watchTitle: "File change watcher",
|
|
27444
29182
|
watchLimit: "Maximum directories to watch",
|
|
27445
29183
|
watchLimitHelp: (defaultLimit) => `Higher values reduce missed updates in deep trees at the cost of file handles. Combine with the Skip list above to keep heavy folders (node_modules, .git, dist...) out of the watch budget. Default: ${defaultLimit}.`
|
|
@@ -27541,6 +29279,11 @@ code-viewer query diff rows --id diff-xyz789 --table users --type inserted --lim
|
|
|
27541
29279
|
uploadsTitle: "アップロード",
|
|
27542
29280
|
uploadEnabledLabel: "ワークツリーへのファイルアップロードを許可する",
|
|
27543
29281
|
uploadEnabledHelp: "オフにすると、このサーバを使う全員に対してワークツリーは読み取り専用になります。",
|
|
29282
|
+
datastoreTitle: "データストア",
|
|
29283
|
+
datastoreInferFkLabel: "Rails 命名規約 (<name>_id → <names>.id) から FK を推測",
|
|
29284
|
+
datastoreInferFkHelp: "SQL テーブルの関連データパネルに Rails 命名規約由来の仮想 FK リンクを表示します。",
|
|
29285
|
+
datastoreS3TooltipLabel: "S3 オブジェクトの hover プレビューを表示",
|
|
29286
|
+
datastoreS3TooltipHelp: "S3 オブジェクト行にホバーすると、完全な key とコンテンツプレビューを表示します。",
|
|
27544
29287
|
watchTitle: "ファイル変更の監視",
|
|
27545
29288
|
watchLimit: "監視するディレクトリ数の上限",
|
|
27546
29289
|
watchLimitHelp: (defaultLimit) => `値を大きくすると深いツリーの変更を取りこぼしにくくなりますが、ファイルハンドル数を消費します。上の Skip リストと併用すると、重いフォルダ(node_modules, .git, dist など)を監視枠から外せます。既定値: ${defaultLimit}。`
|
|
@@ -27662,7 +29405,13 @@ code-viewer query diff rows --id diff-xyz789 --table users --type inserted --lim
|
|
|
27662
29405
|
if (settingsSections[2])
|
|
27663
29406
|
settingsSections[2].textContent = text2.settings.excludedDirectories;
|
|
27664
29407
|
if (settingsSections[3])
|
|
27665
|
-
settingsSections[3].textContent = text2.settings.
|
|
29408
|
+
settingsSections[3].textContent = text2.settings.datastoreTitle;
|
|
29409
|
+
if (settingsSections[4])
|
|
29410
|
+
settingsSections[4].textContent = text2.settings.watchTitle;
|
|
29411
|
+
setElementText("#datastore-infer-fk-label", text2.settings.datastoreInferFkLabel);
|
|
29412
|
+
setElementText("#datastore-infer-fk-help", text2.settings.datastoreInferFkHelp);
|
|
29413
|
+
setElementText("#datastore-s3-tooltip-label", text2.settings.datastoreS3TooltipLabel);
|
|
29414
|
+
setElementText("#datastore-s3-tooltip-help", text2.settings.datastoreS3TooltipHelp);
|
|
27666
29415
|
setElementText("#upload-enabled-label", text2.settings.uploadEnabledLabel);
|
|
27667
29416
|
setElementText("#upload-help", text2.settings.uploadEnabledHelp);
|
|
27668
29417
|
setElementText("#scope-omit-dirs-help", text2.settings.omitDirsHelp);
|
|
@@ -27874,6 +29623,7 @@ code-viewer query diff rows --id diff-xyz789 --table users --type inserted --lim
|
|
|
27874
29623
|
const uploadToggle = document.querySelector("#upload-enabled");
|
|
27875
29624
|
if (uploadToggle)
|
|
27876
29625
|
uploadToggle.checked = APP_SETTINGS.uploadEnabled !== false;
|
|
29626
|
+
syncDatastoreToggles();
|
|
27877
29627
|
source.textContent = uiText().settings.scopeSource(PROJECT_NAME || "default", scopeOmitSourceLabel());
|
|
27878
29628
|
pop.hidden = false;
|
|
27879
29629
|
viewerLanguage.focus();
|
|
@@ -28491,6 +30241,22 @@ code-viewer query diff rows --id diff-xyz789 --table users --type inserted --lim
|
|
|
28491
30241
|
$("#upload-enabled")?.addEventListener("change", (event) => {
|
|
28492
30242
|
saveUploadEnabled(event.currentTarget.checked);
|
|
28493
30243
|
});
|
|
30244
|
+
$("#datastore-infer-fk")?.addEventListener("change", (event) => {
|
|
30245
|
+
DATABASE_VIEW.setDbUiPref("inferFkRails", event.currentTarget.checked);
|
|
30246
|
+
});
|
|
30247
|
+
$("#datastore-s3-tooltip")?.addEventListener("change", (event) => {
|
|
30248
|
+
DATABASE_VIEW.setDbUiPref("s3TooltipEnabled", event.currentTarget.checked);
|
|
30249
|
+
});
|
|
30250
|
+
function syncDatastoreToggles() {
|
|
30251
|
+
const inferToggle = document.querySelector("#datastore-infer-fk");
|
|
30252
|
+
if (inferToggle) {
|
|
30253
|
+
inferToggle.checked = DATABASE_VIEW.getDbUiPref("inferFkRails", false);
|
|
30254
|
+
}
|
|
30255
|
+
const tooltipToggle = document.querySelector("#datastore-s3-tooltip");
|
|
30256
|
+
if (tooltipToggle) {
|
|
30257
|
+
tooltipToggle.checked = DATABASE_VIEW.getDbUiPref("s3TooltipEnabled", true);
|
|
30258
|
+
}
|
|
30259
|
+
}
|
|
28494
30260
|
$("#scope-omit-dirs")?.addEventListener("change", (event) => {
|
|
28495
30261
|
saveScopeOmitDirsField(event.currentTarget.value);
|
|
28496
30262
|
});
|
|
@@ -28912,6 +30678,7 @@ code-viewer query diff rows --id diff-xyz789 --table users --type inserted --lim
|
|
|
28912
30678
|
if (!isCurrentDiffRequest())
|
|
28913
30679
|
return null;
|
|
28914
30680
|
const result = renderShell(data, options.changedPaths);
|
|
30681
|
+
applyHideTestsToMeta();
|
|
28915
30682
|
setStatus("live");
|
|
28916
30683
|
return result;
|
|
28917
30684
|
}).catch(() => {
|
|
@@ -29022,6 +30789,7 @@ code-viewer query diff rows --id diff-xyz789 --table users --type inserted --lim
|
|
|
29022
30789
|
});
|
|
29023
30790
|
},
|
|
29024
30791
|
getSyntaxHighlight: () => STATE.syntaxHighlight,
|
|
30792
|
+
getLanguage: () => STATE.language,
|
|
29025
30793
|
trackLoad
|
|
29026
30794
|
});
|
|
29027
30795
|
const DATABASE_VIEW = createDatabaseView({
|
|
@@ -29033,6 +30801,11 @@ code-viewer query diff rows --id diff-xyz789 --table users --type inserted --lim
|
|
|
29033
30801
|
getLanguage: () => STATE.language
|
|
29034
30802
|
});
|
|
29035
30803
|
relocalizeDatabase = () => DATABASE_VIEW.localize();
|
|
30804
|
+
DATABASE_VIEW.onDbUiPrefChange(() => {
|
|
30805
|
+
if (!document.querySelector("#scope-settings-popover")?.hidden) {
|
|
30806
|
+
syncDatastoreToggles();
|
|
30807
|
+
}
|
|
30808
|
+
});
|
|
29036
30809
|
const REF_PICKER = createRefPicker({
|
|
29037
30810
|
$,
|
|
29038
30811
|
escapeHtml: escapeHtml3,
|
|
@@ -29203,6 +30976,28 @@ code-viewer query diff rows --id diff-xyz789 --table users --type inserted --lim
|
|
|
29203
30976
|
updateTreeDirVisibility();
|
|
29204
30977
|
if (typeof applyViewedState === "function")
|
|
29205
30978
|
applyViewedState();
|
|
30979
|
+
applyHideTestsToMeta();
|
|
30980
|
+
}
|
|
30981
|
+
function applyHideTestsToMeta() {
|
|
30982
|
+
const meta = window._lastMeta;
|
|
30983
|
+
if (!meta || !meta.totals)
|
|
30984
|
+
return;
|
|
30985
|
+
const effective = STATE.hideTests && !isRepositorySidebarMode();
|
|
30986
|
+
if (!effective) {
|
|
30987
|
+
renderMeta(meta);
|
|
30988
|
+
return;
|
|
30989
|
+
}
|
|
30990
|
+
let additions = 0;
|
|
30991
|
+
let deletions = 0;
|
|
30992
|
+
let files = 0;
|
|
30993
|
+
for (const f2 of STATE.files) {
|
|
30994
|
+
if (TEST_RE.test(f2.path || ""))
|
|
30995
|
+
continue;
|
|
30996
|
+
additions += f2.additions || 0;
|
|
30997
|
+
deletions += f2.deletions || 0;
|
|
30998
|
+
files += 1;
|
|
30999
|
+
}
|
|
31000
|
+
renderMeta({ ...meta, totals: { files, additions, deletions } });
|
|
29206
31001
|
}
|
|
29207
31002
|
applyHideTests();
|
|
29208
31003
|
$("#hide-tests").addEventListener("click", () => {
|