@vit-foundation/ui 0.14.0 → 0.16.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 +10 -10
- package/dist/components/jobs/JobList.svelte +11 -19
- package/dist/components/jobs/JobList.svelte.d.ts +1 -1
- package/dist/components/layout/Footer.svelte +21 -42
- package/dist/components/layout/Footer.svelte.d.ts +1 -1
- package/dist/components/layout/Nav.svelte +29 -46
- package/dist/components/layout/Nav.svelte.d.ts +4 -3
- package/dist/components/layout/site-link-edit.d.ts +5 -4
- package/dist/components/projects/ProjectCard.svelte +2 -2
- package/dist/components/projects/ProjectCard.svelte.d.ts +1 -1
- package/dist/components/team/CollaboratorList.svelte +10 -16
- package/dist/components/timeline/Timeline.svelte +10 -23
- package/dist/components/timeline/TimelineMilestone.svelte +2 -2
- package/dist/components/timeline/TimelineMilestone.svelte.d.ts +1 -1
- package/dist/components/weeklies/WeeklieCard.svelte +2 -2
- package/dist/components/weeklies/WeeklieCard.svelte.d.ts +1 -1
- package/dist/config/index.d.ts +1 -1
- package/dist/config/messages.js +5 -1
- package/dist/config/types.d.ts +35 -3
- package/dist/content-components.d.ts +5 -1
- package/dist/content-components.js +4 -1
- package/dist/edit/chrome/EditPanel.svelte +2 -2
- package/dist/edit/chrome/EditPanel.svelte.d.ts +2 -2
- package/dist/edit/chrome/LinkEdit.svelte +85 -6
- package/dist/edit/chrome/LinkEdit.svelte.d.ts +15 -3
- package/dist/edit/chrome/PropertyRow.svelte +40 -9
- package/dist/edit/chrome/PropertyRow.svelte.d.ts +6 -2
- package/dist/edit/collection.svelte.d.ts +49 -0
- package/dist/edit/collection.svelte.js +39 -0
- package/dist/edit/helpers.d.ts +5 -5
- package/dist/edit/helpers.js +2 -2
- package/dist/edit/index.d.ts +4 -1
- package/dist/edit/index.js +1 -0
- package/dist/edit/types.d.ts +24 -9
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -2
- package/dist/utils/milestones.d.ts +15 -0
- package/dist/utils/milestones.js +17 -0
- package/dist/utils/paths.d.ts +6 -0
- package/dist/utils/paths.js +14 -0
- package/dist/utils/url-filters.svelte.d.ts +58 -0
- package/dist/utils/url-filters.svelte.js +47 -0
- package/dist/utils/weekly-list.svelte.d.ts +82 -0
- package/dist/utils/weekly-list.svelte.js +120 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -22,16 +22,16 @@ Requires `svelte` ^5.0.0 as a peer. Import the two stylesheets once:
|
|
|
22
22
|
|
|
23
23
|
Everything exports flat from the root, and again grouped by role:
|
|
24
24
|
|
|
25
|
-
| Entry point | What lives there
|
|
26
|
-
| --------------------------------------------------------------- |
|
|
27
|
-
| [`/primitives`](./docs/components/primitives.md) | Button, GhostButton, Link, CardMedia, CardTitle, CopyIntro, DateText, DecorShapes, FilterChips, Pagination, RichText, SearchInput, ShareRow
|
|
28
|
-
| [`/forms`](./docs/forms.md) | Field, FormFeedback, FormErrorFeedback, FormResultSlot, Honeypot, LocaleField, NewsletterIntentField — plus the remote-form seam: structural types, transport constants, field bounds
|
|
29
|
-
| [`/chrome`](./docs/components/chrome.md) | PageShell, Nav, Footer
|
|
30
|
-
| [`/content`](./docs/components/content.md) | WeeklieCard, ProjectCard, Timeline, TimelineMilestone, TeamMemberCard, CollaboratorList, JobList, SortSelect — plus the data shapes and
|
|
31
|
-
| [`/community`](./docs/components/community.md) | AuthPageShell, LoginForm, SignupForm, GoogleAuthForm, AccountPanel, NewsletterSignup, CommentSection, ReactionBar, ContactForm
|
|
32
|
-
| [`/edit`](./docs/edit-mode.md) | Editable, setEditAdapter/getEditAdapter, descriptors and helpers, LocalizedText |
|
|
33
|
-
| [`/config`](./docs/getting-started.md#wiring-an-app-uiprovider) | UiProvider, UiConfig, the locale set, the default Catalan messages
|
|
34
|
-
| `/testing` | createRemoteFormMock — the remote-form stand-in for stories and tests
|
|
25
|
+
| Entry point | What lives there |
|
|
26
|
+
| --------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
27
|
+
| [`/primitives`](./docs/components/primitives.md) | Button, GhostButton, Link, CardMedia, CardTitle, CopyIntro, DateText, DecorShapes, FilterChips, Pagination, RichText, SearchInput, ShareRow |
|
|
28
|
+
| [`/forms`](./docs/forms.md) | Field, FormFeedback, FormErrorFeedback, FormResultSlot, Honeypot, LocaleField, NewsletterIntentField — plus the remote-form seam: structural types, transport constants, field bounds |
|
|
29
|
+
| [`/chrome`](./docs/components/chrome.md) | PageShell, Nav, Footer |
|
|
30
|
+
| [`/content`](./docs/components/content.md) | WeeklieCard, ProjectCard, Timeline, TimelineMilestone, TeamMemberCard, CollaboratorList, JobList, SortSelect — plus the data shapes, helpers and the two list rules (createWeeklyList, createUrlFilters) |
|
|
31
|
+
| [`/community`](./docs/components/community.md) | AuthPageShell, LoginForm, SignupForm, GoogleAuthForm, AccountPanel, NewsletterSignup, CommentSection, ReactionBar, ContactForm |
|
|
32
|
+
| [`/edit`](./docs/edit-mode.md) | Editable, setEditAdapter/getEditAdapter, descriptors and helpers, collectionEditing, LocalizedText |
|
|
33
|
+
| [`/config`](./docs/getting-started.md#wiring-an-app-uiprovider) | UiProvider, UiConfig, the locale set, the default Catalan messages |
|
|
34
|
+
| `/testing` | createRemoteFormMock — the remote-form stand-in for stories and tests |
|
|
35
35
|
|
|
36
36
|
Every component renders standalone (Catalan copy, identity hrefs); an app
|
|
37
37
|
integrates its i18n and router through one `UiProvider` in the root layout.
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
title?: EditDescriptor;
|
|
10
10
|
description?: EditDescriptor;
|
|
11
11
|
postedOn?: PropertyDescriptor;
|
|
12
|
-
/** Editorial
|
|
12
|
+
/** Editorial-state `flag` row; on while the opening is not a draft. */
|
|
13
13
|
status?: PropertyDescriptor;
|
|
14
14
|
/** Accessible name for the frame — usually the opening's title. */
|
|
15
15
|
label?: string;
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
import DraftBadge from '../../edit/chrome/DraftBadge.svelte';
|
|
26
26
|
import EditFrame from '../../edit/chrome/EditFrame.svelte';
|
|
27
27
|
import EditPanel from '../../edit/chrome/EditPanel.svelte';
|
|
28
|
-
import {
|
|
28
|
+
import { collectionEditing } from '../../edit/collection.svelte.js';
|
|
29
29
|
import Editable from '../../edit/Editable.svelte';
|
|
30
30
|
import type { CollectionRef } from '../../edit/types.js';
|
|
31
31
|
import DateText from '../ui/DateText.svelte';
|
|
@@ -48,27 +48,19 @@
|
|
|
48
48
|
let { jobs, editFor, collection }: Props = $props();
|
|
49
49
|
|
|
50
50
|
const config = getUiConfig();
|
|
51
|
-
const adapter = getEditAdapter();
|
|
52
51
|
const msg = $derived(config.messages);
|
|
53
52
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
);
|
|
57
|
-
|
|
58
|
-
/** The map the host gave, plus removal — the list owns identity. */
|
|
59
|
-
function editMapFor(job: JobOpeningData): JobEditMap | undefined {
|
|
60
|
-
const map = editFor?.(job);
|
|
61
|
-
if (!structural || !collection || job.id === undefined) return map;
|
|
62
|
-
return { ...map, removeOp: { kind: 'remove', collection, id: job.id } };
|
|
63
|
-
}
|
|
53
|
+
// The structural half (add slot, removal for rows with an id) — see
|
|
54
|
+
// collectionEditing.
|
|
55
|
+
const list = collectionEditing<JobOpeningData, JobEditMap>(() => ({ collection, editFor }));
|
|
64
56
|
</script>
|
|
65
57
|
|
|
66
58
|
{#if jobs.length === 0}
|
|
67
59
|
<Editable edit={config.messageEdit?.('jobs_empty')} value={msg.jobs_empty()}>
|
|
68
60
|
{#snippet children(text, attrs)}<p class="empty" {...attrs}>{text}</p>{/snippet}
|
|
69
61
|
</Editable>
|
|
70
|
-
{#if
|
|
71
|
-
<AddSlot op={
|
|
62
|
+
{#if list.add}
|
|
63
|
+
<AddSlot op={list.add} />
|
|
72
64
|
{/if}
|
|
73
65
|
<Editable edit={config.messageEdit?.('jobs_newsletterNudge')} value={msg.jobs_newsletterNudge()}>
|
|
74
66
|
{#snippet children(text, attrs)}<p class="nudge" {...attrs}>{text}</p>{/snippet}
|
|
@@ -76,10 +68,10 @@
|
|
|
76
68
|
{:else}
|
|
77
69
|
<ul>
|
|
78
70
|
{#each jobs as job (job.slug)}
|
|
79
|
-
{@const edit =
|
|
71
|
+
{@const edit = list.mapFor(job)}
|
|
80
72
|
{@const rows = [
|
|
81
73
|
edit?.postedOn && { descriptor: edit.postedOn, value: job.postedOn },
|
|
82
|
-
edit?.status && { descriptor: edit.status, value: job.draft
|
|
74
|
+
edit?.status && { descriptor: edit.status, value: !job.draft }
|
|
83
75
|
].filter((row) => row !== undefined)}
|
|
84
76
|
<li>
|
|
85
77
|
<!-- Inside the li — see CollaboratorList. -->
|
|
@@ -108,9 +100,9 @@
|
|
|
108
100
|
</EditFrame>
|
|
109
101
|
</li>
|
|
110
102
|
{/each}
|
|
111
|
-
{#if
|
|
103
|
+
{#if list.add}
|
|
112
104
|
<li class="add-slot">
|
|
113
|
-
<AddSlot op={
|
|
105
|
+
<AddSlot op={list.add} />
|
|
114
106
|
</li>
|
|
115
107
|
{/if}
|
|
116
108
|
</ul>
|
|
@@ -7,7 +7,7 @@ export interface JobEditMap {
|
|
|
7
7
|
title?: EditDescriptor;
|
|
8
8
|
description?: EditDescriptor;
|
|
9
9
|
postedOn?: PropertyDescriptor;
|
|
10
|
-
/** Editorial
|
|
10
|
+
/** Editorial-state `flag` row; on while the opening is not a draft. */
|
|
11
11
|
status?: PropertyDescriptor;
|
|
12
12
|
/** Accessible name for the frame — usually the opening's title. */
|
|
13
13
|
label?: string;
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { getUiConfig } from '../../config/context.js';
|
|
3
3
|
import type { SiteLink } from '../../config/types.js';
|
|
4
|
-
import ActionLabel from '../../edit/ActionLabel.svelte';
|
|
5
4
|
import AddSlot from '../../edit/chrome/AddSlot.svelte';
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import { getEditAdapter } from '../../edit/context.js';
|
|
5
|
+
import LinkEdit from '../../edit/chrome/LinkEdit.svelte';
|
|
6
|
+
import { collectionEditing } from '../../edit/collection.svelte.js';
|
|
9
7
|
import Editable from '../../edit/Editable.svelte';
|
|
10
8
|
import type { CollectionRef, EditDescriptor } from '../../edit/types.js';
|
|
11
9
|
import Link from '../ui/Link.svelte';
|
|
@@ -20,7 +18,7 @@
|
|
|
20
18
|
links: SiteLink[];
|
|
21
19
|
/** Edit descriptors for the link labels — see Nav. */
|
|
22
20
|
editFor?: (link: SiteLink) => EditDescriptor | undefined;
|
|
23
|
-
/**
|
|
21
|
+
/** The modal's Adreça/Ordre rows and removal per link — see LinkEdit. */
|
|
24
22
|
propertiesFor?: (link: SiteLink) => SiteLinkEditMap | undefined;
|
|
25
23
|
/** Names the links' collection; with `applyOp`, turns on add/remove. */
|
|
26
24
|
collection?: CollectionRef;
|
|
@@ -29,25 +27,12 @@
|
|
|
29
27
|
let { links, editFor = undefined, propertiesFor = undefined, collection }: Props = $props();
|
|
30
28
|
|
|
31
29
|
const config = getUiConfig();
|
|
32
|
-
const adapter = getEditAdapter();
|
|
33
30
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
function editMapFor(link: SiteLink): SiteLinkEditMap | undefined {
|
|
40
|
-
const map = propertiesFor?.(link);
|
|
41
|
-
if (!structural || !collection || link.id === undefined) return map;
|
|
42
|
-
return { ...map, removeOp: { kind: 'remove', collection, id: link.id } };
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function rowsFor(link: SiteLink, map: SiteLinkEditMap | undefined) {
|
|
46
|
-
return [
|
|
47
|
-
map?.href && { descriptor: map.href, value: link.href },
|
|
48
|
-
map?.order && { descriptor: map.order, value: String(link.order ?? 0) }
|
|
49
|
-
].filter((row) => row !== undefined);
|
|
50
|
-
}
|
|
31
|
+
// Same structural half as Nav — see collectionEditing.
|
|
32
|
+
const list = collectionEditing<SiteLink, SiteLinkEditMap>(() => ({
|
|
33
|
+
collection,
|
|
34
|
+
editFor: propertiesFor
|
|
35
|
+
}));
|
|
51
36
|
|
|
52
37
|
const year = new Date().getFullYear();
|
|
53
38
|
</script>
|
|
@@ -59,31 +44,25 @@
|
|
|
59
44
|
</span>
|
|
60
45
|
<ul>
|
|
61
46
|
{#each links as link (link.href)}
|
|
62
|
-
{@const map =
|
|
63
|
-
{@const rows = rowsFor(link, map)}
|
|
47
|
+
{@const map = list.mapFor(link)}
|
|
64
48
|
<li>
|
|
65
|
-
<!--
|
|
66
|
-
<
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
removeOp: map.removeOp
|
|
72
|
-
}
|
|
49
|
+
<!-- One gesture: the modal edits text, destination, order, removal. -->
|
|
50
|
+
<LinkEdit
|
|
51
|
+
text={{ edit: editFor?.(link), value: link.label }}
|
|
52
|
+
href={{ descriptor: map?.href, value: link.href }}
|
|
53
|
+
extras={map?.order
|
|
54
|
+
? [{ descriptor: map.order, value: String(link.order ?? 0) }]
|
|
73
55
|
: undefined}
|
|
56
|
+
removeOp={map?.removeOp}
|
|
57
|
+
label={map?.label ?? link.label}
|
|
74
58
|
>
|
|
75
|
-
{#snippet
|
|
76
|
-
|
|
77
|
-
{/snippet}
|
|
78
|
-
<ActionLabel edit={editFor?.(link)} value={link.label}>
|
|
79
|
-
{#snippet control()}<Link href={link.href}>{link.label}</Link>{/snippet}
|
|
80
|
-
</ActionLabel>
|
|
81
|
-
</EditFrame>
|
|
59
|
+
{#snippet control()}<Link href={link.href}>{link.label}</Link>{/snippet}
|
|
60
|
+
</LinkEdit>
|
|
82
61
|
</li>
|
|
83
62
|
{/each}
|
|
84
|
-
{#if
|
|
63
|
+
{#if list.add}
|
|
85
64
|
<li>
|
|
86
|
-
<AddSlot op={
|
|
65
|
+
<AddSlot op={list.add} />
|
|
87
66
|
</li>
|
|
88
67
|
{/if}
|
|
89
68
|
</ul>
|
|
@@ -10,7 +10,7 @@ interface Props {
|
|
|
10
10
|
links: SiteLink[];
|
|
11
11
|
/** Edit descriptors for the link labels — see Nav. */
|
|
12
12
|
editFor?: (link: SiteLink) => EditDescriptor | undefined;
|
|
13
|
-
/**
|
|
13
|
+
/** The modal's Adreça/Ordre rows and removal per link — see LinkEdit. */
|
|
14
14
|
propertiesFor?: (link: SiteLink) => SiteLinkEditMap | undefined;
|
|
15
15
|
/** Names the links' collection; with `applyOp`, turns on add/remove. */
|
|
16
16
|
collection?: CollectionRef;
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { getUiConfig } from '../../config/context.js';
|
|
3
3
|
import type { SiteLink } from '../../config/types.js';
|
|
4
|
-
import ActionLabel from '../../edit/ActionLabel.svelte';
|
|
5
4
|
import AddSlot from '../../edit/chrome/AddSlot.svelte';
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import { getEditAdapter } from '../../edit/context.js';
|
|
5
|
+
import LinkEdit from '../../edit/chrome/LinkEdit.svelte';
|
|
6
|
+
import { collectionEditing } from '../../edit/collection.svelte.js';
|
|
9
7
|
import type { CollectionRef, EditDescriptor } from '../../edit/types.js';
|
|
10
8
|
import { isPathUnder } from '../../utils/paths.js';
|
|
11
9
|
import GhostButton from '../ui/GhostButton.svelte';
|
|
@@ -26,11 +24,12 @@
|
|
|
26
24
|
/**
|
|
27
25
|
* Edit descriptors for the link LABELS — a function because the host
|
|
28
26
|
* owns both the routes and the message keys behind them. While the
|
|
29
|
-
* adapter is editing, a link with a descriptor
|
|
30
|
-
*
|
|
27
|
+
* adapter is editing, a link with a descriptor swaps for a button that
|
|
28
|
+
* opens the LINK MODAL (see LinkEdit) — every half of the entry edits
|
|
29
|
+
* in that one gesture.
|
|
31
30
|
*/
|
|
32
31
|
editFor?: (link: SiteLink) => EditDescriptor | undefined;
|
|
33
|
-
/**
|
|
32
|
+
/** The modal's Adreça/Ordre rows and removal per link — see LinkEdit. */
|
|
34
33
|
propertiesFor?: (link: SiteLink) => SiteLinkEditMap | undefined;
|
|
35
34
|
/** Names the links' collection; with `applyOp`, turns on add/remove. */
|
|
36
35
|
collection?: CollectionRef;
|
|
@@ -46,25 +45,14 @@
|
|
|
46
45
|
}: Props = $props();
|
|
47
46
|
|
|
48
47
|
const config = getUiConfig();
|
|
49
|
-
const adapter = getEditAdapter();
|
|
50
48
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
)
|
|
49
|
+
// The structural half — add slot and per-link removal — shared with every
|
|
50
|
+
// other editable list; see collectionEditing.
|
|
51
|
+
const list = collectionEditing<SiteLink, SiteLinkEditMap>(() => ({
|
|
52
|
+
collection,
|
|
53
|
+
editFor: propertiesFor
|
|
54
|
+
}));
|
|
54
55
|
|
|
55
|
-
/** The map the host gave, plus removal — the list owns identity. */
|
|
56
|
-
function editMapFor(link: SiteLink): SiteLinkEditMap | undefined {
|
|
57
|
-
const map = propertiesFor?.(link);
|
|
58
|
-
if (!structural || !collection || link.id === undefined) return map;
|
|
59
|
-
return { ...map, removeOp: { kind: 'remove', collection, id: link.id } };
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function rowsFor(link: SiteLink, map: SiteLinkEditMap | undefined) {
|
|
63
|
-
return [
|
|
64
|
-
map?.href && { descriptor: map.href, value: link.href },
|
|
65
|
-
map?.order && { descriptor: map.order, value: String(link.order ?? 0) }
|
|
66
|
-
].filter((row) => row !== undefined);
|
|
67
|
-
}
|
|
68
56
|
const msg = $derived(config.messages);
|
|
69
57
|
|
|
70
58
|
let isMenuOpen = $state(false);
|
|
@@ -120,35 +108,30 @@
|
|
|
120
108
|
<div class="menu" id="site-menu" class:open={isMenuOpen}>
|
|
121
109
|
<ul class="links">
|
|
122
110
|
{#each links as link (link.href)}
|
|
123
|
-
{@const map =
|
|
124
|
-
{@const rows = rowsFor(link, map)}
|
|
111
|
+
{@const map = list.mapFor(link)}
|
|
125
112
|
<li>
|
|
126
|
-
<!--
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
}
|
|
113
|
+
<!-- One gesture: clicking the entry opens the modal that edits
|
|
114
|
+
its text, destination, order and removal together. -->
|
|
115
|
+
<LinkEdit
|
|
116
|
+
text={{ edit: editFor?.(link), value: link.label }}
|
|
117
|
+
href={{ descriptor: map?.href, value: link.href }}
|
|
118
|
+
extras={map?.order
|
|
119
|
+
? [{ descriptor: map.order, value: String(link.order ?? 0) }]
|
|
134
120
|
: undefined}
|
|
121
|
+
removeOp={map?.removeOp}
|
|
122
|
+
label={map?.label ?? link.label}
|
|
135
123
|
>
|
|
136
|
-
{#snippet
|
|
137
|
-
<
|
|
124
|
+
{#snippet control()}
|
|
125
|
+
<Link href={link.href} aria-current={isCurrent(link.href) ? 'page' : undefined}>
|
|
126
|
+
{link.label}
|
|
127
|
+
</Link>
|
|
138
128
|
{/snippet}
|
|
139
|
-
|
|
140
|
-
{#snippet control()}
|
|
141
|
-
<Link href={link.href} aria-current={isCurrent(link.href) ? 'page' : undefined}>
|
|
142
|
-
{link.label}
|
|
143
|
-
</Link>
|
|
144
|
-
{/snippet}
|
|
145
|
-
</ActionLabel>
|
|
146
|
-
</EditFrame>
|
|
129
|
+
</LinkEdit>
|
|
147
130
|
</li>
|
|
148
131
|
{/each}
|
|
149
|
-
{#if
|
|
132
|
+
{#if list.add}
|
|
150
133
|
<li>
|
|
151
|
-
<AddSlot op={
|
|
134
|
+
<AddSlot op={list.add} />
|
|
152
135
|
</li>
|
|
153
136
|
{/if}
|
|
154
137
|
</ul>
|
|
@@ -17,11 +17,12 @@ interface Props {
|
|
|
17
17
|
/**
|
|
18
18
|
* Edit descriptors for the link LABELS — a function because the host
|
|
19
19
|
* owns both the routes and the message keys behind them. While the
|
|
20
|
-
* adapter is editing, a link with a descriptor
|
|
21
|
-
*
|
|
20
|
+
* adapter is editing, a link with a descriptor swaps for a button that
|
|
21
|
+
* opens the LINK MODAL (see LinkEdit) — every half of the entry edits
|
|
22
|
+
* in that one gesture.
|
|
22
23
|
*/
|
|
23
24
|
editFor?: (link: SiteLink) => EditDescriptor | undefined;
|
|
24
|
-
/**
|
|
25
|
+
/** The modal's Adreça/Ordre rows and removal per link — see LinkEdit. */
|
|
25
26
|
propertiesFor?: (link: SiteLink) => SiteLinkEditMap | undefined;
|
|
26
27
|
/** Names the links' collection; with `applyOp`, turns on add/remove. */
|
|
27
28
|
collection?: CollectionRef;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type { EntityOp, PropertyDescriptor } from '../../edit/types.js';
|
|
2
2
|
/**
|
|
3
|
-
* What the CMS may edit on one nav/footer link beyond its LABEL
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* `collection
|
|
3
|
+
* What the CMS may edit on one nav/footer link beyond its LABEL: the href and
|
|
4
|
+
* the display order join the label in ONE modal (LinkEdit — clicking the
|
|
5
|
+
* entry opens it), and removal arrives injected by the list from its
|
|
6
|
+
* `collection`. The modal is entered through the label's `editFor`
|
|
7
|
+
* descriptor, so these rows take effect alongside it.
|
|
7
8
|
*/
|
|
8
9
|
export interface SiteLinkEditMap {
|
|
9
10
|
href?: PropertyDescriptor;
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
publishedOn?: PropertyDescriptor;
|
|
13
13
|
externalUrl?: PropertyDescriptor;
|
|
14
14
|
image?: PropertyDescriptor;
|
|
15
|
-
/** Editorial
|
|
15
|
+
/** Editorial-state `flag` row; on while the project is not a draft. */
|
|
16
16
|
status?: PropertyDescriptor;
|
|
17
17
|
/** Accessible name for the frame, e.g. the project's title. */
|
|
18
18
|
label?: string;
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
edit?.publishedOn && { descriptor: edit.publishedOn, value: project.publishedOn },
|
|
71
71
|
edit?.externalUrl && { descriptor: edit.externalUrl, value: project.externalUrl },
|
|
72
72
|
edit?.image && { descriptor: edit.image, value: project.imageUrl },
|
|
73
|
-
edit?.status && { descriptor: edit.status, value: project.draft
|
|
73
|
+
edit?.status && { descriptor: edit.status, value: !project.draft }
|
|
74
74
|
].filter((row) => row !== undefined)
|
|
75
75
|
);
|
|
76
76
|
const frameSpec = $derived(
|
|
@@ -10,7 +10,7 @@ export interface ProjectEditMap {
|
|
|
10
10
|
publishedOn?: PropertyDescriptor;
|
|
11
11
|
externalUrl?: PropertyDescriptor;
|
|
12
12
|
image?: PropertyDescriptor;
|
|
13
|
-
/** Editorial
|
|
13
|
+
/** Editorial-state `flag` row; on while the project is not a draft. */
|
|
14
14
|
status?: PropertyDescriptor;
|
|
15
15
|
/** Accessible name for the frame, e.g. the project's title. */
|
|
16
16
|
label?: string;
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
import AddSlot from '../../edit/chrome/AddSlot.svelte';
|
|
23
23
|
import EditFrame from '../../edit/chrome/EditFrame.svelte';
|
|
24
24
|
import EditPanel from '../../edit/chrome/EditPanel.svelte';
|
|
25
|
-
import {
|
|
25
|
+
import { collectionEditing } from '../../edit/collection.svelte.js';
|
|
26
26
|
import type { CollectionRef } from '../../edit/types.js';
|
|
27
27
|
|
|
28
28
|
interface Props {
|
|
@@ -39,18 +39,12 @@
|
|
|
39
39
|
|
|
40
40
|
let { collaborators, editFor, collection }: Props = $props();
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const
|
|
45
|
-
collection
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
/** The map the host gave, plus removal — the list owns identity. */
|
|
49
|
-
function editMapFor(collaborator: CollaboratorData): CollaboratorEditMap | undefined {
|
|
50
|
-
const map = editFor?.(collaborator);
|
|
51
|
-
if (!structural || !collection || collaborator.id === undefined) return map;
|
|
52
|
-
return { ...map, removeOp: { kind: 'remove', collection, id: collaborator.id } };
|
|
53
|
-
}
|
|
42
|
+
// The structural half (add slot, removal for rows with an id) — see
|
|
43
|
+
// collectionEditing.
|
|
44
|
+
const list = collectionEditing<CollaboratorData, CollaboratorEditMap>(() => ({
|
|
45
|
+
collection,
|
|
46
|
+
editFor
|
|
47
|
+
}));
|
|
54
48
|
|
|
55
49
|
function rowsFor(collaborator: CollaboratorData, map: CollaboratorEditMap | undefined) {
|
|
56
50
|
return [
|
|
@@ -63,7 +57,7 @@
|
|
|
63
57
|
|
|
64
58
|
<ul>
|
|
65
59
|
{#each collaborators as collaborator (collaborator.id ?? collaborator.personName + collaborator.affiliation)}
|
|
66
|
-
{@const map =
|
|
60
|
+
{@const map = list.mapFor(collaborator)}
|
|
67
61
|
{@const rows = rowsFor(collaborator, map)}
|
|
68
62
|
<li>
|
|
69
63
|
<!-- Inside the li, so the list's own layout never gains a child. -->
|
|
@@ -89,9 +83,9 @@
|
|
|
89
83
|
</EditFrame>
|
|
90
84
|
</li>
|
|
91
85
|
{/each}
|
|
92
|
-
{#if
|
|
86
|
+
{#if list.add}
|
|
93
87
|
<li class="add-slot">
|
|
94
|
-
<AddSlot op={
|
|
88
|
+
<AddSlot op={list.add} />
|
|
95
89
|
</li>
|
|
96
90
|
{/if}
|
|
97
91
|
</ul>
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import type { MilestoneData } from '../../content/types.js';
|
|
4
4
|
import type { MilestoneEditMap } from './TimelineMilestone.svelte';
|
|
5
5
|
import AddSlot from '../../edit/chrome/AddSlot.svelte';
|
|
6
|
-
import {
|
|
6
|
+
import { collectionEditing } from '../../edit/collection.svelte.js';
|
|
7
7
|
import type { CollectionRef } from '../../edit/types.js';
|
|
8
8
|
import { yearOf } from '../../utils/dates.js';
|
|
9
9
|
import TimelineMilestone from './TimelineMilestone.svelte';
|
|
@@ -26,18 +26,10 @@
|
|
|
26
26
|
let { milestones, variant = 'compact', editFor, collection }: Props = $props();
|
|
27
27
|
|
|
28
28
|
const config = getUiConfig();
|
|
29
|
-
const adapter = getEditAdapter();
|
|
30
29
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
);
|
|
34
|
-
|
|
35
|
-
/** The map the host gave, plus removal — the list owns identity and order. */
|
|
36
|
-
function editMapFor(milestone: MilestoneData): MilestoneEditMap | undefined {
|
|
37
|
-
const map = editFor?.(milestone);
|
|
38
|
-
if (!structural || !collection) return map;
|
|
39
|
-
return { ...map, removeOp: { kind: 'remove', collection, id: milestone.id } };
|
|
40
|
-
}
|
|
30
|
+
// The structural half (add slots, per-card removal); the list owns
|
|
31
|
+
// identity and order — see collectionEditing.
|
|
32
|
+
const list = collectionEditing<MilestoneData, MilestoneEditMap>(() => ({ collection, editFor }));
|
|
41
33
|
|
|
42
34
|
interface TimelineEntry {
|
|
43
35
|
milestone: MilestoneData;
|
|
@@ -68,27 +60,22 @@
|
|
|
68
60
|
>
|
|
69
61
|
<ol>
|
|
70
62
|
{#each entries as entry (entry.milestone.id)}
|
|
71
|
-
{
|
|
63
|
+
{@const before = list.addBefore(entry.milestone.id)}
|
|
64
|
+
{#if before}
|
|
72
65
|
<li class="add-slot">
|
|
73
|
-
<AddSlot
|
|
74
|
-
op={{
|
|
75
|
-
kind: 'create',
|
|
76
|
-
collection,
|
|
77
|
-
anchor: { id: entry.milestone.id, placement: 'before' }
|
|
78
|
-
}}
|
|
79
|
-
/>
|
|
66
|
+
<AddSlot op={before} />
|
|
80
67
|
</li>
|
|
81
68
|
{/if}
|
|
82
69
|
<li>
|
|
83
70
|
{#if entry.yearMarker}
|
|
84
71
|
<span class="year">{entry.yearMarker}</span>
|
|
85
72
|
{/if}
|
|
86
|
-
<TimelineMilestone milestone={entry.milestone} edit={
|
|
73
|
+
<TimelineMilestone milestone={entry.milestone} edit={list.mapFor(entry.milestone)} />
|
|
87
74
|
</li>
|
|
88
75
|
{/each}
|
|
89
|
-
{#if
|
|
76
|
+
{#if list.add}
|
|
90
77
|
<li class="add-slot">
|
|
91
|
-
<AddSlot op={
|
|
78
|
+
<AddSlot op={list.add} />
|
|
92
79
|
</li>
|
|
93
80
|
{/if}
|
|
94
81
|
</ol>
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
category?: PropertyDescriptor;
|
|
14
14
|
linkUrl?: PropertyDescriptor;
|
|
15
15
|
image?: PropertyDescriptor;
|
|
16
|
-
/** Editorial
|
|
16
|
+
/** Editorial-state `flag` row; on while the milestone is not a draft. */
|
|
17
17
|
status?: PropertyDescriptor;
|
|
18
18
|
/** Accessible name for the frame, e.g. "Fita: Neix la fundació". */
|
|
19
19
|
label?: string;
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
},
|
|
68
68
|
edit?.linkUrl && { descriptor: edit.linkUrl, value: milestone.linkUrl },
|
|
69
69
|
edit?.image && { descriptor: edit.image, value: milestone.imageUrls[0] ?? null },
|
|
70
|
-
edit?.status && { descriptor: edit.status, value: milestone.draft
|
|
70
|
+
edit?.status && { descriptor: edit.status, value: !milestone.draft }
|
|
71
71
|
].filter((row) => row !== undefined)
|
|
72
72
|
);
|
|
73
73
|
|
|
@@ -11,7 +11,7 @@ export interface MilestoneEditMap {
|
|
|
11
11
|
category?: PropertyDescriptor;
|
|
12
12
|
linkUrl?: PropertyDescriptor;
|
|
13
13
|
image?: PropertyDescriptor;
|
|
14
|
-
/** Editorial
|
|
14
|
+
/** Editorial-state `flag` row; on while the milestone is not a draft. */
|
|
15
15
|
status?: PropertyDescriptor;
|
|
16
16
|
/** Accessible name for the frame, e.g. "Fita: Neix la fundació". */
|
|
17
17
|
label?: string;
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
title?: EditDescriptor;
|
|
11
11
|
excerpt?: EditDescriptor;
|
|
12
12
|
image?: PropertyDescriptor;
|
|
13
|
-
/** Editorial
|
|
13
|
+
/** Editorial-state `flag` row; on while the weekly is not a draft. */
|
|
14
14
|
status?: PropertyDescriptor;
|
|
15
15
|
/** Accessible name for the frame, e.g. "Weekly #12". */
|
|
16
16
|
label?: string;
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
const panelRows = $derived(
|
|
50
50
|
[
|
|
51
51
|
edit?.image && { descriptor: edit.image, value: weekly.imageUrl },
|
|
52
|
-
edit?.status && { descriptor: edit.status, value: weekly.draft
|
|
52
|
+
edit?.status && { descriptor: edit.status, value: !weekly.draft }
|
|
53
53
|
].filter((row) => row !== undefined)
|
|
54
54
|
);
|
|
55
55
|
const frameSpec = $derived(
|
|
@@ -8,7 +8,7 @@ export interface WeeklyEditMap {
|
|
|
8
8
|
title?: EditDescriptor;
|
|
9
9
|
excerpt?: EditDescriptor;
|
|
10
10
|
image?: PropertyDescriptor;
|
|
11
|
-
/** Editorial
|
|
11
|
+
/** Editorial-state `flag` row; on while the weekly is not a draft. */
|
|
12
12
|
status?: PropertyDescriptor;
|
|
13
13
|
/** Accessible name for the frame, e.g. "Weekly #12". */
|
|
14
14
|
label?: string;
|
package/dist/config/index.d.ts
CHANGED
|
@@ -4,4 +4,4 @@ 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, SiteLink, UiConfig, UiConfigInput, UiMessages } from './types.js';
|
|
7
|
+
export type { Locale, NotParameterized, ParameterizedKey, ParameterlessKey, SiteLink, UiConfig, UiConfigInput, UiMessages } from './types.js';
|
package/dist/config/messages.js
CHANGED
|
@@ -104,5 +104,9 @@ export const defaultMessages = {
|
|
|
104
104
|
category_lab: () => 'Laboratori',
|
|
105
105
|
category_education: () => 'Educació',
|
|
106
106
|
category_collaboration: () => 'Col·laboració',
|
|
107
|
-
category_press: () => 'Premsa'
|
|
107
|
+
category_press: () => 'Premsa',
|
|
108
|
+
status_published: () => 'Publicat',
|
|
109
|
+
status_draft: () => 'Esborrany',
|
|
110
|
+
status_open: () => 'Oberta',
|
|
111
|
+
status_closed: () => 'Tancada'
|
|
108
112
|
};
|