@vit-foundation/ui 0.24.2 → 0.26.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 +11 -11
- package/dist/community.d.ts +12 -3
- package/dist/community.js +7 -3
- package/dist/components/pages/GetInvolvedPage.svelte +139 -0
- package/dist/components/pages/GetInvolvedPage.svelte.d.ts +36 -0
- package/dist/components/pages/HomePage.svelte +256 -0
- package/dist/components/pages/HomePage.svelte.d.ts +36 -0
- package/dist/components/pages/LegalPage.svelte +39 -0
- package/dist/components/pages/LegalPage.svelte.d.ts +14 -0
- package/dist/components/pages/ProjectPage.svelte +122 -0
- package/dist/components/pages/ProjectPage.svelte.d.ts +15 -0
- package/dist/components/pages/TransparencyPage.svelte +164 -0
- package/dist/components/pages/TransparencyPage.svelte.d.ts +40 -0
- package/dist/components/pages/WeekliesPage.svelte +184 -0
- package/dist/components/pages/WeekliesPage.svelte.d.ts +46 -0
- package/dist/components/pages/WeeklyPage.svelte +198 -0
- package/dist/components/pages/WeeklyPage.svelte.d.ts +33 -0
- package/dist/components/pages/WhatWeDoPage.svelte +107 -0
- package/dist/components/pages/WhatWeDoPage.svelte.d.ts +27 -0
- package/dist/components/pages/WhoWeArePage.svelte +108 -0
- package/dist/components/pages/WhoWeArePage.svelte.d.ts +32 -0
- package/dist/components/ui/Modal.svelte +32 -11
- package/dist/components/ui/Modal.svelte.d.ts +17 -1
- package/dist/config/context.d.ts +0 -6
- package/dist/config/context.js +1 -1
- package/dist/config/index.d.ts +2 -2
- package/dist/config/index.js +1 -1
- package/dist/config/messages.js +22 -0
- package/dist/config/types.d.ts +36 -0
- package/dist/content/pages.d.ts +64 -0
- package/dist/content/pages.js +71 -0
- package/dist/content/types.d.ts +45 -0
- package/dist/content/types.js +0 -5
- package/dist/content-components.d.ts +22 -1
- package/dist/content-components.js +17 -1
- package/dist/contract.d.ts +11 -4
- package/dist/contract.js +11 -3
- package/dist/edit/Editable.svelte +17 -161
- package/dist/edit/Editable.svelte.d.ts +2 -50
- package/dist/edit/chrome/AddSlot.svelte +11 -78
- package/dist/edit/chrome/AddSlot.svelte.d.ts +2 -15
- package/dist/edit/chrome/EditFrame.svelte +15 -158
- package/dist/edit/chrome/EditFrame.svelte.d.ts +2 -31
- package/dist/edit/chrome/EditPanel.svelte +14 -22
- package/dist/edit/chrome/EditPanel.svelte.d.ts +2 -13
- package/dist/edit/chrome/LinkEdit.svelte +13 -277
- package/dist/edit/chrome/LinkEdit.svelte.d.ts +2 -48
- package/dist/edit/chrome-props.d.ts +96 -0
- package/dist/edit/chrome-props.js +1 -0
- package/dist/edit/context.d.ts +38 -5
- package/dist/edit/context.js +18 -7
- package/dist/edit/index.d.ts +6 -4
- package/dist/edit/index.js +4 -3
- package/dist/edit/live/AddSlot.svelte +83 -0
- package/dist/edit/live/AddSlot.svelte.d.ts +4 -0
- package/dist/edit/live/EditFrame.svelte +153 -0
- package/dist/edit/live/EditFrame.svelte.d.ts +4 -0
- package/dist/edit/live/EditPanel.svelte +25 -0
- package/dist/edit/live/EditPanel.svelte.d.ts +4 -0
- package/dist/edit/live/Editable.svelte +162 -0
- package/dist/edit/live/Editable.svelte.d.ts +4 -0
- package/dist/edit/live/LinkEdit.svelte +276 -0
- package/dist/edit/live/LinkEdit.svelte.d.ts +4 -0
- package/dist/edit/{chrome → live}/PropertyRow.svelte +1 -1
- package/dist/edit/{commit.svelte.d.ts → live/commit.svelte.d.ts} +2 -2
- package/dist/edit/live/index.d.ts +10 -0
- package/dist/edit/live/index.js +14 -0
- package/package.json +1 -1
- package/dist/components/account/index.d.ts +0 -3
- package/dist/components/account/index.js +0 -2
- package/dist/components/auth/index.d.ts +0 -7
- package/dist/components/auth/index.js +0 -4
- package/dist/components/contact/index.d.ts +0 -2
- package/dist/components/contact/index.js +0 -1
- package/dist/content/index.d.ts +0 -4
- package/dist/content/index.js +0 -2
- /package/dist/edit/{chrome → live}/ConfirmDialog.svelte +0 -0
- /package/dist/edit/{chrome → live}/ConfirmDialog.svelte.d.ts +0 -0
- /package/dist/edit/{chrome → live}/EditPopover.svelte +0 -0
- /package/dist/edit/{chrome → live}/EditPopover.svelte.d.ts +0 -0
- /package/dist/edit/{chrome → live}/PropertyRow.svelte.d.ts +0 -0
- /package/dist/edit/{commit.svelte.js → live/commit.svelte.js} +0 -0
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { getUiConfig } from '../../config/context.js';
|
|
3
|
+
import type {
|
|
4
|
+
ArticleEdit,
|
|
5
|
+
CommentThreadData,
|
|
6
|
+
ReactionSummary,
|
|
7
|
+
WeeklyArticleData,
|
|
8
|
+
WeeklyCardData
|
|
9
|
+
} from '../../content/types.js';
|
|
10
|
+
import ActionLabel from '../../edit/ActionLabel.svelte';
|
|
11
|
+
import Editable from '../../edit/Editable.svelte';
|
|
12
|
+
import PageShell from '../layout/PageShell.svelte';
|
|
13
|
+
import DateText from '../ui/DateText.svelte';
|
|
14
|
+
import Link from '../ui/Link.svelte';
|
|
15
|
+
import RichText from '../ui/RichText.svelte';
|
|
16
|
+
import ShareRow from '../ui/ShareRow.svelte';
|
|
17
|
+
import CommentSection, { type CommentFormInstance } from '../weeklies/CommentSection.svelte';
|
|
18
|
+
import ReactionBar, { type ReactionBarForms } from '../weeklies/ReactionBar.svelte';
|
|
19
|
+
import WeeklieCard from '../weeklies/WeeklieCard.svelte';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* A weekly's own page: number and date, title, excerpt, the square hero,
|
|
23
|
+
* the body, Instagram and sources, share, reactions, comments, and the
|
|
24
|
+
* related weeklies. The community forms arrive preflighted by the host —
|
|
25
|
+
* the website's remote forms, a CMS mirror's inert mocks — and
|
|
26
|
+
* `isLoggedIn` is the host's auth answer; those are the props the hosts
|
|
27
|
+
* fill differently. `related` is its own prop because vit-brain loads it
|
|
28
|
+
* apart from the weekly, where the website nests it.
|
|
29
|
+
*/
|
|
30
|
+
interface Props {
|
|
31
|
+
weekly: WeeklyArticleData;
|
|
32
|
+
related: WeeklyCardData[];
|
|
33
|
+
comments: CommentThreadData[];
|
|
34
|
+
reactions: ReactionSummary[];
|
|
35
|
+
isLoggedIn: boolean;
|
|
36
|
+
/** The main comment box, preflighted; see CommentSection. */
|
|
37
|
+
commentForm: CommentFormInstance;
|
|
38
|
+
/** One preflighted instance per reply box; see CommentSection. */
|
|
39
|
+
replyFormFor: (threadId: string) => CommentFormInstance;
|
|
40
|
+
reactionForms: ReactionBarForms;
|
|
41
|
+
/**
|
|
42
|
+
* What a CMS may open on a weekly's own page: title, excerpt, and the body
|
|
43
|
+
* as rich text. The «Dades i fonts» and «Segueix explorant» headings and
|
|
44
|
+
* the back link edit through `config.messageEdit` over their own keys.
|
|
45
|
+
*/
|
|
46
|
+
edit?: ArticleEdit;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
let {
|
|
50
|
+
weekly,
|
|
51
|
+
related,
|
|
52
|
+
comments,
|
|
53
|
+
reactions,
|
|
54
|
+
isLoggedIn,
|
|
55
|
+
commentForm,
|
|
56
|
+
replyFormFor,
|
|
57
|
+
reactionForms,
|
|
58
|
+
edit
|
|
59
|
+
}: Props = $props();
|
|
60
|
+
|
|
61
|
+
const config = getUiConfig();
|
|
62
|
+
const msg = $derived(config.messages);
|
|
63
|
+
</script>
|
|
64
|
+
|
|
65
|
+
<PageShell title={weekly.title} description={weekly.excerpt} variant="reading">
|
|
66
|
+
<p class="back">
|
|
67
|
+
<ActionLabel edit={config.messageEdit?.('back_label')} value={msg.back_label()}>
|
|
68
|
+
{#snippet control()}
|
|
69
|
+
<Link href="/weeklies">← {msg.back_label()}</Link>
|
|
70
|
+
{/snippet}
|
|
71
|
+
</ActionLabel>
|
|
72
|
+
</p>
|
|
73
|
+
|
|
74
|
+
<header>
|
|
75
|
+
<p class="meta">
|
|
76
|
+
<span class="number">{msg.weeklie_number({ number: weekly.number })}</span>
|
|
77
|
+
<DateText value={weekly.publishedOn} />
|
|
78
|
+
</p>
|
|
79
|
+
<Editable edit={edit?.title} value={weekly.title}>
|
|
80
|
+
{#snippet children(text, attrs)}<h1 {...attrs}>{text}</h1>{/snippet}
|
|
81
|
+
</Editable>
|
|
82
|
+
<Editable edit={edit?.excerpt} value={weekly.excerpt}>
|
|
83
|
+
{#snippet children(text, attrs)}<p class="excerpt" {...attrs}>{text}</p>{/snippet}
|
|
84
|
+
</Editable>
|
|
85
|
+
</header>
|
|
86
|
+
|
|
87
|
+
<img class="hero" src={weekly.imageUrl} alt="" width="600" height="600" />
|
|
88
|
+
|
|
89
|
+
{#if weekly.body}
|
|
90
|
+
<RichText body={weekly.body} edit={edit?.body} />
|
|
91
|
+
{/if}
|
|
92
|
+
|
|
93
|
+
{#if weekly.instagramUrl}
|
|
94
|
+
<p><a href={weekly.instagramUrl} rel="external noopener">Instagram ↗</a></p>
|
|
95
|
+
{/if}
|
|
96
|
+
|
|
97
|
+
{#if weekly.sources.length > 0}
|
|
98
|
+
<section aria-labelledby="sources-heading">
|
|
99
|
+
<Editable edit={config.messageEdit?.('weeklie_sources')} value={msg.weeklie_sources()}>
|
|
100
|
+
{#snippet children(text, attrs)}
|
|
101
|
+
<h2 class="subsection-heading" id="sources-heading" {...attrs}>{text}</h2>
|
|
102
|
+
{/snippet}
|
|
103
|
+
</Editable>
|
|
104
|
+
<ul>
|
|
105
|
+
{#each weekly.sources as source (source.url)}
|
|
106
|
+
<li><a href={source.url} rel="external noopener">{source.label}</a></li>
|
|
107
|
+
{/each}
|
|
108
|
+
</ul>
|
|
109
|
+
</section>
|
|
110
|
+
{/if}
|
|
111
|
+
|
|
112
|
+
<ShareRow title={weekly.title} />
|
|
113
|
+
|
|
114
|
+
<ReactionBar
|
|
115
|
+
{reactions}
|
|
116
|
+
target={{ kind: 'weekly', slug: weekly.slug }}
|
|
117
|
+
{isLoggedIn}
|
|
118
|
+
forms={reactionForms}
|
|
119
|
+
/>
|
|
120
|
+
|
|
121
|
+
<CommentSection
|
|
122
|
+
{comments}
|
|
123
|
+
weeklySlug={weekly.slug}
|
|
124
|
+
{isLoggedIn}
|
|
125
|
+
{commentForm}
|
|
126
|
+
{replyFormFor}
|
|
127
|
+
{reactionForms}
|
|
128
|
+
/>
|
|
129
|
+
|
|
130
|
+
{#if related.length > 0}
|
|
131
|
+
<section aria-labelledby="related-heading">
|
|
132
|
+
<Editable
|
|
133
|
+
edit={config.messageEdit?.('weeklie_keepExploring')}
|
|
134
|
+
value={msg.weeklie_keepExploring()}
|
|
135
|
+
>
|
|
136
|
+
{#snippet children(text, attrs)}
|
|
137
|
+
<h2 class="subsection-heading" id="related-heading" {...attrs}>{text}</h2>
|
|
138
|
+
{/snippet}
|
|
139
|
+
</Editable>
|
|
140
|
+
<div class="related">
|
|
141
|
+
{#each related as card (card.slug)}
|
|
142
|
+
<WeeklieCard weekly={card} />
|
|
143
|
+
{/each}
|
|
144
|
+
</div>
|
|
145
|
+
</section>
|
|
146
|
+
{/if}
|
|
147
|
+
</PageShell>
|
|
148
|
+
|
|
149
|
+
<style>
|
|
150
|
+
.back :global(a) {
|
|
151
|
+
text-decoration: none;
|
|
152
|
+
color: var(--color-ink-secondary);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.meta {
|
|
156
|
+
display: flex;
|
|
157
|
+
gap: var(--space-3);
|
|
158
|
+
color: var(--color-ink-muted);
|
|
159
|
+
font-size: var(--text-sm);
|
|
160
|
+
margin: 0;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.number {
|
|
164
|
+
font-weight: 700;
|
|
165
|
+
color: var(--color-brand);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
h1 {
|
|
169
|
+
margin: var(--space-2) 0;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.excerpt {
|
|
173
|
+
font-size: var(--text-lg);
|
|
174
|
+
color: var(--color-ink-secondary);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.hero {
|
|
178
|
+
border-radius: var(--radius-lg);
|
|
179
|
+
margin-block: var(--space-4);
|
|
180
|
+
background: var(--color-band-grey);
|
|
181
|
+
width: 100%;
|
|
182
|
+
max-width: 32rem;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
ul {
|
|
186
|
+
padding-left: var(--space-4);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
a {
|
|
190
|
+
color: var(--color-brand);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
.related {
|
|
194
|
+
display: grid;
|
|
195
|
+
grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr));
|
|
196
|
+
gap: var(--space-4);
|
|
197
|
+
}
|
|
198
|
+
</style>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { ArticleEdit, CommentThreadData, ReactionSummary, WeeklyArticleData, WeeklyCardData } from '../../content/types.js';
|
|
2
|
+
import { type CommentFormInstance } from '../weeklies/CommentSection.svelte';
|
|
3
|
+
import { type ReactionBarForms } from '../weeklies/ReactionBar.svelte';
|
|
4
|
+
/**
|
|
5
|
+
* A weekly's own page: number and date, title, excerpt, the square hero,
|
|
6
|
+
* the body, Instagram and sources, share, reactions, comments, and the
|
|
7
|
+
* related weeklies. The community forms arrive preflighted by the host —
|
|
8
|
+
* the website's remote forms, a CMS mirror's inert mocks — and
|
|
9
|
+
* `isLoggedIn` is the host's auth answer; those are the props the hosts
|
|
10
|
+
* fill differently. `related` is its own prop because vit-brain loads it
|
|
11
|
+
* apart from the weekly, where the website nests it.
|
|
12
|
+
*/
|
|
13
|
+
interface Props {
|
|
14
|
+
weekly: WeeklyArticleData;
|
|
15
|
+
related: WeeklyCardData[];
|
|
16
|
+
comments: CommentThreadData[];
|
|
17
|
+
reactions: ReactionSummary[];
|
|
18
|
+
isLoggedIn: boolean;
|
|
19
|
+
/** The main comment box, preflighted; see CommentSection. */
|
|
20
|
+
commentForm: CommentFormInstance;
|
|
21
|
+
/** One preflighted instance per reply box; see CommentSection. */
|
|
22
|
+
replyFormFor: (threadId: string) => CommentFormInstance;
|
|
23
|
+
reactionForms: ReactionBarForms;
|
|
24
|
+
/**
|
|
25
|
+
* What a CMS may open on a weekly's own page: title, excerpt, and the body
|
|
26
|
+
* as rich text. The «Dades i fonts» and «Segueix explorant» headings and
|
|
27
|
+
* the back link edit through `config.messageEdit` over their own keys.
|
|
28
|
+
*/
|
|
29
|
+
edit?: ArticleEdit;
|
|
30
|
+
}
|
|
31
|
+
declare const WeeklyPage: import("svelte").Component<Props, {}, "">;
|
|
32
|
+
type WeeklyPage = ReturnType<typeof WeeklyPage>;
|
|
33
|
+
export default WeeklyPage;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
<script module lang="ts">
|
|
2
|
+
import type { CopyEditFor } from '../../content/pages.js';
|
|
3
|
+
import type { ProjectCardData } from '../../content/types.js';
|
|
4
|
+
import type { ProjectEditMap } from '../projects/ProjectCard.svelte';
|
|
5
|
+
|
|
6
|
+
/** What a CMS may open on /what-we-do; `projectFor` answers per card by its `id`. */
|
|
7
|
+
export interface WhatWeDoPageEdit {
|
|
8
|
+
copy?: CopyEditFor<'what-we-do'>;
|
|
9
|
+
projectFor?: (project: ProjectCardData) => ProjectEditMap | undefined;
|
|
10
|
+
}
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<script lang="ts">
|
|
14
|
+
import { getUiConfig } from '../../config/context.js';
|
|
15
|
+
import type { PageCopy } from '../../content/pages.js';
|
|
16
|
+
import Editable from '../../edit/Editable.svelte';
|
|
17
|
+
import PageShell from '../layout/PageShell.svelte';
|
|
18
|
+
import ProjectCard from '../projects/ProjectCard.svelte';
|
|
19
|
+
import CopyIntro from '../ui/CopyIntro.svelte';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The projects page: a showcase of the latest project, the collaborations
|
|
23
|
+
* as wide rows, the passion projects as a grid. The three groups are the
|
|
24
|
+
* website's load's (`getLatest`, one `list(kind)` each); a host that
|
|
25
|
+
* groups differently (vit-brain's `projectSections`) answers the same
|
|
26
|
+
* three props.
|
|
27
|
+
*/
|
|
28
|
+
interface Props {
|
|
29
|
+
content: PageCopy<'what-we-do'>;
|
|
30
|
+
/** The showcase slot; `null`/absent renders no «Últim projecte» band. */
|
|
31
|
+
latest?: ProjectCardData | null;
|
|
32
|
+
collaborations: ProjectCardData[];
|
|
33
|
+
passion: ProjectCardData[];
|
|
34
|
+
edit?: WhatWeDoPageEdit;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
let { content, latest = null, collaborations, passion, edit }: Props = $props();
|
|
38
|
+
|
|
39
|
+
const config = getUiConfig();
|
|
40
|
+
const msg = $derived(config.messages);
|
|
41
|
+
</script>
|
|
42
|
+
|
|
43
|
+
<PageShell title={msg.nav_whatWeDo()}>
|
|
44
|
+
<Editable edit={config.messageEdit?.('nav_whatWeDo')} value={msg.nav_whatWeDo()}>
|
|
45
|
+
{#snippet children(text, attrs)}<h1 {...attrs}>{text}</h1>{/snippet}
|
|
46
|
+
</Editable>
|
|
47
|
+
|
|
48
|
+
{#if latest}
|
|
49
|
+
<section class="latest" aria-labelledby="latest-heading">
|
|
50
|
+
<Editable edit={edit?.copy?.('latest_heading')} value={content.latest_heading}>
|
|
51
|
+
{#snippet children(text, attrs)}
|
|
52
|
+
<h2 class="section-heading" id="latest-heading" {...attrs}>{text}</h2>
|
|
53
|
+
{/snippet}
|
|
54
|
+
</Editable>
|
|
55
|
+
<ProjectCard project={latest} variant="wide" edit={edit?.projectFor?.(latest)} />
|
|
56
|
+
</section>
|
|
57
|
+
{/if}
|
|
58
|
+
|
|
59
|
+
<section aria-labelledby="collaborations-heading">
|
|
60
|
+
<Editable edit={edit?.copy?.('collaborations_heading')} value={content.collaborations_heading}>
|
|
61
|
+
{#snippet children(text, attrs)}
|
|
62
|
+
<h2 class="section-heading" id="collaborations-heading" {...attrs}>
|
|
63
|
+
{text}
|
|
64
|
+
</h2>
|
|
65
|
+
{/snippet}
|
|
66
|
+
</Editable>
|
|
67
|
+
<CopyIntro text={content.collaborations_intro} edit={edit?.copy?.('collaborations_intro')} />
|
|
68
|
+
<div class="stack">
|
|
69
|
+
{#each collaborations as project (project.slug)}
|
|
70
|
+
<ProjectCard {project} variant="wide" edit={edit?.projectFor?.(project)} />
|
|
71
|
+
{/each}
|
|
72
|
+
</div>
|
|
73
|
+
</section>
|
|
74
|
+
|
|
75
|
+
<section aria-labelledby="passion-heading">
|
|
76
|
+
<Editable edit={edit?.copy?.('passion_heading')} value={content.passion_heading}>
|
|
77
|
+
{#snippet children(text, attrs)}
|
|
78
|
+
<h2 class="section-heading" id="passion-heading" {...attrs}>{text}</h2>
|
|
79
|
+
{/snippet}
|
|
80
|
+
</Editable>
|
|
81
|
+
<CopyIntro text={content.passion_intro} edit={edit?.copy?.('passion_intro')} />
|
|
82
|
+
<div class="grid">
|
|
83
|
+
{#each passion as project (project.slug)}
|
|
84
|
+
<ProjectCard {project} variant="grid" edit={edit?.projectFor?.(project)} />
|
|
85
|
+
{/each}
|
|
86
|
+
</div>
|
|
87
|
+
</section>
|
|
88
|
+
</PageShell>
|
|
89
|
+
|
|
90
|
+
<style>
|
|
91
|
+
.latest {
|
|
92
|
+
padding-block-end: var(--space-4);
|
|
93
|
+
border-bottom: 1px solid var(--color-hairline);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.stack {
|
|
97
|
+
display: flex;
|
|
98
|
+
flex-direction: column;
|
|
99
|
+
gap: var(--space-5);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.grid {
|
|
103
|
+
display: grid;
|
|
104
|
+
grid-template-columns: repeat(auto-fill, minmax(18rem, 1fr));
|
|
105
|
+
gap: var(--space-5) var(--space-4);
|
|
106
|
+
}
|
|
107
|
+
</style>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { CopyEditFor } from '../../content/pages.js';
|
|
2
|
+
import type { ProjectCardData } from '../../content/types.js';
|
|
3
|
+
import type { ProjectEditMap } from '../projects/ProjectCard.svelte';
|
|
4
|
+
/** What a CMS may open on /what-we-do; `projectFor` answers per card by its `id`. */
|
|
5
|
+
export interface WhatWeDoPageEdit {
|
|
6
|
+
copy?: CopyEditFor<'what-we-do'>;
|
|
7
|
+
projectFor?: (project: ProjectCardData) => ProjectEditMap | undefined;
|
|
8
|
+
}
|
|
9
|
+
import type { PageCopy } from '../../content/pages.js';
|
|
10
|
+
/**
|
|
11
|
+
* The projects page: a showcase of the latest project, the collaborations
|
|
12
|
+
* as wide rows, the passion projects as a grid. The three groups are the
|
|
13
|
+
* website's load's (`getLatest`, one `list(kind)` each); a host that
|
|
14
|
+
* groups differently (vit-brain's `projectSections`) answers the same
|
|
15
|
+
* three props.
|
|
16
|
+
*/
|
|
17
|
+
interface Props {
|
|
18
|
+
content: PageCopy<'what-we-do'>;
|
|
19
|
+
/** The showcase slot; `null`/absent renders no «Últim projecte» band. */
|
|
20
|
+
latest?: ProjectCardData | null;
|
|
21
|
+
collaborations: ProjectCardData[];
|
|
22
|
+
passion: ProjectCardData[];
|
|
23
|
+
edit?: WhatWeDoPageEdit;
|
|
24
|
+
}
|
|
25
|
+
declare const WhatWeDoPage: import("svelte").Component<Props, {}, "">;
|
|
26
|
+
type WhatWeDoPage = ReturnType<typeof WhatWeDoPage>;
|
|
27
|
+
export default WhatWeDoPage;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
<script module lang="ts">
|
|
2
|
+
import type { CopyEditFor } from '../../content/pages.js';
|
|
3
|
+
import type { CollaboratorData, TeamMemberData } from '../../content/types.js';
|
|
4
|
+
import type { CollectionRef } from '../../edit/types.js';
|
|
5
|
+
import type { CollaboratorEditMap } from '../team/CollaboratorList.svelte';
|
|
6
|
+
import type { TeamMemberEditMap } from '../team/TeamMemberCard.svelte';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* What a CMS may open on /who-we-are. `memberFor` answers per row, so a
|
|
10
|
+
* host reads the identity it put on `TeamMemberData.id`; `collaborators`
|
|
11
|
+
* names the list's collection, which turns on its add slot and removes.
|
|
12
|
+
*/
|
|
13
|
+
export interface WhoWeArePageEdit {
|
|
14
|
+
copy?: CopyEditFor<'who-we-are'>;
|
|
15
|
+
memberFor?: (member: TeamMemberData) => TeamMemberEditMap | undefined;
|
|
16
|
+
collaboratorFor?: (collaborator: CollaboratorData) => CollaboratorEditMap | undefined;
|
|
17
|
+
collaborators?: CollectionRef;
|
|
18
|
+
}
|
|
19
|
+
</script>
|
|
20
|
+
|
|
21
|
+
<script lang="ts">
|
|
22
|
+
import { getUiConfig } from '../../config/context.js';
|
|
23
|
+
import type { PageCopy } from '../../content/pages.js';
|
|
24
|
+
import Editable from '../../edit/Editable.svelte';
|
|
25
|
+
import PageShell from '../layout/PageShell.svelte';
|
|
26
|
+
import CollaboratorList from '../team/CollaboratorList.svelte';
|
|
27
|
+
import TeamMemberCard from '../team/TeamMemberCard.svelte';
|
|
28
|
+
import CopyIntro from '../ui/CopyIntro.svelte';
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The team page: the featured members, the board, the collaborators. The
|
|
32
|
+
* split into `featured` and `board` is the website's load's (one `isBoard`
|
|
33
|
+
* filter each), so the module takes the two lists rather than re-deciding.
|
|
34
|
+
*/
|
|
35
|
+
interface Props {
|
|
36
|
+
content: PageCopy<'who-we-are'>;
|
|
37
|
+
featured: TeamMemberData[];
|
|
38
|
+
board: TeamMemberData[];
|
|
39
|
+
collaborators: CollaboratorData[];
|
|
40
|
+
edit?: WhoWeArePageEdit;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
let { content, featured, board, collaborators, edit }: Props = $props();
|
|
44
|
+
|
|
45
|
+
const config = getUiConfig();
|
|
46
|
+
const msg = $derived(config.messages);
|
|
47
|
+
</script>
|
|
48
|
+
|
|
49
|
+
<PageShell title={msg.nav_whoWeAre()}>
|
|
50
|
+
<Editable edit={config.messageEdit?.('nav_whoWeAre')} value={msg.nav_whoWeAre()}>
|
|
51
|
+
{#snippet children(text, attrs)}<h1 {...attrs}>{text}</h1>{/snippet}
|
|
52
|
+
</Editable>
|
|
53
|
+
|
|
54
|
+
<section aria-labelledby="team-heading">
|
|
55
|
+
<Editable edit={edit?.copy?.('team_heading')} value={content.team_heading}>
|
|
56
|
+
{#snippet children(text, attrs)}
|
|
57
|
+
<h2 class="section-heading" id="team-heading" {...attrs}>{text}</h2>
|
|
58
|
+
{/snippet}
|
|
59
|
+
</Editable>
|
|
60
|
+
<CopyIntro text={content.team_intro} edit={edit?.copy?.('team_intro')} />
|
|
61
|
+
<div class="featured">
|
|
62
|
+
{#each featured as member (member.slug)}
|
|
63
|
+
<TeamMemberCard {member} variant="featured" edit={edit?.memberFor?.(member)} />
|
|
64
|
+
{/each}
|
|
65
|
+
</div>
|
|
66
|
+
</section>
|
|
67
|
+
|
|
68
|
+
<section aria-labelledby="board-heading">
|
|
69
|
+
<Editable edit={edit?.copy?.('board_heading')} value={content.board_heading}>
|
|
70
|
+
{#snippet children(text, attrs)}
|
|
71
|
+
<h2 class="section-heading" id="board-heading" {...attrs}>{text}</h2>
|
|
72
|
+
{/snippet}
|
|
73
|
+
</Editable>
|
|
74
|
+
<CopyIntro text={content.board_intro} edit={edit?.copy?.('board_intro')} />
|
|
75
|
+
<div class="board">
|
|
76
|
+
{#each board as member (member.slug)}
|
|
77
|
+
<TeamMemberCard {member} variant="board" edit={edit?.memberFor?.(member)} />
|
|
78
|
+
{/each}
|
|
79
|
+
</div>
|
|
80
|
+
</section>
|
|
81
|
+
|
|
82
|
+
<section aria-labelledby="collaborators-heading">
|
|
83
|
+
<Editable edit={edit?.copy?.('collaborators_heading')} value={content.collaborators_heading}>
|
|
84
|
+
{#snippet children(text, attrs)}
|
|
85
|
+
<h2 class="section-heading" id="collaborators-heading" {...attrs}>{text}</h2>
|
|
86
|
+
{/snippet}
|
|
87
|
+
</Editable>
|
|
88
|
+
<CollaboratorList
|
|
89
|
+
{collaborators}
|
|
90
|
+
editFor={edit?.collaboratorFor}
|
|
91
|
+
collection={edit?.collaborators}
|
|
92
|
+
/>
|
|
93
|
+
</section>
|
|
94
|
+
</PageShell>
|
|
95
|
+
|
|
96
|
+
<style>
|
|
97
|
+
.featured {
|
|
98
|
+
display: grid;
|
|
99
|
+
grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
|
|
100
|
+
gap: var(--space-4);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.board {
|
|
104
|
+
display: grid;
|
|
105
|
+
grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr));
|
|
106
|
+
gap: var(--space-4);
|
|
107
|
+
}
|
|
108
|
+
</style>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { CopyEditFor } from '../../content/pages.js';
|
|
2
|
+
import type { CollaboratorData, TeamMemberData } from '../../content/types.js';
|
|
3
|
+
import type { CollectionRef } from '../../edit/types.js';
|
|
4
|
+
import type { CollaboratorEditMap } from '../team/CollaboratorList.svelte';
|
|
5
|
+
import type { TeamMemberEditMap } from '../team/TeamMemberCard.svelte';
|
|
6
|
+
/**
|
|
7
|
+
* What a CMS may open on /who-we-are. `memberFor` answers per row, so a
|
|
8
|
+
* host reads the identity it put on `TeamMemberData.id`; `collaborators`
|
|
9
|
+
* names the list's collection, which turns on its add slot and removes.
|
|
10
|
+
*/
|
|
11
|
+
export interface WhoWeArePageEdit {
|
|
12
|
+
copy?: CopyEditFor<'who-we-are'>;
|
|
13
|
+
memberFor?: (member: TeamMemberData) => TeamMemberEditMap | undefined;
|
|
14
|
+
collaboratorFor?: (collaborator: CollaboratorData) => CollaboratorEditMap | undefined;
|
|
15
|
+
collaborators?: CollectionRef;
|
|
16
|
+
}
|
|
17
|
+
import type { PageCopy } from '../../content/pages.js';
|
|
18
|
+
/**
|
|
19
|
+
* The team page: the featured members, the board, the collaborators. The
|
|
20
|
+
* split into `featured` and `board` is the website's load's (one `isBoard`
|
|
21
|
+
* filter each), so the module takes the two lists rather than re-deciding.
|
|
22
|
+
*/
|
|
23
|
+
interface Props {
|
|
24
|
+
content: PageCopy<'who-we-are'>;
|
|
25
|
+
featured: TeamMemberData[];
|
|
26
|
+
board: TeamMemberData[];
|
|
27
|
+
collaborators: CollaboratorData[];
|
|
28
|
+
edit?: WhoWeArePageEdit;
|
|
29
|
+
}
|
|
30
|
+
declare const WhoWeArePage: import("svelte").Component<Props, {}, "">;
|
|
31
|
+
type WhoWeArePage = ReturnType<typeof WhoWeArePage>;
|
|
32
|
+
export default WhoWeArePage;
|
|
@@ -1,7 +1,24 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import type
|
|
2
|
+
import { onMount, type Snippet } from 'svelte';
|
|
3
3
|
import IconButton from './IconButton.svelte';
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* A modal over the native <dialog>: showModal() traps focus and layers the
|
|
7
|
+
* backdrop, the cancel event covers Escape, a backdrop click closes.
|
|
8
|
+
*
|
|
9
|
+
* The element is NOT part of the server-rendered document. A <dialog> is
|
|
10
|
+
* flow content, so wherever a caller mounts one inside phrasing content —
|
|
11
|
+
* a LinkEdit inside a <p>, which NewsletterSignup does — the HTML parser
|
|
12
|
+
* closes the paragraph early, Svelte reports `node_invalid_placement_ssr`
|
|
13
|
+
* on the way out and a hydration mismatch on the way back in, on every
|
|
14
|
+
* page that renders the caller. No dialog is open at first paint (opening
|
|
15
|
+
* one takes a click), so the server has nothing to say about it; the
|
|
16
|
+
* element is created after mount, by script, which the content model does
|
|
17
|
+
* not police. Placement is this module's to own — a caller cannot know
|
|
18
|
+
* what its parent is — and owning it here fixes every caller at once,
|
|
19
|
+
* ConfirmDialog included.
|
|
20
|
+
*/
|
|
21
|
+
|
|
5
22
|
interface Props {
|
|
6
23
|
open: boolean;
|
|
7
24
|
title: string;
|
|
@@ -16,9 +33,11 @@
|
|
|
16
33
|
let { open, title, onclose, closeLabel = 'Tanca', children }: Props = $props();
|
|
17
34
|
|
|
18
35
|
let dialog: HTMLDialogElement | undefined = $state();
|
|
36
|
+
let mounted = $state(false);
|
|
37
|
+
onMount(() => {
|
|
38
|
+
mounted = true;
|
|
39
|
+
});
|
|
19
40
|
|
|
20
|
-
// Native <dialog>: showModal() traps focus and layers the backdrop; the
|
|
21
|
-
// cancel event covers Escape.
|
|
22
41
|
$effect(() => {
|
|
23
42
|
if (!dialog) return;
|
|
24
43
|
if (open && !dialog.open) dialog.showModal();
|
|
@@ -32,15 +51,17 @@
|
|
|
32
51
|
}
|
|
33
52
|
</script>
|
|
34
53
|
|
|
35
|
-
|
|
36
|
-
<
|
|
37
|
-
<div class="
|
|
38
|
-
<
|
|
39
|
-
|
|
54
|
+
{#if mounted}
|
|
55
|
+
<dialog bind:this={dialog} oncancel={onclose} {onclick}>
|
|
56
|
+
<div class="panel">
|
|
57
|
+
<div class="top">
|
|
58
|
+
<h2>{title}</h2>
|
|
59
|
+
<IconButton icon="close" label={closeLabel} onclick={onclose} />
|
|
60
|
+
</div>
|
|
61
|
+
{@render children()}
|
|
40
62
|
</div>
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
</dialog>
|
|
63
|
+
</dialog>
|
|
64
|
+
{/if}
|
|
44
65
|
|
|
45
66
|
<style>
|
|
46
67
|
dialog {
|
|
@@ -1,4 +1,20 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type Snippet } from 'svelte';
|
|
2
|
+
/**
|
|
3
|
+
* A modal over the native <dialog>: showModal() traps focus and layers the
|
|
4
|
+
* backdrop, the cancel event covers Escape, a backdrop click closes.
|
|
5
|
+
*
|
|
6
|
+
* The element is NOT part of the server-rendered document. A <dialog> is
|
|
7
|
+
* flow content, so wherever a caller mounts one inside phrasing content —
|
|
8
|
+
* a LinkEdit inside a <p>, which NewsletterSignup does — the HTML parser
|
|
9
|
+
* closes the paragraph early, Svelte reports `node_invalid_placement_ssr`
|
|
10
|
+
* on the way out and a hydration mismatch on the way back in, on every
|
|
11
|
+
* page that renders the caller. No dialog is open at first paint (opening
|
|
12
|
+
* one takes a click), so the server has nothing to say about it; the
|
|
13
|
+
* element is created after mount, by script, which the content model does
|
|
14
|
+
* not police. Placement is this module's to own — a caller cannot know
|
|
15
|
+
* what its parent is — and owning it here fixes every caller at once,
|
|
16
|
+
* ConfirmDialog included.
|
|
17
|
+
*/
|
|
2
18
|
interface Props {
|
|
3
19
|
open: boolean;
|
|
4
20
|
title: string;
|
package/dist/config/context.d.ts
CHANGED
|
@@ -1,10 +1,4 @@
|
|
|
1
1
|
import { type UiConfig, type UiConfigInput } from './types.js';
|
|
2
|
-
/**
|
|
3
|
-
* The provider-less config: identity hrefs, Catalan, no router. Complete on
|
|
4
|
-
* purpose — every component must render without a provider, which is what
|
|
5
|
-
* keeps stories and component tests free of app wiring.
|
|
6
|
-
*/
|
|
7
|
-
export declare const DEFAULT_UI_CONFIG: UiConfig;
|
|
8
2
|
/**
|
|
9
3
|
* Installs the app's config for the subtree. Takes a *thunk* so a provider
|
|
10
4
|
* whose input is a reactive prop stays live: the resolved object delegates
|
package/dist/config/context.js
CHANGED
|
@@ -8,7 +8,7 @@ const KEY = Symbol.for('@vit-foundation/ui:config');
|
|
|
8
8
|
* purpose — every component must render without a provider, which is what
|
|
9
9
|
* keeps stories and component tests free of app wiring.
|
|
10
10
|
*/
|
|
11
|
-
|
|
11
|
+
const DEFAULT_UI_CONFIG = {
|
|
12
12
|
href: (path) => path,
|
|
13
13
|
locale: () => BASE_LOCALE,
|
|
14
14
|
locales: LOCALES,
|
package/dist/config/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { getUiConfig, setUiConfig } from './context.js';
|
|
2
2
|
export { defaultEditMessages } from './edit-messages.js';
|
|
3
3
|
export type { EditMessages } from './edit-messages.js';
|
|
4
4
|
export { defaultMessages } from './messages.js';
|
|
5
5
|
export { default as UiProvider } from './UiProvider.svelte';
|
|
6
6
|
export { BASE_LOCALE, LOCALES } from './types.js';
|
|
7
|
-
export type { Locale, NotParameterized,
|
|
7
|
+
export type { Locale, NotParameterized, ParameterlessKey, SiteLink, UiConfig, UiConfigInput, UiMessages } from './types.js';
|
package/dist/config/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { getUiConfig, setUiConfig } from './context.js';
|
|
2
2
|
export { defaultEditMessages } from './edit-messages.js';
|
|
3
3
|
export { defaultMessages } from './messages.js';
|
|
4
4
|
export { default as UiProvider } from './UiProvider.svelte';
|
package/dist/config/messages.js
CHANGED
|
@@ -105,6 +105,28 @@ export const defaultMessages = {
|
|
|
105
105
|
category_education: () => 'Educació',
|
|
106
106
|
category_collaboration: () => 'Col·laboració',
|
|
107
107
|
category_press: () => 'Premsa',
|
|
108
|
+
nav_whoWeAre: () => 'Qui som',
|
|
109
|
+
nav_whatWeDo: () => 'Què fem',
|
|
110
|
+
nav_getInvolved: () => "Implica-t'hi",
|
|
111
|
+
nav_weeklies: () => 'Weeklies',
|
|
112
|
+
common_seeAll: () => 'Mostra-ho tot',
|
|
113
|
+
common_seeAllHref: () => '/transparency',
|
|
114
|
+
cta_meetTeam: () => "Coneix l'equip",
|
|
115
|
+
cta_meetTeamHref: () => '/who-we-are',
|
|
116
|
+
cta_contactUs: () => "Contacta'ns",
|
|
117
|
+
cta_contactUsHref: () => '/get-involved',
|
|
118
|
+
weeklies_searchPlaceholder: () => 'Cerca un tema…',
|
|
119
|
+
weeklies_exploreOne: () => "…o explora'n un d'aquests",
|
|
120
|
+
weeklies_filterLabel: () => 'Filtra per tema',
|
|
121
|
+
weeklies_searchResultsLabel: () => 'Resultats de la cerca',
|
|
122
|
+
weeklies_loadError: () => "No s'han pogut carregar els resultats. Torna-ho a provar.",
|
|
123
|
+
weeklies_empty: () => 'No hem trobat cap weekly amb aquests criteris.',
|
|
124
|
+
timeline_searchPlaceholder: () => 'Què hem estat fent?',
|
|
125
|
+
timeline_filterLabel: () => 'Filtra per categoria',
|
|
126
|
+
timeline_empty: () => 'No hem trobat cap fita amb aquests criteris.',
|
|
127
|
+
back_label: () => 'Torna enrere',
|
|
128
|
+
weeklie_sources: () => 'Dades i fonts',
|
|
129
|
+
weeklie_keepExploring: () => 'Segueix explorant',
|
|
108
130
|
status_published: () => 'Publicat',
|
|
109
131
|
status_draft: () => 'Esborrany',
|
|
110
132
|
status_open: () => 'Oberta',
|