@liiift-studio/sanity-font-manager 2.5.0 → 2.5.1
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/dist/{UploadModal-NME2W53V.mjs → UploadModal-ADNRGQUI.mjs} +1 -1
- package/dist/UploadModal-WPK2CXLR.js +6 -0
- package/dist/{chunk-FH4QKHOH.js → chunk-JCDZ7SWZ.js} +581 -146
- package/dist/{chunk-646WCBRR.mjs → chunk-TMDE4A54.mjs} +615 -180
- package/dist/index.js +47 -45
- package/dist/index.mjs +4 -2
- package/package.json +1 -1
- package/src/components/FontReviewCard.jsx +41 -1
- package/src/components/UploadModal.jsx +43 -7
- package/src/components/UploadStep2Review.jsx +2 -0
- package/src/components/UploadStep3bInstances.jsx +396 -0
- package/src/index.js +1 -0
- package/src/utils/buildUploadPlan.js +1 -0
- package/src/utils/executeUploadPlan.js +1 -8
- package/src/utils/parseVariableFontInstances.js +237 -147
- package/src/utils/processFontFiles.js +5 -4
- package/src/utils/updateTypefaceDocument.js +15 -2
- package/dist/UploadModal-6LIX7XOK.js +0 -6
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
// Step 3.5 — Variable font instance mapping using the production parseVariableFontInstances matcher
|
|
2
|
+
|
|
3
|
+
import React, { useState, useEffect, useCallback, useMemo } from 'react';
|
|
4
|
+
import { Box, Stack, Flex, Text, Card, Badge, Button, Spinner, Autocomplete } from '@sanity/ui';
|
|
5
|
+
import { CheckmarkCircleIcon, CloseCircleIcon, SearchIcon } from '@sanity/icons';
|
|
6
|
+
import { nanoid } from 'nanoid';
|
|
7
|
+
import { parseVariableFontInstances } from '../utils/parseVariableFontInstances';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Step 3.5 — Variable font instance mapping.
|
|
11
|
+
* After upload, fetches the VF documents from Sanity and uses the production
|
|
12
|
+
* parseVariableFontInstances matcher to auto-match instances to static fonts.
|
|
13
|
+
* User can review, override via searchable autocomplete, and save.
|
|
14
|
+
*/
|
|
15
|
+
export default function UploadStep3bInstances({
|
|
16
|
+
plan,
|
|
17
|
+
executionResult,
|
|
18
|
+
client,
|
|
19
|
+
typefaceTitle,
|
|
20
|
+
onComplete,
|
|
21
|
+
}) {
|
|
22
|
+
const [loading, setLoading] = useState(true);
|
|
23
|
+
const [saving, setSaving] = useState(false);
|
|
24
|
+
const [vfMappings, setVfMappings] = useState({});
|
|
25
|
+
const [allStaticFonts, setAllStaticFonts] = useState([]);
|
|
26
|
+
const [filterUnmatched, setFilterUnmatched] = useState(false);
|
|
27
|
+
|
|
28
|
+
// Identify VF entries from the plan
|
|
29
|
+
const vfEntries = useMemo(() =>
|
|
30
|
+
Object.values(plan.fonts).filter(f => f.variableFont && f.status !== 'error'),
|
|
31
|
+
[plan.fonts]
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
/** Run the production matcher for all VFs — callable on mount and via re-run button */
|
|
35
|
+
const runMatching = useCallback(async () => {
|
|
36
|
+
setLoading(true);
|
|
37
|
+
const mappings = {};
|
|
38
|
+
|
|
39
|
+
// Fetch all static fonts for the autocomplete picker
|
|
40
|
+
let staticFonts = [];
|
|
41
|
+
try {
|
|
42
|
+
const typeface = await client.fetch(
|
|
43
|
+
`*[_type == 'typeface' && title == $typefaceTitle][0]{
|
|
44
|
+
'fonts': styles.fonts[]-> {
|
|
45
|
+
_id, title, subfamily, style, weight, weightName, metaData, variableFont
|
|
46
|
+
}
|
|
47
|
+
}`,
|
|
48
|
+
{ typefaceTitle }
|
|
49
|
+
);
|
|
50
|
+
if (typeface?.fonts?.length > 0) {
|
|
51
|
+
staticFonts = typeface.fonts.filter(f => !f.variableFont);
|
|
52
|
+
}
|
|
53
|
+
if (staticFonts.length === 0) {
|
|
54
|
+
staticFonts = await client.fetch(
|
|
55
|
+
`*[_type == 'font' && typefaceName == $typefaceTitle && variableFont != true]{
|
|
56
|
+
_id, title, subfamily, style, weight, weightName, metaData
|
|
57
|
+
}`,
|
|
58
|
+
{ typefaceTitle }
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
} catch (err) {
|
|
62
|
+
console.error('[InstanceMapper] Failed to fetch static fonts:', err);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Deduplicate by _id (draft + published versions can cause duplicates)
|
|
66
|
+
const deduped = new Map();
|
|
67
|
+
staticFonts.forEach(f => { if (f._id && !deduped.has(f._id)) deduped.set(f._id, f); });
|
|
68
|
+
staticFonts = [...deduped.values()];
|
|
69
|
+
|
|
70
|
+
console.log(`[InstanceMapper] Found ${staticFonts.length} static fonts for "${typefaceTitle}"`);
|
|
71
|
+
setAllStaticFonts(staticFonts);
|
|
72
|
+
|
|
73
|
+
for (const vf of vfEntries) {
|
|
74
|
+
try {
|
|
75
|
+
const vfDoc = await client.fetch(
|
|
76
|
+
`*[_id == $id][0]{
|
|
77
|
+
_id, title, typefaceName, variableFont, variableInstances, metaData
|
|
78
|
+
}`,
|
|
79
|
+
{ id: vf.documentId }
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
if (!vfDoc) {
|
|
83
|
+
console.warn(`[InstanceMapper] VF document not found: ${vf.documentId}`);
|
|
84
|
+
mappings[vf.tempId] = [];
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
console.log(`[InstanceMapper] Running matcher for VF: ${vfDoc.title}, variableInstances: ${vfDoc.variableInstances ? 'present' : 'missing'}`);
|
|
89
|
+
|
|
90
|
+
const instanceMappings = await parseVariableFontInstances(vfDoc, client);
|
|
91
|
+
|
|
92
|
+
console.log(`[InstanceMapper] Matched ${instanceMappings.filter(m => m.value).length}/${instanceMappings.length} instances for ${vfDoc.title}`);
|
|
93
|
+
|
|
94
|
+
mappings[vf.tempId] = instanceMappings.map(m => ({
|
|
95
|
+
instanceName: m.key,
|
|
96
|
+
matchedFontId: m.value?._ref || '',
|
|
97
|
+
matchedFontTitle: '',
|
|
98
|
+
_key: m._key || nanoid(),
|
|
99
|
+
}));
|
|
100
|
+
} catch (err) {
|
|
101
|
+
console.error(`[InstanceMapper] Error matching VF ${vf.documentId}:`, err);
|
|
102
|
+
mappings[vf.tempId] = [];
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Resolve matched font titles
|
|
107
|
+
const allMatchedIds = new Set();
|
|
108
|
+
Object.values(mappings).forEach(m => m.forEach(i => { if (i.matchedFontId) allMatchedIds.add(i.matchedFontId); }));
|
|
109
|
+
if (allMatchedIds.size > 0) {
|
|
110
|
+
try {
|
|
111
|
+
const titles = await client.fetch(`*[_id in $ids]{ _id, title }`, { ids: [...allMatchedIds] });
|
|
112
|
+
const titleMap = {};
|
|
113
|
+
titles.forEach(t => { titleMap[t._id] = t.title; });
|
|
114
|
+
Object.values(mappings).forEach(m => {
|
|
115
|
+
m.forEach(i => { if (i.matchedFontId) i.matchedFontTitle = titleMap[i.matchedFontId] || i.matchedFontId; });
|
|
116
|
+
});
|
|
117
|
+
} catch (err) {
|
|
118
|
+
console.warn('[InstanceMapper] Failed to resolve font titles:', err);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
setVfMappings(mappings);
|
|
123
|
+
setLoading(false);
|
|
124
|
+
}, [vfEntries, client, typefaceTitle]);
|
|
125
|
+
|
|
126
|
+
// Run on mount
|
|
127
|
+
useEffect(() => { runMatching(); }, [runMatching]);
|
|
128
|
+
|
|
129
|
+
// Track claimed font IDs to prevent duplicates in manual selection
|
|
130
|
+
const claimedFontIds = useMemo(() => {
|
|
131
|
+
const claimed = new Set();
|
|
132
|
+
Object.values(vfMappings).forEach(mappings => {
|
|
133
|
+
mappings.forEach(m => { if (m.matchedFontId) claimed.add(m.matchedFontId); });
|
|
134
|
+
});
|
|
135
|
+
return claimed;
|
|
136
|
+
}, [vfMappings]);
|
|
137
|
+
|
|
138
|
+
/** Update a single instance mapping */
|
|
139
|
+
const handleMappingChange = useCallback((vfTempId, instanceKey, fontId) => {
|
|
140
|
+
const font = allStaticFonts.find(sf => sf._id === fontId);
|
|
141
|
+
setVfMappings(prev => ({
|
|
142
|
+
...prev,
|
|
143
|
+
[vfTempId]: prev[vfTempId].map(m =>
|
|
144
|
+
m._key === instanceKey
|
|
145
|
+
? { ...m, matchedFontId: fontId, matchedFontTitle: font?.title || fontId }
|
|
146
|
+
: m
|
|
147
|
+
),
|
|
148
|
+
}));
|
|
149
|
+
}, [allStaticFonts]);
|
|
150
|
+
|
|
151
|
+
/** Save all mappings — patch each VF document */
|
|
152
|
+
const handleSave = useCallback(async () => {
|
|
153
|
+
setSaving(true);
|
|
154
|
+
const errors = [];
|
|
155
|
+
|
|
156
|
+
for (const vf of vfEntries) {
|
|
157
|
+
const mappings = vfMappings[vf.tempId] || [];
|
|
158
|
+
const references = mappings
|
|
159
|
+
.filter(m => m.matchedFontId)
|
|
160
|
+
.map(m => ({
|
|
161
|
+
_key: nanoid(),
|
|
162
|
+
_type: 'object',
|
|
163
|
+
key: m.instanceName,
|
|
164
|
+
value: {
|
|
165
|
+
_type: 'reference',
|
|
166
|
+
_ref: m.matchedFontId,
|
|
167
|
+
_weak: true,
|
|
168
|
+
},
|
|
169
|
+
}));
|
|
170
|
+
|
|
171
|
+
try {
|
|
172
|
+
await client.patch(vf.documentId).set({
|
|
173
|
+
variableInstanceReferences: references,
|
|
174
|
+
}).commit();
|
|
175
|
+
console.log(`Patched VF instance mappings: ${vf.documentId} (${references.length} instances)`);
|
|
176
|
+
} catch (err) {
|
|
177
|
+
console.error(`Failed to patch VF ${vf.documentId}:`, err);
|
|
178
|
+
errors.push({ vfTitle: vf.title, error: err.message });
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
setSaving(false);
|
|
183
|
+
onComplete({ success: errors.length === 0, errors });
|
|
184
|
+
}, [vfEntries, vfMappings, client, onComplete]);
|
|
185
|
+
|
|
186
|
+
// Stats
|
|
187
|
+
const totalInstances = Object.values(vfMappings).reduce((sum, m) => sum + m.length, 0);
|
|
188
|
+
const matchedInstances = Object.values(vfMappings).reduce(
|
|
189
|
+
(sum, m) => sum + m.filter(i => i.matchedFontId).length, 0
|
|
190
|
+
);
|
|
191
|
+
const unmatchedInstances = totalInstances - matchedInstances;
|
|
192
|
+
|
|
193
|
+
// Autocomplete options — exclude already-claimed fonts
|
|
194
|
+
const getAutocompleteOptions = useCallback((currentFontId) => {
|
|
195
|
+
return allStaticFonts
|
|
196
|
+
.filter(sf => !claimedFontIds.has(sf._id) || sf._id === currentFontId)
|
|
197
|
+
.map(sf => ({
|
|
198
|
+
value: sf._id,
|
|
199
|
+
payload: sf,
|
|
200
|
+
}));
|
|
201
|
+
}, [allStaticFonts, claimedFontIds]);
|
|
202
|
+
|
|
203
|
+
if (loading) {
|
|
204
|
+
return (
|
|
205
|
+
<Card border padding={4} radius={2}>
|
|
206
|
+
<Flex align="center" gap={3} justify="center">
|
|
207
|
+
<Spinner />
|
|
208
|
+
<Text size={1}>Matching variable font instances to static fonts...</Text>
|
|
209
|
+
</Flex>
|
|
210
|
+
</Card>
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (vfEntries.length === 0) {
|
|
215
|
+
onComplete({ success: true, errors: [] });
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return (
|
|
220
|
+
<Stack space={4}>
|
|
221
|
+
{/* Header */}
|
|
222
|
+
<Stack space={2}>
|
|
223
|
+
<Text size={2} weight="semibold">Map Variable Font Instances</Text>
|
|
224
|
+
<Text size={1} muted>
|
|
225
|
+
Review the auto-matched instances below. Each named instance should map to its corresponding static font document.
|
|
226
|
+
</Text>
|
|
227
|
+
</Stack>
|
|
228
|
+
|
|
229
|
+
{/* Stats */}
|
|
230
|
+
<Flex gap={2} align="center">
|
|
231
|
+
<Button
|
|
232
|
+
mode="ghost"
|
|
233
|
+
tone="primary"
|
|
234
|
+
icon={SearchIcon}
|
|
235
|
+
text="Re-run Matching"
|
|
236
|
+
fontSize={0}
|
|
237
|
+
padding={2}
|
|
238
|
+
onClick={runMatching}
|
|
239
|
+
disabled={loading}
|
|
240
|
+
style={{ cursor: 'pointer' }}
|
|
241
|
+
/>
|
|
242
|
+
<Badge tone="positive" fontSize={0}>{matchedInstances} matched</Badge>
|
|
243
|
+
{unmatchedInstances > 0 && (
|
|
244
|
+
<Badge
|
|
245
|
+
tone="critical"
|
|
246
|
+
fontSize={0}
|
|
247
|
+
style={{ cursor: 'pointer' }}
|
|
248
|
+
onClick={() => setFilterUnmatched(v => !v)}
|
|
249
|
+
>
|
|
250
|
+
{unmatchedInstances} unmatched {filterUnmatched ? '(showing)' : ''}
|
|
251
|
+
</Badge>
|
|
252
|
+
)}
|
|
253
|
+
{filterUnmatched && (
|
|
254
|
+
<Badge
|
|
255
|
+
mode="outline"
|
|
256
|
+
fontSize={0}
|
|
257
|
+
style={{ cursor: 'pointer' }}
|
|
258
|
+
onClick={() => setFilterUnmatched(false)}
|
|
259
|
+
>
|
|
260
|
+
Clear filter
|
|
261
|
+
</Badge>
|
|
262
|
+
)}
|
|
263
|
+
<Badge mode="outline" fontSize={0}>{allStaticFonts.length} fonts available</Badge>
|
|
264
|
+
</Flex>
|
|
265
|
+
|
|
266
|
+
{/* Per-VF mapping sections */}
|
|
267
|
+
{vfEntries.map(vf => {
|
|
268
|
+
const mappings = vfMappings[vf.tempId] || [];
|
|
269
|
+
const displayMappings = filterUnmatched
|
|
270
|
+
? mappings.filter(m => !m.matchedFontId)
|
|
271
|
+
: mappings;
|
|
272
|
+
const vfMatched = mappings.filter(m => m.matchedFontId).length;
|
|
273
|
+
|
|
274
|
+
return (
|
|
275
|
+
<Card key={vf.tempId} border padding={3} radius={2}>
|
|
276
|
+
<Stack space={3}>
|
|
277
|
+
{/* VF header */}
|
|
278
|
+
<Flex align="center" gap={2}>
|
|
279
|
+
<Badge tone="primary" fontSize={0}>VF</Badge>
|
|
280
|
+
<Text size={1} weight="semibold">{vf.title}</Text>
|
|
281
|
+
<Text size={0} muted style={{ marginLeft: 'auto' }}>
|
|
282
|
+
{vfMatched}/{mappings.length} matched
|
|
283
|
+
</Text>
|
|
284
|
+
</Flex>
|
|
285
|
+
|
|
286
|
+
{/* Column headers */}
|
|
287
|
+
<Flex align="center" gap={2} paddingY={1} style={{ borderBottom: '1px solid var(--card-border-color)' }}>
|
|
288
|
+
<Box style={{ width: 20 }} />
|
|
289
|
+
<Text size={0} weight="semibold" muted style={{ flex: 1 }}>Instance</Text>
|
|
290
|
+
<Text size={0} weight="semibold" muted style={{ flex: 2 }}>Static Font Document</Text>
|
|
291
|
+
</Flex>
|
|
292
|
+
|
|
293
|
+
{/* Instance rows */}
|
|
294
|
+
<Stack space={1}>
|
|
295
|
+
{displayMappings.map(mapping => {
|
|
296
|
+
const isMatched = !!mapping.matchedFontId;
|
|
297
|
+
const options = getAutocompleteOptions(mapping.matchedFontId);
|
|
298
|
+
|
|
299
|
+
return (
|
|
300
|
+
<Flex
|
|
301
|
+
key={mapping._key}
|
|
302
|
+
align="center"
|
|
303
|
+
gap={2}
|
|
304
|
+
paddingY={2}
|
|
305
|
+
style={{ borderBottom: '1px solid var(--card-border-color)' }}
|
|
306
|
+
>
|
|
307
|
+
<Box style={{ width: 20, flexShrink: 0 }}>
|
|
308
|
+
{isMatched
|
|
309
|
+
? <CheckmarkCircleIcon style={{ color: '#43b649', fontSize: 16 }} />
|
|
310
|
+
: <CloseCircleIcon style={{ color: '#f03e2f', fontSize: 16 }} />
|
|
311
|
+
}
|
|
312
|
+
</Box>
|
|
313
|
+
|
|
314
|
+
<Text size={1} style={{ flex: 1, whiteSpace: 'nowrap' }}>{mapping.instanceName}</Text>
|
|
315
|
+
|
|
316
|
+
<Box style={{ flex: 2 }}>
|
|
317
|
+
<Autocomplete
|
|
318
|
+
id={`instance-${mapping._key}`}
|
|
319
|
+
options={options}
|
|
320
|
+
value={mapping.matchedFontId}
|
|
321
|
+
placeholder="Search for a font..."
|
|
322
|
+
icon={SearchIcon}
|
|
323
|
+
fontSize={1}
|
|
324
|
+
filterOption={(query, option) => {
|
|
325
|
+
const sf = option.payload;
|
|
326
|
+
const q = query.toLowerCase();
|
|
327
|
+
return (
|
|
328
|
+
sf.title?.toLowerCase().includes(q) ||
|
|
329
|
+
sf._id?.toLowerCase().includes(q) ||
|
|
330
|
+
sf.weightName?.toLowerCase().includes(q) ||
|
|
331
|
+
String(sf.weight).includes(q) ||
|
|
332
|
+
sf.subfamily?.toLowerCase().includes(q)
|
|
333
|
+
);
|
|
334
|
+
}}
|
|
335
|
+
renderOption={(option) => {
|
|
336
|
+
const sf = option.payload;
|
|
337
|
+
const isClaimed = claimedFontIds.has(sf._id) && sf._id !== mapping.matchedFontId;
|
|
338
|
+
return (
|
|
339
|
+
<Card as="button" padding={2} style={{ opacity: isClaimed ? 0.4 : 1 }}>
|
|
340
|
+
<Flex align="center" gap={2}>
|
|
341
|
+
<Text size={1}>{sf.title}</Text>
|
|
342
|
+
<Text size={0} muted>{sf.weight} {sf.style}</Text>
|
|
343
|
+
{sf.subfamily && sf.subfamily !== 'Regular' && (
|
|
344
|
+
<Badge mode="outline" fontSize={0}>{sf.subfamily}</Badge>
|
|
345
|
+
)}
|
|
346
|
+
</Flex>
|
|
347
|
+
</Card>
|
|
348
|
+
);
|
|
349
|
+
}}
|
|
350
|
+
renderValue={(value, option) => {
|
|
351
|
+
if (option?.payload) return option.payload.title;
|
|
352
|
+
if (mapping.matchedFontTitle) return mapping.matchedFontTitle;
|
|
353
|
+
const font = allStaticFonts.find(sf => sf._id === value);
|
|
354
|
+
return font?.title || value;
|
|
355
|
+
}}
|
|
356
|
+
onSelect={(value) => handleMappingChange(vf.tempId, mapping._key, value)}
|
|
357
|
+
openButton
|
|
358
|
+
/>
|
|
359
|
+
</Box>
|
|
360
|
+
</Flex>
|
|
361
|
+
);
|
|
362
|
+
})}
|
|
363
|
+
</Stack>
|
|
364
|
+
|
|
365
|
+
{mappings.length === 0 && (
|
|
366
|
+
<Text size={1} muted>No named instances found in this variable font.</Text>
|
|
367
|
+
)}
|
|
368
|
+
</Stack>
|
|
369
|
+
</Card>
|
|
370
|
+
);
|
|
371
|
+
})}
|
|
372
|
+
|
|
373
|
+
{/* Actions */}
|
|
374
|
+
<Flex justify="flex-end" gap={2} style={{ position: 'sticky', bottom: 0, background: 'var(--card-bg-color)', paddingTop: 8, paddingBottom: 4 }}>
|
|
375
|
+
<Button
|
|
376
|
+
mode="ghost"
|
|
377
|
+
text="Skip — I'll map instances later"
|
|
378
|
+
fontSize={1}
|
|
379
|
+
padding={3}
|
|
380
|
+
onClick={() => onComplete({ success: true, errors: [], skipped: true })}
|
|
381
|
+
style={{ cursor: 'pointer' }}
|
|
382
|
+
/>
|
|
383
|
+
<Button
|
|
384
|
+
mode="default"
|
|
385
|
+
tone="positive"
|
|
386
|
+
text={saving ? 'Saving...' : `Save Mappings (${matchedInstances}/${totalInstances})`}
|
|
387
|
+
fontSize={1}
|
|
388
|
+
padding={3}
|
|
389
|
+
disabled={saving}
|
|
390
|
+
onClick={handleSave}
|
|
391
|
+
style={{ cursor: 'pointer' }}
|
|
392
|
+
/>
|
|
393
|
+
</Flex>
|
|
394
|
+
</Stack>
|
|
395
|
+
);
|
|
396
|
+
}
|
package/src/index.js
CHANGED
|
@@ -76,6 +76,7 @@ export { default as UploadModal } from './components/UploadModal.jsx';
|
|
|
76
76
|
export { default as UploadStep1Settings } from './components/UploadStep1Settings.jsx';
|
|
77
77
|
export { default as UploadStep2Review } from './components/UploadStep2Review.jsx';
|
|
78
78
|
export { default as UploadStep3Execute } from './components/UploadStep3Execute.jsx';
|
|
79
|
+
export { default as UploadStep3bInstances } from './components/UploadStep3bInstances.jsx';
|
|
79
80
|
export { default as UploadSummary } from './components/UploadSummary.jsx';
|
|
80
81
|
export { default as FontReviewCard } from './components/FontReviewCard.jsx';
|
|
81
82
|
export { default as ExistingDocumentResolver } from './components/ExistingDocumentResolver.jsx';
|
|
@@ -327,9 +327,7 @@ async function executeSingleFont({ entry, plan, client, progress, onProgress })
|
|
|
327
327
|
_key: nanoid(),
|
|
328
328
|
title: entry.title,
|
|
329
329
|
slug: { _type: 'slug', current: fontDocId },
|
|
330
|
-
typefaceName: plan.
|
|
331
|
-
? entry.title.split(' ').slice(0, -1).join(' ') || entry.title
|
|
332
|
-
: entry.title,
|
|
330
|
+
typefaceName: plan.settings.typefaceTitle || entry.title,
|
|
333
331
|
style: entry.style,
|
|
334
332
|
variableFont: entry.variableFont,
|
|
335
333
|
weightName: entry.weightName,
|
|
@@ -341,11 +339,6 @@ async function executeSingleFont({ entry, plan, client, progress, onProgress })
|
|
|
341
339
|
fileInput,
|
|
342
340
|
};
|
|
343
341
|
|
|
344
|
-
// Set typefaceName from the typeface title, not derived from font title
|
|
345
|
-
fontDoc.typefaceName = Object.values(plan.fonts)[0]?.decisions?.title?.original
|
|
346
|
-
? plan.settings.typefaceTitle || fontDoc.typefaceName
|
|
347
|
-
: fontDoc.typefaceName;
|
|
348
|
-
|
|
349
342
|
// Add metadata fields if available
|
|
350
343
|
if (entry.metaData) fontDoc.metaData = entry.metaData;
|
|
351
344
|
if (entry.metrics) fontDoc.metrics = entry.metrics;
|