@celestia-island/hikari 0.45.6 → 0.45.8
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/package.json
CHANGED
|
@@ -126,7 +126,7 @@
|
|
|
126
126
|
color: rgb(var(--color-muted));
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
-
/* ── Credits:
|
|
129
|
+
/* ── Credits: credits sentence + organization blurb ────────────────────── */
|
|
130
130
|
|
|
131
131
|
.s-about-modal-credits {
|
|
132
132
|
display: flex;
|
|
@@ -136,24 +136,19 @@
|
|
|
136
136
|
margin-top: calc(-1 * var(--space-4, 0.25rem));
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
-
|
|
139
|
+
// Inline flow (not flex): the sentence is assembled from text runs and
|
|
140
|
+
// chips, and the runs carry their own spaces around the chips.
|
|
141
|
+
.s-about-modal-credits-line {
|
|
140
142
|
margin: 0;
|
|
141
143
|
font-size: var(--text-sm, 0.8125rem);
|
|
144
|
+
line-height: 2;
|
|
142
145
|
text-align: center;
|
|
146
|
+
text-wrap: pretty;
|
|
143
147
|
color: rgb(var(--color-text));
|
|
144
148
|
}
|
|
145
149
|
|
|
146
|
-
.s-about-modal-
|
|
147
|
-
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
.s-about-modal-row-link {
|
|
151
|
-
color: rgb(var(--color-primary));
|
|
152
|
-
text-decoration: none;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
.s-about-modal-row-link:hover {
|
|
156
|
-
text-decoration: underline;
|
|
150
|
+
.s-about-modal-credit-name {
|
|
151
|
+
color: rgb(var(--color-text));
|
|
157
152
|
}
|
|
158
153
|
|
|
159
154
|
.s-about-modal-description {
|
|
@@ -242,6 +237,14 @@
|
|
|
242
237
|
gap: var(--space-8, 0.5rem);
|
|
243
238
|
}
|
|
244
239
|
|
|
240
|
+
// One chip group (licenses / links). Must span the chips block — inside an
|
|
241
|
+
// `align-items: center` column a plain block would shrink to fit-content and
|
|
242
|
+
// a long chip list would then wrap against that collapsed width instead of
|
|
243
|
+
// the modal's.
|
|
244
|
+
.s-about-modal-links {
|
|
245
|
+
width: 100%;
|
|
246
|
+
}
|
|
247
|
+
|
|
245
248
|
.s-about-modal-links-list {
|
|
246
249
|
display: flex;
|
|
247
250
|
flex-wrap: wrap;
|
|
@@ -249,8 +252,8 @@
|
|
|
249
252
|
gap: var(--space-6, 0.375rem);
|
|
250
253
|
}
|
|
251
254
|
|
|
252
|
-
// Ghost pills:
|
|
253
|
-
//
|
|
255
|
+
// Ghost pills: every link in this dialog is a tag, so they share one face —
|
|
256
|
+
// credits names, licenses, external links and the legal filings.
|
|
254
257
|
.s-about-modal-link {
|
|
255
258
|
padding: var(--space-2, 0.125rem) var(--space-10, 0.625rem);
|
|
256
259
|
border: 1px solid var(--border-subtle, rgb(100 100 100 / 12%));
|
|
@@ -265,6 +268,14 @@
|
|
|
265
268
|
background var(--duration-short, 0.15s) ease;
|
|
266
269
|
}
|
|
267
270
|
|
|
271
|
+
// Chips inside the credits sentence sit in a text run: keep them on the
|
|
272
|
+
// baseline and let the surrounding spaces do the spacing.
|
|
273
|
+
.s-about-modal-credit-link {
|
|
274
|
+
display: inline-block;
|
|
275
|
+
vertical-align: middle;
|
|
276
|
+
margin: 0 0.1em;
|
|
277
|
+
}
|
|
278
|
+
|
|
268
279
|
.s-about-modal-link:hover {
|
|
269
280
|
border-color: var(--c-primary-strong, rgb(var(--color-primary) / 40%));
|
|
270
281
|
background: var(--c-primary-faint, rgb(var(--color-primary) / 5%));
|
|
@@ -284,25 +295,20 @@
|
|
|
284
295
|
|
|
285
296
|
/* ── Legal footer: filing links + copyright ───────────────────────────── */
|
|
286
297
|
|
|
298
|
+
// Filing numbers are long, so the legal chips render a notch smaller than
|
|
299
|
+
// the license / link rows.
|
|
300
|
+
.s-about-modal-footer-link {
|
|
301
|
+
font-size: var(--text-2xs, 0.625rem);
|
|
302
|
+
}
|
|
303
|
+
|
|
287
304
|
.s-about-modal-footer-links {
|
|
288
305
|
display: flex;
|
|
289
306
|
flex-wrap: wrap;
|
|
290
307
|
justify-content: center;
|
|
291
|
-
gap: var(--space-
|
|
308
|
+
gap: var(--space-6, 0.375rem);
|
|
292
309
|
margin-top: calc(-1 * var(--space-4, 0.25rem));
|
|
293
310
|
}
|
|
294
311
|
|
|
295
|
-
.s-about-modal-footer-link {
|
|
296
|
-
font-size: var(--text-xs, 0.75rem);
|
|
297
|
-
color: rgb(var(--color-muted));
|
|
298
|
-
text-decoration: none;
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
.s-about-modal-footer-link:hover {
|
|
302
|
-
color: rgb(var(--color-primary));
|
|
303
|
-
text-decoration: underline;
|
|
304
|
-
}
|
|
305
|
-
|
|
306
312
|
.s-about-modal-footer {
|
|
307
313
|
display: flex;
|
|
308
314
|
justify-content: center;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { dirname, resolve } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
import { compile } from "sass";
|
|
6
|
+
import { describe, expect, it } from "vitest";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Compiled-stylesheet guard for HkAboutModal.
|
|
10
|
+
*
|
|
11
|
+
* Vitest stubs CSS (`test.css` is off), so the runtime suite can never catch
|
|
12
|
+
* a class the component emits that lost its rule — the #493 redesign dropped
|
|
13
|
+
* the `.s-about-modal-links` rule while keeping the element and every test
|
|
14
|
+
* stayed green. This test compiles the component sheet directly and asserts
|
|
15
|
+
* both directions of the class contract:
|
|
16
|
+
*
|
|
17
|
+
* 1. every `s-about-modal-*` class the TSX renders has a rule, and
|
|
18
|
+
* 2. every `s-about-modal-*` rule in the sheet is rendered by the TSX
|
|
19
|
+
* (no dead selectors).
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
const componentDir = resolve(dirname(fileURLToPath(import.meta.url)));
|
|
23
|
+
|
|
24
|
+
function classesFrom(source: string): Set<string> {
|
|
25
|
+
// The component only uses static class strings; collect them from the
|
|
26
|
+
// `class="…"` attributes plus any bare `s-about-modal-*` token.
|
|
27
|
+
const found = new Set<string>();
|
|
28
|
+
for (const match of source.matchAll(/s-about-modal[\w-]*/g)) found.add(match[0]);
|
|
29
|
+
return found;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
describe("HkAboutModal stylesheet contract", () => {
|
|
33
|
+
const tsx = readFileSync(resolve(componentDir, "HkAboutModal.tsx"), "utf8");
|
|
34
|
+
const scss = readFileSync(resolve(componentDir, "HkAboutModal.scss"), "utf8");
|
|
35
|
+
const css = compile(resolve(componentDir, "HkAboutModal.scss"), {
|
|
36
|
+
style: "expanded",
|
|
37
|
+
}).css;
|
|
38
|
+
|
|
39
|
+
it("compiles without warnings", () => {
|
|
40
|
+
expect(css).toContain(".s-about-modal");
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("gives every emitted class a rule", () => {
|
|
44
|
+
// Selector-side scan of the compiled sheet (not the SCSS source): a rule
|
|
45
|
+
// that failed to compile would otherwise still count as "present".
|
|
46
|
+
const declared = new Set([...css.matchAll(/\.(s-about-modal[\w-]*)/g)].map((m) => m[1]!));
|
|
47
|
+
const missing = [...classesFrom(tsx)].filter((name) => !declared.has(name)).sort();
|
|
48
|
+
expect(missing, "classes rendered without a stylesheet rule").toEqual([]);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("declares no rule the component never renders", () => {
|
|
52
|
+
const rendered = classesFrom(tsx);
|
|
53
|
+
const dead = [...new Set([...scss.matchAll(/\.(s-about-modal[\w-]*)/g)].map((m) => m[1]!))]
|
|
54
|
+
.filter((name) => !rendered.has(name))
|
|
55
|
+
.sort();
|
|
56
|
+
expect(dead, "stylesheet selectors with no matching element").toEqual([]);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
@@ -9,8 +9,9 @@ import { setLocale } from "../i18n/context";
|
|
|
9
9
|
* - the first-letter tile is the default identity and `logoSrc` swaps it
|
|
10
10
|
* for an image inside the identity frame
|
|
11
11
|
* - version and tagline share one subtitle line
|
|
12
|
-
* -
|
|
13
|
-
*
|
|
12
|
+
* - the credits sentence assembles text runs and linked name chips, and a
|
|
13
|
+
* name without an href stays plain text
|
|
14
|
+
* - description / license / copyright render the given strings
|
|
14
15
|
* - component rows split the machine value from an optional meta pill
|
|
15
16
|
* - the backdrop factory renders inside the clipped backdrop layer
|
|
16
17
|
* - licenses and links render as chips under one centered block
|
|
@@ -96,27 +97,44 @@ describe("HkAboutModal branding", () => {
|
|
|
96
97
|
expect(query<HTMLElement>(".s-about-modal-description").textContent).toBe("Save the world.");
|
|
97
98
|
});
|
|
98
99
|
|
|
99
|
-
it("renders the
|
|
100
|
+
it("renders the credits sentence with linked name chips", async () => {
|
|
100
101
|
mountAbout({
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
102
|
+
credits: [
|
|
103
|
+
{ text: "来自 " },
|
|
104
|
+
{ name: "Celestia Island", href: "https://github.com/celestia-island" },
|
|
105
|
+
{ text: ",由 " },
|
|
106
|
+
{ name: "伊欧", href: "https://github.com/langyo" },
|
|
107
|
+
{ text: " 倾力设计" },
|
|
108
|
+
],
|
|
105
109
|
});
|
|
106
110
|
await flushModal();
|
|
107
|
-
const line = query<HTMLElement>(".s-about-modal-
|
|
108
|
-
expect(line.textContent).toBe("
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
expect(
|
|
111
|
+
const line = query<HTMLElement>(".s-about-modal-credits-line");
|
|
112
|
+
expect(line.textContent).toBe("来自 Celestia Island,由 伊欧 倾力设计");
|
|
113
|
+
const chips = [
|
|
114
|
+
...document.body.querySelectorAll<HTMLAnchorElement>(".s-about-modal-credit-link"),
|
|
115
|
+
];
|
|
116
|
+
expect(chips.map((chip) => chip.textContent)).toEqual(["Celestia Island", "伊欧"]);
|
|
117
|
+
// Every link in the dialog is a tag and opens in a new tab.
|
|
118
|
+
for (const chip of chips) {
|
|
119
|
+
expect(chip.classList.contains("s-about-modal-link")).toBe(true);
|
|
120
|
+
expect(chip.getAttribute("target")).toBe("_blank");
|
|
121
|
+
expect(chip.getAttribute("rel")).toContain("noopener");
|
|
122
|
+
}
|
|
123
|
+
expect(chips[0]!.getAttribute("href")).toBe("https://github.com/celestia-island");
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("keeps a credit name as plain text when no href is given", async () => {
|
|
127
|
+
mountAbout({ credits: [{ text: "由 " }, { name: "伊欧" }, { text: " 主创" }] });
|
|
128
|
+
await flushModal();
|
|
129
|
+
expect(query<HTMLElement>(".s-about-modal-credits-line").textContent).toBe("由 伊欧 主创");
|
|
130
|
+
expect(query<HTMLElement>(".s-about-modal-credit-name").textContent).toBe("伊欧");
|
|
131
|
+
expect(document.body.querySelector(".s-about-modal-credit-link")).toBeNull();
|
|
113
132
|
});
|
|
114
133
|
|
|
115
|
-
it("
|
|
116
|
-
mountAbout({
|
|
134
|
+
it("skips empty credit parts", async () => {
|
|
135
|
+
mountAbout({ credits: [{ text: "" }, { name: "" }, { text: "仅此一句。" }] });
|
|
117
136
|
await flushModal();
|
|
118
|
-
expect(query<HTMLElement>(".s-about-modal-
|
|
119
|
-
expect(document.body.querySelector(".s-about-modal-row-link")).toBeNull();
|
|
137
|
+
expect(query<HTMLElement>(".s-about-modal-credits-line").textContent).toBe("仅此一句。");
|
|
120
138
|
});
|
|
121
139
|
|
|
122
140
|
it("renders software-component version rows with a meta pill and tone", async () => {
|
|
@@ -151,6 +169,18 @@ describe("HkAboutModal branding", () => {
|
|
|
151
169
|
);
|
|
152
170
|
});
|
|
153
171
|
|
|
172
|
+
it("carries the caution tone through to the pill", async () => {
|
|
173
|
+
mountAbout({
|
|
174
|
+
componentVersions: [
|
|
175
|
+
{ label: "云模型调度引擎版本", value: "0.1.26", meta: "预发", metaTone: "caution" },
|
|
176
|
+
],
|
|
177
|
+
});
|
|
178
|
+
await flushModal();
|
|
179
|
+
expect(query<HTMLElement>(".s-about-modal-row-meta").getAttribute("data-tone")).toBe(
|
|
180
|
+
"caution",
|
|
181
|
+
);
|
|
182
|
+
});
|
|
183
|
+
|
|
154
184
|
it("renders centered license chips", async () => {
|
|
155
185
|
mountAbout({
|
|
156
186
|
licenses: [
|
|
@@ -222,7 +252,7 @@ describe("HkAboutModal branding", () => {
|
|
|
222
252
|
mountAbout();
|
|
223
253
|
await flushModal();
|
|
224
254
|
expect(document.body.querySelectorAll(".s-about-modal-row").length).toBe(0);
|
|
225
|
-
expect(document.body.querySelector(".s-about-modal-
|
|
255
|
+
expect(document.body.querySelector(".s-about-modal-credits-line")).toBeNull();
|
|
226
256
|
expect(document.body.querySelector(".s-about-modal-links")).toBeNull();
|
|
227
257
|
expect(document.body.querySelector(".s-about-modal-chips")).toBeNull();
|
|
228
258
|
expect(document.body.querySelector(".s-about-modal-credits")).toBeNull();
|
|
@@ -10,6 +10,24 @@ export interface HAboutLink {
|
|
|
10
10
|
href: string;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
/**
|
|
14
|
+
* One piece of the credits line.
|
|
15
|
+
*
|
|
16
|
+
* The line is a sentence assembled from parts so a host can phrase (and
|
|
17
|
+
* order) it freely per locale: literal `text` runs sit between linked
|
|
18
|
+
* `name` chips (organization first, author second, …). The names render as
|
|
19
|
+
* the same chips as the license / link rows — every link in this dialog is
|
|
20
|
+
* a tag.
|
|
21
|
+
*/
|
|
22
|
+
export interface HAboutCredit {
|
|
23
|
+
/** Literal sentence fragment (mutually exclusive with `name`). */
|
|
24
|
+
text?: string;
|
|
25
|
+
/** Linked chip label (mutually exclusive with `text`). */
|
|
26
|
+
name?: string;
|
|
27
|
+
/** Target for a `name` chip; opens in a new tab. */
|
|
28
|
+
href?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
13
31
|
/**
|
|
14
32
|
* One software-component version row.
|
|
15
33
|
*
|
|
@@ -33,10 +51,11 @@ export interface HAboutComponentVersion {
|
|
|
33
51
|
*
|
|
34
52
|
* Layout (2026-09 redesign): a centered identity hero (haloed logo, name,
|
|
35
53
|
* then version + tagline on one compact line), an optional credits block
|
|
36
|
-
* (
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
54
|
+
* (a sentence built from text runs and linked name chips, plus the
|
|
55
|
+
* organization blurb), a
|
|
56
|
+
* bordered spec card holding the software-component versions, and the
|
|
57
|
+
* link rows — licenses, external links, legal filings and the credits
|
|
58
|
+
* names all render as the same ghost tag. Every one opens in a new tab. Every branding prop is optional — the modal degrades
|
|
40
59
|
* to the plain identity card when none are given.
|
|
41
60
|
*/
|
|
42
61
|
export const HkAboutModal = defineComponent({
|
|
@@ -53,14 +72,12 @@ export const HkAboutModal = defineComponent({
|
|
|
53
72
|
tagline: { type: String, default: undefined },
|
|
54
73
|
/** Optional centered small line under the credits (organization blurb). */
|
|
55
74
|
description: { type: String, default: undefined },
|
|
56
|
-
/**
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
/** Made-by sentence: text after the name (e.g. " 主创,来自 …"). */
|
|
63
|
-
madeBySuffix: { type: String, default: undefined },
|
|
75
|
+
/**
|
|
76
|
+
* Credits sentence, assembled from text runs and linked name chips
|
|
77
|
+
* (e.g. 来自 <Celestia Island>,由 <伊欧> 倾力设计). Names render as the
|
|
78
|
+
* same chips as the license / link rows.
|
|
79
|
+
*/
|
|
80
|
+
credits: { type: Array as PropType<HAboutCredit[]>, default: () => [] },
|
|
64
81
|
/** License chips (e.g. SySL-1.0 / BUSL-1.1), rendered centered. */
|
|
65
82
|
licenses: { type: Array as PropType<HAboutLink[]>, default: () => [] },
|
|
66
83
|
/** Software-component version rows (WebUI / engines), label + value. */
|
|
@@ -88,25 +105,38 @@ export const HkAboutModal = defineComponent({
|
|
|
88
105
|
setup(props, { emit }) {
|
|
89
106
|
const { t } = useI18n();
|
|
90
107
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
108
|
+
// One chip renderer for every link in the dialog: credits names,
|
|
109
|
+
// licenses, external links and the legal filings all render as tags.
|
|
110
|
+
const renderChip = (label: string, href: string, extraClass: string) => (
|
|
111
|
+
<a
|
|
112
|
+
key={`${extraClass}:${href}`}
|
|
113
|
+
class={`s-about-modal-link ${extraClass}`}
|
|
114
|
+
href={href}
|
|
115
|
+
target="_blank"
|
|
116
|
+
rel="noopener noreferrer"
|
|
117
|
+
>
|
|
118
|
+
{label}
|
|
119
|
+
</a>
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
const renderCredits = () => {
|
|
123
|
+
const parts = props.credits.filter((part) => part.text || part.name);
|
|
124
|
+
if (parts.length === 0) return null;
|
|
105
125
|
return (
|
|
106
|
-
<p class="s-about-modal-
|
|
107
|
-
{
|
|
108
|
-
|
|
109
|
-
|
|
126
|
+
<p class="s-about-modal-credits-line">
|
|
127
|
+
{parts.map((part, index) =>
|
|
128
|
+
part.name ? (
|
|
129
|
+
part.href ? (
|
|
130
|
+
renderChip(part.name, part.href, "s-about-modal-credit-link")
|
|
131
|
+
) : (
|
|
132
|
+
<span key={`name:${index}`} class="s-about-modal-credit-name">
|
|
133
|
+
{part.name}
|
|
134
|
+
</span>
|
|
135
|
+
)
|
|
136
|
+
) : (
|
|
137
|
+
<span key={`text:${index}`}>{part.text}</span>
|
|
138
|
+
),
|
|
139
|
+
)}
|
|
110
140
|
</p>
|
|
111
141
|
);
|
|
112
142
|
};
|
|
@@ -116,17 +146,7 @@ export const HkAboutModal = defineComponent({
|
|
|
116
146
|
return (
|
|
117
147
|
<div class="s-about-modal-links" data-slot={slot}>
|
|
118
148
|
<div class="s-about-modal-links-list">
|
|
119
|
-
{items.map((item) => (
|
|
120
|
-
<a
|
|
121
|
-
key={item.href}
|
|
122
|
-
href={item.href}
|
|
123
|
-
target="_blank"
|
|
124
|
-
rel="noopener noreferrer"
|
|
125
|
-
class="s-about-modal-link"
|
|
126
|
-
>
|
|
127
|
-
{item.label}
|
|
128
|
-
</a>
|
|
129
|
-
))}
|
|
149
|
+
{items.map((item) => renderChip(item.label, item.href, ""))}
|
|
130
150
|
</div>
|
|
131
151
|
</div>
|
|
132
152
|
);
|
|
@@ -177,9 +197,9 @@ export const HkAboutModal = defineComponent({
|
|
|
177
197
|
</div>
|
|
178
198
|
</header>
|
|
179
199
|
|
|
180
|
-
{(props.
|
|
200
|
+
{(props.credits.length > 0 || props.description) && (
|
|
181
201
|
<div class="s-about-modal-credits">
|
|
182
|
-
{
|
|
202
|
+
{renderCredits()}
|
|
183
203
|
{props.description && (
|
|
184
204
|
<p class="s-about-modal-description">{props.description}</p>
|
|
185
205
|
)}
|
|
@@ -216,17 +236,9 @@ export const HkAboutModal = defineComponent({
|
|
|
216
236
|
|
|
217
237
|
{props.footerLinks.length > 0 && (
|
|
218
238
|
<div class="s-about-modal-footer-links">
|
|
219
|
-
{props.footerLinks.map((link) =>
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
href={link.href}
|
|
223
|
-
target="_blank"
|
|
224
|
-
rel="noopener noreferrer"
|
|
225
|
-
class="s-about-modal-footer-link"
|
|
226
|
-
>
|
|
227
|
-
{link.label}
|
|
228
|
-
</a>
|
|
229
|
-
))}
|
|
239
|
+
{props.footerLinks.map((link) =>
|
|
240
|
+
renderChip(link.label, link.href, "s-about-modal-footer-link"),
|
|
241
|
+
)}
|
|
230
242
|
</div>
|
|
231
243
|
)}
|
|
232
244
|
|
package/src/index.ts
CHANGED
|
@@ -356,6 +356,7 @@ export { HkProtocolModal as HProtocolModal } from "./components/HkProtocolModal"
|
|
|
356
356
|
export {
|
|
357
357
|
HkAboutModal as HAboutModal,
|
|
358
358
|
type HAboutComponentVersion,
|
|
359
|
+
type HAboutCredit,
|
|
359
360
|
type HAboutLink,
|
|
360
361
|
} from "./components/HkAboutModal";
|
|
361
362
|
export { HkLogWindow as HLogWindow, type HLogTab } from "./components/HkLogWindow";
|