@glossarist/concept-browser 0.7.104 → 0.7.106

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.
@@ -18,6 +18,22 @@ export interface LogoConfig {
18
18
  localDark?: string;
19
19
  }
20
20
 
21
+ export interface FaviconConfig {
22
+ /** Path prefix for all favicon URLs. Defaults to '/', set to a sub-path
23
+ * when deploying under BASE_PATH != '/'. */
24
+ base_path?: string;
25
+ /** Skip emitting the default <link rel="icon"> and <link rel="apple-touch-icon">
26
+ * tags. Use when the consumer provides their own RFG-compatible markup via
27
+ * `links_html`. Default: false (emit default tags). */
28
+ skip_default_links?: boolean;
29
+ /** Raw HTML string (one or more <link> tags) to emit in <head> instead of
30
+ * the default favicon block. RealFaviconGenerator output goes here. */
31
+ links_html?: string;
32
+ /** Consumer-side directory (relative to cwd) holding canonical favicon files.
33
+ * The CLI's favicon step copies these into public/, overriding defaults. */
34
+ source_dir?: string;
35
+ }
36
+
21
37
  export interface SiteBranding {
22
38
  primaryColor?: string;
23
39
  darkColor?: string;
@@ -27,7 +43,7 @@ export interface SiteBranding {
27
43
  };
28
44
  logo?: LogoConfig;
29
45
  footerLogo?: LogoConfig;
30
- favicon?: string;
46
+ favicon?: string | FaviconConfig;
31
47
  ownerName?: string;
32
48
  ownerUrl?: string;
33
49
  }
@@ -36,28 +36,53 @@ const fontHeader = brandingFonts?.header?.family
36
36
  const fontBody = brandingFonts?.body?.family
37
37
  ? `'${brandingFonts.body.family}', system-ui, sans-serif`
38
38
  : `'DM Sans', system-ui, sans-serif`;
39
+
40
+ // Favicon configuration (issue #173).
41
+ //
42
+ // Consumers provide their own favicon set in one of two ways:
43
+ //
44
+ // 1. Drop canonical files at `branding.favicon.source_dir` (relative
45
+ // to the consumer's cwd). The CLI's existing favicon step already
46
+ // copies these into public/ overriding the defaults — this layout
47
+ // just needs to emit the right <link> tags.
48
+ //
49
+ // 2. Set `branding.favicon.skip_default_links: true` and inject
50
+ // custom <link> tags via the `branding.favicon.links_html`
51
+ // string (e.g. RealFaviconGenerator output). The layout emits
52
+ // that verbatim and skips the default tag block.
53
+ //
54
+ // When neither is set, today's default behavior is preserved.
55
+ const faviconCfg = siteConfig?.branding?.favicon ?? {};
56
+ const skipDefaultFaviconLinks = faviconCfg.skip_default_links === true;
57
+ const customFaviconLinks: string = faviconCfg.links_html ?? '';
58
+ const faviconBase = faviconCfg.base_path ?? '/';
39
59
  ---
40
60
 
41
61
  <!DOCTYPE html>
42
62
  <html lang="en">
43
63
  <head>
44
64
  <meta charset="utf-8" />
45
- <link rel="icon" type="image/x-icon" href="/favicon.ico" />
46
- <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
47
- <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
48
- <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
49
- <link rel="icon" type="image/png" sizes="48x48" href="/favicon-48x48.png" />
50
- <link rel="apple-touch-icon" sizes="57x57" href="/apple-touch-icon-57x57.png" />
51
- <link rel="apple-touch-icon" sizes="60x60" href="/apple-touch-icon-60x60.png" />
52
- <link rel="apple-touch-icon" sizes="72x72" href="/apple-touch-icon-72x72.png" />
53
- <link rel="apple-touch-icon" sizes="76x76" href="/apple-touch-icon-76x76.png" />
54
- <link rel="apple-touch-icon" sizes="114x114" href="/apple-touch-icon-114x114.png" />
55
- <link rel="apple-touch-icon" sizes="120x120" href="/apple-touch-icon-120x120.png" />
56
- <link rel="apple-touch-icon" sizes="144x144" href="/apple-touch-icon-144x144.png" />
57
- <link rel="apple-touch-icon" sizes="152x152" href="/apple-touch-icon-152x152.png" />
58
- <link rel="apple-touch-icon" sizes="167x167" href="/apple-touch-icon-167x167.png" />
59
- <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon-180x180.png" />
60
- <link rel="apple-touch-icon" sizes="1024x1024" href="/apple-touch-icon-1024x1024.png" />
65
+ {!skipDefaultFaviconLinks && (
66
+ <>
67
+ <link rel="icon" type="image/x-icon" href={`${faviconBase}favicon.ico`} />
68
+ <link rel="icon" type="image/svg+xml" href={`${faviconBase}favicon.svg`} />
69
+ <link rel="icon" type="image/png" sizes="16x16" href={`${faviconBase}favicon-16x16.png`} />
70
+ <link rel="icon" type="image/png" sizes="32x32" href={`${faviconBase}favicon-32x32.png`} />
71
+ <link rel="icon" type="image/png" sizes="48x48" href={`${faviconBase}favicon-48x48.png`} />
72
+ <link rel="apple-touch-icon" sizes="57x57" href={`${faviconBase}apple-touch-icon-57x57.png`} />
73
+ <link rel="apple-touch-icon" sizes="60x60" href={`${faviconBase}apple-touch-icon-60x60.png`} />
74
+ <link rel="apple-touch-icon" sizes="72x72" href={`${faviconBase}apple-touch-icon-72x72.png`} />
75
+ <link rel="apple-touch-icon" sizes="76x76" href={`${faviconBase}apple-touch-icon-76x76.png`} />
76
+ <link rel="apple-touch-icon" sizes="114x114" href={`${faviconBase}apple-touch-icon-114x114.png`} />
77
+ <link rel="apple-touch-icon" sizes="120x120" href={`${faviconBase}apple-touch-icon-120x120.png`} />
78
+ <link rel="apple-touch-icon" sizes="144x144" href={`${faviconBase}apple-touch-icon-144x144.png`} />
79
+ <link rel="apple-touch-icon" sizes="152x152" href={`${faviconBase}apple-touch-icon-152x152.png`} />
80
+ <link rel="apple-touch-icon" sizes="167x167" href={`${faviconBase}apple-touch-icon-167x167.png`} />
81
+ <link rel="apple-touch-icon" sizes="180x180" href={`${faviconBase}apple-touch-icon-180x180.png`} />
82
+ <link rel="apple-touch-icon" sizes="1024x1024" href={`${faviconBase}apple-touch-icon-1024x1024.png`} />
83
+ </>
84
+ )}
85
+ {customFaviconLinks && <Fragment set:html={customFaviconLinks} />}
61
86
  <meta name="apple-mobile-web-app-capable" content="yes" />
62
87
  <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
63
88
  <meta name="apple-mobile-web-app-title" content={siteConfig.title} />
@@ -11,6 +11,12 @@ export async function getStaticPaths() {
11
11
  { params: { path: 'about' } },
12
12
  { params: { path: 'contributors' } },
13
13
  { params: { path: 'news' } },
14
+ /* /learn and sub-routes — page-types.ts auto-synthesizes the nav entry.
15
+ Without these static paths the nav link 404s on prefetch (issue #174). */
16
+ { params: { path: 'learn' } },
17
+ { params: { path: 'learn/relationships' } },
18
+ { params: { path: 'learn/designations' } },
19
+ { params: { path: 'learn/statuses' } },
14
20
  ];
15
21
  for (const ds of datasets) {
16
22
  paths.push({ params: { path: `dataset/${ds.data.id}/about` } });
@@ -0,0 +1,74 @@
1
+ /**
2
+ * External concept detection — ISO 704:2022 external concepts.
3
+ *
4
+ * An "external concept" is one referenced from this dataset but defined
5
+ * elsewhere (status: 'external'). In diagrams, external concepts are
6
+ * shown in parentheses with a dashed border per ISO 704:2022 §5.5.4.3.1.
7
+ *
8
+ * Detection requires concept resolution (looking up the referenced
9
+ * concept's status), which is a consumer concern — the model itself
10
+ * stays pure. These utility functions take a conceptStore/lookup
11
+ * callback provided by the caller.
12
+ */
13
+
14
+ export interface ConceptLike {
15
+ status?: string | null;
16
+ relatedConcepts?: ReadonlyArray<{ type?: string | null }>;
17
+ }
18
+
19
+ export interface ConceptStoreLike {
20
+ lookup(ref: { source?: string | null; id?: string | null }): ConceptLike | null;
21
+ }
22
+
23
+ export interface RefLike {
24
+ source?: string | null;
25
+ id?: string | null;
26
+ }
27
+
28
+ /**
29
+ * Check if a member's concept resolves to status: external.
30
+ */
31
+ export function isExternalConcept(
32
+ ref: RefLike | null | undefined,
33
+ store: ConceptStoreLike | null | undefined,
34
+ ): boolean {
35
+ if (!ref || !store) return false;
36
+ const concept = store.lookup(ref);
37
+ return concept?.status === 'external';
38
+ }
39
+
40
+ /**
41
+ * Check if an external concept has a provided_by edge for resolution.
42
+ * Without provided_by, the reference dangles — the decomposition is
43
+ * incomplete.
44
+ */
45
+ export function hasProvidedBy(
46
+ ref: RefLike | null | undefined,
47
+ store: ConceptStoreLike | null | undefined,
48
+ ): boolean {
49
+ if (!ref || !store) return false;
50
+ const concept = store.lookup(ref);
51
+ if (!concept?.relatedConcepts) return false;
52
+ return concept.relatedConcepts.some(rc => rc.type === 'provided_by');
53
+ }
54
+
55
+ /**
56
+ * Check if an external concept's reference dangles (no provided_by edge).
57
+ */
58
+ export function isDanglingExternal(
59
+ ref: RefLike | null | undefined,
60
+ store: ConceptStoreLike | null | undefined,
61
+ ): boolean {
62
+ return isExternalConcept(ref, store) && !hasProvidedBy(ref, store);
63
+ }
64
+
65
+ /**
66
+ * Format a label for an external concept — parenthetical per ISO 704.
67
+ * Returns `"(label)"` for external concepts, `label` for internal.
68
+ */
69
+ export function formatExternalLabel(
70
+ label: string,
71
+ isExternal: boolean,
72
+ ): string {
73
+ return isExternal ? `(${label})` : label;
74
+ }