@vit-foundation/ui 0.3.0 → 0.5.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.
@@ -1,5 +1,6 @@
1
1
  <script lang="ts">
2
2
  import { getUiConfig } from '../../config/context.js';
3
+ import Editable from '../../edit/Editable.svelte';
3
4
  import { withNewsletterIntent } from '../../forms/transport.js';
4
5
  import type { NewsletterToggleFormInstance } from './AccountPanel.svelte';
5
6
  import Button from '../ui/Button.svelte';
@@ -27,19 +28,33 @@
27
28
 
28
29
  <section class="newsletter" aria-labelledby="newsletter-title">
29
30
  <div class="inner band">
30
- <h2 id="newsletter-title">{msg.newsletter_title()}</h2>
31
- <p>{msg.newsletter_intro()}</p>
31
+ <Editable edit={config.messageEdit?.('newsletter_title')} value={msg.newsletter_title()}>
32
+ {#snippet children(text, attrs)}<h2 id="newsletter-title" {...attrs}>{text}</h2>{/snippet}
33
+ </Editable>
34
+ <Editable edit={config.messageEdit?.('newsletter_intro')} value={msg.newsletter_intro()}>
35
+ {#snippet children(text, attrs)}<p {...attrs}>{text}</p>{/snippet}
36
+ </Editable>
32
37
 
33
38
  {#if account === null}
34
39
  <p class="prompt">
35
- {msg.newsletter_promptLoggedOut()}
40
+ <Editable
41
+ edit={config.messageEdit?.('newsletter_promptLoggedOut')}
42
+ value={msg.newsletter_promptLoggedOut()}
43
+ >
44
+ {#snippet children(text, attrs)}<span {...attrs}>{text}</span>{/snippet}
45
+ </Editable>
36
46
  <Link href={withNewsletterIntent('/signup')}>{msg.comments_signupLink()}</Link>
37
47
  ·
38
48
  <Link href={withNewsletterIntent('/login')}>{msg.comments_loginLink()}</Link>
39
49
  </p>
40
50
  {:else if account.newsletterSubscribed}
41
51
  <p class="prompt">
42
- {msg.newsletter_subscribedNote()}
52
+ <Editable
53
+ edit={config.messageEdit?.('newsletter_subscribedNote')}
54
+ value={msg.newsletter_subscribedNote()}
55
+ >
56
+ {#snippet children(text, attrs)}<span {...attrs}>{text}</span>{/snippet}
57
+ </Editable>
43
58
  <Link href="/account">{msg.account_navLabel()}</Link>
44
59
  </p>
45
60
  {:else}
@@ -30,8 +30,12 @@
30
30
  </script>
31
31
 
32
32
  {#if jobs.length === 0}
33
- <p class="empty">{msg.jobs_empty()}</p>
34
- <p class="nudge">{msg.jobs_newsletterNudge()}</p>
33
+ <Editable edit={config.messageEdit?.('jobs_empty')} value={msg.jobs_empty()}>
34
+ {#snippet children(text, attrs)}<p class="empty" {...attrs}>{text}</p>{/snippet}
35
+ </Editable>
36
+ <Editable edit={config.messageEdit?.('jobs_newsletterNudge')} value={msg.jobs_newsletterNudge()}>
37
+ {#snippet children(text, attrs)}<p class="nudge" {...attrs}>{text}</p>{/snippet}
38
+ </Editable>
35
39
  {:else}
36
40
  <ul>
37
41
  {#each jobs as job (job.slug)}
@@ -49,7 +53,9 @@
49
53
  </li>
50
54
  {/each}
51
55
  </ul>
52
- <p class="nudge">{msg.jobs_newsletterNudge()}</p>
56
+ <Editable edit={config.messageEdit?.('jobs_newsletterNudge')} value={msg.jobs_newsletterNudge()}>
57
+ {#snippet children(text, attrs)}<p class="nudge" {...attrs}>{text}</p>{/snippet}
58
+ </Editable>
53
59
  {/if}
54
60
 
55
61
  <style>
@@ -1,6 +1,7 @@
1
1
  <script lang="ts">
2
2
  import { getUiConfig } from '../../config/context.js';
3
3
  import type { SiteLink } from '../../config/types.js';
4
+ import Editable from '../../edit/Editable.svelte';
4
5
  import Link from '../ui/Link.svelte';
5
6
 
6
7
  /**
@@ -29,7 +30,15 @@
29
30
  <li><Link href={link.href}>{link.label}</Link></li>
30
31
  {/each}
31
32
  </ul>
32
- <p class="rights">© {year} {config.messages.footer_rights()}</p>
33
+ <p class="rights">
34
+ © {year}
35
+ <Editable
36
+ edit={config.messageEdit?.('footer_rights')}
37
+ value={config.messages.footer_rights()}
38
+ >
39
+ {#snippet children(text, attrs)}<span {...attrs}>{text}</span>{/snippet}
40
+ </Editable>
41
+ </p>
33
42
  </div>
34
43
  </footer>
35
44
 
@@ -10,6 +10,7 @@
10
10
 
11
11
  <script lang="ts">
12
12
  import { getUiConfig } from '../../config/context.js';
13
+ import Editable from '../../edit/Editable.svelte';
13
14
  import { COMMENT_BODY } from '../../forms/constraints.js';
14
15
  import type { CommentData, CommentThreadData } from '../../content/types.js';
15
16
  import Button from '../ui/Button.svelte';
@@ -67,10 +68,16 @@
67
68
  {/snippet}
68
69
 
69
70
  <section aria-labelledby="comments-heading">
70
- <h2 class="subsection-heading" id="comments-heading">{msg.comments_title()}</h2>
71
+ <Editable edit={config.messageEdit?.('comments_title')} value={msg.comments_title()}>
72
+ {#snippet children(text, attrs)}
73
+ <h2 class="subsection-heading" id="comments-heading" {...attrs}>{text}</h2>
74
+ {/snippet}
75
+ </Editable>
71
76
 
72
77
  {#if comments.length === 0}
73
- <p class="empty">{msg.comments_empty()}</p>
78
+ <Editable edit={config.messageEdit?.('comments_empty')} value={msg.comments_empty()}>
79
+ {#snippet children(text, attrs)}<p class="empty" {...attrs}>{text}</p>{/snippet}
80
+ </Editable>
74
81
  {:else}
75
82
  <ul class="list">
76
83
  {#each comments as thread (thread.id)}
@@ -148,7 +155,12 @@
148
155
  </form>
149
156
  {:else}
150
157
  <p class="login-prompt">
151
- {msg.comments_loginPrompt()}
158
+ <Editable
159
+ edit={config.messageEdit?.('comments_loginPrompt')}
160
+ value={msg.comments_loginPrompt()}
161
+ >
162
+ {#snippet children(text, attrs)}<span {...attrs}>{text}</span>{/snippet}
163
+ </Editable>
152
164
  <Link href="/login">{msg.comments_loginLink()}</Link>
153
165
  ·
154
166
  <Link href="/signup">{msg.comments_signupLink()}</Link>
@@ -37,7 +37,10 @@ export function setUiConfig(input) {
37
37
  },
38
38
  get messages() {
39
39
  return input().messages ?? defaultMessages;
40
- }
40
+ },
41
+ // Undefined means inert: components render their message strings as
42
+ // plain text, which is every app but the CMS.
43
+ messageEdit: (key) => input().messageEdit?.(key)
41
44
  };
42
45
  return setContext(KEY, resolved);
43
46
  }
@@ -1,3 +1,4 @@
1
+ import type { EditDescriptor } from '../edit/types.js';
1
2
  /**
2
3
  * The site's locales and their canonical member. Fixed rather than generic:
3
4
  * `ca` is load-bearing — it is the required key of every localized column
@@ -156,6 +157,14 @@ export interface UiConfig {
156
157
  /** Appended to document titles and shown as the wordmark. */
157
158
  siteName: string;
158
159
  messages: UiMessages;
160
+ /**
161
+ * Edit descriptor for one of THIS config's message strings, by catalog
162
+ * key — the interface-wording half of edit mode. Optional and undefined by
163
+ * default, so a read-only app (and every story/test) renders the messages
164
+ * as plain text. A CMS-shaped host supplies it alongside an EditAdapter;
165
+ * components offer only their parameterless, plain-text message sites.
166
+ */
167
+ messageEdit?: (key: string) => EditDescriptor | undefined;
159
168
  }
160
169
  /** What an app hands UiProvider: any subset; the rest keeps package defaults. */
161
170
  export interface UiConfigInput {
@@ -166,4 +175,5 @@ export interface UiConfigInput {
166
175
  canonicalPathname?: UiConfig['canonicalPathname'];
167
176
  siteName?: string;
168
177
  messages?: UiMessages;
178
+ messageEdit?: UiConfig['messageEdit'];
169
179
  }
@@ -1,5 +1,14 @@
1
1
  import type { Locale } from '../config/types.js';
2
2
  import type { EditableEntity, EditDescriptor } from './types.js';
3
+ /**
4
+ * Descriptor for one interface-wording message (a Paraglide catalog key).
5
+ * Only parameterless messages are sensibly editable in place — editing the
6
+ * RENDERED text of a parameterized one would overwrite its template.
7
+ */
8
+ export declare function chromeEdit(key: string, locale: Locale, options?: {
9
+ format?: EditDescriptor['format'];
10
+ label?: string;
11
+ }): EditDescriptor;
3
12
  /** Descriptor for one page-copy block (`page_content` row). */
4
13
  export declare function pageCopyEdit(page: string, sectionKey: string, locale: Locale, options?: {
5
14
  format?: EditDescriptor['format'];
@@ -1,3 +1,11 @@
1
+ /**
2
+ * Descriptor for one interface-wording message (a Paraglide catalog key).
3
+ * Only parameterless messages are sensibly editable in place — editing the
4
+ * RENDERED text of a parameterized one would overwrite its template.
5
+ */
6
+ export function chromeEdit(key, locale, options) {
7
+ return { ref: { kind: 'chrome', key }, locale, ...options };
8
+ }
1
9
  /** Descriptor for one page-copy block (`page_content` row). */
2
10
  export function pageCopyEdit(page, sectionKey, locale, options) {
3
11
  return { ref: { kind: 'page-copy', page, sectionKey }, locale, ...options };
@@ -1,5 +1,5 @@
1
1
  export { getEditAdapter, setEditAdapter } from './context.js';
2
2
  export { default as Editable } from './Editable.svelte';
3
- export { entityEdit, pageCopyEdit } from './helpers.js';
3
+ export { chromeEdit, entityEdit, pageCopyEdit } from './helpers.js';
4
4
  export { localize } from './types.js';
5
5
  export type { ContentRef, EditableEntity, EditAdapter, EditDescriptor, LocalizedText } from './types.js';
@@ -1,4 +1,4 @@
1
1
  export { getEditAdapter, setEditAdapter } from './context.js';
2
2
  export { default as Editable } from './Editable.svelte';
3
- export { entityEdit, pageCopyEdit } from './helpers.js';
3
+ export { chromeEdit, entityEdit, pageCopyEdit } from './helpers.js';
4
4
  export { localize } from './types.js';
@@ -17,8 +17,12 @@ export declare function localize(value: LocalizedText, locale: Locale): string;
17
17
  */
18
18
  export type EditableEntity = 'weeklies' | 'projects' | 'milestones' | 'team_members' | 'job_openings';
19
19
  /**
20
- * The identity of one editable string in the content model: either a page
21
- * copy block (`page_content` row) or one localized field of a domain entity.
20
+ * The identity of one editable string in the content model: a page copy
21
+ * block (`page_content` row), one localized field of a domain entity, or one
22
+ * interface-wording message (`chrome`) — a Paraglide catalog key. What a
23
+ * chrome save MEANS is the host's decision, like every other ref: the
24
+ * foundation CMS writes the website's `ui_messages` table, from which the
25
+ * catalogs are regenerated at deploy.
22
26
  */
23
27
  export type ContentRef = {
24
28
  kind: 'page-copy';
@@ -29,6 +33,9 @@ export type ContentRef = {
29
33
  entity: EditableEntity;
30
34
  id: string | number;
31
35
  field: string;
36
+ } | {
37
+ kind: 'chrome';
38
+ key: string;
32
39
  };
33
40
  /** What a component hands the adapter alongside the new value. */
34
41
  export interface EditDescriptor {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vit-foundation/ui",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "scripts": {
5
5
  "dev": "vite dev",
6
6
  "build": "vite build && npm run prepack",
@@ -122,7 +122,7 @@
122
122
  "svelte"
123
123
  ],
124
124
  "license": "MIT",
125
- "description": "Fundaci\u00f3 VIT's shared Svelte 5 component library with pluggable edit mode",
125
+ "description": "Fundació VIT's shared Svelte 5 component library with pluggable edit mode",
126
126
  "repository": {
127
127
  "type": "git",
128
128
  "url": "git+https://github.com/fndvit/VizVit-UI.git"