@unisim/sdk 0.167.0 → 0.169.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/README.md +17 -1
- package/android/src/main/AndroidManifest.xml +14 -9
- package/dist/KnowledgeBaseDialog.d.ts +48 -7
- package/dist/KnowledgeBaseDialog.d.ts.map +1 -1
- package/dist/KnowledgeBaseDialog.js +271 -32
- package/dist/KnowledgeBaseDialog.js.map +1 -1
- package/dist/UserCountBreakdown.d.ts.map +1 -1
- package/dist/UserCountBreakdown.js +2 -1
- package/dist/UserCountBreakdown.js.map +1 -1
- package/dist/i18n.d.ts.map +1 -1
- package/dist/i18n.js +120 -0
- package/dist/i18n.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/presence.d.ts +25 -1
- package/dist/presence.d.ts.map +1 -1
- package/dist/presence.js +53 -3
- package/dist/presence.js.map +1 -1
- package/dist/provider.d.ts.map +1 -1
- package/dist/provider.js +8 -0
- package/dist/provider.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -332,6 +332,13 @@ reports the app as in use (guests included) and the menu reads the figure.
|
|
|
332
332
|
for it to launch is a good deal more deliberate than arriving at a URL. For a
|
|
333
333
|
surface that is watched rather than touched — a wall display, a kiosk — pass
|
|
334
334
|
`presence: { requireInteraction: false }`.
|
|
335
|
+
⚠️ **Our own traffic sends no beat at all** (since 0.168.0,
|
|
336
|
+
`isOwnTraffic()`): a browser under automation (`navigator.webdriver`, set by
|
|
337
|
+
Playwright, Puppeteer and Selenium, on any host, the live site included), or a
|
|
338
|
+
web page served from localhost, a loopback or private IP, or a `.local` name.
|
|
339
|
+
Two days of Playwright runs had made 96 of Jukebox's 104 "users". An installed
|
|
340
|
+
app is never judged by its host (Capacitor on Android is `https://localhost`).
|
|
341
|
+
A spec that has to see beats sets `window.__UNISIM_COUNT_OWN_TRAFFIC__ = true`.
|
|
335
342
|
⚠️ **PostgREST resolves an RPC by its argument NAMES**, so sending
|
|
336
343
|
`p_platform` to a database without migration 0187 is "function not found",
|
|
337
344
|
not an ignored argument. A beat refused for that reason retries without it and
|
|
@@ -620,7 +627,16 @@ invisible, keeps a private session, and nothing reports a problem. **Its
|
|
|
620
627
|
apps** — the Java side keeps no second copy, it filters what the package manager
|
|
621
628
|
admits to by the `.unisimsuiteauth` suffix. Adding an app to the suite means
|
|
622
629
|
adding one line there. (The `<package>` list below it is a different list for a
|
|
623
|
-
different job — see the switcher section.
|
|
630
|
+
different job — see the switcher section. ⚠️ But a `<package>` entry makes the
|
|
631
|
+
WHOLE package visible, providers included, so an app on that list is reachable
|
|
632
|
+
for session sharing whether or not it is on the provider list — which is how
|
|
633
|
+
Ergo Assess's native app was reached before it was named there.)
|
|
634
|
+
|
|
635
|
+
**Ergo Assess is the one non-Capacitor member** (2026-09-29). Its Compose app
|
|
636
|
+
hosts a Kotlin port of `SuiteAuthProvider`/`SuiteAuthStore` under the same
|
|
637
|
+
permission and contract (`Ergo_Assess` `android/…/web/SuiteAuthStore.kt`) and
|
|
638
|
+
carries its own copy of the provider list in its manifest — so a new suite app
|
|
639
|
+
goes into both.
|
|
624
640
|
|
|
625
641
|
⚠️ **Same key, in practice, means the same BUILD TYPE.** Debug builds are signed
|
|
626
642
|
with `~/.android/debug.keystore` and release builds with the upload key. Install
|
|
@@ -29,11 +29,16 @@
|
|
|
29
29
|
Two Capacitor apps are deliberately absent, and neither is an oversight:
|
|
30
30
|
Universal AI and Universal Family both run their own Supabase client on
|
|
31
31
|
their own storage key by an explicit decision recorded in their source,
|
|
32
|
-
so they have no shared session to take part in.
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
32
|
+
so they have no shared session to take part in.
|
|
33
|
+
|
|
34
|
+
Ergo Assess IS here (2026-09-29), though it is not Capacitor: its native
|
|
35
|
+
Compose app hosts a Kotlin port of SuiteAuthProvider under the same
|
|
36
|
+
permission and contract (Ergo_Assess android/…/web/SuiteAuthStore.kt),
|
|
37
|
+
keeping the com.universalsimulation. prefix of its Gradle module. Before
|
|
38
|
+
this line it was already reachable by accident — its <package> entry in
|
|
39
|
+
2b makes the whole package, providers included, visible — but sharing a
|
|
40
|
+
session must not hang on the switcher's list. Ergo's own manifest keeps
|
|
41
|
+
a copy of this list; add a new app there too.
|
|
37
42
|
|
|
38
43
|
2b. The PACKAGE list: who the suite switcher can OPEN, via
|
|
39
44
|
UnisimSuiteAuthPlugin.installedApps/openApp. Deliberately a separate
|
|
@@ -73,14 +78,14 @@
|
|
|
73
78
|
|
|
74
79
|
<queries>
|
|
75
80
|
<provider
|
|
76
|
-
android:authorities="uk.co.unisim.coshhassess.unisimsuiteauth;uk.co.unisim.cyberassess.unisimsuiteauth;uk.co.unisim.fireassess.unisimsuiteauth;uk.co.unisim.riskassess.unisimsuiteauth;uk.co.unisim.wfhassess.unisimsuiteauth;uk.co.unisim.blackbook.unisimsuiteauth;uk.co.unisim.compress.unisimsuiteauth;uk.co.unisim.converter.unisimsuiteauth;uk.co.unisim.images.unisimsuiteauth;uk.co.unisim.jukebox.unisimsuiteauth;uk.co.unisim.pdf.unisimsuiteauth;uk.co.unisim.polling.unisimsuiteauth;uk.co.unisim.qr.unisimsuiteauth" />
|
|
81
|
+
android:authorities="uk.co.unisim.coshhassess.unisimsuiteauth;uk.co.unisim.cyberassess.unisimsuiteauth;uk.co.unisim.fireassess.unisimsuiteauth;uk.co.unisim.riskassess.unisimsuiteauth;uk.co.unisim.wfhassess.unisimsuiteauth;uk.co.unisim.blackbook.unisimsuiteauth;uk.co.unisim.compress.unisimsuiteauth;uk.co.unisim.converter.unisimsuiteauth;uk.co.unisim.images.unisimsuiteauth;uk.co.unisim.jukebox.unisimsuiteauth;uk.co.unisim.pdf.unisimsuiteauth;uk.co.unisim.polling.unisimsuiteauth;uk.co.unisim.qr.unisimsuiteauth;com.universalsimulation.ergoassesscapture.unisimsuiteauth" />
|
|
77
82
|
|
|
78
83
|
<!-- Ergo Assess is the one non-Capacitor app here: a Compose app whose
|
|
79
84
|
MainActivity already carries MAIN/LAUNCHER, which is all
|
|
80
85
|
getLaunchIntentForPackage needs. It keeps the
|
|
81
|
-
com.universalsimulation. prefix from its own Gradle module
|
|
82
|
-
|
|
83
|
-
SuiteAuthProvider
|
|
86
|
+
com.universalsimulation. prefix from its own Gradle module. Since
|
|
87
|
+
2026-09-29 it is in the provider list above as well: it hosts a
|
|
88
|
+
port of SuiteAuthProvider and shares the session. -->
|
|
84
89
|
<package android:name="com.universalsimulation.ergoassesscapture" />
|
|
85
90
|
<package android:name="uk.co.unisim.cyberassess" />
|
|
86
91
|
<package android:name="uk.co.unisim.wfhassess" />
|
|
@@ -1,15 +1,37 @@
|
|
|
1
1
|
import { type Language } from './i18n.js';
|
|
2
2
|
import { type MenuTheme } from './menuTheme.js';
|
|
3
|
+
/** Where the suite's knowledge-base PDFs are served from. See note 4. */
|
|
4
|
+
export declare const KB_LIBRARY_URL = "https://opensource.unisim.co.uk/kb";
|
|
5
|
+
export type KnowledgeSourceKind = 'paper' | 'standard' | 'report' | 'guidance' | 'law';
|
|
6
|
+
/** A paper, standard or report an article draws on. */
|
|
7
|
+
export interface KnowledgeSource {
|
|
8
|
+
kind: KnowledgeSourceKind;
|
|
9
|
+
/** In the original language; titles of papers are not translated. */
|
|
10
|
+
title: string;
|
|
11
|
+
authors?: string;
|
|
12
|
+
/** Journal, conference or issuing body — "ACM CCS 2019", "NIST". */
|
|
13
|
+
publisher?: string;
|
|
14
|
+
year?: number;
|
|
15
|
+
/** The canonical free link: a DOI, the publisher's page or their own PDF. */
|
|
16
|
+
href: string;
|
|
17
|
+
/**
|
|
18
|
+
* Our hosted copy, relative to the library (`papers/fips-197-aes.pdf`) or
|
|
19
|
+
* absolute. Only when the licence permits redistribution (note 5).
|
|
20
|
+
*/
|
|
21
|
+
pdf?: string;
|
|
22
|
+
/** The licence the hosted copy is shared under. Shown beside it. */
|
|
23
|
+
licence?: string;
|
|
24
|
+
}
|
|
3
25
|
export interface KnowledgeArticle {
|
|
4
|
-
/** Stable slug. Also what `initialArticle` matches. */
|
|
26
|
+
/** Stable slug. Also what `initialArticle` matches, and the guide PDF's name. */
|
|
5
27
|
id: string;
|
|
6
28
|
title: string;
|
|
7
|
-
/** One line under the title
|
|
29
|
+
/** One line under the title on its card. */
|
|
8
30
|
summary?: string;
|
|
9
31
|
/**
|
|
10
|
-
* A heading the
|
|
32
|
+
* A heading the cards are grouped under — "The basics", "Privacy and
|
|
11
33
|
* security". Articles keep their order; a group starts where its label
|
|
12
|
-
* first appears. Leave unset on every article for
|
|
34
|
+
* first appears. Leave unset on every article for one ungrouped grid.
|
|
13
35
|
*/
|
|
14
36
|
group?: string;
|
|
15
37
|
/**
|
|
@@ -17,22 +39,41 @@ export interface KnowledgeArticle {
|
|
|
17
39
|
* `1. ` numbered steps and `**bold**`. Nothing else is interpreted.
|
|
18
40
|
*/
|
|
19
41
|
body: string;
|
|
42
|
+
/** The research behind the article, shown beside it. Not translated. */
|
|
43
|
+
sources?: KnowledgeSource[];
|
|
20
44
|
}
|
|
21
45
|
export type KnowledgeArticles = KnowledgeArticle[] | ((language: Language) => KnowledgeArticle[] | Promise<KnowledgeArticle[]>);
|
|
22
46
|
/** What an app hands the navbar (`knowledgeBase`) or <AdvancedMenu>. */
|
|
23
47
|
export interface KnowledgeBaseConfig {
|
|
24
48
|
articles: KnowledgeArticles;
|
|
49
|
+
/**
|
|
50
|
+
* The app's guide PDFs have been generated and published (see
|
|
51
|
+
* `scripts/kb-pdfs.mjs` in universal-platform). Turns on the "Download
|
|
52
|
+
* guide" buttons, which link to
|
|
53
|
+
* `<library>/<product>/<language>/<article id>.pdf`. Leave unset until
|
|
54
|
+
* they exist.
|
|
55
|
+
*/
|
|
56
|
+
guides?: boolean;
|
|
57
|
+
/** Override the library's address. Tests and self-hosters only. */
|
|
58
|
+
library?: string;
|
|
25
59
|
}
|
|
26
60
|
export interface KnowledgeBaseDialogProps extends KnowledgeBaseConfig {
|
|
27
61
|
open: boolean;
|
|
28
62
|
onClose: () => void;
|
|
29
|
-
/** Open straight onto this article rather than the
|
|
63
|
+
/** Open straight onto this article rather than the cards. */
|
|
30
64
|
initialArticle?: string;
|
|
31
65
|
/** Heading beside the mark. Defaults to the product's name from the SDK. */
|
|
32
66
|
name?: string;
|
|
33
67
|
theme?: MenuTheme;
|
|
34
68
|
}
|
|
35
|
-
|
|
69
|
+
/**
|
|
70
|
+
* The folder a language's guides are filed under. The generator writes one
|
|
71
|
+
* folder per translation the app has; both Englishes share `en`.
|
|
72
|
+
*/
|
|
73
|
+
export declare function guideLanguage(language: string): string;
|
|
74
|
+
/** Resolve a source's `pdf` (library-relative or absolute) to a URL. */
|
|
75
|
+
export declare function libraryUrl(path: string, library?: string): string;
|
|
76
|
+
export declare function KnowledgeBaseDialog({ open, onClose, articles, guides, library, initialArticle, name, theme, }: KnowledgeBaseDialogProps): import("react").ReactPortal | null;
|
|
36
77
|
type Block = {
|
|
37
78
|
kind: 'h';
|
|
38
79
|
text: string;
|
|
@@ -46,7 +87,7 @@ type Block = {
|
|
|
46
87
|
kind: 'ol';
|
|
47
88
|
items: string[];
|
|
48
89
|
};
|
|
49
|
-
/** Exported for the tests — splits
|
|
90
|
+
/** Exported for the tests and the PDF generator — splits a body into blocks. */
|
|
50
91
|
export declare function parseArticleBody(body: string): Block[];
|
|
51
92
|
/** The book glyph for the menu row. Exported so <AdvancedMenu> can use it. */
|
|
52
93
|
export declare function IconBook(): import("react/jsx-runtime").JSX.Element;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"KnowledgeBaseDialog.d.ts","sourceRoot":"","sources":["../src/KnowledgeBaseDialog.tsx"],"names":[],"mappings":"AAKA,OAAO,EAAK,KAAK,QAAQ,EAAE,MAAM,WAAW,CAAA;AAC5C,OAAO,EAAQ,KAAK,SAAS,EAAE,MAAM,gBAAgB,CAAA;
|
|
1
|
+
{"version":3,"file":"KnowledgeBaseDialog.d.ts","sourceRoot":"","sources":["../src/KnowledgeBaseDialog.tsx"],"names":[],"mappings":"AAKA,OAAO,EAAK,KAAK,QAAQ,EAAE,MAAM,WAAW,CAAA;AAC5C,OAAO,EAAQ,KAAK,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAgErD,yEAAyE;AACzE,eAAO,MAAM,cAAc,uCAAuC,CAAA;AAElE,MAAM,MAAM,mBAAmB,GAAG,OAAO,GAAG,UAAU,GAAG,QAAQ,GAAG,UAAU,GAAG,KAAK,CAAA;AAEtF,uDAAuD;AACvD,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,mBAAmB,CAAA;IACzB,qEAAqE;IACrE,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,oEAAoE;IACpE,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,6EAA6E;IAC7E,IAAI,EAAE,MAAM,CAAA;IACZ;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,oEAAoE;IACpE,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,gBAAgB;IAC/B,iFAAiF;IACjF,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,4CAA4C;IAC5C,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;IACd;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAA;IACZ,wEAAwE;IACxE,OAAO,CAAC,EAAE,eAAe,EAAE,CAAA;CAC5B;AAED,MAAM,MAAM,iBAAiB,GACzB,gBAAgB,EAAE,GAClB,CAAC,CAAC,QAAQ,EAAE,QAAQ,KAAK,gBAAgB,EAAE,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAAC,CAAA;AAE9E,wEAAwE;AACxE,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,iBAAiB,CAAA;IAC3B;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,mEAAmE;IACnE,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,wBAAyB,SAAQ,mBAAmB;IACnE,IAAI,EAAE,OAAO,CAAA;IACb,OAAO,EAAE,MAAM,IAAI,CAAA;IACnB,6DAA6D;IAC7D,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,4EAA4E;IAC5E,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,SAAS,CAAA;CAClB;AAID;;;GAGG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED,wEAAwE;AACxE,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,SAAiB,GAAG,MAAM,CAEzE;AAED,wBAAgB,mBAAmB,CAAC,EAClC,IAAI,EACJ,OAAO,EACP,QAAQ,EACR,MAAc,EACd,OAAwB,EACxB,cAAc,EACd,IAAI,EACJ,KAAe,GAChB,EAAE,wBAAwB,sCAkJ1B;AAgND,KAAK,KAAK,GACN;IAAE,IAAI,EAAE,GAAG,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC3B;IAAE,IAAI,EAAE,GAAG,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC3B;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,GAC/B;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,CAAA;AAEnC,gFAAgF;AAChF,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,KAAK,EAAE,CAkCtD;AAqED,8EAA8E;AAC9E,wBAAgB,QAAQ,4CAQvB"}
|
|
@@ -7,17 +7,91 @@ import { t } from './i18n.js';
|
|
|
7
7
|
import { MENU } from './menuTheme.js';
|
|
8
8
|
import { useLanguage, useUniversal } from './provider.js';
|
|
9
9
|
import { lookupProduct } from './AboutAppDialog.js';
|
|
10
|
-
|
|
10
|
+
// ──────────────────────────────────────────────────────────────────────────────
|
|
11
|
+
// <KnowledgeBaseDialog /> — an app's own knowledge base, read in the app.
|
|
12
|
+
//
|
|
13
|
+
// Owner ask, 2026-09-27: "introduce the knowledge base as per Ergo Assess but
|
|
14
|
+
// in the universal apps it should be more discreet e.g. in advanced in the
|
|
15
|
+
// actions pill" — explainers like "What actually is a PDF?" beside how the app
|
|
16
|
+
// itself works ("level of encryption for send and sign").
|
|
17
|
+
//
|
|
18
|
+
// Owner ask, 2026-09-29: "it should be more like the knowledge base on Ergo
|
|
19
|
+
// Assess with a knowledge base page, cards to click through and downloads
|
|
20
|
+
// available in PDFs. Research papers are the best if we can find." So the
|
|
21
|
+
// entry stays discreet (Advanced ▸ Knowledge base) but what opens is a page:
|
|
22
|
+
// a card grid, and an article view with its guide PDF and its research
|
|
23
|
+
// beside it — Ergo's /knowledge-base and /[method] in one dialog. Ergo-style
|
|
24
|
+
// whole-guide and download-library banners were built and taken out the same
|
|
25
|
+
// day at the owner's ask ("remove the download whole knowledge base and
|
|
26
|
+
// download library buttons"): each download lives on the article it belongs to.
|
|
27
|
+
//
|
|
28
|
+
// Five decisions worth keeping:
|
|
29
|
+
//
|
|
30
|
+
// 1. **A dialog, not a route.** Ergo's knowledge base is a set of Next.js
|
|
31
|
+
// pages. The Universal Apps are single-page Vite apps that also ship as
|
|
32
|
+
// Capacitor and Electron builds with no server behind them, so a route
|
|
33
|
+
// would mean nineteen routers and a knowledge base that 404s offline. The
|
|
34
|
+
// articles are bundled into the app and read here, which works in Airplane
|
|
35
|
+
// mode and needs nothing from the host but the words.
|
|
36
|
+
//
|
|
37
|
+
// 2. **The body is a tiny, closed markdown — never HTML.** Blank-line
|
|
38
|
+
// paragraphs, `## ` subheadings, `- ` bullets, `1. ` steps, `**bold**`.
|
|
39
|
+
// That is enough for an explainer, easy for a translator to keep intact,
|
|
40
|
+
// and rendered as React elements, so nothing an article says can inject
|
|
41
|
+
// markup. Anything else in the text is shown literally.
|
|
42
|
+
//
|
|
43
|
+
// 3. **Articles are per language and may load lazily.** `articles` can be a
|
|
44
|
+
// function of the language returning a promise, so an app keeps seven
|
|
45
|
+
// translations out of its first paint and imports only the one it needs.
|
|
46
|
+
// A language the app has not translated falls back to English (the
|
|
47
|
+
// function decides; the dialog just shows what it is given).
|
|
48
|
+
//
|
|
49
|
+
// 4. **The PDFs live on the web, not in the app.** Guides and hosted papers
|
|
50
|
+
// are served from one library, `opensource.unisim.co.uk/kb/`, never from
|
|
51
|
+
// the app's own bundle. A relative link to a bundled PDF inside a Capacitor
|
|
52
|
+
// webview NAVIGATES the webview to the PDF, with no back button out; an
|
|
53
|
+
// https link with target=_blank goes to the system browser on iOS, Android
|
|
54
|
+
// and Electron alike. It also keeps ~10 MB of papers out of every app
|
|
55
|
+
// bundle, and lets two apps that cite FIPS 197 share one copy. The cost is
|
|
56
|
+
// that downloads need a connection; the articles themselves never do.
|
|
57
|
+
//
|
|
58
|
+
// 5. **A hosted copy only when its licence allows it.** `source.pdf` is set
|
|
59
|
+
// only for public-domain (US Government), Open Government Licence, CC BY,
|
|
60
|
+
// EUR-Lex and IETF documents; everything else — ACM, IEEE, ISO, Adobe —
|
|
61
|
+
// is a link to the publisher's own free copy. `licence` says which, and
|
|
62
|
+
// the dialog shows it beside the download. Same rule as Ergo's
|
|
63
|
+
// `research-papers.ts`.
|
|
64
|
+
//
|
|
65
|
+
// ⚠️ Inline styles, like every SDK dialog — this ships inside node_modules,
|
|
66
|
+
// where the apps' Tailwind builds do not scan. Layout switches on the card's
|
|
67
|
+
// measured width (ResizeObserver), since inline styles have no media queries.
|
|
68
|
+
// ──────────────────────────────────────────────────────────────────────────────
|
|
69
|
+
/** Where the suite's knowledge-base PDFs are served from. See note 4. */
|
|
70
|
+
export const KB_LIBRARY_URL = 'https://opensource.unisim.co.uk/kb';
|
|
71
|
+
/**
|
|
72
|
+
* The folder a language's guides are filed under. The generator writes one
|
|
73
|
+
* folder per translation the app has; both Englishes share `en`.
|
|
74
|
+
*/
|
|
75
|
+
export function guideLanguage(language) {
|
|
76
|
+
return language === 'en-gb' || language === 'en' ? 'en' : language;
|
|
77
|
+
}
|
|
78
|
+
/** Resolve a source's `pdf` (library-relative or absolute) to a URL. */
|
|
79
|
+
export function libraryUrl(path, library = KB_LIBRARY_URL) {
|
|
80
|
+
return /^https?:\/\//.test(path) ? path : `${library.replace(/\/$/, '')}/${path.replace(/^\//, '')}`;
|
|
81
|
+
}
|
|
82
|
+
export function KnowledgeBaseDialog({ open, onClose, articles, guides = false, library = KB_LIBRARY_URL, initialArticle, name, theme = 'light', }) {
|
|
11
83
|
const { language } = useLanguage();
|
|
12
84
|
const { config } = useUniversal();
|
|
13
85
|
const p = MENU[theme];
|
|
14
86
|
const [list, setList] = useState(Array.isArray(articles) ? articles : null);
|
|
15
87
|
const [failed, setFailed] = useState(false);
|
|
16
|
-
const [
|
|
88
|
+
const [view, setView] = useState(initialArticle ? { kind: 'article', id: initialArticle } : { kind: 'index' });
|
|
89
|
+
const [width, setWidth] = useState(0);
|
|
17
90
|
const cardRef = useRef(null);
|
|
18
91
|
// The card scrolls, and a long article leaves it scrolled: moving between
|
|
19
|
-
//
|
|
20
|
-
|
|
92
|
+
// views starts at the top, as turning a page would.
|
|
93
|
+
const viewKey = view.kind === 'article' ? view.id : '';
|
|
94
|
+
useEffect(() => { cardRef.current?.scrollTo?.({ top: 0 }); }, [viewKey]);
|
|
21
95
|
// Resolve the articles for the reader's language each time the dialog opens
|
|
22
96
|
// (or the language changes under it). A plain array needs no work.
|
|
23
97
|
useEffect(() => {
|
|
@@ -37,10 +111,10 @@ export function KnowledgeBaseDialog({ open, onClose, articles, initialArticle, n
|
|
|
37
111
|
setFailed(true); });
|
|
38
112
|
return () => { live = false; };
|
|
39
113
|
}, [open, language, articles]);
|
|
40
|
-
// Every open starts where the caller asked — the
|
|
114
|
+
// Every open starts where the caller asked — the cards, unless told otherwise.
|
|
41
115
|
useEffect(() => {
|
|
42
116
|
if (open)
|
|
43
|
-
|
|
117
|
+
setView(initialArticle ? { kind: 'article', id: initialArticle } : { kind: 'index' });
|
|
44
118
|
}, [open, initialArticle]);
|
|
45
119
|
useEffect(() => {
|
|
46
120
|
if (!open)
|
|
@@ -48,25 +122,82 @@ export function KnowledgeBaseDialog({ open, onClose, articles, initialArticle, n
|
|
|
48
122
|
function onKey(e) {
|
|
49
123
|
if (e.key !== 'Escape')
|
|
50
124
|
return;
|
|
51
|
-
// Escape steps back out of an article before it closes
|
|
52
|
-
// same as the back arrow — reading three articles should
|
|
53
|
-
// mouse between each.
|
|
54
|
-
if (
|
|
55
|
-
|
|
125
|
+
// Escape steps back out of an article before it closes
|
|
126
|
+
// the dialog, the same as the back arrow — reading three articles should
|
|
127
|
+
// not need the mouse between each.
|
|
128
|
+
if (view.kind !== 'index')
|
|
129
|
+
setView({ kind: 'index' });
|
|
56
130
|
else
|
|
57
131
|
onClose();
|
|
58
132
|
}
|
|
59
133
|
document.addEventListener('keydown', onKey);
|
|
60
134
|
return () => document.removeEventListener('keydown', onKey);
|
|
61
|
-
}, [open, onClose,
|
|
62
|
-
|
|
135
|
+
}, [open, onClose, view.kind]);
|
|
136
|
+
// Measure the card so the grid and the sidebar can reflow (see the ⚠️ above).
|
|
137
|
+
useEffect(() => {
|
|
138
|
+
const el = cardRef.current;
|
|
139
|
+
if (!open || !el)
|
|
140
|
+
return;
|
|
141
|
+
setWidth(el.clientWidth);
|
|
142
|
+
if (typeof ResizeObserver === 'undefined')
|
|
143
|
+
return;
|
|
144
|
+
const ro = new ResizeObserver(() => setWidth(el.clientWidth));
|
|
145
|
+
ro.observe(el);
|
|
146
|
+
return () => ro.disconnect();
|
|
147
|
+
}, [open]);
|
|
148
|
+
const index = view.kind === 'article' && list ? list.findIndex((a) => a.id === view.id) : -1;
|
|
149
|
+
const article = index >= 0 && list ? list[index] ?? null : null;
|
|
63
150
|
if (!open || typeof document === 'undefined')
|
|
64
151
|
return null;
|
|
65
152
|
const entry = lookupProduct(config.product);
|
|
66
153
|
const title = name ?? entry?.name ?? t(language, 'menu.knowledge_base');
|
|
67
|
-
|
|
154
|
+
const guideBase = guides && config.product
|
|
155
|
+
? `${library.replace(/\/$/, '')}/${config.product}/${guideLanguage(language)}`
|
|
156
|
+
: null;
|
|
157
|
+
const wide = width >= 720;
|
|
158
|
+
let content;
|
|
159
|
+
if (failed)
|
|
160
|
+
content = _jsx("p", { style: bodyStyle(p), children: t(language, 'kb.load_failed') });
|
|
161
|
+
else if (!list)
|
|
162
|
+
content = _jsx("p", { style: { ...bodyStyle(p), color: p.faint }, children: t(language, 'kb.loading') });
|
|
163
|
+
else if (article) {
|
|
164
|
+
content = (_jsx(ArticleView, { article: article, prev: list[index - 1], next: list[index + 1], colour: groupColour(list, article, theme), guideHref: guideBase ? `${guideBase}/${article.id}.pdf` : null, library: library, wide: wide, language: language, palette: p, onOpen: (id) => setView({ kind: 'article', id }), onBack: () => setView({ kind: 'index' }) }));
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
content = (_jsxs(_Fragment, { children: [_jsxs("div", { style: identityRowStyle, children: [entry?.glyph && _jsx("span", { style: markTileStyle(p), "aria-hidden": true, children: entry.glyph }), _jsxs("div", { style: { minWidth: 0 }, children: [_jsx("h2", { style: titleStyle(p), children: t(language, 'menu.knowledge_base') }), _jsx("p", { style: subtitleStyle(p), children: title })] })] }), list.length === 0 ? (_jsx("p", { style: { ...bodyStyle(p), color: p.faint }, children: t(language, 'kb.no_articles') })) : (_jsxs(_Fragment, { children: [_jsx("p", { style: { ...bodyStyle(p), margin: '0 0 16px', color: p.muted }, children: t(language, 'kb.intro').replace('{app}', title) }), _jsx(CardGrid, { articles: list, columns: width >= 760 ? 3 : width >= 480 ? 2 : 1, theme: theme, language: language, palette: p, onOpen: (id) => setView({ kind: 'article', id }) })] }))] }));
|
|
168
|
+
}
|
|
169
|
+
return createPortal(_jsx("div", { style: overlayStyle, onClick: onClose, role: "presentation", children: _jsxs("div", { role: "dialog", "aria-modal": "true", "aria-label": `${t(language, 'menu.knowledge_base')} — ${title}`, ref: cardRef, style: cardStyle(p), onClick: (e) => e.stopPropagation(), children: [_jsx("button", { type: "button", onClick: onClose, "aria-label": t(language, 'nav.menu_close'), style: closeButtonStyle(p), onMouseEnter: (e) => { e.currentTarget.style.color = p.text; }, onMouseLeave: (e) => { e.currentTarget.style.color = p.muted; }, children: _jsx(IconClose, {}) }), content] }) }), document.body);
|
|
170
|
+
}
|
|
171
|
+
const HUES = {
|
|
172
|
+
light: [
|
|
173
|
+
{ fg: '#1d4ed8', bg: '#eff6ff', bar: '#3b82f6' },
|
|
174
|
+
{ fg: '#6d28d9', bg: '#f5f3ff', bar: '#8b5cf6' },
|
|
175
|
+
{ fg: '#047857', bg: '#ecfdf5', bar: '#10b981' },
|
|
176
|
+
{ fg: '#b45309', bg: '#fffbeb', bar: '#f59e0b' },
|
|
177
|
+
{ fg: '#be123c', bg: '#fff1f2', bar: '#f43f5e' },
|
|
178
|
+
],
|
|
179
|
+
dark: [
|
|
180
|
+
{ fg: '#93c5fd', bg: 'rgba(59,130,246,0.16)', bar: '#3b82f6' },
|
|
181
|
+
{ fg: '#c4b5fd', bg: 'rgba(139,92,246,0.16)', bar: '#8b5cf6' },
|
|
182
|
+
{ fg: '#6ee7b7', bg: 'rgba(16,185,129,0.16)', bar: '#10b981' },
|
|
183
|
+
{ fg: '#fcd34d', bg: 'rgba(245,158,11,0.16)', bar: '#f59e0b' },
|
|
184
|
+
{ fg: '#fda4af', bg: 'rgba(244,63,94,0.16)', bar: '#f43f5e' },
|
|
185
|
+
],
|
|
186
|
+
};
|
|
187
|
+
function groupOrder(articles) {
|
|
188
|
+
const seen = [];
|
|
189
|
+
for (const a of articles)
|
|
190
|
+
if (!seen.includes(a.group ?? ''))
|
|
191
|
+
seen.push(a.group ?? '');
|
|
192
|
+
return seen;
|
|
68
193
|
}
|
|
69
|
-
function
|
|
194
|
+
function groupColour(articles, a, theme) {
|
|
195
|
+
const hues = HUES[theme];
|
|
196
|
+
const i = Math.max(0, groupOrder(articles).indexOf(a.group ?? ''));
|
|
197
|
+
return hues[i % hues.length];
|
|
198
|
+
}
|
|
199
|
+
// ── The index: the card grid ────────────────────────────────────────────────
|
|
200
|
+
function CardGrid({ articles, columns, theme, language, palette: p, onOpen }) {
|
|
70
201
|
// Consecutive runs of the same group, in the order the app gave them.
|
|
71
202
|
const runs = [];
|
|
72
203
|
for (const a of articles) {
|
|
@@ -76,9 +207,25 @@ function ArticleList({ articles, onOpen, palette: p }) {
|
|
|
76
207
|
else
|
|
77
208
|
runs.push({ group: a.group, items: [a] });
|
|
78
209
|
}
|
|
79
|
-
return (_jsx("div", { style: { display: 'grid', gap:
|
|
210
|
+
return (_jsx("div", { style: { display: 'grid', gap: 20 }, children: runs.map((run, i) => {
|
|
211
|
+
const hue = groupColour(articles, run.items[0], theme);
|
|
212
|
+
return (_jsxs("section", { children: [run.group && _jsx("p", { style: groupLabelStyle(p), children: run.group }), _jsx("div", { style: { display: 'grid', gridTemplateColumns: `repeat(${columns}, minmax(0, 1fr))`, gap: 10 }, children: run.items.map((a) => (_jsxs("button", { type: "button", onClick: () => onOpen(a.id), style: cardButtonStyle(p, hue), onMouseEnter: (e) => { e.currentTarget.style.borderColor = hue.bar; e.currentTarget.style.transform = 'translateY(-1px)'; }, onMouseLeave: (e) => { e.currentTarget.style.borderColor = p.divider; e.currentTarget.style.transform = 'none'; }, children: [_jsx("span", { style: { display: 'block', fontSize: 14, fontWeight: 700, lineHeight: 1.35, color: p.text }, children: a.title }), a.summary && (_jsx("span", { style: { display: 'block', marginTop: 6, fontSize: 12.5, lineHeight: 1.45, color: p.muted }, children: a.summary })), _jsxs("span", { style: cardFooterStyle(p), children: [_jsx("span", { children: a.sources?.length ? count(language, 'kb.sources_count', a.sources.length) : '' }), _jsxs("span", { style: { color: hue.fg, fontWeight: 650 }, children: [t(language, 'kb.read'), " \u2192"] })] })] }, a.id))) })] }, `${run.group ?? ''}-${i}`));
|
|
213
|
+
}) }));
|
|
214
|
+
}
|
|
215
|
+
// ── An article ──────────────────────────────────────────────────────────────
|
|
216
|
+
function ArticleView({ article, prev, next, colour, guideHref, library, wide, language, palette: p, onOpen, onBack }) {
|
|
217
|
+
const sources = article.sources ?? [];
|
|
218
|
+
const aside = (guideHref || sources.length > 0) && (_jsxs("aside", { style: { display: 'grid', gap: 14, alignContent: 'start' }, children: [guideHref && (_jsxs("a", { href: guideHref, target: "_blank", rel: "noopener noreferrer", style: guideButtonStyle(colour), children: [_jsx(IconDownload, {}), _jsx("span", { children: t(language, 'kb.download_guide') })] })), sources.length > 0 && (_jsxs("div", { style: asideBoxStyle(p), children: [_jsx("p", { style: { ...groupLabelStyle(p), margin: '0 0 10px' }, children: t(language, 'kb.sources') }), _jsx("div", { style: { display: 'grid', gap: 12 }, children: sources.map((s) => _jsx(SourceItem, { source: s, library: library, language: language, palette: p }, s.href)) })] }))] }));
|
|
219
|
+
return (_jsxs(_Fragment, { children: [_jsxs("button", { type: "button", onClick: onBack, style: backButtonStyle(p), children: [_jsx("span", { "aria-hidden": true, style: { display: 'inline-flex' }, children: _jsx(IconBack, {}) }), t(language, 'kb.all_articles')] }), _jsxs("div", { style: { ...heroStyle, background: colour.bg, borderColor: colour.bar }, children: [article.group && _jsx("p", { style: { ...groupLabelStyle(p), color: colour.fg, margin: '0 0 6px' }, children: article.group }), _jsx("h2", { style: { ...titleStyle(p), fontSize: 20, margin: '0 34px 0 0' }, children: article.title }), article.summary && _jsx("p", { style: { ...bodyStyle(p), margin: '6px 0 0', color: p.muted }, children: article.summary })] }), _jsxs("div", { style: { display: 'grid', gridTemplateColumns: wide && aside ? 'minmax(0, 1fr) 260px' : 'minmax(0, 1fr)', gap: 24 }, children: [_jsx("div", { style: { minWidth: 0, maxWidth: 640 }, children: _jsx(ArticleBody, { body: article.body, palette: p }) }), aside] }), (prev || next) && (_jsxs("nav", { style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10, marginTop: 26, paddingTop: 16, borderTop: `1px solid ${p.divider}` }, children: [prev ? _jsx(PageLink, { label: t(language, 'kb.previous'), title: prev.title, onClick: () => onOpen(prev.id), palette: p }) : _jsx("span", {}), next ? _jsx(PageLink, { label: t(language, 'kb.next'), title: next.title, onClick: () => onOpen(next.id), palette: p, align: "right" }) : _jsx("span", {})] }))] }));
|
|
80
220
|
}
|
|
81
|
-
|
|
221
|
+
function PageLink({ label, title, onClick, palette: p, align = 'left' }) {
|
|
222
|
+
return (_jsxs("button", { type: "button", onClick: onClick, style: { ...rowStyle, display: 'block', textAlign: align, border: `1px solid ${p.divider}`, padding: '10px 12px' }, onMouseEnter: (e) => { e.currentTarget.style.background = p.rowHover; }, onMouseLeave: (e) => { e.currentTarget.style.background = 'transparent'; }, children: [_jsx("span", { style: { display: 'block', fontSize: 11, color: p.faint }, children: align === 'left' ? `← ${label}` : `${label} →` }), _jsx("span", { style: { display: 'block', marginTop: 2, fontSize: 13, fontWeight: 600, color: p.text }, children: title })] }));
|
|
223
|
+
}
|
|
224
|
+
function SourceItem({ source: s, library, language, palette: p }) {
|
|
225
|
+
const byline = [s.authors, s.publisher, s.year].filter(Boolean).join(' · ');
|
|
226
|
+
return (_jsxs("div", { children: [_jsx("span", { style: kindChipStyle(p), children: t(language, `kb.kind.${s.kind}`) }), _jsx("a", { href: s.href, target: "_blank", rel: "noopener noreferrer", style: sourceTitleStyle(p), children: s.title }), byline && _jsx("span", { style: { display: 'block', marginTop: 2, fontSize: 11.5, lineHeight: 1.4, color: p.muted }, children: byline }), _jsxs("span", { style: { display: 'flex', flexWrap: 'wrap', gap: 6, marginTop: 6 }, children: [s.pdf && (_jsxs("a", { href: libraryUrl(s.pdf, library), target: "_blank", rel: "noopener noreferrer", style: pillLinkStyle(p, true), title: s.licence, children: [_jsx(IconDownload, { size: 11 }), " PDF"] })), _jsxs("a", { href: s.href, target: "_blank", rel: "noopener noreferrer", style: pillLinkStyle(p, false), children: [t(language, s.pdf ? 'kb.publisher' : 'kb.read_source'), " \u2197"] })] }), s.pdf && s.licence && (_jsx("span", { style: { display: 'block', marginTop: 4, fontSize: 10.5, lineHeight: 1.35, color: p.faint }, children: s.licence }))] }));
|
|
227
|
+
}
|
|
228
|
+
/** Exported for the tests and the PDF generator — splits a body into blocks. */
|
|
82
229
|
export function parseArticleBody(body) {
|
|
83
230
|
const blocks = [];
|
|
84
231
|
const lines = body.replace(/\r\n?/g, '\n').split('\n');
|
|
@@ -142,6 +289,10 @@ function ArticleBody({ body, palette: p }) {
|
|
|
142
289
|
return (_jsx(List, { style: listStyle(p), children: b.items.map((item, j) => _jsx("li", { style: { marginTop: j ? 4 : 0 }, children: inline(item) }, j)) }, i));
|
|
143
290
|
}) }));
|
|
144
291
|
}
|
|
292
|
+
/** `{n}` into the singular or plural key (`<key>_one` / `<key>`). */
|
|
293
|
+
function count(language, key, n) {
|
|
294
|
+
return t(language, n === 1 ? `${key}_one` : key).replace('{n}', String(n));
|
|
295
|
+
}
|
|
145
296
|
// ── Icons ───────────────────────────────────────────────────────────────────
|
|
146
297
|
function IconClose() {
|
|
147
298
|
return (_jsx("svg", { viewBox: "0 0 24 24", width: "16", height: "16", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", "aria-hidden": "true", children: _jsx("path", { d: "M6 6l12 12M18 6L6 18" }) }));
|
|
@@ -152,6 +303,9 @@ function IconBack() {
|
|
|
152
303
|
function IconChevron() {
|
|
153
304
|
return (_jsx("svg", { viewBox: "0 0 12 12", width: "11", height: "11", "aria-hidden": "true", children: _jsx("path", { d: "M4 2 L8 6 L4 10", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) }));
|
|
154
305
|
}
|
|
306
|
+
function IconDownload({ size = 16 }) {
|
|
307
|
+
return (_jsx("svg", { viewBox: "0 0 24 24", width: size, height: size, fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: _jsx("path", { d: "M12 3v12M7 10l5 5 5-5M5 21h14" }) }));
|
|
308
|
+
}
|
|
155
309
|
/** The book glyph for the menu row. Exported so <AdvancedMenu> can use it. */
|
|
156
310
|
export function IconBook() {
|
|
157
311
|
return (_jsxs("svg", { viewBox: "0 0 24 24", width: "15", height: "15", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [_jsx("path", { d: "M4 5.5A2.5 2.5 0 0 1 6.5 3H20v15H6.5A2.5 2.5 0 0 0 4 20.5z" }), _jsx("path", { d: "M4 20.5A2.5 2.5 0 0 0 6.5 23H20v-5" }), _jsx("path", { d: "M8 7h8M8 11h6" })] }));
|
|
@@ -168,20 +322,22 @@ const overlayStyle = {
|
|
|
168
322
|
zIndex: NAV_OVERLAY_Z_INDEX + 100,
|
|
169
323
|
fontFamily: "'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif",
|
|
170
324
|
};
|
|
171
|
-
//
|
|
172
|
-
//
|
|
325
|
+
// A page, not a panel: wide enough for three columns of cards, or an article
|
|
326
|
+
// beside its sources. The body column itself stops at 640px, since 60-odd
|
|
327
|
+
// characters a line is where prose stops being tiring.
|
|
173
328
|
const cardStyle = (p) => ({
|
|
174
329
|
position: 'relative',
|
|
175
330
|
width: '100%',
|
|
176
|
-
maxWidth:
|
|
177
|
-
|
|
331
|
+
maxWidth: 980,
|
|
332
|
+
height: 'calc(100vh - 32px)',
|
|
333
|
+
maxHeight: 900,
|
|
178
334
|
overflowY: 'auto',
|
|
179
335
|
background: p.surface,
|
|
180
336
|
color: p.text,
|
|
181
337
|
borderRadius: 16,
|
|
182
338
|
border: `1px solid ${p.border}`,
|
|
183
339
|
boxShadow: p.shadow,
|
|
184
|
-
padding: '22px 24px
|
|
340
|
+
padding: '22px 24px 28px',
|
|
185
341
|
boxSizing: 'border-box',
|
|
186
342
|
});
|
|
187
343
|
const closeButtonStyle = (p) => ({
|
|
@@ -198,13 +354,14 @@ const closeButtonStyle = (p) => ({
|
|
|
198
354
|
background: 'transparent',
|
|
199
355
|
color: p.muted,
|
|
200
356
|
cursor: 'pointer',
|
|
357
|
+
zIndex: 1,
|
|
201
358
|
});
|
|
202
359
|
const backButtonStyle = (p) => ({
|
|
203
360
|
display: 'inline-flex',
|
|
204
361
|
alignItems: 'center',
|
|
205
362
|
gap: 4,
|
|
206
363
|
padding: '4px 6px 4px 2px',
|
|
207
|
-
margin: '-4px 0
|
|
364
|
+
margin: '-4px 0 10px -2px',
|
|
208
365
|
border: 0,
|
|
209
366
|
borderRadius: 6,
|
|
210
367
|
background: 'transparent',
|
|
@@ -218,7 +375,7 @@ const identityRowStyle = {
|
|
|
218
375
|
display: 'flex',
|
|
219
376
|
alignItems: 'center',
|
|
220
377
|
gap: 12,
|
|
221
|
-
margin: '0 34px
|
|
378
|
+
margin: '0 34px 14px 0',
|
|
222
379
|
};
|
|
223
380
|
const markTileStyle = (p) => ({
|
|
224
381
|
display: 'inline-flex',
|
|
@@ -232,7 +389,7 @@ const markTileStyle = (p) => ({
|
|
|
232
389
|
});
|
|
233
390
|
const titleStyle = (p) => ({
|
|
234
391
|
margin: 0,
|
|
235
|
-
fontSize:
|
|
392
|
+
fontSize: 18,
|
|
236
393
|
fontWeight: 700,
|
|
237
394
|
letterSpacing: '-0.01em',
|
|
238
395
|
lineHeight: 1.3,
|
|
@@ -244,13 +401,95 @@ const subtitleStyle = (p) => ({
|
|
|
244
401
|
color: p.muted,
|
|
245
402
|
});
|
|
246
403
|
const groupLabelStyle = (p) => ({
|
|
247
|
-
margin: '0 0
|
|
404
|
+
margin: '0 0 8px',
|
|
248
405
|
fontSize: 10.5,
|
|
249
406
|
fontWeight: 700,
|
|
250
407
|
letterSpacing: '0.07em',
|
|
251
408
|
textTransform: 'uppercase',
|
|
252
409
|
color: p.faint,
|
|
253
410
|
});
|
|
411
|
+
const cardButtonStyle = (p, hue) => ({
|
|
412
|
+
display: 'flex',
|
|
413
|
+
flexDirection: 'column',
|
|
414
|
+
alignItems: 'stretch',
|
|
415
|
+
minHeight: 132,
|
|
416
|
+
padding: '14px 14px 12px 16px',
|
|
417
|
+
borderRadius: 12,
|
|
418
|
+
border: `1px solid ${p.divider}`,
|
|
419
|
+
borderLeft: `4px solid ${hue.bar}`,
|
|
420
|
+
background: 'transparent',
|
|
421
|
+
textAlign: 'left',
|
|
422
|
+
fontFamily: 'inherit',
|
|
423
|
+
cursor: 'pointer',
|
|
424
|
+
transition: 'border-color 120ms, transform 120ms',
|
|
425
|
+
});
|
|
426
|
+
const cardFooterStyle = (p) => ({
|
|
427
|
+
display: 'flex',
|
|
428
|
+
justifyContent: 'space-between',
|
|
429
|
+
gap: 8,
|
|
430
|
+
marginTop: 'auto',
|
|
431
|
+
paddingTop: 12,
|
|
432
|
+
fontSize: 11.5,
|
|
433
|
+
color: p.faint,
|
|
434
|
+
});
|
|
435
|
+
const heroStyle = {
|
|
436
|
+
padding: '16px 18px',
|
|
437
|
+
margin: '0 0 20px',
|
|
438
|
+
borderRadius: 12,
|
|
439
|
+
borderLeft: '4px solid',
|
|
440
|
+
};
|
|
441
|
+
const guideButtonStyle = (hue) => ({
|
|
442
|
+
display: 'flex',
|
|
443
|
+
alignItems: 'center',
|
|
444
|
+
justifyContent: 'center',
|
|
445
|
+
gap: 8,
|
|
446
|
+
padding: '10px 12px',
|
|
447
|
+
borderRadius: 10,
|
|
448
|
+
background: hue.bar,
|
|
449
|
+
color: '#ffffff',
|
|
450
|
+
fontSize: 13,
|
|
451
|
+
fontWeight: 650,
|
|
452
|
+
textDecoration: 'none',
|
|
453
|
+
});
|
|
454
|
+
const asideBoxStyle = (p) => ({
|
|
455
|
+
padding: '14px 14px 16px',
|
|
456
|
+
borderRadius: 12,
|
|
457
|
+
border: `1px solid ${p.divider}`,
|
|
458
|
+
});
|
|
459
|
+
const kindChipStyle = (p) => ({
|
|
460
|
+
display: 'inline-block',
|
|
461
|
+
marginBottom: 4,
|
|
462
|
+
padding: '1px 7px',
|
|
463
|
+
borderRadius: 999,
|
|
464
|
+
background: p.chipBg,
|
|
465
|
+
color: p.chipText,
|
|
466
|
+
fontSize: 10,
|
|
467
|
+
fontWeight: 650,
|
|
468
|
+
letterSpacing: '0.02em',
|
|
469
|
+
});
|
|
470
|
+
const sourceTitleStyle = (p) => ({
|
|
471
|
+
display: 'block',
|
|
472
|
+
fontSize: 12.5,
|
|
473
|
+
fontWeight: 650,
|
|
474
|
+
lineHeight: 1.4,
|
|
475
|
+
color: p.text,
|
|
476
|
+
textDecoration: 'none',
|
|
477
|
+
});
|
|
478
|
+
const pillLinkStyle = (p, primary) => ({
|
|
479
|
+
display: 'inline-flex',
|
|
480
|
+
alignItems: 'center',
|
|
481
|
+
gap: 4,
|
|
482
|
+
flexShrink: 0,
|
|
483
|
+
padding: '3px 9px',
|
|
484
|
+
borderRadius: 999,
|
|
485
|
+
border: `1px solid ${primary ? p.accentBorder : p.divider}`,
|
|
486
|
+
background: primary ? p.accentBg : 'transparent',
|
|
487
|
+
color: primary ? p.accentText : p.muted,
|
|
488
|
+
fontSize: 11,
|
|
489
|
+
fontWeight: 650,
|
|
490
|
+
textDecoration: 'none',
|
|
491
|
+
whiteSpace: 'nowrap',
|
|
492
|
+
});
|
|
254
493
|
const rowStyle = {
|
|
255
494
|
display: 'flex',
|
|
256
495
|
alignItems: 'center',
|
|
@@ -266,22 +505,22 @@ const rowStyle = {
|
|
|
266
505
|
transition: 'background 120ms',
|
|
267
506
|
};
|
|
268
507
|
const subheadingStyle = (p) => ({
|
|
269
|
-
margin: '
|
|
270
|
-
fontSize:
|
|
508
|
+
margin: '8px 0 0',
|
|
509
|
+
fontSize: 14.5,
|
|
271
510
|
fontWeight: 700,
|
|
272
511
|
color: p.text,
|
|
273
512
|
});
|
|
274
513
|
const bodyStyle = (p) => ({
|
|
275
514
|
margin: 0,
|
|
276
|
-
fontSize:
|
|
277
|
-
lineHeight: 1.
|
|
515
|
+
fontSize: 14,
|
|
516
|
+
lineHeight: 1.65,
|
|
278
517
|
color: p.body,
|
|
279
518
|
});
|
|
280
519
|
const listStyle = (p) => ({
|
|
281
520
|
margin: 0,
|
|
282
521
|
paddingLeft: 20,
|
|
283
|
-
fontSize:
|
|
284
|
-
lineHeight: 1.
|
|
522
|
+
fontSize: 14,
|
|
523
|
+
lineHeight: 1.6,
|
|
285
524
|
color: p.body,
|
|
286
525
|
});
|
|
287
526
|
//# sourceMappingURL=KnowledgeBaseDialog.js.map
|