dsh-qr-share 0.1.0 → 0.2.0
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/CHANGELOG.md +52 -0
- package/README.md +12 -0
- package/README.zh.md +9 -0
- package/lib/client-registry.js +89 -48
- package/lib/client.js +89 -48
- package/lib/types/client/QrDialog.d.ts +7 -0
- package/package.json +12 -3
- package/src/client/QrButton.tsx +3 -2
- package/src/client/QrDialog.test.tsx +113 -0
- package/src/client/QrDialog.tsx +60 -29
- package/src/client/api.test.ts +126 -0
- package/src/client/locales.ts +6 -6
- package/src/client/styles.module.css +28 -9
- package/src/client/styles.module.css.d.ts +2 -0
- package/src/config.test.ts +71 -0
- package/src/test/setup.ts +12 -0
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,58 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.2.0] — 2026-08-30
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **Test infrastructure** — vitest 2 + @testing-library/react, configured
|
|
15
|
+
for happy-dom. `pnpm test` runs 27 specs covering `config`,
|
|
16
|
+
`client/api` and `client/QrDialog`. `tsconfig.build.json` now
|
|
17
|
+
excludes `src/**/*.test.ts(x)` and `src/test/**` so the tests never
|
|
18
|
+
reach the published `.d.ts` output.
|
|
19
|
+
- **Ambient CSS module declaration** — `src/client/styles.module.css.d.ts`
|
|
20
|
+
gives `tsc` the default-export shape that `lightningcss` produces at
|
|
21
|
+
build time, unblocking `import styles from './styles.module.css'`
|
|
22
|
+
in `QrButton.tsx` and `QrDialog.tsx` without weakening the type
|
|
23
|
+
checker.
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
|
|
27
|
+
- **Dialog now renders into `document.body` via `createPortal`** —
|
|
28
|
+
the sidebar's `transform`/`filter` would otherwise trap
|
|
29
|
+
`position: fixed` inside the sidebar's box; portalling escapes every
|
|
30
|
+
ancestor's containing block so the backdrop covers the viewport and
|
|
31
|
+
the dialog is centered on screen.
|
|
32
|
+
- **Dialog surface is theme-aware and fully opaque** — background
|
|
33
|
+
resolves to the DSH shell's `--dsw-alias-bg-layer-1` token (alpha = 1),
|
|
34
|
+
with a `currentColor 12%` hairline border and a soft shadow. A
|
|
35
|
+
`--dsh-qr-share-dialog-bg` override hook is exposed for power users.
|
|
36
|
+
- **QR code canvas wrapped in a white card** — 8px padding + `#fff`
|
|
37
|
+
background guarantees the black modules stay scannable against
|
|
38
|
+
either light or dark DSH surface.
|
|
39
|
+
- **Sidebar action is now a labeled icon** — wide mode renders both
|
|
40
|
+
the `HiOutlineQrCode` icon and the `button.label` text; rail mode
|
|
41
|
+
keeps the icon only.
|
|
42
|
+
- **Copy localized** — `button.label` is now "分享" (zh) / "Share"
|
|
43
|
+
(en); `button.tooltip` and `dialog.title` were rewritten in the
|
|
44
|
+
same voice ("扫码把当前会话分享到手机" / "Scan to share"). The host
|
|
45
|
+
handshake's semantics are unchanged.
|
|
46
|
+
|
|
47
|
+
### Fixed
|
|
48
|
+
|
|
49
|
+
- **QR canvas was blank on first render** — the `useEffect` that
|
|
50
|
+
calls `qrcode.toCanvas` ran once before the portal-mounted `<canvas>`
|
|
51
|
+
was in the DOM. Adding `portalTarget` to the effect's dependency
|
|
52
|
+
list gives the canvas a second pass after the portal resolves.
|
|
53
|
+
- **CSS Module class names were hardcoded** — `QrDialog.tsx` and
|
|
54
|
+
`QrButton.tsx` used the literal `'dsh-qr-share-backdrop'`
|
|
55
|
+
strings, but tsdown's `lightningcss` compiles them under the
|
|
56
|
+
`[hash]_[local]` pattern (`i2jIGq_backdrop`). The two forms never
|
|
57
|
+
matched, so the dialog had no styles at all and fell back to
|
|
58
|
+
`position: static`. Both files now consume the `styles.*` import
|
|
59
|
+
map; the bundle ships the hashed class names in the inline
|
|
60
|
+
`<style data-plugin>` tag.
|
|
61
|
+
|
|
10
62
|
## [0.1.0] — 2026-08-29
|
|
11
63
|
|
|
12
64
|
### Added
|
package/README.md
CHANGED
|
@@ -99,12 +99,24 @@ layout needs a symlink — see `manual` install below).
|
|
|
99
99
|
## Build
|
|
100
100
|
|
|
101
101
|
```bash
|
|
102
|
+
# Recommended: pnpm (matches the project lockfile-less layout)
|
|
102
103
|
pnpm install
|
|
103
104
|
pnpm build # tsc declarations + tsdown bundles
|
|
104
105
|
pnpm typecheck # strict check
|
|
105
106
|
pnpm pack # produce dsh-qr-share-<version>.tgz (used by the install commands above)
|
|
107
|
+
|
|
108
|
+
# Also supported: npm (any Node >= 20, no extra install)
|
|
109
|
+
npm install
|
|
110
|
+
npm run build # same tsc + tsdown pipeline
|
|
111
|
+
npm run typecheck
|
|
112
|
+
npm pack
|
|
106
113
|
```
|
|
107
114
|
|
|
115
|
+
> For npm publishing we ship [`./npm_publish.sh`](npm_publish.sh) — a
|
|
116
|
+
> single-file mirror of the [core_ts](https://github.com/xiaoguomeiyitian/core_ts)
|
|
117
|
+
> release flow (`npm login` → `npm install` → `npm run build` →
|
|
118
|
+
> `npm pack --dry-run` review → `npm publish --access public`).
|
|
119
|
+
|
|
108
120
|
Outputs:
|
|
109
121
|
|
|
110
122
|
- `lib/index.js` — Node host half (the `/_qr/share` route)
|
package/README.zh.md
CHANGED
|
@@ -74,12 +74,21 @@ pnpm add dsh-qr-share
|
|
|
74
74
|
## 构建
|
|
75
75
|
|
|
76
76
|
```bash
|
|
77
|
+
# 推荐:pnpm(与项目无 lockfile 的设计一致)
|
|
77
78
|
pnpm install
|
|
78
79
|
pnpm build # tsc 声明 + tsdown bundle
|
|
79
80
|
pnpm typecheck # 严格检查
|
|
80
81
|
pnpm pack # 产出 dsh-qr-share-<version>.tgz
|
|
82
|
+
|
|
83
|
+
# 也支持:npm(任意 Node >= 20, 无需额外安装)
|
|
84
|
+
npm install
|
|
85
|
+
npm run build # 同样的 tsc + tsdown 管线
|
|
86
|
+
npm run typecheck
|
|
87
|
+
npm pack
|
|
81
88
|
```
|
|
82
89
|
|
|
90
|
+
> 发版用 [`./npm_publish.sh`](npm_publish.sh)——单文件脚本,照搬 [core_ts](https://github.com/xiaoguomeiyitian/core_ts) 的发布流程(`npm login` → `npm install` → `npm run build` → `npm pack --dry-run` 预检 → `npm publish --access public`)。
|
|
91
|
+
|
|
83
92
|
产物:
|
|
84
93
|
|
|
85
94
|
- `lib/index.js` — Node 宿主半(`/_qr/share` 路由)
|
package/lib/client-registry.js
CHANGED
|
@@ -29,7 +29,8 @@ window.__ModuleLoader__.load({
|
|
|
29
29
|
//#endregion
|
|
30
30
|
let react = require("react");
|
|
31
31
|
react = __toESM(react, 1);
|
|
32
|
-
|
|
32
|
+
let react_dom = require("react-dom");
|
|
33
|
+
//#region node_modules/react-icons/lib/iconContext.mjs
|
|
33
34
|
var DefaultContext = {
|
|
34
35
|
color: void 0,
|
|
35
36
|
size: void 0,
|
|
@@ -39,7 +40,7 @@ window.__ModuleLoader__.load({
|
|
|
39
40
|
};
|
|
40
41
|
var IconContext = react.default.createContext && /*#__PURE__*/ react.default.createContext(DefaultContext);
|
|
41
42
|
//#endregion
|
|
42
|
-
//#region node_modules
|
|
43
|
+
//#region node_modules/react-icons/lib/iconBase.mjs
|
|
43
44
|
var _excluded = [
|
|
44
45
|
"attr",
|
|
45
46
|
"size",
|
|
@@ -143,7 +144,7 @@ window.__ModuleLoader__.load({
|
|
|
143
144
|
return IconContext !== void 0 ? /*#__PURE__*/ react.default.createElement(IconContext.Consumer, null, (conf) => elem(conf)) : elem(DefaultContext);
|
|
144
145
|
}
|
|
145
146
|
//#endregion
|
|
146
|
-
//#region node_modules
|
|
147
|
+
//#region node_modules/react-icons/hi2/index.mjs
|
|
147
148
|
function HiOutlineQrCode(props) {
|
|
148
149
|
return GenIcon({
|
|
149
150
|
"tag": "svg",
|
|
@@ -174,14 +175,14 @@ window.__ModuleLoader__.load({
|
|
|
174
175
|
})(props);
|
|
175
176
|
}
|
|
176
177
|
//#endregion
|
|
177
|
-
//#region node_modules
|
|
178
|
+
//#region node_modules/qrcode/lib/can-promise.js
|
|
178
179
|
var require_can_promise = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
179
180
|
module.exports = function() {
|
|
180
181
|
return typeof Promise === "function" && Promise.prototype && Promise.prototype.then;
|
|
181
182
|
};
|
|
182
183
|
}));
|
|
183
184
|
//#endregion
|
|
184
|
-
//#region node_modules
|
|
185
|
+
//#region node_modules/qrcode/lib/core/utils.js
|
|
185
186
|
var require_utils$1 = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
186
187
|
let toSJISFunction;
|
|
187
188
|
const CODEWORDS_COUNT = [
|
|
@@ -273,7 +274,7 @@ window.__ModuleLoader__.load({
|
|
|
273
274
|
};
|
|
274
275
|
}));
|
|
275
276
|
//#endregion
|
|
276
|
-
//#region node_modules
|
|
277
|
+
//#region node_modules/qrcode/lib/core/error-correction-level.js
|
|
277
278
|
var require_error_correction_level = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
278
279
|
exports.L = { bit: 1 };
|
|
279
280
|
exports.M = { bit: 0 };
|
|
@@ -306,7 +307,7 @@ window.__ModuleLoader__.load({
|
|
|
306
307
|
};
|
|
307
308
|
}));
|
|
308
309
|
//#endregion
|
|
309
|
-
//#region node_modules
|
|
310
|
+
//#region node_modules/qrcode/lib/core/bit-buffer.js
|
|
310
311
|
var require_bit_buffer = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
311
312
|
function BitBuffer() {
|
|
312
313
|
this.buffer = [];
|
|
@@ -333,7 +334,7 @@ window.__ModuleLoader__.load({
|
|
|
333
334
|
module.exports = BitBuffer;
|
|
334
335
|
}));
|
|
335
336
|
//#endregion
|
|
336
|
-
//#region node_modules
|
|
337
|
+
//#region node_modules/qrcode/lib/core/bit-matrix.js
|
|
337
338
|
var require_bit_matrix = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
338
339
|
/**
|
|
339
340
|
* Helper class to handle QR Code symbol modules
|
|
@@ -394,7 +395,7 @@ window.__ModuleLoader__.load({
|
|
|
394
395
|
module.exports = BitMatrix;
|
|
395
396
|
}));
|
|
396
397
|
//#endregion
|
|
397
|
-
//#region node_modules
|
|
398
|
+
//#region node_modules/qrcode/lib/core/alignment-pattern.js
|
|
398
399
|
var require_alignment_pattern = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
399
400
|
/**
|
|
400
401
|
* Alignment pattern are fixed reference pattern in defined positions
|
|
@@ -462,7 +463,7 @@ window.__ModuleLoader__.load({
|
|
|
462
463
|
};
|
|
463
464
|
}));
|
|
464
465
|
//#endregion
|
|
465
|
-
//#region node_modules
|
|
466
|
+
//#region node_modules/qrcode/lib/core/finder-pattern.js
|
|
466
467
|
var require_finder_pattern = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
467
468
|
const getSymbolSize = require_utils$1().getSymbolSize;
|
|
468
469
|
const FINDER_PATTERN_SIZE = 7;
|
|
@@ -483,7 +484,7 @@ window.__ModuleLoader__.load({
|
|
|
483
484
|
};
|
|
484
485
|
}));
|
|
485
486
|
//#endregion
|
|
486
|
-
//#region node_modules
|
|
487
|
+
//#region node_modules/qrcode/lib/core/mask-pattern.js
|
|
487
488
|
var require_mask_pattern = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
488
489
|
/**
|
|
489
490
|
* Data mask pattern reference
|
|
@@ -674,7 +675,7 @@ window.__ModuleLoader__.load({
|
|
|
674
675
|
};
|
|
675
676
|
}));
|
|
676
677
|
//#endregion
|
|
677
|
-
//#region node_modules
|
|
678
|
+
//#region node_modules/qrcode/lib/core/error-correction-code.js
|
|
678
679
|
var require_error_correction_code = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
679
680
|
const ECLevel = require_error_correction_level();
|
|
680
681
|
const EC_BLOCKS_TABLE = [
|
|
@@ -1037,7 +1038,7 @@ window.__ModuleLoader__.load({
|
|
|
1037
1038
|
};
|
|
1038
1039
|
}));
|
|
1039
1040
|
//#endregion
|
|
1040
|
-
//#region node_modules
|
|
1041
|
+
//#region node_modules/qrcode/lib/core/galois-field.js
|
|
1041
1042
|
var require_galois_field = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
1042
1043
|
const EXP_TABLE = /* @__PURE__ */ new Uint8Array(512);
|
|
1043
1044
|
const LOG_TABLE = /* @__PURE__ */ new Uint8Array(256);
|
|
@@ -1083,7 +1084,7 @@ window.__ModuleLoader__.load({
|
|
|
1083
1084
|
};
|
|
1084
1085
|
}));
|
|
1085
1086
|
//#endregion
|
|
1086
|
-
//#region node_modules
|
|
1087
|
+
//#region node_modules/qrcode/lib/core/polynomial.js
|
|
1087
1088
|
var require_polynomial = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
1088
1089
|
const GF = require_galois_field();
|
|
1089
1090
|
/**
|
|
@@ -1130,7 +1131,7 @@ window.__ModuleLoader__.load({
|
|
|
1130
1131
|
};
|
|
1131
1132
|
}));
|
|
1132
1133
|
//#endregion
|
|
1133
|
-
//#region node_modules
|
|
1134
|
+
//#region node_modules/qrcode/lib/core/reed-solomon-encoder.js
|
|
1134
1135
|
var require_reed_solomon_encoder = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
1135
1136
|
const Polynomial = require_polynomial();
|
|
1136
1137
|
function ReedSolomonEncoder(degree) {
|
|
@@ -1170,7 +1171,7 @@ window.__ModuleLoader__.load({
|
|
|
1170
1171
|
module.exports = ReedSolomonEncoder;
|
|
1171
1172
|
}));
|
|
1172
1173
|
//#endregion
|
|
1173
|
-
//#region node_modules
|
|
1174
|
+
//#region node_modules/qrcode/lib/core/version-check.js
|
|
1174
1175
|
var require_version_check = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
1175
1176
|
/**
|
|
1176
1177
|
* Check if QR Code version is valid
|
|
@@ -1183,7 +1184,7 @@ window.__ModuleLoader__.load({
|
|
|
1183
1184
|
};
|
|
1184
1185
|
}));
|
|
1185
1186
|
//#endregion
|
|
1186
|
-
//#region node_modules
|
|
1187
|
+
//#region node_modules/qrcode/lib/core/regex.js
|
|
1187
1188
|
var require_regex = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
1188
1189
|
const numeric = "[0-9]+";
|
|
1189
1190
|
const alphanumeric = "[A-Z $%*+\\-./:]+";
|
|
@@ -1209,7 +1210,7 @@ window.__ModuleLoader__.load({
|
|
|
1209
1210
|
};
|
|
1210
1211
|
}));
|
|
1211
1212
|
//#endregion
|
|
1212
|
-
//#region node_modules
|
|
1213
|
+
//#region node_modules/qrcode/lib/core/mode.js
|
|
1213
1214
|
var require_mode = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
1214
1215
|
const VersionCheck = require_version_check();
|
|
1215
1216
|
const Regex = require_regex();
|
|
@@ -1366,7 +1367,7 @@ window.__ModuleLoader__.load({
|
|
|
1366
1367
|
};
|
|
1367
1368
|
}));
|
|
1368
1369
|
//#endregion
|
|
1369
|
-
//#region node_modules
|
|
1370
|
+
//#region node_modules/qrcode/lib/core/version.js
|
|
1370
1371
|
var require_version = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
1371
1372
|
const Utils = require_utils$1();
|
|
1372
1373
|
const ECCode = require_error_correction_code();
|
|
@@ -1463,7 +1464,7 @@ window.__ModuleLoader__.load({
|
|
|
1463
1464
|
};
|
|
1464
1465
|
}));
|
|
1465
1466
|
//#endregion
|
|
1466
|
-
//#region node_modules
|
|
1467
|
+
//#region node_modules/qrcode/lib/core/format-info.js
|
|
1467
1468
|
var require_format_info = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
1468
1469
|
const Utils = require_utils$1();
|
|
1469
1470
|
const G15 = 1335;
|
|
@@ -1487,7 +1488,7 @@ window.__ModuleLoader__.load({
|
|
|
1487
1488
|
};
|
|
1488
1489
|
}));
|
|
1489
1490
|
//#endregion
|
|
1490
|
-
//#region node_modules
|
|
1491
|
+
//#region node_modules/qrcode/lib/core/numeric-data.js
|
|
1491
1492
|
var require_numeric_data = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
1492
1493
|
const Mode = require_mode();
|
|
1493
1494
|
function NumericData(data) {
|
|
@@ -1520,7 +1521,7 @@ window.__ModuleLoader__.load({
|
|
|
1520
1521
|
module.exports = NumericData;
|
|
1521
1522
|
}));
|
|
1522
1523
|
//#endregion
|
|
1523
|
-
//#region node_modules
|
|
1524
|
+
//#region node_modules/qrcode/lib/core/alphanumeric-data.js
|
|
1524
1525
|
var require_alphanumeric_data = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
1525
1526
|
const Mode = require_mode();
|
|
1526
1527
|
/**
|
|
@@ -1604,7 +1605,7 @@ window.__ModuleLoader__.load({
|
|
|
1604
1605
|
module.exports = AlphanumericData;
|
|
1605
1606
|
}));
|
|
1606
1607
|
//#endregion
|
|
1607
|
-
//#region node_modules
|
|
1608
|
+
//#region node_modules/qrcode/lib/core/byte-data.js
|
|
1608
1609
|
var require_byte_data = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
1609
1610
|
const Mode = require_mode();
|
|
1610
1611
|
function ByteData(data) {
|
|
@@ -1627,7 +1628,7 @@ window.__ModuleLoader__.load({
|
|
|
1627
1628
|
module.exports = ByteData;
|
|
1628
1629
|
}));
|
|
1629
1630
|
//#endregion
|
|
1630
|
-
//#region node_modules
|
|
1631
|
+
//#region node_modules/qrcode/lib/core/kanji-data.js
|
|
1631
1632
|
var require_kanji_data = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
1632
1633
|
const Mode = require_mode();
|
|
1633
1634
|
const Utils = require_utils$1();
|
|
@@ -1658,7 +1659,7 @@ window.__ModuleLoader__.load({
|
|
|
1658
1659
|
module.exports = KanjiData;
|
|
1659
1660
|
}));
|
|
1660
1661
|
//#endregion
|
|
1661
|
-
//#region node_modules
|
|
1662
|
+
//#region node_modules/dijkstrajs/dijkstra.js
|
|
1662
1663
|
var require_dijkstra = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
1663
1664
|
/******************************************************************************
|
|
1664
1665
|
* Created 2008-08-19.
|
|
@@ -1774,7 +1775,7 @@ window.__ModuleLoader__.load({
|
|
|
1774
1775
|
if (typeof module !== "undefined") module.exports = dijkstra;
|
|
1775
1776
|
}));
|
|
1776
1777
|
//#endregion
|
|
1777
|
-
//#region node_modules
|
|
1778
|
+
//#region node_modules/qrcode/lib/core/segments.js
|
|
1778
1779
|
var require_segments = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
1779
1780
|
const Mode = require_mode();
|
|
1780
1781
|
const NumericData = require_numeric_data();
|
|
@@ -2053,7 +2054,7 @@ window.__ModuleLoader__.load({
|
|
|
2053
2054
|
};
|
|
2054
2055
|
}));
|
|
2055
2056
|
//#endregion
|
|
2056
|
-
//#region node_modules
|
|
2057
|
+
//#region node_modules/qrcode/lib/core/qrcode.js
|
|
2057
2058
|
var require_qrcode = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
2058
2059
|
const Utils = require_utils$1();
|
|
2059
2060
|
const ECLevel = require_error_correction_level();
|
|
@@ -2349,7 +2350,7 @@ window.__ModuleLoader__.load({
|
|
|
2349
2350
|
};
|
|
2350
2351
|
}));
|
|
2351
2352
|
//#endregion
|
|
2352
|
-
//#region node_modules
|
|
2353
|
+
//#region node_modules/qrcode/lib/renderer/utils.js
|
|
2353
2354
|
var require_utils = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
2354
2355
|
function hex2rgba(hex) {
|
|
2355
2356
|
if (typeof hex === "number") hex = hex.toString();
|
|
@@ -2417,7 +2418,7 @@ window.__ModuleLoader__.load({
|
|
|
2417
2418
|
};
|
|
2418
2419
|
}));
|
|
2419
2420
|
//#endregion
|
|
2420
|
-
//#region node_modules
|
|
2421
|
+
//#region node_modules/qrcode/lib/renderer/canvas.js
|
|
2421
2422
|
var require_canvas = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
2422
2423
|
const Utils = require_utils();
|
|
2423
2424
|
function clearCanvas(ctx, canvas, size) {
|
|
@@ -2466,7 +2467,7 @@ window.__ModuleLoader__.load({
|
|
|
2466
2467
|
};
|
|
2467
2468
|
}));
|
|
2468
2469
|
//#endregion
|
|
2469
|
-
//#region node_modules
|
|
2470
|
+
//#region node_modules/qrcode/lib/renderer/svg-tag.js
|
|
2470
2471
|
var require_svg_tag = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
2471
2472
|
const Utils = require_utils();
|
|
2472
2473
|
function getColorAttrib(color, attrib) {
|
|
@@ -2583,24 +2584,51 @@ window.__ModuleLoader__.load({
|
|
|
2583
2584
|
*/
|
|
2584
2585
|
const LOCALE_NS = "qr-share";
|
|
2585
2586
|
const zh = {
|
|
2586
|
-
"button.label": "
|
|
2587
|
-
"button.tooltip": "
|
|
2588
|
-
"dialog.title": "
|
|
2587
|
+
"button.label": "分享",
|
|
2588
|
+
"button.tooltip": "扫码把当前会话分享到手机",
|
|
2589
|
+
"dialog.title": "扫码分享",
|
|
2589
2590
|
"dialog.hint": "用手机相机扫描下方二维码。手机打开后会自动以当前账号登录,token 与浏览器 cookie 同寿命。",
|
|
2590
2591
|
"dialog.urlLabel": "链接",
|
|
2591
2592
|
"dialog.close": "关闭",
|
|
2592
2593
|
"dialog.error": "二维码生成失败"
|
|
2593
2594
|
};
|
|
2594
2595
|
const en = {
|
|
2595
|
-
"button.label": "
|
|
2596
|
-
"button.tooltip": "Scan
|
|
2597
|
-
"dialog.title": "Scan to
|
|
2596
|
+
"button.label": "Share",
|
|
2597
|
+
"button.tooltip": "Scan to share this session with your phone",
|
|
2598
|
+
"dialog.title": "Scan to share",
|
|
2598
2599
|
"dialog.hint": "Scan the QR code below with your phone camera. The phone will log in to this session; the token lives as long as the browser cookie.",
|
|
2599
2600
|
"dialog.urlLabel": "URL",
|
|
2600
2601
|
"dialog.close": "Close",
|
|
2601
2602
|
"dialog.error": "Failed to render QR code"
|
|
2602
2603
|
};
|
|
2603
2604
|
//#endregion
|
|
2605
|
+
//#region \0dsh-css:/root/ai/dsh-qr-share/src/client/styles.module.css.mjs
|
|
2606
|
+
const css = ".GjS9-q_button{width:100%;color:inherit;font:inherit;cursor:pointer;text-align:left;background:0 0;border:0;border-radius:6px;align-items:center;gap:8px;padding:8px 10px;display:inline-flex}.GjS9-q_button:hover{background:color-mix(in srgb, currentColor 8%, transparent)}.GjS9-q_button:focus-visible{outline-offset:-2px;outline:2px solid}.GjS9-q_buttonLabel{white-space:nowrap;text-overflow:ellipsis;font-size:13px;overflow:hidden}.GjS9-q_backdrop{z-index:9999;background:#00000073;place-items:center;animation:.12s ease-out GjS9-q_dsh-qr-share-fade-in;display:grid;position:fixed;inset:0}.GjS9-q_dialog{background:var(--dsh-qr-share-dialog-bg,var(--dsw-alias-bg-layer-1,var(--dsw-alias-bg-overlay,#2c2c2e)));color:var(--dsh-qr-share-dialog-fg,currentColor);border:1px solid color-mix(in srgb, currentColor 12%, transparent);border-radius:12px;flex-direction:column;align-items:center;gap:12px;min-width:320px;max-width:420px;padding:24px;animation:.16s cubic-bezier(.2,.9,.3,1.2) GjS9-q_dsh-qr-share-pop-in;display:flex;box-shadow:0 12px 32px #00000040}.GjS9-q_title{margin:0;font-size:16px;font-weight:600}.GjS9-q_canvas{image-rendering:pixelated;background:#fff;border-radius:8px;padding:8px;display:block}.GjS9-q_err{color:#d23f3f;margin:0;font-size:13px}.GjS9-q_urlBlock{flex-direction:column;align-items:center;gap:4px;width:100%;display:flex}.GjS9-q_urlLabel{text-transform:uppercase;letter-spacing:.05em;opacity:.6;font-size:11px}.GjS9-q_url{word-break:break-all;background:color-mix(in srgb, currentColor 6%, transparent);text-align:center;border-radius:4px;max-width:100%;padding:6px 8px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11px}.GjS9-q_hint{opacity:.7;text-align:center;margin:0;font-size:12px}.GjS9-q_close{border:1px solid color-mix(in srgb, currentColor 20%, transparent);color:inherit;font:inherit;cursor:pointer;background:0 0;border-radius:6px;margin-top:4px;padding:8px 16px}.GjS9-q_close:hover{background:color-mix(in srgb, currentColor 8%, transparent)}.GjS9-q_close:focus-visible{outline-offset:-2px;outline:2px solid}@keyframes GjS9-q_dsh-qr-share-fade-in{0%{opacity:0}to{opacity:1}}@keyframes GjS9-q_dsh-qr-share-pop-in{0%{opacity:0;transform:scale(.92)}to{opacity:1;transform:scale(1)}}";
|
|
2607
|
+
const tagId = "dsh-external/dsh-qr-share/styles.module.css";
|
|
2608
|
+
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
2609
|
+
const tag = document.createElement("style");
|
|
2610
|
+
tag.dataset.plugin = "dsh-external/dsh-qr-share";
|
|
2611
|
+
tag.dataset.pluginCss = tagId;
|
|
2612
|
+
tag.textContent = css;
|
|
2613
|
+
document.head.appendChild(tag);
|
|
2614
|
+
}
|
|
2615
|
+
var styles_module_css_default = {
|
|
2616
|
+
"title": "GjS9-q_title",
|
|
2617
|
+
"urlBlock": "GjS9-q_urlBlock",
|
|
2618
|
+
"buttonLabel": "GjS9-q_buttonLabel",
|
|
2619
|
+
"err": "GjS9-q_err",
|
|
2620
|
+
"urlLabel": "GjS9-q_urlLabel",
|
|
2621
|
+
"url": "GjS9-q_url",
|
|
2622
|
+
"hint": "GjS9-q_hint",
|
|
2623
|
+
"backdrop": "GjS9-q_backdrop",
|
|
2624
|
+
"dsh-qr-share-pop-in": "GjS9-q_dsh-qr-share-pop-in",
|
|
2625
|
+
"close": "GjS9-q_close",
|
|
2626
|
+
"dialog": "GjS9-q_dialog",
|
|
2627
|
+
"dsh-qr-share-fade-in": "GjS9-q_dsh-qr-share-fade-in",
|
|
2628
|
+
"button": "GjS9-q_button",
|
|
2629
|
+
"canvas": "GjS9-q_canvas"
|
|
2630
|
+
};
|
|
2631
|
+
//#endregion
|
|
2604
2632
|
//#region src/client/QrDialog.tsx
|
|
2605
2633
|
/**
|
|
2606
2634
|
* QR-code modal: a fixed-position overlay with a canvas, the resolved URL
|
|
@@ -2613,6 +2641,13 @@ window.__ModuleLoader__.load({
|
|
|
2613
2641
|
* exchanges on path `/` (see browser-auth.ts). We enforce that here so a
|
|
2614
2642
|
* future reverse-proxy sub-path does NOT silently produce a 401 on the
|
|
2615
2643
|
* phone.
|
|
2644
|
+
*
|
|
2645
|
+
* Portal: the dialog is rendered into `document.body` via createPortal.
|
|
2646
|
+
* The shell's sidebar uses transforms/filters on its ancestors, which
|
|
2647
|
+
* would otherwise trap `position: fixed` inside the sidebar's box and
|
|
2648
|
+
* make the dialog appear *inside* the sidebar column. Portaling escapes
|
|
2649
|
+
* every ancestor's containing block so the backdrop covers the viewport
|
|
2650
|
+
* and the dialog is centered on screen.
|
|
2616
2651
|
*/
|
|
2617
2652
|
function composeQrUrl(token) {
|
|
2618
2653
|
const url = new URL("/", window.location.origin);
|
|
@@ -2623,7 +2658,12 @@ window.__ModuleLoader__.load({
|
|
|
2623
2658
|
const canvasRef = (0, react.useRef)(null);
|
|
2624
2659
|
const [payload, setPayload] = (0, react.useState)("");
|
|
2625
2660
|
const [error, setError] = (0, react.useState)(null);
|
|
2661
|
+
const [portalTarget, setPortalTarget] = (0, react.useState)(null);
|
|
2662
|
+
(0, react.useEffect)(() => {
|
|
2663
|
+
setPortalTarget(document.body);
|
|
2664
|
+
}, []);
|
|
2626
2665
|
(0, react.useEffect)(() => {
|
|
2666
|
+
if (portalTarget === null) return;
|
|
2627
2667
|
if (token === "") return;
|
|
2628
2668
|
const url = composeQrUrl(token);
|
|
2629
2669
|
setPayload(url);
|
|
@@ -2639,7 +2679,7 @@ window.__ModuleLoader__.load({
|
|
|
2639
2679
|
}).catch((reason) => {
|
|
2640
2680
|
setError(reason instanceof Error ? reason.message : String(reason));
|
|
2641
2681
|
});
|
|
2642
|
-
}, [token]);
|
|
2682
|
+
}, [token, portalTarget]);
|
|
2643
2683
|
(0, react.useEffect)(() => {
|
|
2644
2684
|
const onKey = (e) => {
|
|
2645
2685
|
if (e.key === "Escape") onClose();
|
|
@@ -2652,28 +2692,29 @@ window.__ModuleLoader__.load({
|
|
|
2652
2692
|
const onBackdropClick = (e) => {
|
|
2653
2693
|
if (e.target === e.currentTarget) onClose();
|
|
2654
2694
|
};
|
|
2655
|
-
|
|
2656
|
-
|
|
2695
|
+
if (portalTarget === null) return null;
|
|
2696
|
+
return (0, react_dom.createPortal)((0, react.createElement)("div", {
|
|
2697
|
+
className: styles_module_css_default.backdrop,
|
|
2657
2698
|
role: "presentation",
|
|
2658
2699
|
onClick: onBackdropClick
|
|
2659
2700
|
}, (0, react.createElement)("div", {
|
|
2660
|
-
className:
|
|
2701
|
+
className: styles_module_css_default.dialog,
|
|
2661
2702
|
role: "dialog",
|
|
2662
2703
|
"aria-modal": "true",
|
|
2663
2704
|
"aria-labelledby": "dsh-qr-share-title"
|
|
2664
2705
|
}, (0, react.createElement)("h2", {
|
|
2665
2706
|
id: "dsh-qr-share-title",
|
|
2666
|
-
className:
|
|
2667
|
-
}, t("dialog.title")), error !== null ? (0, react.createElement)("p", { className:
|
|
2707
|
+
className: styles_module_css_default.title
|
|
2708
|
+
}, t("dialog.title")), error !== null ? (0, react.createElement)("p", { className: styles_module_css_default.err }, `${t("dialog.error")}: ${error}`) : (0, react.createElement)("canvas", {
|
|
2668
2709
|
ref: canvasRef,
|
|
2669
|
-
className:
|
|
2710
|
+
className: styles_module_css_default.canvas,
|
|
2670
2711
|
width: 256,
|
|
2671
2712
|
height: 256
|
|
2672
|
-
}), (0, react.createElement)("div", { className:
|
|
2713
|
+
}), (0, react.createElement)("div", { className: styles_module_css_default.urlBlock }, (0, react.createElement)("span", { className: styles_module_css_default.urlLabel }, t("dialog.urlLabel")), (0, react.createElement)("code", { className: styles_module_css_default.url }, payload)), (0, react.createElement)("p", { className: styles_module_css_default.hint }, t("dialog.hint")), (0, react.createElement)("button", {
|
|
2673
2714
|
type: "button",
|
|
2674
|
-
className:
|
|
2715
|
+
className: styles_module_css_default.close,
|
|
2675
2716
|
onClick: onClose
|
|
2676
|
-
}, t("dialog.close"))));
|
|
2717
|
+
}, t("dialog.close")))), portalTarget);
|
|
2677
2718
|
}
|
|
2678
2719
|
//#endregion
|
|
2679
2720
|
//#region src/client/QrButton.tsx
|
|
@@ -2688,13 +2729,13 @@ window.__ModuleLoader__.load({
|
|
|
2688
2729
|
const [open, setOpen] = (0, react.useState)(false);
|
|
2689
2730
|
return (0, react.createElement)(react.Fragment, null, (0, react.createElement)("button", {
|
|
2690
2731
|
type: "button",
|
|
2691
|
-
className:
|
|
2732
|
+
className: styles_module_css_default.button,
|
|
2692
2733
|
"aria-label": t("button.label"),
|
|
2693
2734
|
title: t("button.tooltip"),
|
|
2694
2735
|
onClick: () => {
|
|
2695
2736
|
setOpen(true);
|
|
2696
2737
|
}
|
|
2697
|
-
}, (0, react.createElement)(HiOutlineQrCode, { size: wide ? 18 : 20 }), wide ? (0, react.createElement)("span", { className:
|
|
2738
|
+
}, (0, react.createElement)(HiOutlineQrCode, { size: wide ? 18 : 20 }), wide ? (0, react.createElement)("span", { className: styles_module_css_default.buttonLabel }, t("button.label")) : null), open ? (0, react.createElement)(QrDialog, {
|
|
2698
2739
|
token: info.token,
|
|
2699
2740
|
tokenTtlMs: info.tokenTtlMs,
|
|
2700
2741
|
t,
|