@protobase/schema 0.15.2 → 0.15.3

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.
@@ -13,4 +13,15 @@ export declare const fillAddress: (to: AppAddress, record?: Record<string, unkno
13
13
  export declare const leavesApp: (href: string) => boolean;
14
14
  /** Why an href cannot be a link's `href`: it points inside the app, which takes an address in `to`. */
15
15
  export declare const appHrefError: (href: string) => string | undefined;
16
+ /** The field an action's href is made of alone, `{field}`: the record's value is the whole URL, as it is stored. */
17
+ export declare const hrefField: (href: string) => string | undefined;
18
+ /**
19
+ * Why a link action cannot be built: an `href` that points inside the app (one that is a single `{field}`, the URL the
20
+ * record holds, is fine), or `{host}` in the label of a link that goes `to` an address in the app, which has no host.
21
+ */
22
+ export declare const linkActionError: (label: string, run: {
23
+ to: AppAddress;
24
+ } | {
25
+ href: string;
26
+ }) => string | undefined;
16
27
  //#endregion
@@ -39,5 +39,15 @@ const fillAddress = (to, record = {}) => {
39
39
  const leavesApp = (href) => /^([a-z][a-z0-9+.-]*:|\/\/)/i.test(href);
40
40
  /** Why an href cannot be a link's `href`: it points inside the app, which takes an address in `to`. */
41
41
  const appHrefError = (href) => leavesApp(href) ? void 0 : `href "${href}" points inside the app; give the link \`to\` instead, an address such as { kind: 'record', resource: 'orders', key: '{order_id}' }`;
42
+ /** The field an action's href is made of alone, `{field}`: the record's value is the whole URL, as it is stored. */
43
+ const hrefField = (href) => /^\{([A-Za-z_][A-Za-z0-9_]*)\}$/.exec(href)?.[1];
44
+ /**
45
+ * Why a link action cannot be built: an `href` that points inside the app (one that is a single `{field}`, the URL the
46
+ * record holds, is fine), or `{host}` in the label of a link that goes `to` an address in the app, which has no host.
47
+ */
48
+ const linkActionError = (label, run) => {
49
+ if ("href" in run) return hrefField(run.href) ? void 0 : appHrefError(run.href);
50
+ return label.includes("{host}") ? "the label names {host}, which only a link with an href has" : void 0;
51
+ };
42
52
  //#endregion
43
- export { addressPlaceholders, appHrefError, fillAddress, hrefPlaceholders, leavesApp };
53
+ export { addressPlaceholders, appHrefError, fillAddress, hrefField, hrefPlaceholders, leavesApp, linkActionError };
@@ -10,8 +10,8 @@ import { SearchTerm, matchedSearchField, matchesSearch, searchTerms, termMatches
10
10
  import { defaultRecentLimit, navRecentErrors, statusTones } from "./nav-recent.js";
11
11
  import { PluginHost, PluginRequirement, inRange, pluginProblem } from "./plugin-version.js";
12
12
  import { redactSecrets } from "./redact-secrets.js";
13
- import { addressPlaceholders, appHrefError, fillAddress, hrefPlaceholders, leavesApp } from "./address-template.js";
13
+ import { addressPlaceholders, appHrefError, fillAddress, hrefField, hrefPlaceholders, leavesApp, linkActionError } from "./address-template.js";
14
14
  import { AddressCheck, addressErrors } from "./address-errors.js";
15
15
  import { applyAssistantEvent, emptyAssistantState } from "./assistant/apply-event.js";
16
16
  import { readEventStream } from "./assistant/event-stream.js";
17
- export { type AddressCheck, type PluginHost, type PluginRequirement, type PropShapes, type RefShape, type SearchTerm, addressErrors, addressPlaceholders, appHrefError, applyAssistantEvent, bottomBarFor, cardPhotoLimit, checkEventFilter, checkedAccept, checkedProvider, decodeKey, defaultRecentLimit, displayFields, emptyAssistantState, encodeKey, fieldRefsIn, fillAddress, hrefPlaceholders, inRange, isFieldRef, leavesApp, markFieldRefs, matchedSearchField, matchesSearch, navRecentErrors, parseSize, pluginProblem, pruneByShape, readEventStream, redactSecrets, searchTerms, shapes, statusTones, tableDisplay, termMatches, unmarkFieldRefs, webhookResources };
17
+ export { type AddressCheck, type PluginHost, type PluginRequirement, type PropShapes, type RefShape, type SearchTerm, addressErrors, addressPlaceholders, appHrefError, applyAssistantEvent, bottomBarFor, cardPhotoLimit, checkEventFilter, checkedAccept, checkedProvider, decodeKey, defaultRecentLimit, displayFields, emptyAssistantState, encodeKey, fieldRefsIn, fillAddress, hrefField, hrefPlaceholders, inRange, isFieldRef, leavesApp, linkActionError, markFieldRefs, matchedSearchField, matchesSearch, navRecentErrors, parseSize, pluginProblem, pruneByShape, readEventStream, redactSecrets, searchTerms, shapes, statusTones, tableDisplay, termMatches, unmarkFieldRefs, webhookResources };
package/dist/internal.js CHANGED
@@ -2,7 +2,7 @@ import { checkedAccept, checkedProvider, parseSize } from "./files/file-options.
2
2
  import { matchedSearchField, matchesSearch, searchTerms, termMatches } from "./search-match.js";
3
3
  import { checkEventFilter } from "./events/event-models.js";
4
4
  import { defaultRecentLimit, navRecentErrors, statusTones } from "./nav-recent.js";
5
- import { addressPlaceholders, appHrefError, fillAddress, hrefPlaceholders, leavesApp } from "./address-template.js";
5
+ import { addressPlaceholders, appHrefError, fillAddress, hrefField, hrefPlaceholders, leavesApp, linkActionError } from "./address-template.js";
6
6
  import { fieldRefsIn, isFieldRef, markFieldRefs, unmarkFieldRefs } from "./field-ref.js";
7
7
  import { cardPhotoLimit, displayFields, tableDisplay } from "./view-displays.js";
8
8
  import { bottomBarFor } from "./bottom-bar.js";
@@ -14,4 +14,4 @@ import { redactSecrets } from "./redact-secrets.js";
14
14
  import { addressErrors } from "./address-errors.js";
15
15
  import { applyAssistantEvent, emptyAssistantState } from "./assistant/apply-event.js";
16
16
  import { readEventStream } from "./assistant/event-stream.js";
17
- export { addressErrors, addressPlaceholders, appHrefError, applyAssistantEvent, bottomBarFor, cardPhotoLimit, checkEventFilter, checkedAccept, checkedProvider, decodeKey, defaultRecentLimit, displayFields, emptyAssistantState, encodeKey, fieldRefsIn, fillAddress, hrefPlaceholders, inRange, isFieldRef, leavesApp, markFieldRefs, matchedSearchField, matchesSearch, navRecentErrors, parseSize, pluginProblem, pruneByShape, readEventStream, redactSecrets, searchTerms, shapes, statusTones, tableDisplay, termMatches, unmarkFieldRefs, webhookResources };
17
+ export { addressErrors, addressPlaceholders, appHrefError, applyAssistantEvent, bottomBarFor, cardPhotoLimit, checkEventFilter, checkedAccept, checkedProvider, decodeKey, defaultRecentLimit, displayFields, emptyAssistantState, encodeKey, fieldRefsIn, fillAddress, hrefField, hrefPlaceholders, inRange, isFieldRef, leavesApp, linkActionError, markFieldRefs, matchedSearchField, matchesSearch, navRecentErrors, parseSize, pluginProblem, pruneByShape, readEventStream, redactSecrets, searchTerms, shapes, statusTones, tableDisplay, termMatches, unmarkFieldRefs, webhookResources };
package/dist/model.d.ts CHANGED
@@ -585,9 +585,12 @@ export type ActionRun = {
585
585
  /** The address, with `{field}` replaced by the record's values. */
586
586
  to: AppAddress;
587
587
  } | {
588
- /** Opens a link that leaves the app. */
588
+ /** Opens a link that leaves the app, an `https:` or `http:` one in a new tab without the referrer. */
589
589
  kind: 'link';
590
- /** The link (`https:`, `mailto:`), with `{field}` replaced by the record's values, percent-encoded. */
590
+ /**
591
+ * The link (`https:`, `mailto:`), with `{field}` replaced by the record's values, percent-encoded; or only `{field}`,
592
+ * the `https:` or `http:` URL the record holds, without which the action does not show.
593
+ */
591
594
  href: string;
592
595
  };
593
596
  /** A named action of a view (`.actions((a) => ...)`); see [Named actions](website/src/content/docs/reference/layouts.md#named-actions). */
@@ -596,7 +599,7 @@ export type ActionModel = {
596
599
  name: string;
597
600
  /** The button's label. */
598
601
  label: string;
599
- /** The button's icon: `bed`, `file-text`, `ruler`, `send` or `wallet`; another name shows a lightning bolt. */
602
+ /** The button's icon: `bed`, `bell`, `bookmark`, `external-link`, `file-text`, `heart`, `ruler`, `send` or `wallet`; another name shows a lightning bolt. */
600
603
  icon?: string;
601
604
  /** The question the button asks before it runs the action. */
602
605
  confirm?: string;
@@ -19,8 +19,10 @@ export declare const actions: {
19
19
  remove: (name: string, o: ActionOptions) => ActionModel;
20
20
  /**
21
21
  * Goes `to` an address in the app, such as `{ kind: 'record', resource: 'orders', key: '{order_id}' }`, or opens an
22
- * `href` that leaves it (`https:`, `mailto:`); `{field}` is replaced by the record's value. A path in the app is
23
- * refused as an `href`, so every address in the app is written one way.
22
+ * `href` that leaves it (`https:`, `mailto:`); `{field}` is replaced by the record's value. An `https:` or `http:` link
23
+ * opens in a new tab without the referrer. An href that is only `{field}`, such as `'{website}'`, opens the URL the
24
+ * record holds, and the button shows only when it is an `https:` or `http:` URL; `{host}` in the label becomes its host,
25
+ * as in `'View on {host}'`. A path in the app is refused as an `href`, so every address in the app is written one way.
24
26
  */
25
27
  link: (name: string, o: Omit<ActionOptions, "confirm"> & ({
26
28
  to: AppAddress;
@@ -1,4 +1,4 @@
1
- import { appHrefError } from "./address-template.js";
1
+ import { linkActionError } from "./address-template.js";
2
2
  //#region src/view-actions.ts
3
3
  const named = (name, o) => ({
4
4
  name,
@@ -25,10 +25,14 @@ const actions = {
25
25
  }),
26
26
  /**
27
27
  * Goes `to` an address in the app, such as `{ kind: 'record', resource: 'orders', key: '{order_id}' }`, or opens an
28
- * `href` that leaves it (`https:`, `mailto:`); `{field}` is replaced by the record's value. A path in the app is
29
- * refused as an `href`, so every address in the app is written one way.
28
+ * `href` that leaves it (`https:`, `mailto:`); `{field}` is replaced by the record's value. An `https:` or `http:` link
29
+ * opens in a new tab without the referrer. An href that is only `{field}`, such as `'{website}'`, opens the URL the
30
+ * record holds, and the button shows only when it is an `https:` or `http:` URL; `{host}` in the label becomes its host,
31
+ * as in `'View on {host}'`. A path in the app is refused as an `href`, so every address in the app is written one way.
30
32
  */
31
33
  link: (name, o) => {
34
+ const error = linkActionError(o.label, o);
35
+ if (error) throw new Error(`Action "${name}": ${error}`);
32
36
  if ("to" in o) return {
33
37
  ...named(name, o),
34
38
  run: {
@@ -36,8 +40,6 @@ const actions = {
36
40
  to: structuredClone(o.to)
37
41
  }
38
42
  };
39
- const error = appHrefError(o.href);
40
- if (error) throw new Error(`Action "${name}": ${error}`);
41
43
  return {
42
44
  ...named(name, o),
43
45
  run: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@protobase/schema",
3
- "version": "0.15.2",
3
+ "version": "0.15.3",
4
4
  "description": "Resource, field, view and access definitions for Protobase configs.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {