dsh-db-tool 1.5.4 → 1.5.5
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/client/client.js +83 -27
- package/package.json +3 -2
package/client/client.js
CHANGED
|
@@ -107,7 +107,6 @@ window.__ModuleLoader__.load({
|
|
|
107
107
|
truncatedNoEdit: "值已截断显示,就地编辑可能覆盖数据,请改用 SQL 控制台",
|
|
108
108
|
blobNoEdit: "二进制值不支持就地编辑",
|
|
109
109
|
redisTypeRo: "Redis 该类型不支持就地编辑",
|
|
110
|
-
cellSaved: "已保存",
|
|
111
110
|
// SQL 控制台
|
|
112
111
|
modeQuery: "只读查询",
|
|
113
112
|
modeExecute: "写入执行",
|
|
@@ -217,7 +216,6 @@ window.__ModuleLoader__.load({
|
|
|
217
216
|
truncatedNoEdit: "Value shown truncated; edit via SQL Console instead",
|
|
218
217
|
blobNoEdit: "Binary values cannot be edited here",
|
|
219
218
|
redisTypeRo: "This Redis type cannot be edited here",
|
|
220
|
-
cellSaved: "Saved",
|
|
221
219
|
modeQuery: "query (read-only)",
|
|
222
220
|
modeExecute: "execute (write)",
|
|
223
221
|
modeScript: "script",
|
|
@@ -352,7 +350,7 @@ window.__ModuleLoader__.load({
|
|
|
352
350
|
".dbt-btn.danger:hover{background:rgba(255,69,58,.12);}",
|
|
353
351
|
".dbt-btn:disabled{opacity:.4;cursor:default;transform:none;}",
|
|
354
352
|
"/* ===== 文本反馈 ===== */",
|
|
355
|
-
".dbt-muted{color:var(--dbt-text-secondary);font-size:11px;}",
|
|
353
|
+
".dbt-muted,.dbt-readhint{color:var(--dbt-text-secondary);font-size:11px;}",
|
|
356
354
|
".dbt-err{color:var(--dbt-danger);font-size:12px;white-space:pre-wrap;}",
|
|
357
355
|
".dbt-msg{color:var(--dbt-success);font-size:12px;white-space:pre-wrap;}",
|
|
358
356
|
"/* ===== 行 hairline 表格 + 毛玻璃 sticky 表头 ===== */",
|
|
@@ -417,7 +415,6 @@ window.__ModuleLoader__.load({
|
|
|
417
415
|
".dbt-celledit textarea{font-family:var(--dbt-mono);font-size:13px;min-height:96px;resize:vertical;background:var(--dbt-surface-strong);border:none;color:inherit;border-radius:8px;padding:8px;}",
|
|
418
416
|
".dbt-celledit textarea:focus{outline:2px solid var(--dbt-accent);outline-offset:-1px;}",
|
|
419
417
|
".dbt-nullchip{display:inline-flex;align-items:center;border:1px solid var(--dbt-warning);color:var(--dbt-warning);border-radius:999px;padding:1px 8px;font-size:11px;font-weight:600;}",
|
|
420
|
-
".dbt-readhint{color:var(--dbt-text-secondary);font-size:11px;}",
|
|
421
418
|
"/* ===== 视图切换入场(仅 transform/opacity;reduced-motion 全局已禁) ===== */",
|
|
422
419
|
"@keyframes dbt-in{from{opacity:0;transform:translateY(4px);}to{opacity:1;transform:none;}}",
|
|
423
420
|
".dbt-view{display:flex;flex-direction:column;gap:10px;animation:dbt-in .22s var(--dbt-ease);}",
|
|
@@ -427,7 +424,7 @@ window.__ModuleLoader__.load({
|
|
|
427
424
|
".dbt-seg button.active{background:var(--dbt-seg-active);font-weight:600;box-shadow:0 1px 3px rgba(0,0,0,.12);}",
|
|
428
425
|
".dbt-browse-root .dbt-seg button{font-size:12px;padding:4px 12px;}",
|
|
429
426
|
"/* ===== 降级:透明度减弱 / 动效减弱 ===== */",
|
|
430
|
-
"@media (prefers-reduced-transparency: reduce){.dbt-overlay{backdrop-filter:none;-webkit-backdrop-filter:none;background:rgba(0,0,0,.55);}.dbt-dialog,.dbt-browse-dialog,.dbt-cellpop{backdrop-filter:none;-webkit-backdrop-filter:none;
|
|
427
|
+
"@media (prefers-reduced-transparency: reduce){.dbt-overlay{backdrop-filter:none;-webkit-backdrop-filter:none;background:rgba(0,0,0,.55);}.dbt-dialog,.dbt-browse-dialog,.dbt-cellpop{backdrop-filter:none;-webkit-backdrop-filter:none;background:#2c2c2e;}.dbt-table th{backdrop-filter:none;-webkit-backdrop-filter:none;background:#1e1e20;}.dbt-browse-root .dbt-table th{background:#1e1e20;}}",
|
|
431
428
|
"@media (prefers-color-scheme: light) and (prefers-reduced-transparency: reduce){.dbt-dialog,.dbt-browse-dialog,.dbt-cellpop{background:#f5f5f7;}.dbt-table th,.dbt-browse-root .dbt-table th{background:#f2f2f7;}}",
|
|
432
429
|
"@media (prefers-reduced-motion: reduce){.dbt-panel *,.dbt-browse-root *{transition:none!important;animation:none!important;}.dbt-btn:active{transform:none;}}",
|
|
433
430
|
].join("\n");
|
|
@@ -454,7 +451,8 @@ window.__ModuleLoader__.load({
|
|
|
454
451
|
React.createElement("div", { className: "dbt-muted" }, t("confirmHint")),
|
|
455
452
|
React.createElement(
|
|
456
453
|
"div",
|
|
457
|
-
|
|
454
|
+
// 末对齐由样式层 .dbt-dialog .dbt-row 规则承担,无需内联 style
|
|
455
|
+
{ className: "dbt-row" },
|
|
458
456
|
React.createElement("button", { className: "dbt-btn danger", onClick: () => done(true) }, t("confirmRun")),
|
|
459
457
|
React.createElement("button", { className: "dbt-btn", onClick: () => done(false) }, t("confirmCancel")),
|
|
460
458
|
),
|
|
@@ -497,15 +495,43 @@ window.__ModuleLoader__.load({
|
|
|
497
495
|
}
|
|
498
496
|
// PiP 独立窗口渲染时禁 portal:PiP root 本身就挂在 pipDoc.body 上,再 portal 回主 document 会把 DOM 拉出独立窗口
|
|
499
497
|
let portalDisabled = false;
|
|
500
|
-
// createRoot
|
|
498
|
+
// createRoot 三重解析(PiP 独立窗口需要独立 React root,结构对齐上方 portal 解析):
|
|
499
|
+
// ①模块加载时同步 require react-dom → react-dom/client(宿主按 specifier 解析,两者是不同键)
|
|
500
|
+
// ②宿主模块系统异步 import("react-dom/client") ③均不可用则 PiP 超时降级 DOM 浮窗
|
|
501
501
|
let reactCreateRoot = null;
|
|
502
|
+
let rootTried = false; // 异步 import 只发起一次
|
|
503
|
+
let rootPending = null; // 订阅解析成功的回调列表
|
|
504
|
+
let rootSource = null; // 就绪方式:sync / async / null(未就绪,诊断日志用)
|
|
502
505
|
try {
|
|
503
506
|
var rd2 = require("react-dom");
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
+
if (rd2 && typeof rd2.createRoot === "function") {
|
|
508
|
+
reactCreateRoot = rd2.createRoot.bind(rd2); rootTried = true; rootSource = "sync";
|
|
509
|
+
} else {
|
|
510
|
+
try { var rdc = require("react-dom/client"); if (rdc && typeof rdc.createRoot === "function") { reactCreateRoot = rdc.createRoot.bind(rdc); rootTried = true; rootSource = "sync"; } } catch (e2) { /* 保持 null */ }
|
|
511
|
+
}
|
|
512
|
+
} catch (e) { /* 保持 null,等异步解析 */ }
|
|
513
|
+
function resolveRootAsync(ctx) {
|
|
514
|
+
if (rootTried) return;
|
|
515
|
+
rootTried = true;
|
|
516
|
+
if (reactCreateRoot) return;
|
|
517
|
+
var mods = ctx && ctx.modules;
|
|
518
|
+
var p = mods && typeof mods.import === "function" ? mods.import("react-dom/client") : null;
|
|
519
|
+
if (p && typeof p.then === "function") {
|
|
520
|
+
p.then(function (m) {
|
|
521
|
+
if (m && typeof m.createRoot === "function") {
|
|
522
|
+
reactCreateRoot = m.createRoot.bind(m);
|
|
523
|
+
rootSource = "async";
|
|
524
|
+
var waiters = rootPending || []; rootPending = null;
|
|
525
|
+
for (var i = 0; i < waiters.length; i++) waiters[i]();
|
|
526
|
+
}
|
|
527
|
+
}).catch(function () { /* 解析失败,PiP 走超时降级 */ });
|
|
507
528
|
}
|
|
508
|
-
}
|
|
529
|
+
}
|
|
530
|
+
function onRootReady(cb) {
|
|
531
|
+
if (reactCreateRoot) { cb(); return; }
|
|
532
|
+
(rootPending = rootPending || []).push(cb);
|
|
533
|
+
}
|
|
534
|
+
let pipResult = "not-tried"; // 最近一次 PiP 尝试结果:opened / rejected:<err.name>(诊断日志用)
|
|
509
535
|
|
|
510
536
|
/* ---------------- 连接管理 ---------------- */
|
|
511
537
|
const EMPTY_FORM = { id: "", kind: "mysql", name: "", mode: "url", url: "", urlUser: "", urlPassword: "", host: "", port: "", user: "", password: "", database: "", ssl: false };
|
|
@@ -1345,17 +1371,20 @@ window.__ModuleLoader__.load({
|
|
|
1345
1371
|
|
|
1346
1372
|
function BrowseDialog(props) {
|
|
1347
1373
|
const { conns, projectPath, grants, askConfirm, onClose, ctx } = props;
|
|
1348
|
-
// grants
|
|
1374
|
+
// grants(editable 判定)与 askConfirm(单元格写回确认)下传 PreviewGrid
|
|
1349
1375
|
// portal 异步解析接入:portalReady 仅用于触发重渲染(mountDialog 内部读 portalImpl)。
|
|
1350
1376
|
// createPortal 不换组件实例——portalReady 切换前后是同一 DOM 节点,pos state 保持,居中定位不受 portal 重挂影响。
|
|
1351
1377
|
const [portalReady, setPortalReady] = React.useState(!!portalImpl);
|
|
1352
1378
|
React.useEffect(() => {
|
|
1353
1379
|
resolvePortalAsync(ctx);
|
|
1380
|
+
resolveRootAsync(ctx);
|
|
1354
1381
|
onPortalReady(function () { setPortalReady(true); });
|
|
1355
|
-
// 诊断:一次性输出(帮助真机排查 iframe 隔离与 portal
|
|
1382
|
+
// 诊断:一次性输出(帮助真机排查 iframe 隔离与 portal/createRoot 解析结果、PiP 尝试结果)
|
|
1356
1383
|
if (!window.__dbtPortalDiag) {
|
|
1357
1384
|
window.__dbtPortalDiag = true;
|
|
1358
|
-
console.info("[dbt] portal:", portalImpl ? "sync/async ok" : "unavailable",
|
|
1385
|
+
console.info("[dbt] portal:", portalImpl ? "sync/async ok" : "unavailable",
|
|
1386
|
+
"createRoot:", rootSource || "none", "pip:", pipResult,
|
|
1387
|
+
"sameDoc:", window.top === window.self);
|
|
1359
1388
|
}
|
|
1360
1389
|
}, []);
|
|
1361
1390
|
const [sel, setSel] = React.useState(null); // 弹窗内当前选中(null=未选表,右栏显示引导空态)
|
|
@@ -1577,12 +1606,16 @@ window.__ModuleLoader__.load({
|
|
|
1577
1606
|
confirmReq ? React.createElement(DangerDialog, { challenge: confirmReq, onClose: () => setConfirmReq(null) }) : null,
|
|
1578
1607
|
);
|
|
1579
1608
|
}
|
|
1580
|
-
function openBrowsePip(ctx, deps) { // deps: {conns, projectPath, grants, onClosed} → 返回 pipWindow | null
|
|
1609
|
+
function openBrowsePip(ctx, deps) { // deps: {conns, projectPath, grants, onClosed, onFallback} → 返回 pipWindow | null
|
|
1581
1610
|
try {
|
|
1582
1611
|
const dpip = window.documentPictureInPicture;
|
|
1583
|
-
|
|
1612
|
+
// 入口只硬性要求 dpip;createRoot/portal 未就绪不再放弃——手势内先开窗,root 异步就绪后渲染,超时由调用方降级
|
|
1613
|
+
if (!dpip || typeof dpip.requestWindow !== "function") return null;
|
|
1614
|
+
resolveRootAsync(ctx); // createRoot 同步未就绪时,趁 3 秒超时窗口发起宿主异步 import
|
|
1584
1615
|
// PiP 只能在用户手势内同步发起 requestWindow,Promise then 里再做 DOM 装配
|
|
1585
|
-
|
|
1616
|
+
const p = dpip.requestWindow({ width: 1100, height: 720 });
|
|
1617
|
+
pipResult = "opened"; // requestWindow 已发起;拒绝走下方 catch
|
|
1618
|
+
return p.then((w) => {
|
|
1586
1619
|
const doc = w.document;
|
|
1587
1620
|
// 相对地址基准:PiP 文档是 about:blank,fetch 的相对 API 路径须按主 origin 解析
|
|
1588
1621
|
const base = doc.createElement("base");
|
|
@@ -1591,25 +1624,46 @@ window.__ModuleLoader__.load({
|
|
|
1591
1624
|
// 样式:整套 --dbt-* token 与规则注入 PiP 文档
|
|
1592
1625
|
if (styleEl) doc.head.appendChild(doc.importNode(styleEl, true));
|
|
1593
1626
|
doc.body.style.margin = "0";
|
|
1594
|
-
|
|
1595
|
-
const
|
|
1627
|
+
// 加载占位:createRoot 异步解析期间给用户可见反馈(PiP 场景少用,不做 i18n key)
|
|
1628
|
+
const ph = doc.createElement("div");
|
|
1629
|
+
ph.style.cssText = "display:flex;align-items:center;justify-content:center;height:100%;font:14px sans-serif;color:#888;";
|
|
1630
|
+
ph.textContent = "正在打开数据浏览…";
|
|
1631
|
+
doc.body.appendChild(ph);
|
|
1632
|
+
let root = null;
|
|
1633
|
+
let settled = false; // root 已渲染或窗口已关闭:晚到的 onRootReady 回调与超时兜底均不再处理
|
|
1596
1634
|
const closePip = () => {
|
|
1597
|
-
try { root.unmount(); } catch (e) { /* 已卸载 */ }
|
|
1635
|
+
if (root) { try { root.unmount(); } catch (e) { /* 已卸载 */ } }
|
|
1598
1636
|
portalDisabled = false;
|
|
1599
1637
|
try { w.close(); } catch (e2) { /* 已关闭 */ }
|
|
1600
1638
|
};
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1639
|
+
onRootReady(() => {
|
|
1640
|
+
if (settled) return;
|
|
1641
|
+
settled = true;
|
|
1642
|
+
portalDisabled = true; // PiP root 已在 pipDoc.body 上,禁 portal 防 DOM 被拉回主窗口
|
|
1643
|
+
root = reactCreateRoot(doc.body);
|
|
1644
|
+
root.render(React.createElement(PipBrowseApp, {
|
|
1645
|
+
ctx, conns: deps.conns, projectPath: deps.projectPath, grants: deps.grants,
|
|
1646
|
+
onClose: () => { closePip(); deps.onClosed(); },
|
|
1647
|
+
}));
|
|
1648
|
+
ph.remove();
|
|
1649
|
+
});
|
|
1605
1650
|
w.addEventListener("pagehide", () => { // 用户点 PiP 窗口系统关闭钮
|
|
1651
|
+
settled = true;
|
|
1652
|
+
if (root) { try { root.unmount(); } catch (e) { /* 已卸载 */ } }
|
|
1606
1653
|
portalDisabled = false;
|
|
1607
|
-
try { root.unmount(); } catch (e) { /* 已卸载 */ }
|
|
1608
1654
|
deps.onClosed();
|
|
1609
1655
|
});
|
|
1656
|
+
// 超时兜底:3 秒内 createRoot 没就绪 → 关 PiP 清理并回调降级(调用方打开 DOM 浮窗)
|
|
1657
|
+
setTimeout(() => {
|
|
1658
|
+
if (settled) return;
|
|
1659
|
+
settled = true;
|
|
1660
|
+
closePip();
|
|
1661
|
+
deps.onFallback();
|
|
1662
|
+
}, 3000);
|
|
1610
1663
|
return w;
|
|
1611
|
-
}).catch(() => null);
|
|
1664
|
+
}).catch((err) => { pipResult = "rejected:" + (err && err.name || "Error"); return null; });
|
|
1612
1665
|
} catch (e) {
|
|
1666
|
+
pipResult = "rejected:" + (e && e.name || "Error");
|
|
1613
1667
|
return Promise.resolve(null);
|
|
1614
1668
|
}
|
|
1615
1669
|
}
|
|
@@ -1750,6 +1804,8 @@ window.__ModuleLoader__.load({
|
|
|
1750
1804
|
const p = openBrowsePip(props.ctx, {
|
|
1751
1805
|
conns, projectPath, grants,
|
|
1752
1806
|
onClosed: () => setPipWin(null),
|
|
1807
|
+
// PiP 超时降级(createRoot 未就绪):清掉窗口句柄并回退 DOM 浮窗
|
|
1808
|
+
onFallback: () => { setPipWin(null); setBrowseOpen(true); },
|
|
1753
1809
|
});
|
|
1754
1810
|
if (p && typeof p.then === "function") {
|
|
1755
1811
|
p.then((w) => { if (w) setPipWin(w); else setBrowseOpen(true); });
|
|
@@ -1914,7 +1970,7 @@ window.__ModuleLoader__.load({
|
|
|
1914
1970
|
return ctx.betterSidebar.registerTab({
|
|
1915
1971
|
id: TAB_ID,
|
|
1916
1972
|
title: function () { return t("tabTitle"); },
|
|
1917
|
-
icon
|
|
1973
|
+
icon,
|
|
1918
1974
|
order: 45,
|
|
1919
1975
|
single: true,
|
|
1920
1976
|
component: function (p) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-db-tool",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.5",
|
|
4
4
|
"description": "DSH community plugin: chat-operated multi-database admin tool with sidebar management, project-scoped grants, ro/rw modes and dangerous-operation confirmation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -39,7 +39,8 @@
|
|
|
39
39
|
"client": {
|
|
40
40
|
"inject": [
|
|
41
41
|
"@deepseek-ai/dsh-client-locale",
|
|
42
|
-
"react-dom"
|
|
42
|
+
"react-dom",
|
|
43
|
+
"react-dom/client"
|
|
43
44
|
],
|
|
44
45
|
"platform": "web"
|
|
45
46
|
}
|