pte-interpolation-core 1.1.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/LICENSE +21 -0
- package/README.md +141 -0
- package/dist/index.cjs +28 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +42 -0
- package/dist/index.d.ts +42 -0
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -0
- package/package.json +53 -0
- package/src/__tests__/extractVariableKeys.test.ts +46 -0
- package/src/__tests__/fixtures.ts +94 -0
- package/src/__tests__/interpolateToString.test.ts +59 -0
- package/src/constants.ts +2 -0
- package/src/extractVariableKeys.ts +22 -0
- package/src/index.ts +10 -0
- package/src/interpolateToString.ts +30 -0
- package/src/types.ts +27 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jordan Lawrence
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# pte-interpolation-core
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/pte-interpolation-core)
|
|
4
|
+
|
|
5
|
+
Framework-agnostic utilities for [Portable Text](https://portabletext.org/) variable interpolation. Extract variable keys from PTE blocks and resolve them to plain strings - with zero dependencies.
|
|
6
|
+
|
|
7
|
+
Part of [sanity-pte-interpolation](https://github.com/jordanl17/sanity-pte-interpolation). For adding variable picker inline blocks to Sanity Studio, see [`sanity-plugin-pte-interpolation`](https://www.npmjs.com/package/sanity-plugin-pte-interpolation). For React rendering with rich text output, see [`pte-interpolation-react`](https://www.npmjs.com/package/pte-interpolation-react).
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
npm install pte-interpolation-core
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
No peer dependencies required.
|
|
16
|
+
|
|
17
|
+
## When to Use
|
|
18
|
+
|
|
19
|
+
Use this package when you need plain string output from interpolated Portable Text - without a React dependency. Common use cases include:
|
|
20
|
+
|
|
21
|
+
- Email templates (server-side rendering)
|
|
22
|
+
- PDF generation
|
|
23
|
+
- Node.js scripts and background jobs
|
|
24
|
+
- SMS or push notification text
|
|
25
|
+
- Any non-React framework (Vue, Svelte, Angular, etc.)
|
|
26
|
+
|
|
27
|
+
If you are rendering in React and want rich text output, use [`pte-interpolation-react`](https://www.npmjs.com/package/pte-interpolation-react) instead - it re-exports everything from this package, so you get both APIs without installing core separately.
|
|
28
|
+
|
|
29
|
+
## Usage
|
|
30
|
+
|
|
31
|
+
### Extract variable keys
|
|
32
|
+
|
|
33
|
+
`extractVariableKeys` returns the unique variable keys found in an array of PTE blocks, in first-occurrence order:
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import {extractVariableKeys} from 'pte-interpolation-core'
|
|
37
|
+
|
|
38
|
+
const keys = extractVariableKeys(blocks)
|
|
39
|
+
// ['firstName', 'email']
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Interpolate to a plain string
|
|
43
|
+
|
|
44
|
+
`interpolateToString` resolves PTE blocks to a plain string, substituting variable blocks with the provided values:
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
import {interpolateToString} from 'pte-interpolation-core'
|
|
48
|
+
|
|
49
|
+
const text = interpolateToString(blocks, {
|
|
50
|
+
firstName: 'Alice',
|
|
51
|
+
email: 'alice@example.com',
|
|
52
|
+
})
|
|
53
|
+
// "Hello, Alice! Your email is alice@example.com."
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Custom fallback for missing values
|
|
57
|
+
|
|
58
|
+
By default, unresolved variables render as `{variableKey}` (e.g. `{firstName}`). Provide a fallback function to customise this:
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
const text = interpolateToString(blocks, {}, (variableKey) => `[${variableKey}]`)
|
|
62
|
+
// "Hello, [firstName]! Your email is [email]."
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Authoring Variables in Sanity Studio
|
|
66
|
+
|
|
67
|
+
This package handles **resolution** of variable blocks that already exist in Portable Text. To add the variable picker to Sanity Studio's Portable Text Editor, use [`sanity-plugin-pte-interpolation`](https://www.npmjs.com/package/sanity-plugin-pte-interpolation):
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
import {interpolationVariables} from 'sanity-plugin-pte-interpolation'
|
|
71
|
+
|
|
72
|
+
defineField({
|
|
73
|
+
name: 'body',
|
|
74
|
+
type: 'array',
|
|
75
|
+
of: [
|
|
76
|
+
interpolationVariables([
|
|
77
|
+
{id: 'firstName', name: 'First name'},
|
|
78
|
+
{id: 'email', name: 'Email address'},
|
|
79
|
+
]),
|
|
80
|
+
],
|
|
81
|
+
})
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Data Shape
|
|
85
|
+
|
|
86
|
+
Variable blocks in stored Portable Text look like this:
|
|
87
|
+
|
|
88
|
+
```json
|
|
89
|
+
{
|
|
90
|
+
"_type": "block",
|
|
91
|
+
"children": [
|
|
92
|
+
{"_type": "span", "text": "Hello, "},
|
|
93
|
+
{"_type": "pteInterpolationVariable", "variableKey": "firstName"},
|
|
94
|
+
{"_type": "span", "text": "!"}
|
|
95
|
+
]
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
The `variableKey` maps to the `id` defined in the Studio variable definitions and the keys in the values record passed to `interpolateToString`.
|
|
100
|
+
|
|
101
|
+
## API Reference
|
|
102
|
+
|
|
103
|
+
### `extractVariableKeys(blocks)`
|
|
104
|
+
|
|
105
|
+
Returns the unique variable keys from an array of PTE blocks, in first-occurrence order.
|
|
106
|
+
|
|
107
|
+
| Parameter | Type | Description |
|
|
108
|
+
| --------- | ------------------------- | ---------------------------- |
|
|
109
|
+
| `blocks` | `PortableTextBlockLike[]` | Portable Text blocks to scan |
|
|
110
|
+
|
|
111
|
+
Returns `string[]`.
|
|
112
|
+
|
|
113
|
+
### `interpolateToString(blocks, values, fallback?)`
|
|
114
|
+
|
|
115
|
+
Resolves PTE blocks to a plain string, replacing variable blocks with the corresponding values. Multiple blocks are joined with newlines.
|
|
116
|
+
|
|
117
|
+
| Parameter | Type | Description |
|
|
118
|
+
| ---------- | --------------------------------- | ------------------------------------------------------------------------ |
|
|
119
|
+
| `blocks` | `PortableTextBlockLike[]` | Portable Text blocks to resolve |
|
|
120
|
+
| `values` | `Record<string, string>` | Map of variable IDs to their resolved values |
|
|
121
|
+
| `fallback` | `(variableKey: string) => string` | Optional function for unresolved variables (defaults to `{variableKey}`) |
|
|
122
|
+
|
|
123
|
+
Returns `string`.
|
|
124
|
+
|
|
125
|
+
### `VARIABLE_TYPE_PREFIX`
|
|
126
|
+
|
|
127
|
+
The constant `'pteInterpolationVariable'` - the `_type` string used for variable inline blocks in stored Portable Text. Exported for advanced use cases.
|
|
128
|
+
|
|
129
|
+
### Types
|
|
130
|
+
|
|
131
|
+
| Type | Description |
|
|
132
|
+
| ------------------------------- | ----------------------------------------------------- |
|
|
133
|
+
| `PortableTextBlockLike` | Minimal block shape with optional `children` array |
|
|
134
|
+
| `PortableTextChild` | A child node within a block (span or variable) |
|
|
135
|
+
| `PteInterpolationVariableBlock` | A variable inline block with `variableKey` |
|
|
136
|
+
| `InterpolationValues` | `Record<string, string>` - variable ID to value map |
|
|
137
|
+
| `InterpolationFallback` | `(variableKey: string) => string` - fallback function |
|
|
138
|
+
|
|
139
|
+
## License
|
|
140
|
+
|
|
141
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: !0 });
|
|
3
|
+
const VARIABLE_TYPE_PREFIX = "pteInterpolationVariable";
|
|
4
|
+
function extractVariableKeys(blocks) {
|
|
5
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6
|
+
return blocks.reduce((keys, block) => (block.children ?? []).reduce((accumulated, child) => {
|
|
7
|
+
if (child._type !== VARIABLE_TYPE_PREFIX) return accumulated;
|
|
8
|
+
const variableKey = child.variableKey;
|
|
9
|
+
return typeof variableKey != "string" || seen.has(variableKey) ? accumulated : (seen.add(variableKey), [...accumulated, variableKey]);
|
|
10
|
+
}, keys), []);
|
|
11
|
+
}
|
|
12
|
+
function defaultFallback(variableKey) {
|
|
13
|
+
return `{${variableKey}}`;
|
|
14
|
+
}
|
|
15
|
+
function interpolateToString(blocks, values, fallback = defaultFallback) {
|
|
16
|
+
return blocks.map((block) => (block.children ?? []).map((child) => {
|
|
17
|
+
if (child._type === VARIABLE_TYPE_PREFIX) {
|
|
18
|
+
const variableKey = child.variableKey;
|
|
19
|
+
return values[variableKey] !== void 0 ? values[variableKey] : fallback(variableKey);
|
|
20
|
+
}
|
|
21
|
+
return typeof child.text == "string" ? child.text : "";
|
|
22
|
+
}).join("")).join(`
|
|
23
|
+
`);
|
|
24
|
+
}
|
|
25
|
+
exports.VARIABLE_TYPE_PREFIX = VARIABLE_TYPE_PREFIX;
|
|
26
|
+
exports.extractVariableKeys = extractVariableKeys;
|
|
27
|
+
exports.interpolateToString = interpolateToString;
|
|
28
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":["../src/constants.ts","../src/extractVariableKeys.ts","../src/interpolateToString.ts"],"sourcesContent":["/** @public */\nexport const VARIABLE_TYPE_PREFIX = 'pteInterpolationVariable'\n","import {VARIABLE_TYPE_PREFIX} from './constants'\nimport type {PortableTextBlockLike} from './types'\n\n/** @public */\nexport function extractVariableKeys(blocks: PortableTextBlockLike[]): string[] {\n const seen = new Set<string>()\n\n return blocks.reduce<string[]>((keys, block) => {\n const children = block.children ?? []\n\n return children.reduce((accumulated, child) => {\n if (child._type !== VARIABLE_TYPE_PREFIX) return accumulated\n\n const variableKey = child.variableKey\n if (typeof variableKey !== 'string') return accumulated\n if (seen.has(variableKey)) return accumulated\n\n seen.add(variableKey)\n return [...accumulated, variableKey]\n }, keys)\n }, [])\n}\n","import {VARIABLE_TYPE_PREFIX} from './constants'\nimport type {InterpolationFallback, InterpolationValues, PortableTextBlockLike} from './types'\n\nfunction defaultFallback(variableKey: string): string {\n return `{${variableKey}}`\n}\n\n/** @public */\nexport function interpolateToString(\n blocks: PortableTextBlockLike[],\n values: InterpolationValues,\n fallback: InterpolationFallback = defaultFallback,\n): string {\n return blocks\n .map((block) => {\n const children = block.children ?? []\n\n return children\n .map((child) => {\n if (child._type === VARIABLE_TYPE_PREFIX) {\n const variableKey = child.variableKey as string\n return values[variableKey] !== undefined ? values[variableKey] : fallback(variableKey)\n }\n\n return typeof child.text === 'string' ? child.text : ''\n })\n .join('')\n })\n .join('\\n')\n}\n"],"names":[],"mappings":";;AACO,MAAM,uBAAuB;ACG7B,SAAS,oBAAoB,QAA2C;AAC7E,QAAM,2BAAW,IAAA;AAEjB,SAAO,OAAO,OAAiB,CAAC,MAAM,WACnB,MAAM,YAAY,CAAA,GAEnB,OAAO,CAAC,aAAa,UAAU;AAC7C,QAAI,MAAM,UAAU,qBAAsB,QAAO;AAEjD,UAAM,cAAc,MAAM;AAE1B,WADI,OAAO,eAAgB,YACvB,KAAK,IAAI,WAAW,IAAU,eAElC,KAAK,IAAI,WAAW,GACb,CAAC,GAAG,aAAa,WAAW;AAAA,EACrC,GAAG,IAAI,GACN,EAAE;AACP;AClBA,SAAS,gBAAgB,aAA6B;AACpD,SAAO,IAAI,WAAW;AACxB;AAGO,SAAS,oBACd,QACA,QACA,WAAkC,iBAC1B;AACR,SAAO,OACJ,IAAI,CAAC,WACa,MAAM,YAAY,CAAA,GAGhC,IAAI,CAAC,UAAU;AACd,QAAI,MAAM,UAAU,sBAAsB;AACxC,YAAM,cAAc,MAAM;AAC1B,aAAO,OAAO,WAAW,MAAM,SAAY,OAAO,WAAW,IAAI,SAAS,WAAW;AAAA,IACvF;AAEA,WAAO,OAAO,MAAM,QAAS,WAAW,MAAM,OAAO;AAAA,EACvD,CAAC,EACA,KAAK,EAAE,CACX,EACA,KAAK;AAAA,CAAI;AACd;;;;"}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/** @public */
|
|
2
|
+
export declare function extractVariableKeys(blocks: PortableTextBlockLike[]): string[]
|
|
3
|
+
|
|
4
|
+
/** @public */
|
|
5
|
+
export declare function interpolateToString(
|
|
6
|
+
blocks: PortableTextBlockLike[],
|
|
7
|
+
values: InterpolationValues,
|
|
8
|
+
fallback?: InterpolationFallback,
|
|
9
|
+
): string
|
|
10
|
+
|
|
11
|
+
/** @public */
|
|
12
|
+
export declare type InterpolationFallback = (variableKey: string) => string
|
|
13
|
+
|
|
14
|
+
/** @public */
|
|
15
|
+
export declare type InterpolationValues = Record<string, string>
|
|
16
|
+
|
|
17
|
+
/** @public */
|
|
18
|
+
export declare interface PortableTextBlockLike {
|
|
19
|
+
_type: string
|
|
20
|
+
_key?: string
|
|
21
|
+
children?: PortableTextChild[]
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** @public */
|
|
25
|
+
export declare interface PortableTextChild {
|
|
26
|
+
_type: string
|
|
27
|
+
_key?: string
|
|
28
|
+
text?: string
|
|
29
|
+
variableKey?: string
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** @public */
|
|
33
|
+
export declare interface PteInterpolationVariableBlock {
|
|
34
|
+
_type: 'pteInterpolationVariable'
|
|
35
|
+
_key: string
|
|
36
|
+
variableKey: string
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** @public */
|
|
40
|
+
export declare const VARIABLE_TYPE_PREFIX = 'pteInterpolationVariable'
|
|
41
|
+
|
|
42
|
+
export {}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/** @public */
|
|
2
|
+
export declare function extractVariableKeys(blocks: PortableTextBlockLike[]): string[]
|
|
3
|
+
|
|
4
|
+
/** @public */
|
|
5
|
+
export declare function interpolateToString(
|
|
6
|
+
blocks: PortableTextBlockLike[],
|
|
7
|
+
values: InterpolationValues,
|
|
8
|
+
fallback?: InterpolationFallback,
|
|
9
|
+
): string
|
|
10
|
+
|
|
11
|
+
/** @public */
|
|
12
|
+
export declare type InterpolationFallback = (variableKey: string) => string
|
|
13
|
+
|
|
14
|
+
/** @public */
|
|
15
|
+
export declare type InterpolationValues = Record<string, string>
|
|
16
|
+
|
|
17
|
+
/** @public */
|
|
18
|
+
export declare interface PortableTextBlockLike {
|
|
19
|
+
_type: string
|
|
20
|
+
_key?: string
|
|
21
|
+
children?: PortableTextChild[]
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** @public */
|
|
25
|
+
export declare interface PortableTextChild {
|
|
26
|
+
_type: string
|
|
27
|
+
_key?: string
|
|
28
|
+
text?: string
|
|
29
|
+
variableKey?: string
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** @public */
|
|
33
|
+
export declare interface PteInterpolationVariableBlock {
|
|
34
|
+
_type: 'pteInterpolationVariable'
|
|
35
|
+
_key: string
|
|
36
|
+
variableKey: string
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** @public */
|
|
40
|
+
export declare const VARIABLE_TYPE_PREFIX = 'pteInterpolationVariable'
|
|
41
|
+
|
|
42
|
+
export {}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
const VARIABLE_TYPE_PREFIX = "pteInterpolationVariable";
|
|
2
|
+
function extractVariableKeys(blocks) {
|
|
3
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4
|
+
return blocks.reduce((keys, block) => (block.children ?? []).reduce((accumulated, child) => {
|
|
5
|
+
if (child._type !== VARIABLE_TYPE_PREFIX) return accumulated;
|
|
6
|
+
const variableKey = child.variableKey;
|
|
7
|
+
return typeof variableKey != "string" || seen.has(variableKey) ? accumulated : (seen.add(variableKey), [...accumulated, variableKey]);
|
|
8
|
+
}, keys), []);
|
|
9
|
+
}
|
|
10
|
+
function defaultFallback(variableKey) {
|
|
11
|
+
return `{${variableKey}}`;
|
|
12
|
+
}
|
|
13
|
+
function interpolateToString(blocks, values, fallback = defaultFallback) {
|
|
14
|
+
return blocks.map((block) => (block.children ?? []).map((child) => {
|
|
15
|
+
if (child._type === VARIABLE_TYPE_PREFIX) {
|
|
16
|
+
const variableKey = child.variableKey;
|
|
17
|
+
return values[variableKey] !== void 0 ? values[variableKey] : fallback(variableKey);
|
|
18
|
+
}
|
|
19
|
+
return typeof child.text == "string" ? child.text : "";
|
|
20
|
+
}).join("")).join(`
|
|
21
|
+
`);
|
|
22
|
+
}
|
|
23
|
+
export {
|
|
24
|
+
VARIABLE_TYPE_PREFIX,
|
|
25
|
+
extractVariableKeys,
|
|
26
|
+
interpolateToString
|
|
27
|
+
};
|
|
28
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/constants.ts","../src/extractVariableKeys.ts","../src/interpolateToString.ts"],"sourcesContent":["/** @public */\nexport const VARIABLE_TYPE_PREFIX = 'pteInterpolationVariable'\n","import {VARIABLE_TYPE_PREFIX} from './constants'\nimport type {PortableTextBlockLike} from './types'\n\n/** @public */\nexport function extractVariableKeys(blocks: PortableTextBlockLike[]): string[] {\n const seen = new Set<string>()\n\n return blocks.reduce<string[]>((keys, block) => {\n const children = block.children ?? []\n\n return children.reduce((accumulated, child) => {\n if (child._type !== VARIABLE_TYPE_PREFIX) return accumulated\n\n const variableKey = child.variableKey\n if (typeof variableKey !== 'string') return accumulated\n if (seen.has(variableKey)) return accumulated\n\n seen.add(variableKey)\n return [...accumulated, variableKey]\n }, keys)\n }, [])\n}\n","import {VARIABLE_TYPE_PREFIX} from './constants'\nimport type {InterpolationFallback, InterpolationValues, PortableTextBlockLike} from './types'\n\nfunction defaultFallback(variableKey: string): string {\n return `{${variableKey}}`\n}\n\n/** @public */\nexport function interpolateToString(\n blocks: PortableTextBlockLike[],\n values: InterpolationValues,\n fallback: InterpolationFallback = defaultFallback,\n): string {\n return blocks\n .map((block) => {\n const children = block.children ?? []\n\n return children\n .map((child) => {\n if (child._type === VARIABLE_TYPE_PREFIX) {\n const variableKey = child.variableKey as string\n return values[variableKey] !== undefined ? values[variableKey] : fallback(variableKey)\n }\n\n return typeof child.text === 'string' ? child.text : ''\n })\n .join('')\n })\n .join('\\n')\n}\n"],"names":[],"mappings":"AACO,MAAM,uBAAuB;ACG7B,SAAS,oBAAoB,QAA2C;AAC7E,QAAM,2BAAW,IAAA;AAEjB,SAAO,OAAO,OAAiB,CAAC,MAAM,WACnB,MAAM,YAAY,CAAA,GAEnB,OAAO,CAAC,aAAa,UAAU;AAC7C,QAAI,MAAM,UAAU,qBAAsB,QAAO;AAEjD,UAAM,cAAc,MAAM;AAE1B,WADI,OAAO,eAAgB,YACvB,KAAK,IAAI,WAAW,IAAU,eAElC,KAAK,IAAI,WAAW,GACb,CAAC,GAAG,aAAa,WAAW;AAAA,EACrC,GAAG,IAAI,GACN,EAAE;AACP;AClBA,SAAS,gBAAgB,aAA6B;AACpD,SAAO,IAAI,WAAW;AACxB;AAGO,SAAS,oBACd,QACA,QACA,WAAkC,iBAC1B;AACR,SAAO,OACJ,IAAI,CAAC,WACa,MAAM,YAAY,CAAA,GAGhC,IAAI,CAAC,UAAU;AACd,QAAI,MAAM,UAAU,sBAAsB;AACxC,YAAM,cAAc,MAAM;AAC1B,aAAO,OAAO,WAAW,MAAM,SAAY,OAAO,WAAW,IAAI,SAAS,WAAW;AAAA,IACvF;AAEA,WAAO,OAAO,MAAM,QAAS,WAAW,MAAM,OAAO;AAAA,EACvD,CAAC,EACA,KAAK,EAAE,CACX,EACA,KAAK;AAAA,CAAI;AACd;"}
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pte-interpolation-core",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"description": "Framework-agnostic utilities for Portable Text variable interpolation",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"portable-text",
|
|
9
|
+
"interpolation",
|
|
10
|
+
"variables"
|
|
11
|
+
],
|
|
12
|
+
"homepage": "https://github.com/jordanl17/sanity-pte-interpolation#readme",
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git+https://github.com/jordanl17/sanity-pte-interpolation.git",
|
|
16
|
+
"directory": "packages/pte-interpolation-core"
|
|
17
|
+
},
|
|
18
|
+
"bugs": {
|
|
19
|
+
"url": "https://github.com/jordanl17/sanity-pte-interpolation/issues"
|
|
20
|
+
},
|
|
21
|
+
"sideEffects": false,
|
|
22
|
+
"exports": {
|
|
23
|
+
".": {
|
|
24
|
+
"import": "./dist/index.js",
|
|
25
|
+
"require": "./dist/index.cjs",
|
|
26
|
+
"default": "./dist/index.js"
|
|
27
|
+
},
|
|
28
|
+
"./package.json": "./package.json"
|
|
29
|
+
},
|
|
30
|
+
"main": "./dist/index.cjs",
|
|
31
|
+
"module": "./dist/index.js",
|
|
32
|
+
"types": "./dist/index.d.ts",
|
|
33
|
+
"files": [
|
|
34
|
+
"src",
|
|
35
|
+
"dist"
|
|
36
|
+
],
|
|
37
|
+
"browserslist": "extends @sanity/browserslist-config",
|
|
38
|
+
"publishConfig": {
|
|
39
|
+
"access": "public"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@sanity/pkg-utils": "^10.4.4",
|
|
43
|
+
"rimraf": "^6.1.3",
|
|
44
|
+
"typescript": "~5.9.3"
|
|
45
|
+
},
|
|
46
|
+
"scripts": {
|
|
47
|
+
"build": "pkg-utils build --strict --check --clean",
|
|
48
|
+
"clean": "rimraf dist",
|
|
49
|
+
"link-watch": "pkg-utils watch --tsconfig tsconfig.build.json",
|
|
50
|
+
"type-check": "tsc --noEmit",
|
|
51
|
+
"yalc:publish": "npx yalc publish"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import {describe, expect, it} from 'vitest'
|
|
2
|
+
import {extractVariableKeys} from '../extractVariableKeys'
|
|
3
|
+
import {
|
|
4
|
+
blockWithNoChildren,
|
|
5
|
+
blockWithNonStringVariableKey,
|
|
6
|
+
consecutiveVariablesBlock,
|
|
7
|
+
duplicateVariableBlock,
|
|
8
|
+
emptyBlocksContent,
|
|
9
|
+
multipleVariablesBlock,
|
|
10
|
+
plainTextBlock,
|
|
11
|
+
singleVariableBlock,
|
|
12
|
+
} from './fixtures'
|
|
13
|
+
|
|
14
|
+
describe('extractVariableKeys', () => {
|
|
15
|
+
it('returns an empty array for empty blocks', () => {
|
|
16
|
+
expect(extractVariableKeys(emptyBlocksContent)).toEqual([])
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('returns an empty array when no variables exist', () => {
|
|
20
|
+
expect(extractVariableKeys(plainTextBlock)).toEqual([])
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('extracts a single variable key', () => {
|
|
24
|
+
expect(extractVariableKeys(singleVariableBlock)).toEqual(['firstName'])
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('extracts multiple variable keys in order', () => {
|
|
28
|
+
expect(extractVariableKeys(multipleVariablesBlock)).toEqual(['firstName', 'lastName', 'email'])
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('extracts consecutive variable keys', () => {
|
|
32
|
+
expect(extractVariableKeys(consecutiveVariablesBlock)).toEqual(['firstName', 'lastName'])
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('deduplicates variable keys preserving first-occurrence order', () => {
|
|
36
|
+
expect(extractVariableKeys(duplicateVariableBlock)).toEqual(['firstName'])
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('handles blocks with no children property', () => {
|
|
40
|
+
expect(extractVariableKeys(blockWithNoChildren)).toEqual([])
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('skips children with non-string variableKey', () => {
|
|
44
|
+
expect(extractVariableKeys(blockWithNonStringVariableKey)).toEqual([])
|
|
45
|
+
})
|
|
46
|
+
})
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type {PortableTextBlockLike} from '../types'
|
|
2
|
+
|
|
3
|
+
export const singleVariableBlock: PortableTextBlockLike[] = [
|
|
4
|
+
{
|
|
5
|
+
_type: 'block',
|
|
6
|
+
_key: 'block-1',
|
|
7
|
+
children: [
|
|
8
|
+
{_type: 'span', _key: 'span-1', text: 'Hello, '},
|
|
9
|
+
{_type: 'pteInterpolationVariable', _key: 'var-1', variableKey: 'firstName'},
|
|
10
|
+
{_type: 'span', _key: 'span-2', text: '!'},
|
|
11
|
+
],
|
|
12
|
+
},
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
export const multipleVariablesBlock: PortableTextBlockLike[] = [
|
|
16
|
+
{
|
|
17
|
+
_type: 'block',
|
|
18
|
+
_key: 'block-1',
|
|
19
|
+
children: [
|
|
20
|
+
{_type: 'span', _key: 'span-1', text: 'Name: '},
|
|
21
|
+
{_type: 'pteInterpolationVariable', _key: 'var-1', variableKey: 'firstName'},
|
|
22
|
+
{_type: 'span', _key: 'span-2', text: ' '},
|
|
23
|
+
{_type: 'pteInterpolationVariable', _key: 'var-2', variableKey: 'lastName'},
|
|
24
|
+
{_type: 'span', _key: 'span-3', text: ', Email: '},
|
|
25
|
+
{_type: 'pteInterpolationVariable', _key: 'var-3', variableKey: 'email'},
|
|
26
|
+
],
|
|
27
|
+
},
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
export const plainTextBlock: PortableTextBlockLike[] = [
|
|
31
|
+
{
|
|
32
|
+
_type: 'block',
|
|
33
|
+
_key: 'block-1',
|
|
34
|
+
children: [{_type: 'span', _key: 'span-1', text: 'No variables here.'}],
|
|
35
|
+
},
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
export const consecutiveVariablesBlock: PortableTextBlockLike[] = [
|
|
39
|
+
{
|
|
40
|
+
_type: 'block',
|
|
41
|
+
_key: 'block-1',
|
|
42
|
+
children: [
|
|
43
|
+
{_type: 'pteInterpolationVariable', _key: 'var-1', variableKey: 'firstName'},
|
|
44
|
+
{_type: 'pteInterpolationVariable', _key: 'var-2', variableKey: 'lastName'},
|
|
45
|
+
],
|
|
46
|
+
},
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
export const multiBlockContent: PortableTextBlockLike[] = [
|
|
50
|
+
{
|
|
51
|
+
_type: 'block',
|
|
52
|
+
_key: 'block-1',
|
|
53
|
+
children: [
|
|
54
|
+
{_type: 'span', _key: 'span-1', text: 'Dear '},
|
|
55
|
+
{_type: 'pteInterpolationVariable', _key: 'var-1', variableKey: 'firstName'},
|
|
56
|
+
{_type: 'span', _key: 'span-2', text: ','},
|
|
57
|
+
],
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
_type: 'block',
|
|
61
|
+
_key: 'block-2',
|
|
62
|
+
children: [
|
|
63
|
+
{_type: 'span', _key: 'span-3', text: 'Your email is '},
|
|
64
|
+
{_type: 'pteInterpolationVariable', _key: 'var-2', variableKey: 'email'},
|
|
65
|
+
{_type: 'span', _key: 'span-4', text: '.'},
|
|
66
|
+
],
|
|
67
|
+
},
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
export const duplicateVariableBlock: PortableTextBlockLike[] = [
|
|
71
|
+
{
|
|
72
|
+
_type: 'block',
|
|
73
|
+
_key: 'block-1',
|
|
74
|
+
children: [
|
|
75
|
+
{_type: 'pteInterpolationVariable', _key: 'var-1', variableKey: 'firstName'},
|
|
76
|
+
{_type: 'span', _key: 'span-1', text: ' and '},
|
|
77
|
+
{_type: 'pteInterpolationVariable', _key: 'var-2', variableKey: 'firstName'},
|
|
78
|
+
],
|
|
79
|
+
},
|
|
80
|
+
]
|
|
81
|
+
|
|
82
|
+
export const blockWithNoChildren: PortableTextBlockLike[] = [{_type: 'block', _key: 'block-1'}]
|
|
83
|
+
|
|
84
|
+
export const blockWithNonStringVariableKey: PortableTextBlockLike[] = [
|
|
85
|
+
{
|
|
86
|
+
_type: 'block',
|
|
87
|
+
_key: 'block-1',
|
|
88
|
+
children: [
|
|
89
|
+
{_type: 'pteInterpolationVariable', _key: 'var-1', variableKey: 123 as unknown as string},
|
|
90
|
+
],
|
|
91
|
+
},
|
|
92
|
+
]
|
|
93
|
+
|
|
94
|
+
export const emptyBlocksContent: PortableTextBlockLike[] = []
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import {describe, expect, it} from 'vitest'
|
|
2
|
+
import {interpolateToString} from '../interpolateToString'
|
|
3
|
+
import {
|
|
4
|
+
consecutiveVariablesBlock,
|
|
5
|
+
emptyBlocksContent,
|
|
6
|
+
multiBlockContent,
|
|
7
|
+
multipleVariablesBlock,
|
|
8
|
+
plainTextBlock,
|
|
9
|
+
singleVariableBlock,
|
|
10
|
+
} from './fixtures'
|
|
11
|
+
|
|
12
|
+
describe('interpolateToString', () => {
|
|
13
|
+
it('returns an empty string for empty blocks', () => {
|
|
14
|
+
expect(interpolateToString(emptyBlocksContent, {})).toBe('')
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
it('returns plain text unchanged', () => {
|
|
18
|
+
expect(interpolateToString(plainTextBlock, {})).toBe('No variables here.')
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it('resolves a single variable from values', () => {
|
|
22
|
+
expect(interpolateToString(singleVariableBlock, {firstName: 'Alice'})).toBe('Hello, Alice!')
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
it('uses default fallback for missing variables', () => {
|
|
26
|
+
expect(interpolateToString(singleVariableBlock, {})).toBe('Hello, {firstName}!')
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('uses a custom fallback for missing variables', () => {
|
|
30
|
+
const customFallback = (variableKey: string) => `[${variableKey}]`
|
|
31
|
+
expect(interpolateToString(singleVariableBlock, {}, customFallback)).toBe('Hello, [firstName]!')
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it('uses the value when it is an empty string', () => {
|
|
35
|
+
expect(interpolateToString(singleVariableBlock, {firstName: ''})).toBe('Hello, !')
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('resolves consecutive variables', () => {
|
|
39
|
+
expect(
|
|
40
|
+
interpolateToString(consecutiveVariablesBlock, {firstName: 'Alice', lastName: 'Smith'}),
|
|
41
|
+
).toBe('AliceSmith')
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('joins multiple blocks with newlines', () => {
|
|
45
|
+
expect(
|
|
46
|
+
interpolateToString(multiBlockContent, {firstName: 'Alice', email: 'alice@example.com'}),
|
|
47
|
+
).toBe('Dear Alice,\nYour email is alice@example.com.')
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it('resolves multiple variables in one block', () => {
|
|
51
|
+
expect(
|
|
52
|
+
interpolateToString(multipleVariablesBlock, {
|
|
53
|
+
firstName: 'Alice',
|
|
54
|
+
lastName: 'Smith',
|
|
55
|
+
email: 'alice@example.com',
|
|
56
|
+
}),
|
|
57
|
+
).toBe('Name: Alice Smith, Email: alice@example.com')
|
|
58
|
+
})
|
|
59
|
+
})
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import {VARIABLE_TYPE_PREFIX} from './constants'
|
|
2
|
+
import type {PortableTextBlockLike} from './types'
|
|
3
|
+
|
|
4
|
+
/** @public */
|
|
5
|
+
export function extractVariableKeys(blocks: PortableTextBlockLike[]): string[] {
|
|
6
|
+
const seen = new Set<string>()
|
|
7
|
+
|
|
8
|
+
return blocks.reduce<string[]>((keys, block) => {
|
|
9
|
+
const children = block.children ?? []
|
|
10
|
+
|
|
11
|
+
return children.reduce((accumulated, child) => {
|
|
12
|
+
if (child._type !== VARIABLE_TYPE_PREFIX) return accumulated
|
|
13
|
+
|
|
14
|
+
const variableKey = child.variableKey
|
|
15
|
+
if (typeof variableKey !== 'string') return accumulated
|
|
16
|
+
if (seen.has(variableKey)) return accumulated
|
|
17
|
+
|
|
18
|
+
seen.add(variableKey)
|
|
19
|
+
return [...accumulated, variableKey]
|
|
20
|
+
}, keys)
|
|
21
|
+
}, [])
|
|
22
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export {VARIABLE_TYPE_PREFIX} from './constants'
|
|
2
|
+
export {extractVariableKeys} from './extractVariableKeys'
|
|
3
|
+
export {interpolateToString} from './interpolateToString'
|
|
4
|
+
export type {
|
|
5
|
+
InterpolationFallback,
|
|
6
|
+
InterpolationValues,
|
|
7
|
+
PortableTextBlockLike,
|
|
8
|
+
PortableTextChild,
|
|
9
|
+
PteInterpolationVariableBlock,
|
|
10
|
+
} from './types'
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import {VARIABLE_TYPE_PREFIX} from './constants'
|
|
2
|
+
import type {InterpolationFallback, InterpolationValues, PortableTextBlockLike} from './types'
|
|
3
|
+
|
|
4
|
+
function defaultFallback(variableKey: string): string {
|
|
5
|
+
return `{${variableKey}}`
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/** @public */
|
|
9
|
+
export function interpolateToString(
|
|
10
|
+
blocks: PortableTextBlockLike[],
|
|
11
|
+
values: InterpolationValues,
|
|
12
|
+
fallback: InterpolationFallback = defaultFallback,
|
|
13
|
+
): string {
|
|
14
|
+
return blocks
|
|
15
|
+
.map((block) => {
|
|
16
|
+
const children = block.children ?? []
|
|
17
|
+
|
|
18
|
+
return children
|
|
19
|
+
.map((child) => {
|
|
20
|
+
if (child._type === VARIABLE_TYPE_PREFIX) {
|
|
21
|
+
const variableKey = child.variableKey as string
|
|
22
|
+
return values[variableKey] !== undefined ? values[variableKey] : fallback(variableKey)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return typeof child.text === 'string' ? child.text : ''
|
|
26
|
+
})
|
|
27
|
+
.join('')
|
|
28
|
+
})
|
|
29
|
+
.join('\n')
|
|
30
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** @public */
|
|
2
|
+
export interface PortableTextChild {
|
|
3
|
+
_type: string
|
|
4
|
+
_key?: string
|
|
5
|
+
text?: string
|
|
6
|
+
variableKey?: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** @public */
|
|
10
|
+
export interface PortableTextBlockLike {
|
|
11
|
+
_type: string
|
|
12
|
+
_key?: string
|
|
13
|
+
children?: PortableTextChild[]
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** @public */
|
|
17
|
+
export interface PteInterpolationVariableBlock {
|
|
18
|
+
_type: 'pteInterpolationVariable'
|
|
19
|
+
_key: string
|
|
20
|
+
variableKey: string
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** @public */
|
|
24
|
+
export type InterpolationValues = Record<string, string>
|
|
25
|
+
|
|
26
|
+
/** @public */
|
|
27
|
+
export type InterpolationFallback = (variableKey: string) => string
|