storyloco 2.1.0 → 2.2.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 CHANGED
@@ -15,6 +15,7 @@ A collection of slick Storyblok field plugins built with Svelte 5, TypeScript, a
15
15
  - **🏗️ Plans** - Floor plan manager with drag-and-drop sorting for types, floors, dimensions, and rooms
16
16
  - **📝 Input** - Comprehensive form input field with support for all HTML input types, checkboxes, radio buttons, selects, and textareas
17
17
  - **⏰ Time** - Simple time input field with step control
18
+ - **💷 Currency** - Amount input with a currency selector; configure allowed codes via the `currencies` and `default_currency` options
18
19
 
19
20
  ### Vite Plugins
20
21
 
@@ -185,6 +186,14 @@ Your params are spread last, so any default can be overridden:
185
186
  await storyblok.story('layout', { resolve_assets: 0 })
186
187
  ```
187
188
 
189
+ `story` treats a missing story as a 404. Where a slug is allowed not to exist, `find` returns
190
+ `null` instead — the miss is then neither raised nor logged:
191
+
192
+ ```typescript
193
+ const product = await storyblok.find<Product>(`products/${slug}`)
194
+ if (!product) error(404, 'Product not found')
195
+ ```
196
+
188
197
  It's callable too, for anything the methods don't cover:
189
198
 
190
199
  ```typescript
@@ -258,6 +267,7 @@ export interface StoryblokCustomPlugins {
258
267
  twitter_description: string
259
268
  }
260
269
  'loco-time': string
270
+ 'loco-currency': import('storyloco/currency').Currency
261
271
  'loco-asset': import('storyloco/asset').Asset
262
272
  }
263
273
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "storyloco",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "description": "A collection of slick Storyblok field plugins built with Svelte 5, TypeScript, and Tailwind CSS",
5
5
  "keywords": [
6
6
  "storyblok",
@@ -33,7 +33,8 @@
33
33
  "packages/input/types.ts",
34
34
  "packages/time/types.ts",
35
35
  "packages/seo/types.ts",
36
- "packages/hubspot_form/types.ts"
36
+ "packages/hubspot_form/types.ts",
37
+ "packages/currency/types.ts"
37
38
  ],
38
39
  "exports": {
39
40
  ".": {
@@ -93,6 +94,10 @@
93
94
  "./hubspot_form": {
94
95
  "types": "./packages/hubspot_form/types.ts",
95
96
  "svelte": "./packages/hubspot_form/src/app.svelte.ts"
97
+ },
98
+ "./currency": {
99
+ "types": "./packages/currency/types.ts",
100
+ "svelte": "./packages/currency/src/app.svelte.ts"
96
101
  }
97
102
  },
98
103
  "devDependencies": {
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Narrow `Code` to the codes configured in the field's `currencies` option,
3
+ * e.g. `Currency<'GBP' | 'USD'>`.
4
+ */
5
+ export interface Currency<Code extends string = Uppercase<string>> {
6
+ /** ISO 4217 code, e.g. `GBP` */
7
+ currency: Code
8
+ amount: number | null
9
+ /** `amount` formatted for display, e.g. `£130,000.00`; `null` when there's no amount */
10
+ formatted: string | null
11
+ }
@@ -19,6 +19,7 @@ export declare function version(): Version;
19
19
  *
20
20
  * // anywhere on the server
21
21
  * await storyblok.story("layout")
22
+ * await storyblok.find<Product>(`products/${slug}`) // null when missing
22
23
  * await storyblok.stories<Blog>({ content_type: "blog", per_page: 2 })
23
24
  *
24
25
  * const { client, version } = storyblok()
@@ -29,15 +30,12 @@ export declare function setup(client: StoryblokClient): (() => {
29
30
  client: StoryblokClient;
30
31
  version: Version;
31
32
  }) & {
32
- /**
33
- * A single story by full slug. Resolves the client's `relations`, unlike `stories`.
34
- *
35
- * `T` is the story's content type; it defaults to the SDK's permissive one, so an
36
- * unparameterised call behaves as before.
37
- */
38
- story<T = import("storyblok-js-client").ISbComponentType<string> & {
33
+ story: <T = import("storyblok-js-client").ISbComponentType<string> & {
34
+ [index: string]: any;
35
+ }>(slug: string, params?: ISbStoryParams) => Promise<ISbStoryData<T>>;
36
+ find: <T = import("storyblok-js-client").ISbComponentType<string> & {
39
37
  [index: string]: any;
40
- }>(slug: string, params?: ISbStoryParams): Promise<ISbStoryData<T>>;
38
+ }>(slug: string, params?: ISbStoryParams) => Promise<ISbStoryData<T> | null>;
41
39
  /**
42
40
  * The link tree as a flat array — Storyblok returns it keyed by uuid, which is
43
41
  * almost never what you want. Filtering and ordering are left to the caller.
@@ -1,5 +1,9 @@
1
1
  import { getRequestEvent } from "$app/server";
2
+ import { error } from "@sveltejs/kit";
2
3
  import { handle_error } from "./storyblok.svelte.js";
4
+ function is_not_found(err) {
5
+ return typeof err === "object" && err !== null && "status" in err && err.status === 404;
6
+ }
3
7
  /** The content version for the current request, as set by the `version` hook. */
4
8
  export function version() {
5
9
  return getRequestEvent().locals.version;
@@ -20,6 +24,7 @@ export function version() {
20
24
  *
21
25
  * // anywhere on the server
22
26
  * await storyblok.story("layout")
27
+ * await storyblok.find<Product>(`products/${slug}`) // null when missing
23
28
  * await storyblok.stories<Blog>({ content_type: "blog", per_page: 2 })
24
29
  *
25
30
  * const { client, version } = storyblok()
@@ -28,25 +33,37 @@ export function version() {
28
33
  */
29
34
  export function setup(client) {
30
35
  const context = () => ({ client, version: version() });
36
+ /**
37
+ * A single story by full slug, or `null` when there isn't one. Resolves the client's
38
+ * `relations`, unlike `stories`. For slugs that are allowed not to exist — a miss is
39
+ * routine, so it's neither raised nor logged. `T` is the story's content type.
40
+ */
41
+ async function find(slug, params = {}) {
42
+ const response = await client
43
+ .get(`cdn/stories/${slug}`, {
44
+ version: version(),
45
+ resolve_links: "url",
46
+ resolve_relations: client.relations,
47
+ resolve_assets: 1,
48
+ ...params,
49
+ })
50
+ .catch((err) => {
51
+ if (is_not_found(err))
52
+ return null;
53
+ return handle_error(err);
54
+ });
55
+ return (response?.data.story ?? null);
56
+ }
57
+ /** A single story by full slug; a missing one is a 404. `find` for stories that may not exist. */
58
+ async function story(slug, params = {}) {
59
+ const found = await find(slug, params);
60
+ if (!found)
61
+ error(404, "Story not found");
62
+ return found;
63
+ }
31
64
  return Object.assign(context, {
32
- /**
33
- * A single story by full slug. Resolves the client's `relations`, unlike `stories`.
34
- *
35
- * `T` is the story's content type; it defaults to the SDK's permissive one, so an
36
- * unparameterised call behaves as before.
37
- */
38
- async story(slug, params = {}) {
39
- const response = await client
40
- .get(`cdn/stories/${slug}`, {
41
- version: version(),
42
- resolve_links: "url",
43
- resolve_relations: client.relations,
44
- resolve_assets: 1,
45
- ...params,
46
- })
47
- .catch(handle_error);
48
- return response.data.story;
49
- },
65
+ story,
66
+ find,
50
67
  /**
51
68
  * The link tree as a flat array — Storyblok returns it keyed by uuid, which is
52
69
  * almost never what you want. Filtering and ordering are left to the caller.