@seedprotocol/mapping 0.5.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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ # Changelog
2
+
3
+ ## 0.5.1
4
+
5
+ - Initial Phase 1 release: headless mapping core, markdown + RSS adapters, `FieldMapper` UI.
package/README.md ADDED
@@ -0,0 +1,105 @@
1
+ # @seedprotocol/mapping
2
+
3
+ Source → Seed model property mapping helpers and a props-driven React UI.
4
+
5
+ Use this package to:
6
+
7
+ 1. Parse markdown or RSS/Atom into `SourceNode[]`
8
+ 2. Author 1:1 `FieldMapping`s (manually, via `autoMap`, or via `FieldMapper`)
9
+ 3. `applyMapping` to a coerced property bag for `createItem` / publish
10
+
11
+ ## Install
12
+
13
+ ```bash
14
+ bun add @seedprotocol/mapping
15
+ ```
16
+
17
+ Peers: `react`, `react-dom`. RSS parsing uses `rss-parser` with the same custom fields as `@seedprotocol/feed` `parseRssString` (aligned, no hard dependency on the feed package barrel).
18
+
19
+ ## Headless API
20
+
21
+ ```ts
22
+ import {
23
+ markdownToSources,
24
+ rssXmlToSources,
25
+ autoMap,
26
+ applyMapping,
27
+ type TargetProperty,
28
+ type MappingDocument,
29
+ } from '@seedprotocol/mapping'
30
+
31
+ const sources = markdownToSources(markdown)
32
+ const targets: TargetProperty[] = [
33
+ { name: 'title', dataType: 'String' },
34
+ { name: 'body', dataType: 'Text' },
35
+ ]
36
+ const mappings = autoMap(sources, targets)
37
+ const properties = applyMapping(sources, mappings, targets)
38
+
39
+ const doc: MappingDocument = {
40
+ version: 1,
41
+ sourceKind: 'markdown',
42
+ mappings,
43
+ }
44
+ ```
45
+
46
+ RSS:
47
+
48
+ ```ts
49
+ const { channel, items } = await rssXmlToSources(xml)
50
+ const itemSources = items[0]!
51
+ const mappings = autoMap(itemSources, postTargets)
52
+ const properties = applyMapping(itemSources, mappings, postTargets)
53
+ ```
54
+
55
+ ## FieldMapper UI
56
+
57
+ Props-driven only — no Seed hooks or routing. The host app supplies sources/targets and handles create/navigate.
58
+
59
+ ```tsx
60
+ import { useState } from 'react'
61
+ import {
62
+ FieldMapper,
63
+ markdownToSources,
64
+ applyMapping,
65
+ type FieldMapping,
66
+ } from '@seedprotocol/mapping'
67
+
68
+ function ImportMap({ markdown, targets, onSubmit }) {
69
+ const sources = markdownToSources(markdown)
70
+ const [mappings, setMappings] = useState<FieldMapping[]>([])
71
+
72
+ return (
73
+ <>
74
+ <FieldMapper
75
+ sources={sources}
76
+ targets={targets}
77
+ mappings={mappings}
78
+ onChange={setMappings}
79
+ />
80
+ <button
81
+ onClick={() => onSubmit(applyMapping(sources, mappings, targets))}
82
+ >
83
+ Create
84
+ </button>
85
+ </>
86
+ )
87
+ }
88
+ ```
89
+
90
+ ## Relationship to `FeedFieldManifest`
91
+
92
+ | Type | Package | Meaning |
93
+ |------|---------|---------|
94
+ | `FieldMapping` / `MappingDocument` | `@seedprotocol/mapping` | Source id → **model property name** |
95
+ | `FeedFieldManifest` | `@seedprotocol/sdk` | Property/key → **role** (`image` / `html` / `text`) for `normalizeFeedItemFields` |
96
+
97
+ Compose them: map external fields into a plain item, then optionally normalize roles for media/HTML display.
98
+
99
+ ## Phase 1 limits
100
+
101
+ - Markdown: flat YAML frontmatter; `#` / `##` sections only
102
+ - RSS: top-level item fields via `parseRssString` (no general XPath)
103
+ - UI: self-contained dark theme; no desktop/Tailwind coupling
104
+
105
+ See [MAPPING_PHASE2.md](../../docs/MAPPING_PHASE2.md) for consumer migration and hardening.
@@ -0,0 +1,34 @@
1
+ import type { SourceNode } from '../types';
2
+ export declare const SECTION_FULL_ID = "section-full";
3
+ type FrontmatterField = {
4
+ id: string;
5
+ key: string;
6
+ value: string;
7
+ valueType: 'string' | 'boolean' | 'array';
8
+ };
9
+ type ParsedMarkdown = {
10
+ frontmatter: FrontmatterField[];
11
+ body: string;
12
+ };
13
+ type MarkdownSection = {
14
+ id: string;
15
+ heading: string;
16
+ level: number;
17
+ content: string;
18
+ };
19
+ /**
20
+ * Parse YAML-like frontmatter (flat keys only) and body.
21
+ * Mirrors desktop MarkdownMapper limits: no nested YAML.
22
+ */
23
+ export declare function parseMarkdownWithFrontmatter(markdownContent: string): ParsedMarkdown;
24
+ /**
25
+ * Split markdown body into H1/H2 sections (###+ ignored as section breaks).
26
+ */
27
+ export declare function parseMarkdownSections(md: string): MarkdownSection[];
28
+ /**
29
+ * Convert markdown into SourceNodes for FieldMapper / applyMapping.
30
+ * Order: frontmatter fields, full document body, then H1/H2 sections.
31
+ */
32
+ export declare function markdownToSources(markdown: string): SourceNode[];
33
+ export {};
34
+ //# sourceMappingURL=markdown.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../../src/adapters/markdown.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAE1C,eAAO,MAAM,eAAe,iBAAiB,CAAA;AAE7C,KAAK,gBAAgB,GAAG;IACtB,EAAE,EAAE,MAAM,CAAA;IACV,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,QAAQ,GAAG,SAAS,GAAG,OAAO,CAAA;CAC1C,CAAA;AAED,KAAK,cAAc,GAAG;IACpB,WAAW,EAAE,gBAAgB,EAAE,CAAA;IAC/B,IAAI,EAAE,MAAM,CAAA;CACb,CAAA;AAED,KAAK,eAAe,GAAG;IACrB,EAAE,EAAE,MAAM,CAAA;IACV,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;CAChB,CAAA;AAED;;;GAGG;AACH,wBAAgB,4BAA4B,CAC1C,eAAe,EAAE,MAAM,GACtB,cAAc,CAqEhB;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,EAAE,EAAE,MAAM,GAAG,eAAe,EAAE,CA+BnE;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,UAAU,EAAE,CAmChE"}
@@ -0,0 +1,28 @@
1
+ import type { SourceNode } from '../types';
2
+ export type ParsedRssChannel = {
3
+ title?: string;
4
+ link?: string;
5
+ description?: string;
6
+ /** Plain serializable item records (dates become ISO strings). */
7
+ items: Record<string, unknown>[];
8
+ };
9
+ /**
10
+ * Flatten a parsed RSS/Atom item into SourceNodes (one per string-coercible key).
11
+ * Stable ids: `rss-{key}`.
12
+ */
13
+ export declare function rssItemToSources(item: Record<string, unknown>): SourceNode[];
14
+ export type RssXmlSourcesResult = {
15
+ channel: {
16
+ title?: string;
17
+ link?: string;
18
+ description?: string;
19
+ };
20
+ /** Source nodes for each feed item, in document order. */
21
+ items: SourceNode[][];
22
+ };
23
+ /**
24
+ * Parse RSS/Atom XML and map each item to SourceNodes via rssItemToSources.
25
+ * Compatible with @seedprotocol/feed `parseRssString` field set.
26
+ */
27
+ export declare function rssXmlToSources(xml: string): Promise<RssXmlSourcesResult>;
28
+ //# sourceMappingURL=rss.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rss.d.ts","sourceRoot":"","sources":["../../src/adapters/rss.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAE1C,MAAM,MAAM,gBAAgB,GAAG;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,kEAAkE;IAClE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAA;CACjC,CAAA;AAiED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,UAAU,EAAE,CAoB5E;AAED,MAAM,MAAM,mBAAmB,GAAG;IAChC,OAAO,EAAE;QACP,KAAK,CAAC,EAAE,MAAM,CAAA;QACd,IAAI,CAAC,EAAE,MAAM,CAAA;QACb,WAAW,CAAC,EAAE,MAAM,CAAA;KACrB,CAAA;IACD,0DAA0D;IAC1D,KAAK,EAAE,UAAU,EAAE,EAAE,CAAA;CACtB,CAAA;AAED;;;GAGG;AACH,wBAAsB,eAAe,CACnC,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,mBAAmB,CAAC,CAU9B"}
@@ -0,0 +1,11 @@
1
+ import type { FieldMapping, PropertyBag, SourceNode, TargetProperty } from './types';
2
+ /**
3
+ * Coerce a raw string value to the Seed model property dataType.
4
+ */
5
+ export declare function coerceValue(rawValue: string, dataType: string): unknown;
6
+ /**
7
+ * Apply field mappings to produce a property bag for createItem / publish.
8
+ * Missing sources or unknown target properties are skipped (no throw).
9
+ */
10
+ export declare function applyMapping(sources: SourceNode[], mappings: FieldMapping[], targets: TargetProperty[]): PropertyBag;
11
+ //# sourceMappingURL=applyMapping.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"applyMapping.d.ts","sourceRoot":"","sources":["../src/applyMapping.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,YAAY,EACZ,WAAW,EACX,UAAU,EACV,cAAc,EACf,MAAM,SAAS,CAAA;AAEhB;;GAEG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAuBvE;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAC1B,OAAO,EAAE,UAAU,EAAE,EACrB,QAAQ,EAAE,YAAY,EAAE,EACxB,OAAO,EAAE,cAAc,EAAE,GACxB,WAAW,CAeb"}
@@ -0,0 +1,8 @@
1
+ import type { FieldMapping, SourceNode, TargetProperty } from './types';
2
+ /**
3
+ * Heuristic auto-map: full-document sources to body-like props,
4
+ * then fuzzy name match for remaining sources ↔ targets.
5
+ * Returns 1:1 mappings (each source and property used at most once).
6
+ */
7
+ export declare function autoMap(sources: SourceNode[], targets: TargetProperty[]): FieldMapping[];
8
+ //# sourceMappingURL=autoMap.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"autoMap.d.ts","sourceRoot":"","sources":["../src/autoMap.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAcvE;;;;GAIG;AACH,wBAAgB,OAAO,CACrB,OAAO,EAAE,UAAU,EAAE,EACrB,OAAO,EAAE,cAAc,EAAE,GACxB,YAAY,EAAE,CAsEhB"}
@@ -0,0 +1,7 @@
1
+ export type { SourceKind, SourceNode, TargetProperty, FieldMapping, MappingDocument, PropertyBag, } from './types';
2
+ export { applyMapping, coerceValue } from './applyMapping';
3
+ export { autoMap } from './autoMap';
4
+ export { markdownToSources, parseMarkdownWithFrontmatter, parseMarkdownSections, SECTION_FULL_ID, } from './adapters/markdown';
5
+ export { rssItemToSources, rssXmlToSources, type RssXmlSourcesResult, } from './adapters/rss';
6
+ export { FieldMapper, type FieldMapperProps } from './react/FieldMapper';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,UAAU,EACV,UAAU,EACV,cAAc,EACd,YAAY,EACZ,eAAe,EACf,WAAW,GACZ,MAAM,SAAS,CAAA;AAEhB,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAC1D,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAEnC,OAAO,EACL,iBAAiB,EACjB,4BAA4B,EAC5B,qBAAqB,EACrB,eAAe,GAChB,MAAM,qBAAqB,CAAA;AAE5B,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,KAAK,mBAAmB,GACzB,MAAM,gBAAgB,CAAA;AAEvB,OAAO,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAE,MAAM,qBAAqB,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,544 @@
1
+ import e from "rss-parser";
2
+ import { useCallback as t, useEffect as n, useMemo as r, useRef as i, useState as a } from "react";
3
+ import { jsx as o, jsxs as s } from "react/jsx-runtime";
4
+ //#region src/applyMapping.ts
5
+ function c(e, t) {
6
+ if (t === "Number") return parseFloat(String(e)) || 0;
7
+ if (t === "Boolean") return e === "true" || e === "1" || String(e).toLowerCase() === "yes";
8
+ if (t === "Json") try {
9
+ return JSON.parse(e);
10
+ } catch {
11
+ return e;
12
+ }
13
+ if (t === "Date") {
14
+ let t = new Date(String(e));
15
+ return Number.isNaN(t.getTime()) ? e : t.getTime();
16
+ }
17
+ return String(e ?? "");
18
+ }
19
+ function l(e, t, n) {
20
+ let r = new Map(e.map((e) => [e.id, e])), i = new Map(n.map((e) => [e.name, e])), a = {};
21
+ for (let e of t) {
22
+ let t = r.get(e.sourceId), n = i.get(e.propertyName);
23
+ if (!t || !n) continue;
24
+ let o = t.value || t.label;
25
+ a[e.propertyName] = c(o, n.dataType);
26
+ }
27
+ return a;
28
+ }
29
+ //#endregion
30
+ //#region src/autoMap.ts
31
+ var u = [
32
+ "text",
33
+ "content",
34
+ "body",
35
+ "fulltext",
36
+ "markdown"
37
+ ];
38
+ function d(e) {
39
+ return e.toLowerCase().replace(/\s+/g, "").replace(/[:_-]/g, "");
40
+ }
41
+ function f(e, t) {
42
+ let n = d(e), r = d(t);
43
+ return n === r || n.includes(r) || r.includes(n);
44
+ }
45
+ function p(e, t) {
46
+ let n = [], r = /* @__PURE__ */ new Set(), i = /* @__PURE__ */ new Set(), a = e.find((e) => e.kind === "full");
47
+ if (a) {
48
+ let e = t.find((e) => u.includes(e.name.toLowerCase()));
49
+ e && (n.push({
50
+ sourceId: a.id,
51
+ propertyName: e.name
52
+ }), r.add(e.name), i.add(a.id));
53
+ }
54
+ let o = {
55
+ title: [
56
+ "title",
57
+ "name",
58
+ "headline"
59
+ ],
60
+ "content:encoded": [
61
+ "html",
62
+ "content",
63
+ "body",
64
+ "text"
65
+ ],
66
+ content: [
67
+ "html",
68
+ "content",
69
+ "body",
70
+ "text"
71
+ ],
72
+ description: [
73
+ "summary",
74
+ "description",
75
+ "excerpt"
76
+ ],
77
+ summary: [
78
+ "summary",
79
+ "description",
80
+ "excerpt"
81
+ ],
82
+ featureimage: [
83
+ "featureimage",
84
+ "feature_image",
85
+ "image",
86
+ "cover"
87
+ ],
88
+ feature_image: [
89
+ "featureimage",
90
+ "feature_image",
91
+ "image",
92
+ "cover"
93
+ ],
94
+ link: [
95
+ "url",
96
+ "link",
97
+ "permalink",
98
+ "slug"
99
+ ],
100
+ creator: [
101
+ "author",
102
+ "authors",
103
+ "creator",
104
+ "byline"
105
+ ],
106
+ author: [
107
+ "author",
108
+ "authors",
109
+ "creator",
110
+ "byline"
111
+ ]
112
+ };
113
+ for (let a of e) {
114
+ if (i.has(a.id)) continue;
115
+ let e = d(a.label), s = a.id.replace(/^rss-/, "").replace(/^fm-/, ""), c = [
116
+ ...o[a.label] ?? [],
117
+ ...o[s] ?? [],
118
+ ...o[e] ?? []
119
+ ], l;
120
+ for (let e of c) if (l = t.find((t) => !r.has(t.name) && f(t.name, e)), l) break;
121
+ l ||= t.find((e) => !r.has(e.name) && (f(e.name, a.label) || f(e.name, s))), l && (n.push({
122
+ sourceId: a.id,
123
+ propertyName: l.name
124
+ }), r.add(l.name), i.add(a.id));
125
+ }
126
+ return n;
127
+ }
128
+ //#endregion
129
+ //#region src/adapters/markdown.ts
130
+ var m = "section-full";
131
+ function h(e) {
132
+ try {
133
+ let t = e.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
134
+ if (!t) return {
135
+ frontmatter: [],
136
+ body: e
137
+ };
138
+ let n = t[1] ?? "", r = (t[2] ?? "").trimStart(), i = [], a = {};
139
+ for (let e of n.split("\n")) {
140
+ let t = e.match(/^([a-zA-Z_][a-zA-Z0-9_-]*):\s*(.*)$/);
141
+ if (!t) continue;
142
+ let n = t[1], r = t[2].trim(), i = r;
143
+ if (r === "true") i = !0;
144
+ else if (r === "false") i = !1;
145
+ else if (r === "null" || r === "") i = null;
146
+ else if (r.startsWith("[") && r.endsWith("]")) try {
147
+ i = JSON.parse(r.replace(/'/g, "\""));
148
+ } catch {
149
+ i = r.slice(1, -1).split(",").map((e) => e.trim().replace(/^['"]|['"]$/g, ""));
150
+ }
151
+ else (r.startsWith("\"") || r.startsWith("'")) && (i = r.slice(1, -1).replace(/\\"/g, "\""));
152
+ a[n] = i;
153
+ }
154
+ for (let [e, t] of Object.entries(a)) {
155
+ let n = "string", r;
156
+ Array.isArray(t) ? (n = "array", r = t.map((e) => typeof e == "object" ? JSON.stringify(e) : String(e)).join(", ")) : typeof t == "boolean" ? (n = "boolean", r = String(t)) : r = typeof t == "object" && t ? JSON.stringify(t) : t == null ? "" : String(t), i.push({
157
+ id: `fm-${e}`,
158
+ key: e,
159
+ value: r,
160
+ valueType: n
161
+ });
162
+ }
163
+ return {
164
+ frontmatter: i,
165
+ body: r
166
+ };
167
+ } catch {
168
+ return {
169
+ frontmatter: [],
170
+ body: e
171
+ };
172
+ }
173
+ }
174
+ function g(e) {
175
+ let t = e.split("\n"), n = [], r = null;
176
+ for (let e of t) {
177
+ let t = e.match(/^## (.+)/), i = e.match(/^# (.+)/);
178
+ i ? (r && n.push(r), r = {
179
+ id: `section-${n.length}`,
180
+ heading: i[1],
181
+ level: 1,
182
+ content: ""
183
+ }) : t ? (r && n.push(r), r = {
184
+ id: `section-${n.length}`,
185
+ heading: t[1],
186
+ level: 2,
187
+ content: ""
188
+ }) : r && (r.content += (r.content ? "\n" : "") + e);
189
+ }
190
+ return r && n.push(r), n;
191
+ }
192
+ function _(e) {
193
+ let t = h(e), n = g(t.body), r = [];
194
+ for (let e of t.frontmatter) r.push({
195
+ id: e.id,
196
+ label: e.key,
197
+ kind: "frontmatter",
198
+ value: e.value,
199
+ meta: { valueType: e.valueType }
200
+ });
201
+ r.push({
202
+ id: m,
203
+ label: "Full document",
204
+ kind: "full",
205
+ value: t.body
206
+ });
207
+ for (let e of n) {
208
+ let t = e.content.trim().length > 0 ? e.content.trim() : e.heading;
209
+ r.push({
210
+ id: e.id,
211
+ label: e.heading,
212
+ kind: "section",
213
+ value: t,
214
+ meta: { level: e.level }
215
+ });
216
+ }
217
+ return r;
218
+ }
219
+ //#endregion
220
+ //#region src/adapters/rss.ts
221
+ function v(e) {
222
+ try {
223
+ return JSON.parse(JSON.stringify(e));
224
+ } catch {
225
+ return { ...e };
226
+ }
227
+ }
228
+ var y = new e({ customFields: { item: [
229
+ "featureImage",
230
+ "feature_image",
231
+ "content:encoded",
232
+ "seedUid",
233
+ "SeedUid"
234
+ ] } });
235
+ async function b(e) {
236
+ let t = await y.parseString(e);
237
+ return {
238
+ title: t.title,
239
+ link: t.link,
240
+ description: t.description,
241
+ items: (t.items ?? []).map(v)
242
+ };
243
+ }
244
+ function x(e) {
245
+ if (e == null) return null;
246
+ if (typeof e == "string") {
247
+ let t = e.trim();
248
+ return t.length > 0 ? t : null;
249
+ }
250
+ if (typeof e == "number" || typeof e == "boolean") return String(e);
251
+ if (Array.isArray(e)) try {
252
+ return JSON.stringify(e);
253
+ } catch {
254
+ return null;
255
+ }
256
+ if (typeof e == "object") {
257
+ let t = e;
258
+ if (typeof t._ == "string" && t._.trim()) return t._.trim();
259
+ if (typeof t.value == "string" && t.value.trim()) return t.value.trim();
260
+ try {
261
+ return JSON.stringify(e);
262
+ } catch {
263
+ return null;
264
+ }
265
+ }
266
+ return null;
267
+ }
268
+ function S(e) {
269
+ let t = [], n = /* @__PURE__ */ new Set();
270
+ for (let [r, i] of Object.entries(e)) {
271
+ if (r === "items" || r.startsWith("$")) continue;
272
+ let e = x(i);
273
+ e != null && (n.has(r) || (n.add(r), t.push({
274
+ id: `rss-${r}`,
275
+ label: r,
276
+ kind: "rssField",
277
+ value: e
278
+ })));
279
+ }
280
+ return t;
281
+ }
282
+ async function C(e) {
283
+ let t = await b(e);
284
+ return {
285
+ channel: {
286
+ title: t.title,
287
+ link: t.link,
288
+ description: t.description
289
+ },
290
+ items: t.items.map((e) => S(e))
291
+ };
292
+ }
293
+ //#endregion
294
+ //#region src/react/FieldMapper.tsx
295
+ var w = [
296
+ "#3b82f6",
297
+ "#8b5cf6",
298
+ "#ec4899",
299
+ "#f59e0b",
300
+ "#10b981",
301
+ "#06b6d4",
302
+ "#f97316",
303
+ "#14b8a6"
304
+ ], T = {
305
+ String: {
306
+ bg: "rgba(59,130,246,0.12)",
307
+ border: "#3b82f6",
308
+ text: "#93c5fd"
309
+ },
310
+ Text: {
311
+ bg: "rgba(59,130,246,0.12)",
312
+ border: "#3b82f6",
313
+ text: "#93c5fd"
314
+ },
315
+ Number: {
316
+ bg: "rgba(16,185,129,0.12)",
317
+ border: "#10b981",
318
+ text: "#6ee7b7"
319
+ },
320
+ Boolean: {
321
+ bg: "rgba(167,139,250,0.12)",
322
+ border: "#a78bfa",
323
+ text: "#c4b5fd"
324
+ },
325
+ Json: {
326
+ bg: "rgba(251,191,36,0.12)",
327
+ border: "#fbbf24",
328
+ text: "#fcd34d"
329
+ },
330
+ Date: {
331
+ bg: "rgba(244,114,182,0.12)",
332
+ border: "#f472b6",
333
+ text: "#f9a8d4"
334
+ },
335
+ Relation: {
336
+ bg: "rgba(34,211,238,0.12)",
337
+ border: "#22d3ee",
338
+ text: "#67e8f9"
339
+ }
340
+ };
341
+ function E(e) {
342
+ return T[e] ?? {
343
+ bg: "rgba(148,163,184,0.12)",
344
+ border: "#94a3b8",
345
+ text: "#cbd5e1"
346
+ };
347
+ }
348
+ function D(e, t) {
349
+ let n = e.replace(/\s+/g, " ").trim();
350
+ return n.length <= t ? n : `${n.slice(0, t)}…`;
351
+ }
352
+ function O({ sources: e, targets: c, mappings: u, onChange: d, onAutoMap: f, className: m, showPreview: h = !0 }) {
353
+ let [g, _] = a(null), [v, y] = a(null), [b, x] = a(null), [S, C] = a(!1), [T, O] = a({
354
+ width: 0,
355
+ height: 0
356
+ }), [k, A] = a([]), j = i(null), M = i({}), N = i({}), P = t((e) => w[e % w.length], []), F = t(() => {
357
+ if (!j.current) return [];
358
+ let e = j.current.getBoundingClientRect();
359
+ return u.map((t, n) => {
360
+ let r = M.current[t.sourceId], i = N.current[t.propertyName];
361
+ if (!r || !i) return null;
362
+ let a = r.getBoundingClientRect(), o = i.getBoundingClientRect();
363
+ return {
364
+ x1: a.right - e.left,
365
+ y1: a.top + a.height / 2 - e.top,
366
+ x2: o.left - e.left,
367
+ y2: o.top + o.height / 2 - e.top,
368
+ color: P(n),
369
+ mapping: t
370
+ };
371
+ }).filter((e) => e !== null);
372
+ }, [u, P]);
373
+ n(() => {
374
+ let e = () => {
375
+ if (j.current) {
376
+ let e = j.current.getBoundingClientRect();
377
+ O({
378
+ width: e.width,
379
+ height: e.height
380
+ });
381
+ }
382
+ A(F());
383
+ };
384
+ e(), window.addEventListener("resize", e);
385
+ let t = setInterval(e, 200);
386
+ return () => {
387
+ window.removeEventListener("resize", e), clearInterval(t);
388
+ };
389
+ }, [
390
+ F,
391
+ u,
392
+ e,
393
+ c
394
+ ]);
395
+ let I = (e) => {
396
+ _((t) => t === e ? null : e);
397
+ }, L = (e) => {
398
+ if (!g) return;
399
+ let t = [...u];
400
+ t = t.filter((t) => t.sourceId !== g && t.propertyName !== e), t.push({
401
+ sourceId: g,
402
+ propertyName: e
403
+ }), d(t), _(null);
404
+ }, R = (e) => {
405
+ d(u.filter((t) => t.sourceId !== e));
406
+ }, z = (e) => u.find((t) => t.sourceId === e), B = (e) => u.find((t) => t.propertyName === e), V = v ? u.find((e) => e.propertyName === v) : b ? u.find((e) => e.sourceId === b) : null, H = r(() => l(e, u, c), [
407
+ e,
408
+ u,
409
+ c
410
+ ]);
411
+ return /* @__PURE__ */ s("div", {
412
+ className: `seed-field-mapper ${m ?? ""}`.trim(),
413
+ children: [
414
+ /* @__PURE__ */ o("style", { children: "\n .seed-field-mapper {\n font-family: 'DM Sans', system-ui, sans-serif;\n color: #e2e8f0;\n background: #0f172a;\n border-radius: 12px;\n padding: 16px;\n }\n .seed-field-mapper * { box-sizing: border-box; }\n .seed-field-mapper .fm-toolbar {\n display: flex;\n gap: 8px;\n align-items: center;\n margin-bottom: 12px;\n flex-wrap: wrap;\n }\n .seed-field-mapper .fm-btn {\n background: #1e293b;\n border: 1px solid #334155;\n color: #e2e8f0;\n border-radius: 8px;\n padding: 6px 12px;\n cursor: pointer;\n font-size: 13px;\n }\n .seed-field-mapper .fm-btn:hover { border-color: #64748b; }\n .seed-field-mapper .fm-btn-primary {\n background: #2563eb;\n border-color: #3b82f6;\n }\n .seed-field-mapper .fm-meta {\n font-size: 12px;\n color: #94a3b8;\n margin-left: auto;\n }\n .seed-field-mapper .fm-grid {\n position: relative;\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 48px;\n min-height: 240px;\n }\n .seed-field-mapper .fm-col-title {\n font-size: 11px;\n text-transform: uppercase;\n letter-spacing: 0.06em;\n color: #64748b;\n margin-bottom: 8px;\n }\n .seed-field-mapper .fm-card {\n border: 1px solid #334155;\n border-radius: 8px;\n padding: 10px 12px;\n margin-bottom: 8px;\n background: #1e293b;\n cursor: pointer;\n transition: border-color 0.15s, box-shadow 0.15s;\n }\n .seed-field-mapper .fm-card.active {\n border-color: #3b82f6;\n box-shadow: 0 0 0 1px #3b82f6;\n }\n .seed-field-mapper .fm-card.mapped {\n border-color: #475569;\n }\n .seed-field-mapper .fm-card.highlight {\n border-color: #f59e0b;\n }\n .seed-field-mapper .fm-label {\n font-weight: 600;\n font-size: 13px;\n margin-bottom: 4px;\n }\n .seed-field-mapper .fm-value {\n font-family: 'JetBrains Mono', ui-monospace, monospace;\n font-size: 11px;\n color: #94a3b8;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n .seed-field-mapper .fm-kind {\n font-size: 10px;\n color: #64748b;\n margin-left: 6px;\n }\n .seed-field-mapper .fm-prop-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n }\n .seed-field-mapper .fm-dtype {\n font-size: 10px;\n padding: 2px 6px;\n border-radius: 4px;\n border: 1px solid;\n }\n .seed-field-mapper .fm-remove {\n background: transparent;\n border: none;\n color: #94a3b8;\n cursor: pointer;\n font-size: 14px;\n padding: 0 4px;\n }\n .seed-field-mapper .fm-remove:hover { color: #f87171; }\n .seed-field-mapper .fm-svg {\n position: absolute;\n inset: 0;\n pointer-events: none;\n overflow: visible;\n }\n .seed-field-mapper .fm-preview {\n margin-top: 12px;\n background: #020617;\n border: 1px solid #334155;\n border-radius: 8px;\n padding: 12px;\n font-family: 'JetBrains Mono', ui-monospace, monospace;\n font-size: 11px;\n white-space: pre-wrap;\n color: #cbd5e1;\n max-height: 200px;\n overflow: auto;\n }\n " }),
415
+ /* @__PURE__ */ s("div", {
416
+ className: "fm-toolbar",
417
+ children: [
418
+ /* @__PURE__ */ o("button", {
419
+ type: "button",
420
+ className: "fm-btn fm-btn-primary",
421
+ onClick: () => {
422
+ d(f ? f() : p(e, c));
423
+ },
424
+ children: "Auto-map"
425
+ }),
426
+ h && /* @__PURE__ */ o("button", {
427
+ type: "button",
428
+ className: "fm-btn",
429
+ onClick: () => C((e) => !e),
430
+ children: S ? "Hide preview" : "Preview JSON"
431
+ }),
432
+ /* @__PURE__ */ s("span", {
433
+ className: "fm-meta",
434
+ children: [
435
+ u.length,
436
+ " / ",
437
+ c.length,
438
+ " mapped",
439
+ g ? " · click a property" : " · click a source"
440
+ ]
441
+ })
442
+ ]
443
+ }),
444
+ /* @__PURE__ */ s("div", {
445
+ className: "fm-grid",
446
+ ref: j,
447
+ children: [
448
+ /* @__PURE__ */ o("svg", {
449
+ className: "fm-svg",
450
+ width: T.width,
451
+ height: T.height,
452
+ children: k.map((e) => {
453
+ let t = V?.sourceId === e.mapping.sourceId && V?.propertyName === e.mapping.propertyName, n = (e.x1 + e.x2) / 2;
454
+ return /* @__PURE__ */ o("path", {
455
+ d: `M ${e.x1} ${e.y1} C ${n} ${e.y1}, ${n} ${e.y2}, ${e.x2} ${e.y2}`,
456
+ stroke: e.color,
457
+ strokeWidth: t ? 3 : 2,
458
+ fill: "none",
459
+ opacity: V && !t ? .25 : .85
460
+ }, `${e.mapping.sourceId}-${e.mapping.propertyName}`);
461
+ })
462
+ }),
463
+ /* @__PURE__ */ s("div", { children: [/* @__PURE__ */ o("div", {
464
+ className: "fm-col-title",
465
+ children: "Sources"
466
+ }), e.map((e) => {
467
+ let t = z(e.id), n = g === e.id, r = V?.sourceId === e.id;
468
+ return /* @__PURE__ */ s("div", {
469
+ ref: (t) => {
470
+ M.current[e.id] = t;
471
+ },
472
+ className: `fm-card${n ? " active" : ""}${t ? " mapped" : ""}${r ? " highlight" : ""}`,
473
+ onClick: () => I(e.id),
474
+ onMouseEnter: () => x(e.id),
475
+ onMouseLeave: () => x(null),
476
+ children: [/* @__PURE__ */ s("div", {
477
+ className: "fm-prop-row",
478
+ children: [/* @__PURE__ */ s("div", {
479
+ className: "fm-label",
480
+ children: [e.label, /* @__PURE__ */ o("span", {
481
+ className: "fm-kind",
482
+ children: e.kind
483
+ })]
484
+ }), t && /* @__PURE__ */ o("button", {
485
+ type: "button",
486
+ className: "fm-remove",
487
+ "aria-label": "Remove mapping",
488
+ onClick: (t) => {
489
+ t.stopPropagation(), R(e.id);
490
+ },
491
+ children: "×"
492
+ })]
493
+ }), /* @__PURE__ */ o("div", {
494
+ className: "fm-value",
495
+ children: D(e.value, 80)
496
+ })]
497
+ }, e.id);
498
+ })] }),
499
+ /* @__PURE__ */ s("div", { children: [/* @__PURE__ */ o("div", {
500
+ className: "fm-col-title",
501
+ children: "Model properties"
502
+ }), c.map((e) => {
503
+ let t = B(e.name), n = E(e.dataType), r = V?.propertyName === e.name;
504
+ return /* @__PURE__ */ s("div", {
505
+ ref: (t) => {
506
+ N.current[e.name] = t;
507
+ },
508
+ className: `fm-card${t ? " mapped" : ""}${r ? " highlight" : ""}${g ? " active" : ""}`,
509
+ onClick: () => L(e.name),
510
+ onMouseEnter: () => y(e.name),
511
+ onMouseLeave: () => y(null),
512
+ children: [/* @__PURE__ */ s("div", {
513
+ className: "fm-prop-row",
514
+ children: [/* @__PURE__ */ o("div", {
515
+ className: "fm-label",
516
+ children: e.name
517
+ }), /* @__PURE__ */ o("span", {
518
+ className: "fm-dtype",
519
+ style: {
520
+ background: n.bg,
521
+ borderColor: n.border,
522
+ color: n.text
523
+ },
524
+ children: e.dataType
525
+ })]
526
+ }), t && /* @__PURE__ */ s("div", {
527
+ className: "fm-value",
528
+ children: ["← ", t.sourceId]
529
+ })]
530
+ }, e.name);
531
+ })] })
532
+ ]
533
+ }),
534
+ h && S && /* @__PURE__ */ o("pre", {
535
+ className: "fm-preview",
536
+ children: JSON.stringify(H, null, 2)
537
+ })
538
+ ]
539
+ });
540
+ }
541
+ //#endregion
542
+ export { O as FieldMapper, m as SECTION_FULL_ID, l as applyMapping, p as autoMap, c as coerceValue, _ as markdownToSources, g as parseMarkdownSections, h as parseMarkdownWithFrontmatter, S as rssItemToSources, C as rssXmlToSources };
543
+
544
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/applyMapping.ts","../src/autoMap.ts","../src/adapters/markdown.ts","../src/adapters/rss.ts","../src/react/FieldMapper.tsx"],"sourcesContent":["import type {\n FieldMapping,\n PropertyBag,\n SourceNode,\n TargetProperty,\n} from './types'\n\n/**\n * Coerce a raw string value to the Seed model property dataType.\n */\nexport function coerceValue(rawValue: string, dataType: string): unknown {\n if (dataType === 'Number') {\n return parseFloat(String(rawValue)) || 0\n }\n if (dataType === 'Boolean') {\n return (\n rawValue === 'true' ||\n rawValue === '1' ||\n String(rawValue).toLowerCase() === 'yes'\n )\n }\n if (dataType === 'Json') {\n try {\n return JSON.parse(rawValue)\n } catch {\n return rawValue\n }\n }\n if (dataType === 'Date') {\n const d = new Date(String(rawValue))\n return Number.isNaN(d.getTime()) ? rawValue : d.getTime()\n }\n return String(rawValue ?? '')\n}\n\n/**\n * Apply field mappings to produce a property bag for createItem / publish.\n * Missing sources or unknown target properties are skipped (no throw).\n */\nexport function applyMapping(\n sources: SourceNode[],\n mappings: FieldMapping[],\n targets: TargetProperty[],\n): PropertyBag {\n const byId = new Map(sources.map((s) => [s.id, s]))\n const byName = new Map(targets.map((t) => [t.name, t]))\n const properties: PropertyBag = {}\n\n for (const mapping of mappings) {\n const source = byId.get(mapping.sourceId)\n const target = byName.get(mapping.propertyName)\n if (!source || !target) continue\n\n const raw = source.value || source.label\n properties[mapping.propertyName] = coerceValue(raw, target.dataType)\n }\n\n return properties\n}\n","import type { FieldMapping, SourceNode, TargetProperty } from './types'\n\nconst FULL_DOC_PROP_NAMES = ['text', 'content', 'body', 'fulltext', 'markdown']\n\nfunction normalizeName(name: string): string {\n return name.toLowerCase().replace(/\\s+/g, '').replace(/[:_-]/g, '')\n}\n\nfunction namesMatch(a: string, b: string): boolean {\n const na = normalizeName(a)\n const nb = normalizeName(b)\n return na === nb || na.includes(nb) || nb.includes(na)\n}\n\n/**\n * Heuristic auto-map: full-document sources to body-like props,\n * then fuzzy name match for remaining sources ↔ targets.\n * Returns 1:1 mappings (each source and property used at most once).\n */\nexport function autoMap(\n sources: SourceNode[],\n targets: TargetProperty[],\n): FieldMapping[] {\n const mappings: FieldMapping[] = []\n const usedProps = new Set<string>()\n const usedSources = new Set<string>()\n\n const fullSource = sources.find((s) => s.kind === 'full')\n if (fullSource) {\n const fullDocMatch = targets.find((p) =>\n FULL_DOC_PROP_NAMES.includes(p.name.toLowerCase()),\n )\n if (fullDocMatch) {\n mappings.push({\n sourceId: fullSource.id,\n propertyName: fullDocMatch.name,\n })\n usedProps.add(fullDocMatch.name)\n usedSources.add(fullSource.id)\n }\n }\n\n // Prefer common RSS aliases before generic fuzzy match\n const rssAliases: Record<string, string[]> = {\n title: ['title', 'name', 'headline'],\n 'content:encoded': ['html', 'content', 'body', 'text'],\n content: ['html', 'content', 'body', 'text'],\n description: ['summary', 'description', 'excerpt'],\n summary: ['summary', 'description', 'excerpt'],\n featureimage: ['featureimage', 'feature_image', 'image', 'cover'],\n feature_image: ['featureimage', 'feature_image', 'image', 'cover'],\n link: ['url', 'link', 'permalink', 'slug'],\n creator: ['author', 'authors', 'creator', 'byline'],\n author: ['author', 'authors', 'creator', 'byline'],\n }\n\n for (const source of sources) {\n if (usedSources.has(source.id)) continue\n\n const labelKey = normalizeName(source.label)\n const idKey = source.id.replace(/^rss-/, '').replace(/^fm-/, '')\n const aliasKeys = [\n ...(rssAliases[source.label] ?? []),\n ...(rssAliases[idKey] ?? []),\n ...(rssAliases[labelKey] ?? []),\n ]\n\n let match: TargetProperty | undefined\n\n for (const alias of aliasKeys) {\n match = targets.find(\n (p) => !usedProps.has(p.name) && namesMatch(p.name, alias),\n )\n if (match) break\n }\n\n if (!match) {\n match = targets.find(\n (p) =>\n !usedProps.has(p.name) &&\n (namesMatch(p.name, source.label) || namesMatch(p.name, idKey)),\n )\n }\n\n if (match) {\n mappings.push({ sourceId: source.id, propertyName: match.name })\n usedProps.add(match.name)\n usedSources.add(source.id)\n }\n }\n\n return mappings\n}\n","import type { SourceNode } from '../types'\n\nexport const SECTION_FULL_ID = 'section-full'\n\ntype FrontmatterField = {\n id: string\n key: string\n value: string\n valueType: 'string' | 'boolean' | 'array'\n}\n\ntype ParsedMarkdown = {\n frontmatter: FrontmatterField[]\n body: string\n}\n\ntype MarkdownSection = {\n id: string\n heading: string\n level: number\n content: string\n}\n\n/**\n * Parse YAML-like frontmatter (flat keys only) and body.\n * Mirrors desktop MarkdownMapper limits: no nested YAML.\n */\nexport function parseMarkdownWithFrontmatter(\n markdownContent: string,\n): ParsedMarkdown {\n try {\n const match = markdownContent.match(\n /^---\\r?\\n([\\s\\S]*?)\\r?\\n---\\r?\\n?([\\s\\S]*)$/,\n )\n if (!match) return { frontmatter: [], body: markdownContent }\n\n const yamlBlock = match[1] ?? ''\n const body = (match[2] ?? '').trimStart()\n const frontmatter: FrontmatterField[] = []\n const data: Record<string, unknown> = {}\n\n for (const line of yamlBlock.split('\\n')) {\n const keyMatch = line.match(/^([a-zA-Z_][a-zA-Z0-9_-]*):\\s*(.*)$/)\n if (!keyMatch) continue\n\n const key = keyMatch[1]!\n const raw = keyMatch[2]!.trim()\n let val: unknown = raw\n\n if (raw === 'true') val = true\n else if (raw === 'false') val = false\n else if (raw === 'null' || raw === '') val = null\n else if (raw.startsWith('[') && raw.endsWith(']')) {\n try {\n val = JSON.parse(raw.replace(/'/g, '\"'))\n } catch {\n val = raw\n .slice(1, -1)\n .split(',')\n .map((s: string) => s.trim().replace(/^['\"]|['\"]$/g, ''))\n }\n } else if (raw.startsWith('\"') || raw.startsWith(\"'\")) {\n val = raw.slice(1, -1).replace(/\\\\\"/g, '\"')\n }\n\n data[key] = val\n }\n\n for (const [key, val] of Object.entries(data)) {\n let valueType: 'string' | 'boolean' | 'array' = 'string'\n let value: string\n\n if (Array.isArray(val)) {\n valueType = 'array'\n value = val\n .map((v) => (typeof v === 'object' ? JSON.stringify(v) : String(v)))\n .join(', ')\n } else if (typeof val === 'boolean') {\n valueType = 'boolean'\n value = String(val)\n } else if (val != null && typeof val === 'object') {\n value = JSON.stringify(val)\n } else {\n value = val != null ? String(val) : ''\n }\n\n frontmatter.push({\n id: `fm-${key}`,\n key,\n value,\n valueType,\n })\n }\n\n return { frontmatter, body }\n } catch {\n return { frontmatter: [], body: markdownContent }\n }\n}\n\n/**\n * Split markdown body into H1/H2 sections (###+ ignored as section breaks).\n */\nexport function parseMarkdownSections(md: string): MarkdownSection[] {\n const lines = md.split('\\n')\n const sections: MarkdownSection[] = []\n let current: MarkdownSection | null = null\n\n for (const line of lines) {\n const h2Match = line.match(/^## (.+)/)\n const h1Match = line.match(/^# (.+)/)\n\n if (h1Match) {\n if (current) sections.push(current)\n current = {\n id: `section-${sections.length}`,\n heading: h1Match[1]!,\n level: 1,\n content: '',\n }\n } else if (h2Match) {\n if (current) sections.push(current)\n current = {\n id: `section-${sections.length}`,\n heading: h2Match[1]!,\n level: 2,\n content: '',\n }\n } else if (current) {\n current.content += (current.content ? '\\n' : '') + line\n }\n }\n if (current) sections.push(current)\n return sections\n}\n\n/**\n * Convert markdown into SourceNodes for FieldMapper / applyMapping.\n * Order: frontmatter fields, full document body, then H1/H2 sections.\n */\nexport function markdownToSources(markdown: string): SourceNode[] {\n const parsed = parseMarkdownWithFrontmatter(markdown)\n const sections = parseMarkdownSections(parsed.body)\n const sources: SourceNode[] = []\n\n for (const fm of parsed.frontmatter) {\n sources.push({\n id: fm.id,\n label: fm.key,\n kind: 'frontmatter',\n value: fm.value,\n meta: { valueType: fm.valueType },\n })\n }\n\n sources.push({\n id: SECTION_FULL_ID,\n label: 'Full document',\n kind: 'full',\n value: parsed.body,\n })\n\n for (const section of sections) {\n const value =\n section.content.trim().length > 0 ? section.content.trim() : section.heading\n sources.push({\n id: section.id,\n label: section.heading,\n kind: 'section',\n value,\n meta: { level: section.level },\n })\n }\n\n return sources\n}\n","import Parser from 'rss-parser'\nimport type { SourceNode } from '../types'\n\nexport type ParsedRssChannel = {\n title?: string\n link?: string\n description?: string\n /** Plain serializable item records (dates become ISO strings). */\n items: Record<string, unknown>[]\n}\n\nfunction toPlainItem(item: unknown): Record<string, unknown> {\n try {\n return JSON.parse(JSON.stringify(item)) as Record<string, unknown>\n } catch {\n return { ...(item as Record<string, unknown>) }\n }\n}\n\n/**\n * Same shape and customFields as @seedprotocol/feed `parseRssString`\n * so RSS mapping stays aligned without importing feed's heavy barrel.\n */\nconst parser = new Parser({\n customFields: {\n item: [\n 'featureImage',\n 'feature_image',\n 'content:encoded',\n 'seedUid',\n 'SeedUid',\n ],\n },\n})\n\nasync function parseRssString(xml: string): Promise<ParsedRssChannel> {\n const feed = await parser.parseString(xml)\n return {\n title: feed.title,\n link: feed.link,\n description: feed.description,\n items: (feed.items ?? []).map(toPlainItem),\n }\n}\n\nfunction coerceFieldValue(val: unknown): string | null {\n if (val == null) return null\n if (typeof val === 'string') {\n const t = val.trim()\n return t.length > 0 ? t : null\n }\n if (typeof val === 'number' || typeof val === 'boolean') {\n return String(val)\n }\n if (Array.isArray(val)) {\n try {\n return JSON.stringify(val)\n } catch {\n return null\n }\n }\n if (typeof val === 'object') {\n const obj = val as Record<string, unknown>\n if (typeof obj._ === 'string' && obj._.trim()) return obj._.trim()\n if (typeof obj.value === 'string' && obj.value.trim()) return obj.value.trim()\n try {\n return JSON.stringify(val)\n } catch {\n return null\n }\n }\n return null\n}\n\n/**\n * Flatten a parsed RSS/Atom item into SourceNodes (one per string-coercible key).\n * Stable ids: `rss-{key}`.\n */\nexport function rssItemToSources(item: Record<string, unknown>): SourceNode[] {\n const sources: SourceNode[] = []\n const seen = new Set<string>()\n\n for (const [key, raw] of Object.entries(item)) {\n if (key === 'items' || key.startsWith('$')) continue\n const value = coerceFieldValue(raw)\n if (value == null) continue\n if (seen.has(key)) continue\n seen.add(key)\n\n sources.push({\n id: `rss-${key}`,\n label: key,\n kind: 'rssField',\n value,\n })\n }\n\n return sources\n}\n\nexport type RssXmlSourcesResult = {\n channel: {\n title?: string\n link?: string\n description?: string\n }\n /** Source nodes for each feed item, in document order. */\n items: SourceNode[][]\n}\n\n/**\n * Parse RSS/Atom XML and map each item to SourceNodes via rssItemToSources.\n * Compatible with @seedprotocol/feed `parseRssString` field set.\n */\nexport async function rssXmlToSources(\n xml: string,\n): Promise<RssXmlSourcesResult> {\n const parsed = await parseRssString(xml)\n return {\n channel: {\n title: parsed.title,\n link: parsed.link,\n description: parsed.description,\n },\n items: parsed.items.map((item) => rssItemToSources(item)),\n }\n}\n","import React, {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from 'react'\nimport { applyMapping } from '../applyMapping'\nimport { autoMap as defaultAutoMap } from '../autoMap'\nimport type { FieldMapping, SourceNode, TargetProperty } from '../types'\n\nconst CONNECTION_COLORS = [\n '#3b82f6',\n '#8b5cf6',\n '#ec4899',\n '#f59e0b',\n '#10b981',\n '#06b6d4',\n '#f97316',\n '#14b8a6',\n]\n\ntype ConnectorPoint = {\n x1: number\n y1: number\n x2: number\n y2: number\n color: string\n mapping: FieldMapping\n}\n\nexport type FieldMapperProps = {\n sources: SourceNode[]\n targets: TargetProperty[]\n mappings: FieldMapping[]\n onChange: (mappings: FieldMapping[]) => void\n /** Override default autoMap heuristics. */\n onAutoMap?: () => FieldMapping[]\n className?: string\n /** Show JSON preview of applyMapping result. Default true. */\n showPreview?: boolean\n}\n\nconst DATA_TYPE_COLORS: Record<\n string,\n { bg: string; border: string; text: string }\n> = {\n String: { bg: 'rgba(59,130,246,0.12)', border: '#3b82f6', text: '#93c5fd' },\n Text: { bg: 'rgba(59,130,246,0.12)', border: '#3b82f6', text: '#93c5fd' },\n Number: { bg: 'rgba(16,185,129,0.12)', border: '#10b981', text: '#6ee7b7' },\n Boolean: { bg: 'rgba(167,139,250,0.12)', border: '#a78bfa', text: '#c4b5fd' },\n Json: { bg: 'rgba(251,191,36,0.12)', border: '#fbbf24', text: '#fcd34d' },\n Date: { bg: 'rgba(244,114,182,0.12)', border: '#f472b6', text: '#f9a8d4' },\n Relation: { bg: 'rgba(34,211,238,0.12)', border: '#22d3ee', text: '#67e8f9' },\n}\n\nfunction getDataTypeStyle(dataType: string) {\n return (\n DATA_TYPE_COLORS[dataType] ?? {\n bg: 'rgba(148,163,184,0.12)',\n border: '#94a3b8',\n text: '#cbd5e1',\n }\n )\n}\n\nfunction truncate(s: string, n: number): string {\n const t = s.replace(/\\s+/g, ' ').trim()\n if (t.length <= n) return t\n return `${t.slice(0, n)}…`\n}\n\n/**\n * Generic two-pane field mapper. Props-driven; no Seed hooks or routing.\n */\nexport function FieldMapper({\n sources,\n targets,\n mappings,\n onChange,\n onAutoMap,\n className,\n showPreview = true,\n}: FieldMapperProps) {\n const [activeSource, setActiveSource] = useState<string | null>(null)\n const [hoveredProp, setHoveredProp] = useState<string | null>(null)\n const [hoveredSource, setHoveredSource] = useState<string | null>(null)\n const [previewOpen, setPreviewOpen] = useState(false)\n const [svgDimensions, setSvgDimensions] = useState({ width: 0, height: 0 })\n const [connectorPoints, setConnectorPoints] = useState<ConnectorPoint[]>([])\n\n const containerRef = useRef<HTMLDivElement>(null)\n const sourceRefs = useRef<Record<string, HTMLDivElement | null>>({})\n const propRefs = useRef<Record<string, HTMLDivElement | null>>({})\n\n const getConnectionColor = useCallback(\n (index: number) => CONNECTION_COLORS[index % CONNECTION_COLORS.length]!,\n [],\n )\n\n const getConnectorPoints = useCallback((): ConnectorPoint[] => {\n if (!containerRef.current) return []\n const containerRect = containerRef.current.getBoundingClientRect()\n return mappings\n .map((mapping, i) => {\n const sourceEl = sourceRefs.current[mapping.sourceId]\n const propEl = propRefs.current[mapping.propertyName]\n if (!sourceEl || !propEl) return null\n\n const sR = sourceEl.getBoundingClientRect()\n const pR = propEl.getBoundingClientRect()\n\n return {\n x1: sR.right - containerRect.left,\n y1: sR.top + sR.height / 2 - containerRect.top,\n x2: pR.left - containerRect.left,\n y2: pR.top + pR.height / 2 - containerRect.top,\n color: getConnectionColor(i),\n mapping,\n }\n })\n .filter((pt): pt is ConnectorPoint => pt !== null)\n }, [mappings, getConnectionColor])\n\n useEffect(() => {\n const update = () => {\n if (containerRef.current) {\n const rect = containerRef.current.getBoundingClientRect()\n setSvgDimensions({ width: rect.width, height: rect.height })\n }\n setConnectorPoints(getConnectorPoints())\n }\n update()\n window.addEventListener('resize', update)\n const timer = setInterval(update, 200)\n return () => {\n window.removeEventListener('resize', update)\n clearInterval(timer)\n }\n }, [getConnectorPoints, mappings, sources, targets])\n\n const handleSourceClick = (sourceId: string) => {\n setActiveSource((prev) => (prev === sourceId ? null : sourceId))\n }\n\n const handlePropClick = (propertyName: string) => {\n if (!activeSource) return\n let next = [...mappings]\n next = next.filter(\n (c) =>\n c.sourceId !== activeSource && c.propertyName !== propertyName,\n )\n next.push({ sourceId: activeSource, propertyName })\n onChange(next)\n setActiveSource(null)\n }\n\n const removeMapping = (sourceId: string) => {\n onChange(mappings.filter((c) => c.sourceId !== sourceId))\n }\n\n const mappingForSource = (sourceId: string) =>\n mappings.find((c) => c.sourceId === sourceId)\n const mappingForProp = (propName: string) =>\n mappings.find((c) => c.propertyName === propName)\n\n const highlightedPair = hoveredProp\n ? mappings.find((c) => c.propertyName === hoveredProp)\n : hoveredSource\n ? mappings.find((c) => c.sourceId === hoveredSource)\n : null\n\n const preview = useMemo(\n () => applyMapping(sources, mappings, targets),\n [sources, mappings, targets],\n )\n\n const runAutoMap = () => {\n const next = onAutoMap ? onAutoMap() : defaultAutoMap(sources, targets)\n onChange(next)\n }\n\n return (\n <div className={`seed-field-mapper ${className ?? ''}`.trim()}>\n <style>{`\n .seed-field-mapper {\n font-family: 'DM Sans', system-ui, sans-serif;\n color: #e2e8f0;\n background: #0f172a;\n border-radius: 12px;\n padding: 16px;\n }\n .seed-field-mapper * { box-sizing: border-box; }\n .seed-field-mapper .fm-toolbar {\n display: flex;\n gap: 8px;\n align-items: center;\n margin-bottom: 12px;\n flex-wrap: wrap;\n }\n .seed-field-mapper .fm-btn {\n background: #1e293b;\n border: 1px solid #334155;\n color: #e2e8f0;\n border-radius: 8px;\n padding: 6px 12px;\n cursor: pointer;\n font-size: 13px;\n }\n .seed-field-mapper .fm-btn:hover { border-color: #64748b; }\n .seed-field-mapper .fm-btn-primary {\n background: #2563eb;\n border-color: #3b82f6;\n }\n .seed-field-mapper .fm-meta {\n font-size: 12px;\n color: #94a3b8;\n margin-left: auto;\n }\n .seed-field-mapper .fm-grid {\n position: relative;\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 48px;\n min-height: 240px;\n }\n .seed-field-mapper .fm-col-title {\n font-size: 11px;\n text-transform: uppercase;\n letter-spacing: 0.06em;\n color: #64748b;\n margin-bottom: 8px;\n }\n .seed-field-mapper .fm-card {\n border: 1px solid #334155;\n border-radius: 8px;\n padding: 10px 12px;\n margin-bottom: 8px;\n background: #1e293b;\n cursor: pointer;\n transition: border-color 0.15s, box-shadow 0.15s;\n }\n .seed-field-mapper .fm-card.active {\n border-color: #3b82f6;\n box-shadow: 0 0 0 1px #3b82f6;\n }\n .seed-field-mapper .fm-card.mapped {\n border-color: #475569;\n }\n .seed-field-mapper .fm-card.highlight {\n border-color: #f59e0b;\n }\n .seed-field-mapper .fm-label {\n font-weight: 600;\n font-size: 13px;\n margin-bottom: 4px;\n }\n .seed-field-mapper .fm-value {\n font-family: 'JetBrains Mono', ui-monospace, monospace;\n font-size: 11px;\n color: #94a3b8;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n .seed-field-mapper .fm-kind {\n font-size: 10px;\n color: #64748b;\n margin-left: 6px;\n }\n .seed-field-mapper .fm-prop-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n }\n .seed-field-mapper .fm-dtype {\n font-size: 10px;\n padding: 2px 6px;\n border-radius: 4px;\n border: 1px solid;\n }\n .seed-field-mapper .fm-remove {\n background: transparent;\n border: none;\n color: #94a3b8;\n cursor: pointer;\n font-size: 14px;\n padding: 0 4px;\n }\n .seed-field-mapper .fm-remove:hover { color: #f87171; }\n .seed-field-mapper .fm-svg {\n position: absolute;\n inset: 0;\n pointer-events: none;\n overflow: visible;\n }\n .seed-field-mapper .fm-preview {\n margin-top: 12px;\n background: #020617;\n border: 1px solid #334155;\n border-radius: 8px;\n padding: 12px;\n font-family: 'JetBrains Mono', ui-monospace, monospace;\n font-size: 11px;\n white-space: pre-wrap;\n color: #cbd5e1;\n max-height: 200px;\n overflow: auto;\n }\n `}</style>\n\n <div className=\"fm-toolbar\">\n <button type=\"button\" className=\"fm-btn fm-btn-primary\" onClick={runAutoMap}>\n Auto-map\n </button>\n {showPreview && (\n <button\n type=\"button\"\n className=\"fm-btn\"\n onClick={() => setPreviewOpen((v) => !v)}\n >\n {previewOpen ? 'Hide preview' : 'Preview JSON'}\n </button>\n )}\n <span className=\"fm-meta\">\n {mappings.length} / {targets.length} mapped\n {activeSource ? ' · click a property' : ' · click a source'}\n </span>\n </div>\n\n <div className=\"fm-grid\" ref={containerRef}>\n <svg\n className=\"fm-svg\"\n width={svgDimensions.width}\n height={svgDimensions.height}\n >\n {connectorPoints.map((pt) => {\n const isHi =\n highlightedPair?.sourceId === pt.mapping.sourceId &&\n highlightedPair?.propertyName === pt.mapping.propertyName\n const midX = (pt.x1 + pt.x2) / 2\n return (\n <path\n key={`${pt.mapping.sourceId}-${pt.mapping.propertyName}`}\n d={`M ${pt.x1} ${pt.y1} C ${midX} ${pt.y1}, ${midX} ${pt.y2}, ${pt.x2} ${pt.y2}`}\n stroke={pt.color}\n strokeWidth={isHi ? 3 : 2}\n fill=\"none\"\n opacity={highlightedPair && !isHi ? 0.25 : 0.85}\n />\n )\n })}\n </svg>\n\n <div>\n <div className=\"fm-col-title\">Sources</div>\n {sources.map((source) => {\n const mapped = mappingForSource(source.id)\n const isActive = activeSource === source.id\n const isHi = highlightedPair?.sourceId === source.id\n return (\n <div\n key={source.id}\n ref={(el) => {\n sourceRefs.current[source.id] = el\n }}\n className={`fm-card${isActive ? ' active' : ''}${mapped ? ' mapped' : ''}${isHi ? ' highlight' : ''}`}\n onClick={() => handleSourceClick(source.id)}\n onMouseEnter={() => setHoveredSource(source.id)}\n onMouseLeave={() => setHoveredSource(null)}\n >\n <div className=\"fm-prop-row\">\n <div className=\"fm-label\">\n {source.label}\n <span className=\"fm-kind\">{source.kind}</span>\n </div>\n {mapped && (\n <button\n type=\"button\"\n className=\"fm-remove\"\n aria-label=\"Remove mapping\"\n onClick={(e) => {\n e.stopPropagation()\n removeMapping(source.id)\n }}\n >\n ×\n </button>\n )}\n </div>\n <div className=\"fm-value\">{truncate(source.value, 80)}</div>\n </div>\n )\n })}\n </div>\n\n <div>\n <div className=\"fm-col-title\">Model properties</div>\n {targets.map((prop) => {\n const mapped = mappingForProp(prop.name)\n const style = getDataTypeStyle(prop.dataType)\n const isHi = highlightedPair?.propertyName === prop.name\n return (\n <div\n key={prop.name}\n ref={(el) => {\n propRefs.current[prop.name] = el\n }}\n className={`fm-card${mapped ? ' mapped' : ''}${isHi ? ' highlight' : ''}${activeSource ? ' active' : ''}`}\n onClick={() => handlePropClick(prop.name)}\n onMouseEnter={() => setHoveredProp(prop.name)}\n onMouseLeave={() => setHoveredProp(null)}\n >\n <div className=\"fm-prop-row\">\n <div className=\"fm-label\">{prop.name}</div>\n <span\n className=\"fm-dtype\"\n style={{\n background: style.bg,\n borderColor: style.border,\n color: style.text,\n }}\n >\n {prop.dataType}\n </span>\n </div>\n {mapped && (\n <div className=\"fm-value\">← {mapped.sourceId}</div>\n )}\n </div>\n )\n })}\n </div>\n </div>\n\n {showPreview && previewOpen && (\n <pre className=\"fm-preview\">{JSON.stringify(preview, null, 2)}</pre>\n )}\n </div>\n )\n}\n\nexport default FieldMapper\n"],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import React from 'react';
2
+ import type { FieldMapping, SourceNode, TargetProperty } from '../types';
3
+ export type FieldMapperProps = {
4
+ sources: SourceNode[];
5
+ targets: TargetProperty[];
6
+ mappings: FieldMapping[];
7
+ onChange: (mappings: FieldMapping[]) => void;
8
+ /** Override default autoMap heuristics. */
9
+ onAutoMap?: () => FieldMapping[];
10
+ className?: string;
11
+ /** Show JSON preview of applyMapping result. Default true. */
12
+ showPreview?: boolean;
13
+ };
14
+ /**
15
+ * Generic two-pane field mapper. Props-driven; no Seed hooks or routing.
16
+ */
17
+ export declare function FieldMapper({ sources, targets, mappings, onChange, onAutoMap, className, showPreview, }: FieldMapperProps): React.JSX.Element;
18
+ export default FieldMapper;
19
+ //# sourceMappingURL=FieldMapper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FieldMapper.d.ts","sourceRoot":"","sources":["../../src/react/FieldMapper.tsx"],"names":[],"mappings":"AAAA,OAAO,KAMN,MAAM,OAAO,CAAA;AAGd,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AAsBxE,MAAM,MAAM,gBAAgB,GAAG;IAC7B,OAAO,EAAE,UAAU,EAAE,CAAA;IACrB,OAAO,EAAE,cAAc,EAAE,CAAA;IACzB,QAAQ,EAAE,YAAY,EAAE,CAAA;IACxB,QAAQ,EAAE,CAAC,QAAQ,EAAE,YAAY,EAAE,KAAK,IAAI,CAAA;IAC5C,2CAA2C;IAC3C,SAAS,CAAC,EAAE,MAAM,YAAY,EAAE,CAAA;IAChC,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,8DAA8D;IAC9D,WAAW,CAAC,EAAE,OAAO,CAAA;CACtB,CAAA;AA+BD;;GAEG;AACH,wBAAgB,WAAW,CAAC,EAC1B,OAAO,EACP,OAAO,EACP,QAAQ,EACR,QAAQ,EACR,SAAS,EACT,SAAS,EACT,WAAkB,GACnB,EAAE,gBAAgB,qBAsWlB;AAED,eAAe,WAAW,CAAA"}
@@ -0,0 +1,29 @@
1
+ /** Kind of a source field extracted from an external document. */
2
+ export type SourceKind = 'frontmatter' | 'section' | 'full' | 'rssField' | 'xmlPath';
3
+ /** A mappable value from markdown, RSS, or XML. */
4
+ export type SourceNode = {
5
+ id: string;
6
+ label: string;
7
+ kind: SourceKind;
8
+ value: string;
9
+ meta?: Record<string, unknown>;
10
+ };
11
+ /** Target Seed model property. */
12
+ export type TargetProperty = {
13
+ name: string;
14
+ dataType: string;
15
+ };
16
+ /** One-to-one connection from a source node to a model property. */
17
+ export type FieldMapping = {
18
+ sourceId: string;
19
+ propertyName: string;
20
+ };
21
+ /** Persistable mapping authored in the UI (or programmatically). */
22
+ export type MappingDocument = {
23
+ version: 1;
24
+ sourceKind: 'markdown' | 'rss' | 'xml';
25
+ mappings: FieldMapping[];
26
+ };
27
+ /** Result of applying mappings: property name → coerced value. */
28
+ export type PropertyBag = Record<string, unknown>;
29
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,kEAAkE;AAClE,MAAM,MAAM,UAAU,GAClB,aAAa,GACb,SAAS,GACT,MAAM,GACN,UAAU,GACV,SAAS,CAAA;AAEb,mDAAmD;AACnD,MAAM,MAAM,UAAU,GAAG;IACvB,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,UAAU,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAC/B,CAAA;AAED,kCAAkC;AAClC,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,MAAM,CAAA;CACjB,CAAA;AAED,oEAAoE;AACpE,MAAM,MAAM,YAAY,GAAG;IACzB,QAAQ,EAAE,MAAM,CAAA;IAChB,YAAY,EAAE,MAAM,CAAA;CACrB,CAAA;AAED,oEAAoE;AACpE,MAAM,MAAM,eAAe,GAAG;IAC5B,OAAO,EAAE,CAAC,CAAA;IACV,UAAU,EAAE,UAAU,GAAG,KAAK,GAAG,KAAK,CAAA;IACtC,QAAQ,EAAE,YAAY,EAAE,CAAA;CACzB,CAAA;AAED,kEAAkE;AAClE,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA"}
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@seedprotocol/mapping",
3
+ "version": "0.5.3",
4
+ "description": "Source-to-model-property field mapping helpers and UI for Seed Protocol",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "exports": {
8
+ ".": {
9
+ "import": {
10
+ "types": "./dist/index.d.ts",
11
+ "default": "./dist/index.js"
12
+ },
13
+ "require": {
14
+ "types": "./dist/index.d.ts",
15
+ "default": "./dist/index.js"
16
+ }
17
+ },
18
+ "./package.json": "./package.json"
19
+ },
20
+ "files": [
21
+ "dist",
22
+ "CHANGELOG.md"
23
+ ],
24
+ "scripts": {
25
+ "build": "tsc -p tsconfig.build.json && vite build",
26
+ "prepublishOnly": "npm run build && node ../../scripts/sync-versions.js",
27
+ "test": "vitest run --config vitest.config.ts"
28
+ },
29
+ "dependencies": {
30
+ "rss-parser": "^3.13.0"
31
+ },
32
+ "devDependencies": {
33
+ "@types/react": "^19.0.2",
34
+ "@types/react-dom": "^19.0.2",
35
+ "@vitejs/plugin-react": "^6.0.0",
36
+ "react": "^19.0.0",
37
+ "react-dom": "^19.0.0",
38
+ "typescript": "~5.9.3",
39
+ "vite": "^8.0.0",
40
+ "vitest": "^4.0.16"
41
+ },
42
+ "peerDependencies": {
43
+ "react": "^18 || ^19",
44
+ "react-dom": "^18 || ^19"
45
+ },
46
+ "author": "Keith Axline <keith@journodao.xyz>",
47
+ "license": "MIT",
48
+ "publishConfig": {
49
+ "access": "public"
50
+ }
51
+ }