@edoxen/browser 0.1.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 +45 -0
- package/dist/cli.js +4475 -0
- package/package.json +82 -0
- package/src/astro/components/ActionTypeBadge.astro +28 -0
- package/src/astro/components/AgendaTable.astro +49 -0
- package/src/astro/components/BodyBadge.astro +28 -0
- package/src/astro/components/CommitteeCard.astro +58 -0
- package/src/astro/components/CountryFlag.astro +34 -0
- package/src/astro/components/DecadeTimeline.astro +52 -0
- package/src/astro/components/DecisionList.astro +66 -0
- package/src/astro/components/EmptyState.astro +19 -0
- package/src/astro/components/LocaleTabs.astro +44 -0
- package/src/astro/components/MeetingList.astro +63 -0
- package/src/astro/components/MinutesSection.astro +59 -0
- package/src/astro/components/OfficerList.astro +49 -0
- package/src/astro/components/PageHero.astro +42 -0
- package/src/astro/components/PrevNextNav.astro +62 -0
- package/src/astro/components/ReferenceDocuments.astro +38 -0
- package/src/astro/components/UrnBar.astro +49 -0
- package/src/astro/layouts/BaseLayout.astro +79 -0
- package/src/astro/layouts/DecisionLayout.astro +21 -0
- package/src/astro/layouts/MeetingLayout.astro +21 -0
- package/src/astro/pages/404.astro +10 -0
- package/src/astro/pages/[lang]/about.astro +65 -0
- package/src/astro/pages/[lang]/decisions/[urn].astro +126 -0
- package/src/astro/pages/[lang]/decisions/index.astro +27 -0
- package/src/astro/pages/[lang]/index.astro +50 -0
- package/src/astro/pages/[lang]/meetings/[urn].astro +114 -0
- package/src/astro/pages/[lang]/meetings/index.astro +27 -0
- package/src/astro/pages/about.astro +65 -0
- package/src/astro/pages/decisions/[urn].astro +122 -0
- package/src/astro/pages/decisions/index.astro +67 -0
- package/src/astro/pages/index.astro +50 -0
- package/src/astro/pages/meetings/[urn].astro +110 -0
- package/src/astro/pages/meetings/index.astro +20 -0
- package/src/cli/index.ts +195 -0
- package/src/config/index.ts +33 -0
- package/src/config/paths.spec.ts +59 -0
- package/src/config/paths.ts +26 -0
- package/src/config/schema.spec.ts +263 -0
- package/src/config/schema.ts +132 -0
- package/src/config/tokens.spec.ts +57 -0
- package/src/config/tokens.ts +48 -0
- package/src/data/index.ts +36 -0
- package/src/data/lint.spec.ts +79 -0
- package/src/data/lint.ts +77 -0
- package/src/data/load.spec.ts +58 -0
- package/src/data/load.ts +50 -0
- package/src/data/prepare.spec.ts +137 -0
- package/src/data/prepare.ts +199 -0
- package/src/data/project.ts +11 -0
- package/src/data/validate.ts +38 -0
- package/src/errors.ts +25 -0
- package/src/i18n/index.spec.ts +121 -0
- package/src/i18n/index.ts +90 -0
- package/src/index.ts +74 -0
- package/src/integration.ts +233 -0
- package/src/islands/decade-scroller.ts +18 -0
- package/src/islands/print-button.ts +12 -0
- package/src/islands/search-filter-core.spec.ts +84 -0
- package/src/islands/search-filter-core.ts +89 -0
- package/src/islands/search-filter.ts +166 -0
- package/src/islands/section-tabs.ts +35 -0
- package/src/islands/theme-toggle.ts +33 -0
- package/src/islands/urn-copy.ts +29 -0
- package/src/seo/index.spec.ts +76 -0
- package/src/seo/index.ts +85 -0
- package/src/virtual.d.ts +11 -0
- package/styles/base.css +117 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
---
|
|
2
|
+
import cfg from 'virtual:edoxen-config'
|
|
3
|
+
import payloads from 'virtual:edoxen-payloads'
|
|
4
|
+
import BaseLayout from '../../../layouts/BaseLayout.astro'
|
|
5
|
+
import MeetingList from '../../../components/MeetingList.astro'
|
|
6
|
+
import DecadeTimeline from '../../../components/DecadeTimeline.astro'
|
|
7
|
+
|
|
8
|
+
export function getStaticPaths() {
|
|
9
|
+
return cfg.locales
|
|
10
|
+
.filter((l) => (l.routePrefix ?? '') !== '')
|
|
11
|
+
.map((l) => ({ params: { lang: l.code } }))
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const items = payloads.meetingsList.items
|
|
15
|
+
const decades = payloads.meetingsList.facets.decades
|
|
16
|
+
const lang = Astro.params.lang ?? cfg.site.locale
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
<BaseLayout config={cfg} lang={lang} title={`Meetings — ${cfg.site.title}`}>
|
|
20
|
+
<h1>Meetings</h1>
|
|
21
|
+
|
|
22
|
+
<decade-scroller>
|
|
23
|
+
<DecadeTimeline decades={decades} />
|
|
24
|
+
</decade-scroller>
|
|
25
|
+
|
|
26
|
+
<MeetingList items={items} />
|
|
27
|
+
</BaseLayout>
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
---
|
|
2
|
+
import cfg from 'virtual:edoxen-config'
|
|
3
|
+
import payloads from 'virtual:edoxen-payloads'
|
|
4
|
+
import BaseLayout from '../layouts/BaseLayout.astro'
|
|
5
|
+
|
|
6
|
+
export function getStaticPaths() {
|
|
7
|
+
return cfg.locales
|
|
8
|
+
.filter((l) => (l.routePrefix ?? '') !== '')
|
|
9
|
+
.map((l) => ({ params: { lang: l.code } }))
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const decisionCount = payloads.decisionsList.items.length
|
|
13
|
+
const meetingCount = payloads.meetingsList.items.length
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
<BaseLayout config={cfg} title={`About — ${cfg.site.title}`}>
|
|
17
|
+
<h1>About</h1>
|
|
18
|
+
|
|
19
|
+
<section>
|
|
20
|
+
<h2>{cfg.site.title}</h2>
|
|
21
|
+
<p>{cfg.site.description}</p>
|
|
22
|
+
<dl class="edoxen-stats">
|
|
23
|
+
<div><dt>Decisions</dt><dd>{decisionCount}</dd></div>
|
|
24
|
+
<div><dt>Meetings</dt><dd>{meetingCount}</dd></div>
|
|
25
|
+
</dl>
|
|
26
|
+
</section>
|
|
27
|
+
|
|
28
|
+
<section>
|
|
29
|
+
<h2>Edoxen format</h2>
|
|
30
|
+
<p>
|
|
31
|
+
This site renders meeting and decision data using the
|
|
32
|
+
<a href="https://edoxen.github.io">Edoxen</a> information model — a
|
|
33
|
+
YAML-based schema for formal proceedings of standards bodies.
|
|
34
|
+
</p>
|
|
35
|
+
</section>
|
|
36
|
+
|
|
37
|
+
<section>
|
|
38
|
+
<h2>Using this site</h2>
|
|
39
|
+
<ul>
|
|
40
|
+
<li><strong>Decisions</strong> — browse the resolutions archive.</li>
|
|
41
|
+
<li><strong>Meetings</strong> — see meetings and their agendas, minutes, and adopted decisions.</li>
|
|
42
|
+
<li><strong>URNs</strong> — every entity has a stable URN for citation.</li>
|
|
43
|
+
</ul>
|
|
44
|
+
</section>
|
|
45
|
+
</BaseLayout>
|
|
46
|
+
|
|
47
|
+
<style>
|
|
48
|
+
.edoxen-stats {
|
|
49
|
+
display: flex;
|
|
50
|
+
flex-wrap: wrap;
|
|
51
|
+
gap: 1.5rem;
|
|
52
|
+
margin: 1rem 0 0;
|
|
53
|
+
}
|
|
54
|
+
.edoxen-stats dd {
|
|
55
|
+
margin: 0;
|
|
56
|
+
font-size: 1.5rem;
|
|
57
|
+
font-weight: 600;
|
|
58
|
+
}
|
|
59
|
+
.edoxen-stats dt {
|
|
60
|
+
font-size: 0.85rem;
|
|
61
|
+
color: var(--edoxen-color-muted);
|
|
62
|
+
text-transform: uppercase;
|
|
63
|
+
letter-spacing: 0.05em;
|
|
64
|
+
}
|
|
65
|
+
</style>
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
---
|
|
2
|
+
import cfg from 'virtual:edoxen-config'
|
|
3
|
+
import payloads from 'virtual:edoxen-payloads'
|
|
4
|
+
import DecisionLayout from '../../layouts/DecisionLayout.astro'
|
|
5
|
+
import UrnBar from '../../components/UrnBar.astro'
|
|
6
|
+
import BodyBadge from '../../components/BodyBadge.astro'
|
|
7
|
+
import LocaleTabs from '../../components/LocaleTabs.astro'
|
|
8
|
+
import { decisionJsonLd } from '../../../seo/index.js'
|
|
9
|
+
import { pickLocalizedValue, availableSpellings } from '../../../i18n/index.js'
|
|
10
|
+
|
|
11
|
+
export function getStaticPaths() {
|
|
12
|
+
return Object.keys(payloads.decisionByUrn).map((urn) => ({
|
|
13
|
+
params: { urn },
|
|
14
|
+
props: { decision: payloads.decisionByUrn[urn] },
|
|
15
|
+
}))
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface Props {
|
|
19
|
+
decision: typeof payloads.decisionByUrn[string]
|
|
20
|
+
}
|
|
21
|
+
const { decision } = Astro.props
|
|
22
|
+
const locale = cfg.site.locale
|
|
23
|
+
const title = pickLocalizedValue(decision.title, locale, decision.urn ?? '')
|
|
24
|
+
const subject = pickLocalizedValue(decision.subject, locale)
|
|
25
|
+
const considering = pickLocalizedValue(decision.considering, locale)
|
|
26
|
+
const actions = decision.actions ?? []
|
|
27
|
+
const considerations = decision.considerations ?? []
|
|
28
|
+
const approvals = decision.approvals ?? []
|
|
29
|
+
const titleSpellings = availableSpellings(decision.title)
|
|
30
|
+
const jsonld = decisionJsonLd(decision, { siteUrl: cfg.site.url, siteTitle: cfg.site.title, defaultLocale: locale })
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
<DecisionLayout
|
|
34
|
+
config={cfg}
|
|
35
|
+
item={{
|
|
36
|
+
urn: decision.urn ?? '',
|
|
37
|
+
identifier: (decision.identifier[0]?.prefix ?? '') + '-' + (decision.identifier[0]?.number ?? ''),
|
|
38
|
+
title,
|
|
39
|
+
kind: decision.kind ?? '',
|
|
40
|
+
dates: (decision.dates ?? []).map((d) => ({ type: d.type ?? '', date: d.date ?? '' })),
|
|
41
|
+
bodyType: decision.body_type,
|
|
42
|
+
}}
|
|
43
|
+
>
|
|
44
|
+
<script type="application/ld+json" set:html={JSON.stringify(jsonld)} is:inline />
|
|
45
|
+
<UrnBar urn={decision.urn ?? ''} />
|
|
46
|
+
|
|
47
|
+
{decision.body_type && <p><BodyBadge code={decision.body_type} /></p>}
|
|
48
|
+
|
|
49
|
+
{titleSpellings.length > 1 && <LocaleTabs spellings={titleSpellings} active={locale} field="title" />}
|
|
50
|
+
|
|
51
|
+
{subject && (
|
|
52
|
+
<section>
|
|
53
|
+
<h2>Subject</h2>
|
|
54
|
+
<p>{subject}</p>
|
|
55
|
+
</section>
|
|
56
|
+
)}
|
|
57
|
+
|
|
58
|
+
{considering && (
|
|
59
|
+
<section>
|
|
60
|
+
<h2>Considering</h2>
|
|
61
|
+
<p>{considering}</p>
|
|
62
|
+
</section>
|
|
63
|
+
)}
|
|
64
|
+
|
|
65
|
+
{considerations.length > 0 && (
|
|
66
|
+
<section>
|
|
67
|
+
<h2>Considerations</h2>
|
|
68
|
+
<ul>
|
|
69
|
+
{considerations.map((c) => (
|
|
70
|
+
<li>{c.kind}{c.references && c.references.length > 0 ? `: ${c.references.map((r) => r.urn).join(', ')}` : ''}</li>
|
|
71
|
+
))}
|
|
72
|
+
</ul>
|
|
73
|
+
</section>
|
|
74
|
+
)}
|
|
75
|
+
|
|
76
|
+
{actions.length > 0 && (
|
|
77
|
+
<section>
|
|
78
|
+
<h2>Actions</h2>
|
|
79
|
+
<ul>
|
|
80
|
+
{actions.map((a) => (
|
|
81
|
+
<li>
|
|
82
|
+
<strong>{a.type}</strong>
|
|
83
|
+
{a.message && <div>{pickLocalizedValue(a.message, locale)}</div>}
|
|
84
|
+
</li>
|
|
85
|
+
))}
|
|
86
|
+
</ul>
|
|
87
|
+
</section>
|
|
88
|
+
)}
|
|
89
|
+
|
|
90
|
+
{approvals.length > 0 && (
|
|
91
|
+
<section>
|
|
92
|
+
<h2>Approvals</h2>
|
|
93
|
+
<ul>
|
|
94
|
+
{approvals.map((a) => (
|
|
95
|
+
<li>{a.degree}{a.body ? ` — ${a.body}` : ''}{a.date ? ` (${a.date})` : ''}</li>
|
|
96
|
+
))}
|
|
97
|
+
</ul>
|
|
98
|
+
</section>
|
|
99
|
+
)}
|
|
100
|
+
|
|
101
|
+
{(decision.dates ?? []).length > 0 && (
|
|
102
|
+
<section>
|
|
103
|
+
<h2>Dates</h2>
|
|
104
|
+
<ul>
|
|
105
|
+
{(decision.dates ?? []).map((d) => (
|
|
106
|
+
<li>{d.type}: {d.date}</li>
|
|
107
|
+
))}
|
|
108
|
+
</ul>
|
|
109
|
+
</section>
|
|
110
|
+
)}
|
|
111
|
+
|
|
112
|
+
{(decision.urls ?? []).length > 0 && (
|
|
113
|
+
<section>
|
|
114
|
+
<h2>Reference documents</h2>
|
|
115
|
+
<ul>
|
|
116
|
+
{(decision.urls ?? []).map((u) => (
|
|
117
|
+
<li><a href={u.ref}>{u.kind ?? 'document'}</a></li>
|
|
118
|
+
))}
|
|
119
|
+
</ul>
|
|
120
|
+
</section>
|
|
121
|
+
)}
|
|
122
|
+
</DecisionLayout>
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
---
|
|
2
|
+
import cfg from 'virtual:edoxen-config'
|
|
3
|
+
import payloads from 'virtual:edoxen-payloads'
|
|
4
|
+
import BaseLayout from '../../layouts/BaseLayout.astro'
|
|
5
|
+
import DecisionList from '../../components/DecisionList.astro'
|
|
6
|
+
|
|
7
|
+
const items = payloads.decisionsList.items
|
|
8
|
+
const endpoint = `${cfg.site.basePath}data/decisions.json`.replace(/\/+/g, '/')
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
<BaseLayout config={cfg} title={`Decisions — ${cfg.site.title}`}>
|
|
12
|
+
<h1>Decisions</h1>
|
|
13
|
+
|
|
14
|
+
<search-filter data-endpoint={endpoint} data-base-path="/decisions"></search-filter>
|
|
15
|
+
<noscript>
|
|
16
|
+
<DecisionList items={items} />
|
|
17
|
+
</noscript>
|
|
18
|
+
</BaseLayout>
|
|
19
|
+
|
|
20
|
+
<style>
|
|
21
|
+
:global(search-filter) {
|
|
22
|
+
display: block;
|
|
23
|
+
margin: 1rem 0;
|
|
24
|
+
}
|
|
25
|
+
:global(.edoxen-search-filter__form) {
|
|
26
|
+
display: flex;
|
|
27
|
+
flex-direction: column;
|
|
28
|
+
gap: 0.75rem;
|
|
29
|
+
}
|
|
30
|
+
:global(.edoxen-search-filter__facets) {
|
|
31
|
+
display: flex;
|
|
32
|
+
flex-wrap: wrap;
|
|
33
|
+
gap: 0.4rem;
|
|
34
|
+
}
|
|
35
|
+
:global(.edoxen-search-filter__facet) {
|
|
36
|
+
border: 1px solid var(--edoxen-color-border);
|
|
37
|
+
background: var(--edoxen-color-surface);
|
|
38
|
+
color: var(--edoxen-color-text);
|
|
39
|
+
padding: 0.2rem 0.6rem;
|
|
40
|
+
border-radius: var(--edoxen-radius-sm);
|
|
41
|
+
font-size: 0.8rem;
|
|
42
|
+
cursor: pointer;
|
|
43
|
+
}
|
|
44
|
+
:global(.edoxen-search-filter__facet[aria-pressed='true']) {
|
|
45
|
+
background: var(--edoxen-color-accent);
|
|
46
|
+
color: #fff;
|
|
47
|
+
border-color: var(--edoxen-color-accent);
|
|
48
|
+
}
|
|
49
|
+
:global(.edoxen-search-filter__results) {
|
|
50
|
+
list-style: none;
|
|
51
|
+
padding: 0;
|
|
52
|
+
margin: 0;
|
|
53
|
+
display: flex;
|
|
54
|
+
flex-direction: column;
|
|
55
|
+
gap: 0.5rem;
|
|
56
|
+
}
|
|
57
|
+
:global(.edoxen-search-filter__result) {
|
|
58
|
+
padding: 0.6rem 0.85rem;
|
|
59
|
+
border: 1px solid var(--edoxen-color-border);
|
|
60
|
+
border-radius: var(--edoxen-radius-sm);
|
|
61
|
+
background: var(--edoxen-color-surface);
|
|
62
|
+
}
|
|
63
|
+
:global(.edoxen-search-filter__result a) {
|
|
64
|
+
font-weight: 500;
|
|
65
|
+
color: var(--edoxen-color-text);
|
|
66
|
+
}
|
|
67
|
+
</style>
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
---
|
|
2
|
+
import cfg from 'virtual:edoxen-config'
|
|
3
|
+
import payloads from 'virtual:edoxen-payloads'
|
|
4
|
+
import BaseLayout from '../layouts/BaseLayout.astro'
|
|
5
|
+
|
|
6
|
+
export function getStaticPaths() {
|
|
7
|
+
return cfg.locales
|
|
8
|
+
.filter((l) => (l.routePrefix ?? '') !== '')
|
|
9
|
+
.map((l) => ({ params: { lang: l.code } }))
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const recentDecisions = payloads.decisionsList.items.slice(0, 5)
|
|
13
|
+
const recentMeetings = payloads.meetingsList.items.slice(0, 3)
|
|
14
|
+
const lang = Astro.params.lang
|
|
15
|
+
const prefix = lang && cfg.locales.find((l) => l.code === lang)?.routePrefix
|
|
16
|
+
const urlPrefix = prefix ?? ''
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
<BaseLayout config={cfg}>
|
|
20
|
+
<section>
|
|
21
|
+
<h1>{cfg.site.title}</h1>
|
|
22
|
+
<p>{cfg.site.description}</p>
|
|
23
|
+
</section>
|
|
24
|
+
|
|
25
|
+
<section>
|
|
26
|
+
<h2>Latest decisions</h2>
|
|
27
|
+
<ul>
|
|
28
|
+
{recentDecisions.map((d) => (
|
|
29
|
+
<li>
|
|
30
|
+
<a href={`${urlPrefix}/decisions/${encodeURIComponent(d.urn)}`}>{d.title}</a>
|
|
31
|
+
{d.bodyType && <span class="edoxen-badge">{d.bodyType}</span>}
|
|
32
|
+
</li>
|
|
33
|
+
))}
|
|
34
|
+
</ul>
|
|
35
|
+
</section>
|
|
36
|
+
|
|
37
|
+
{recentMeetings.length > 0 && (
|
|
38
|
+
<section>
|
|
39
|
+
<h2>Recent meetings</h2>
|
|
40
|
+
<ul>
|
|
41
|
+
{recentMeetings.map((m) => (
|
|
42
|
+
<li>
|
|
43
|
+
<a href={`${urlPrefix}/meetings/${encodeURIComponent(m.urn)}`}>{m.title}</a>
|
|
44
|
+
{m.startDate && <span> — {m.startDate}</span>}
|
|
45
|
+
</li>
|
|
46
|
+
))}
|
|
47
|
+
</ul>
|
|
48
|
+
</section>
|
|
49
|
+
)}
|
|
50
|
+
</BaseLayout>
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
---
|
|
2
|
+
import cfg from 'virtual:edoxen-config'
|
|
3
|
+
import payloads from 'virtual:edoxen-payloads'
|
|
4
|
+
import MeetingLayout from '../../layouts/MeetingLayout.astro'
|
|
5
|
+
import UrnBar from '../../components/UrnBar.astro'
|
|
6
|
+
import BodyBadge from '../../components/BodyBadge.astro'
|
|
7
|
+
import AgendaTable from '../../components/AgendaTable.astro'
|
|
8
|
+
import MinutesSection from '../../components/MinutesSection.astro'
|
|
9
|
+
import { meetingJsonLd } from '../../../seo/index.js'
|
|
10
|
+
import { pickLocalizedValue } from '../../../i18n/index.js'
|
|
11
|
+
|
|
12
|
+
export function getStaticPaths() {
|
|
13
|
+
return Object.keys(payloads.meetingByUrn).map((urn) => ({
|
|
14
|
+
params: { urn },
|
|
15
|
+
props: { meeting: payloads.meetingByUrn[urn] },
|
|
16
|
+
}))
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface Props {
|
|
20
|
+
meeting: typeof payloads.meetingByUrn[string]
|
|
21
|
+
}
|
|
22
|
+
const { meeting } = Astro.props
|
|
23
|
+
const locale = cfg.site.locale
|
|
24
|
+
const title = pickLocalizedValue(meeting.title, locale, meeting.urn ?? '')
|
|
25
|
+
const officers = meeting.officers ?? []
|
|
26
|
+
const agenda = meeting.agenda
|
|
27
|
+
const minutes = meeting.minutes ?? []
|
|
28
|
+
const linkedDecisions = (meeting.decisions ?? []).map((id) =>
|
|
29
|
+
payloads.decisionsList.items.find((d) =>
|
|
30
|
+
d.identifier === `${id.prefix}-${id.number}`
|
|
31
|
+
)
|
|
32
|
+
).filter((d): d is NonNullable<typeof d> => d !== undefined)
|
|
33
|
+
const jsonld = meetingJsonLd(meeting, { siteUrl: cfg.site.url, siteTitle: cfg.site.title, defaultLocale: locale })
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
<MeetingLayout
|
|
37
|
+
config={cfg}
|
|
38
|
+
item={{
|
|
39
|
+
urn: meeting.urn ?? '',
|
|
40
|
+
identifier: (meeting.identifier[0]?.prefix ?? '') + '-' + (meeting.identifier[0]?.number ?? ''),
|
|
41
|
+
title,
|
|
42
|
+
startDate: meeting.date_range?.start,
|
|
43
|
+
endDate: meeting.date_range?.end,
|
|
44
|
+
bodyType: meeting.body_type,
|
|
45
|
+
city: meeting.city,
|
|
46
|
+
countryCode: meeting.country_code,
|
|
47
|
+
status: meeting.status,
|
|
48
|
+
}}
|
|
49
|
+
>
|
|
50
|
+
<script type="application/ld+json" set:html={JSON.stringify(jsonld)} is:inline />
|
|
51
|
+
<UrnBar urn={meeting.urn ?? ''} />
|
|
52
|
+
|
|
53
|
+
{meeting.body_type && <p><BodyBadge code={meeting.body_type} /></p>}
|
|
54
|
+
|
|
55
|
+
{meeting.city && (
|
|
56
|
+
<section>
|
|
57
|
+
<h2>Venue</h2>
|
|
58
|
+
<p>{meeting.city}{meeting.country_code ? `, ${meeting.country_code}` : ''}</p>
|
|
59
|
+
</section>
|
|
60
|
+
)}
|
|
61
|
+
|
|
62
|
+
{officers.length > 0 && (
|
|
63
|
+
<section>
|
|
64
|
+
<h2>Officers</h2>
|
|
65
|
+
<ul>
|
|
66
|
+
{officers.map((o) => (
|
|
67
|
+
<li>{o.role}{o.person?.name ? `: ${o.person.name}` : ''}</li>
|
|
68
|
+
))}
|
|
69
|
+
</ul>
|
|
70
|
+
</section>
|
|
71
|
+
)}
|
|
72
|
+
|
|
73
|
+
{agenda && agenda.items && agenda.items.length > 0 && (
|
|
74
|
+
<section>
|
|
75
|
+
<h2>Agenda</h2>
|
|
76
|
+
<AgendaTable items={agenda.items} locale={locale} />
|
|
77
|
+
</section>
|
|
78
|
+
)}
|
|
79
|
+
|
|
80
|
+
{minutes.length > 0 && (
|
|
81
|
+
<section>
|
|
82
|
+
<h2>Minutes</h2>
|
|
83
|
+
{minutes.map((m) => (
|
|
84
|
+
<MinutesSection minutes={m} locale={locale} />
|
|
85
|
+
))}
|
|
86
|
+
</section>
|
|
87
|
+
)}
|
|
88
|
+
|
|
89
|
+
{linkedDecisions.length > 0 && (
|
|
90
|
+
<section>
|
|
91
|
+
<h2>Adopted decisions</h2>
|
|
92
|
+
<ul>
|
|
93
|
+
{linkedDecisions.map((d) => (
|
|
94
|
+
<li><a href={`/decisions/${encodeURIComponent(d.urn)}`}>{d.title}</a></li>
|
|
95
|
+
))}
|
|
96
|
+
</ul>
|
|
97
|
+
</section>
|
|
98
|
+
)}
|
|
99
|
+
|
|
100
|
+
{(meeting.source_urls ?? []).length > 0 && (
|
|
101
|
+
<section>
|
|
102
|
+
<h2>Source documents</h2>
|
|
103
|
+
<ul>
|
|
104
|
+
{(meeting.source_urls ?? []).map((s) => (
|
|
105
|
+
<li><a href={s.ref}>{s.format ?? 'document'}</a></li>
|
|
106
|
+
))}
|
|
107
|
+
</ul>
|
|
108
|
+
</section>
|
|
109
|
+
)}
|
|
110
|
+
</MeetingLayout>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
---
|
|
2
|
+
import cfg from 'virtual:edoxen-config'
|
|
3
|
+
import payloads from 'virtual:edoxen-payloads'
|
|
4
|
+
import BaseLayout from '../../layouts/BaseLayout.astro'
|
|
5
|
+
import MeetingList from '../../components/MeetingList.astro'
|
|
6
|
+
import DecadeTimeline from '../../components/DecadeTimeline.astro'
|
|
7
|
+
|
|
8
|
+
const items = payloads.meetingsList.items
|
|
9
|
+
const decades = payloads.meetingsList.facets.decades
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
<BaseLayout config={cfg} title={`Meetings — ${cfg.site.title}`}>
|
|
13
|
+
<h1>Meetings</h1>
|
|
14
|
+
|
|
15
|
+
<decade-scroller>
|
|
16
|
+
<DecadeTimeline decades={decades} />
|
|
17
|
+
</decade-scroller>
|
|
18
|
+
|
|
19
|
+
<MeetingList items={items} />
|
|
20
|
+
</BaseLayout>
|
package/src/cli/index.ts
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { parseArgs } from 'node:util'
|
|
3
|
+
import { existsSync } from 'node:fs'
|
|
4
|
+
import { resolve, extname } from 'node:path'
|
|
5
|
+
import { pathToFileURL } from 'node:url'
|
|
6
|
+
|
|
7
|
+
import { EdoxenConfigSchema } from '../config/index.js'
|
|
8
|
+
import { loadAll, validateAll, lintProject, buildProjectFromLoaded } from '../data/index.js'
|
|
9
|
+
import { EdoxenBrowserError } from '../errors.js'
|
|
10
|
+
|
|
11
|
+
const HELP = `edoxen-browser — CLI for the @edoxen/browser package
|
|
12
|
+
|
|
13
|
+
Usage:
|
|
14
|
+
edoxen-browser <command> [options]
|
|
15
|
+
|
|
16
|
+
Commands:
|
|
17
|
+
validate Validate the data + config without building.
|
|
18
|
+
lint Run structural lint (duplicate URNs, broken relations).
|
|
19
|
+
check validate + lint in one shot.
|
|
20
|
+
config Print the resolved config as JSON.
|
|
21
|
+
build Run astro build (thin wrapper).
|
|
22
|
+
help Show this help.
|
|
23
|
+
|
|
24
|
+
Options:
|
|
25
|
+
--config <path> Path to edoxen.config.ts (default: ./edoxen.config.ts)
|
|
26
|
+
--cwd <path> Working directory (default: process.cwd())
|
|
27
|
+
--strict Treat warnings as errors (lint only).
|
|
28
|
+
-h, --help Show this help.
|
|
29
|
+
`
|
|
30
|
+
|
|
31
|
+
interface ParsedArgs {
|
|
32
|
+
command: string
|
|
33
|
+
configPath: string
|
|
34
|
+
cwd: string
|
|
35
|
+
strict: boolean
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function parseCliArgs(argv: string[]): ParsedArgs {
|
|
39
|
+
const [command, ...rest] = argv.slice(2)
|
|
40
|
+
const { values } = parseArgs({
|
|
41
|
+
args: rest,
|
|
42
|
+
options: {
|
|
43
|
+
config: { type: 'string', default: './edoxen.config.ts' },
|
|
44
|
+
cwd: { type: 'string', default: process.cwd() },
|
|
45
|
+
strict: { type: 'boolean', default: false },
|
|
46
|
+
help: { type: 'boolean', short: 'h', default: false },
|
|
47
|
+
},
|
|
48
|
+
allowPositionals: true,
|
|
49
|
+
})
|
|
50
|
+
if (values.help || !command || command === 'help') {
|
|
51
|
+
process.stdout.write(HELP)
|
|
52
|
+
process.exit(0)
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
command,
|
|
56
|
+
configPath: String(values.config),
|
|
57
|
+
cwd: String(values.cwd),
|
|
58
|
+
strict: Boolean(values.strict),
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function loadConfigFile(configPath: string, cwd: string): Promise<unknown> {
|
|
63
|
+
const absolute = resolve(cwd, configPath)
|
|
64
|
+
if (!existsSync(absolute)) {
|
|
65
|
+
throw new EdoxenBrowserError('config', `Config file not found: ${absolute}`, [])
|
|
66
|
+
}
|
|
67
|
+
const ext = extname(absolute)
|
|
68
|
+
if (ext === '.ts' || ext === '.tsx' || ext === '.mts') {
|
|
69
|
+
const { createJiti } = await import('jiti')
|
|
70
|
+
const jiti = createJiti(import.meta.url, { interopDefault: true })
|
|
71
|
+
const mod = (await jiti.import(absolute)) as { default?: unknown }
|
|
72
|
+
return mod.default
|
|
73
|
+
}
|
|
74
|
+
const url = pathToFileURL(absolute).href
|
|
75
|
+
const mod = (await import(url)) as { default?: unknown }
|
|
76
|
+
if (!mod.default) {
|
|
77
|
+
throw new EdoxenBrowserError('config', `Config file ${absolute} does not export a default config`, [])
|
|
78
|
+
}
|
|
79
|
+
return mod.default
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function loadValidatedConfig(args: ParsedArgs) {
|
|
83
|
+
const raw = await loadConfigFile(args.configPath, args.cwd)
|
|
84
|
+
const parseResult = EdoxenConfigSchema.safeParse(raw)
|
|
85
|
+
if (!parseResult.success) {
|
|
86
|
+
throw new EdoxenBrowserError(
|
|
87
|
+
'config',
|
|
88
|
+
'edoxen.config.ts failed schema validation',
|
|
89
|
+
parseResult.error.issues.map((i) => `${i.path.join('.') || '<root>'}: ${i.message}`),
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
const cfg = parseResult.data
|
|
93
|
+
return {
|
|
94
|
+
...cfg,
|
|
95
|
+
data: {
|
|
96
|
+
decisions: resolve(args.cwd, cfg.data.decisions),
|
|
97
|
+
...(cfg.data.meetings ? { meetings: resolve(args.cwd, cfg.data.meetings) } : {}),
|
|
98
|
+
...(cfg.data.agendas ? { agendas: resolve(args.cwd, cfg.data.agendas) } : {}),
|
|
99
|
+
...(cfg.data.minutes ? { minutes: resolve(args.cwd, cfg.data.minutes) } : {}),
|
|
100
|
+
...(cfg.data.committee ? { committee: resolve(args.cwd, cfg.data.committee) } : {}),
|
|
101
|
+
},
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function runValidate(args: ParsedArgs): Promise<void> {
|
|
106
|
+
const cfg = await loadValidatedConfig(args)
|
|
107
|
+
const loaded = await loadAll(cfg.data)
|
|
108
|
+
if (!loaded.ok) {
|
|
109
|
+
throw new EdoxenBrowserError(
|
|
110
|
+
'load',
|
|
111
|
+
'Failed to load one or more data sources',
|
|
112
|
+
loaded.error.map((e) => `${e.source} (${e.path}): ${e.cause.message}`),
|
|
113
|
+
)
|
|
114
|
+
}
|
|
115
|
+
const report = await validateAll(loaded.value)
|
|
116
|
+
if (!report.valid) {
|
|
117
|
+
const details: string[] = []
|
|
118
|
+
if (report.decisions) details.push(...report.decisions.errors.map((e) => e.message))
|
|
119
|
+
if (report.meetings) details.push(...report.meetings.errors.map((e) => e.message))
|
|
120
|
+
throw new EdoxenBrowserError('validate', 'Schema validation failed', details)
|
|
121
|
+
}
|
|
122
|
+
process.stdout.write(`OK — schema validation passed for ${cfg.data.decisions}\n`)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async function runLint(args: ParsedArgs): Promise<void> {
|
|
126
|
+
const cfg = await loadValidatedConfig(args)
|
|
127
|
+
const loaded = await loadAll(cfg.data)
|
|
128
|
+
if (!loaded.ok) {
|
|
129
|
+
throw new EdoxenBrowserError(
|
|
130
|
+
'load',
|
|
131
|
+
'Failed to load data',
|
|
132
|
+
loaded.error.map((e) => `${e.source}: ${e.cause.message}`),
|
|
133
|
+
)
|
|
134
|
+
}
|
|
135
|
+
const project = buildProjectFromLoaded(loaded.value)
|
|
136
|
+
const report = lintProject(project)
|
|
137
|
+
if (report.findings.length === 0) {
|
|
138
|
+
process.stdout.write('OK — no lint findings\n')
|
|
139
|
+
return
|
|
140
|
+
}
|
|
141
|
+
for (const finding of report.findings) {
|
|
142
|
+
const prefix = finding.severity === 'error' ? 'ERROR' : 'WARN'
|
|
143
|
+
process.stdout.write(`${prefix} [${finding.code}] ${finding.message}\n`)
|
|
144
|
+
}
|
|
145
|
+
const hasErrors = args.strict
|
|
146
|
+
? report.findings.length > 0
|
|
147
|
+
: report.findings.some((f) => f.severity === 'error')
|
|
148
|
+
if (hasErrors) {
|
|
149
|
+
throw new EdoxenBrowserError('lint', `Lint reported ${report.findings.length} finding(s)`)
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async function runCheck(args: ParsedArgs): Promise<void> {
|
|
154
|
+
await runValidate(args)
|
|
155
|
+
await runLint(args)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
async function runConfig(args: ParsedArgs): Promise<void> {
|
|
159
|
+
const cfg = await loadValidatedConfig(args)
|
|
160
|
+
process.stdout.write(`${JSON.stringify(cfg, null, 2)}\n`)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async function runBuild(args: ParsedArgs): Promise<void> {
|
|
164
|
+
await runValidate(args)
|
|
165
|
+
const { spawnSync } = await import('node:child_process')
|
|
166
|
+
const result = spawnSync('astro', ['build'], { stdio: 'inherit', cwd: args.cwd })
|
|
167
|
+
if (result.status !== 0) {
|
|
168
|
+
throw new EdoxenBrowserError('build', `astro build exited with status ${result.status ?? 'null'}`)
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async function main(): Promise<void> {
|
|
173
|
+
const args = parseCliArgs(process.argv)
|
|
174
|
+
switch (args.command) {
|
|
175
|
+
case 'validate': return await runValidate(args)
|
|
176
|
+
case 'lint': return await runLint(args)
|
|
177
|
+
case 'check': return await runCheck(args)
|
|
178
|
+
case 'config': return await runConfig(args)
|
|
179
|
+
case 'build': return await runBuild(args)
|
|
180
|
+
default:
|
|
181
|
+
process.stderr.write(`Unknown command: ${args.command}\n\n${HELP}`)
|
|
182
|
+
process.exit(1)
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
main().catch((e: unknown) => {
|
|
187
|
+
if (e instanceof EdoxenBrowserError) {
|
|
188
|
+
process.stderr.write(`${e.toString()}\n`)
|
|
189
|
+
process.exit(1)
|
|
190
|
+
}
|
|
191
|
+
process.stderr.write(`Unexpected error: ${e instanceof Error ? e.stack : String(e)}\n`)
|
|
192
|
+
process.exit(1)
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
export const _cliExtAccept = extname
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export {
|
|
2
|
+
EdoxenConfigSchema,
|
|
3
|
+
SiteSchema,
|
|
4
|
+
DataSchema,
|
|
5
|
+
OutputSchema,
|
|
6
|
+
BodySchema,
|
|
7
|
+
LocaleEntrySchema,
|
|
8
|
+
ThemeSchema,
|
|
9
|
+
NavItemSchema,
|
|
10
|
+
SocialItemSchema,
|
|
11
|
+
FeaturesSchema,
|
|
12
|
+
defineConfig,
|
|
13
|
+
type EdoxenConfig,
|
|
14
|
+
type EdoxenConfigInput,
|
|
15
|
+
type SiteConfig,
|
|
16
|
+
type DataConfig,
|
|
17
|
+
type OutputConfig,
|
|
18
|
+
type BodyEntry,
|
|
19
|
+
type LocaleEntry,
|
|
20
|
+
type ThemeConfig,
|
|
21
|
+
type NavItem,
|
|
22
|
+
type SocialItem,
|
|
23
|
+
type FeaturesConfig,
|
|
24
|
+
} from './schema.js'
|
|
25
|
+
|
|
26
|
+
export {
|
|
27
|
+
resolveDataPaths,
|
|
28
|
+
activeDataKeys,
|
|
29
|
+
type ResolvedDataPaths,
|
|
30
|
+
type DataPathKey,
|
|
31
|
+
} from './paths.js'
|
|
32
|
+
|
|
33
|
+
export { generateCssTokens } from './tokens.js'
|