@liiift-studio/sanity-font-manager 2.4.0 → 2.5.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/dist/UploadModal-6LIX7XOK.js +6 -0
- package/dist/UploadModal-NME2W53V.mjs +6 -0
- package/dist/chunk-646WCBRR.mjs +7276 -0
- package/dist/chunk-FH4QKHOH.js +7276 -0
- package/dist/index.js +664 -1647
- package/dist/index.mjs +317 -1209
- package/package.json +5 -5
- package/src/components/BatchUploadFonts.jsx +57 -44
- package/src/components/BulkActions.jsx +99 -0
- package/src/components/ExistingDocumentResolver.jsx +152 -0
- package/src/components/FontReviewCard.jsx +415 -0
- package/src/components/SingleUploaderTool.jsx +3 -4
- package/src/components/UploadModal.jsx +268 -0
- package/src/components/UploadScriptsComponent.jsx +23 -21
- package/src/components/UploadStep1Settings.jsx +272 -0
- package/src/components/UploadStep2Review.jsx +472 -0
- package/src/components/UploadStep3Execute.jsx +234 -0
- package/src/components/UploadSummary.jsx +196 -0
- package/src/index.js +45 -0
- package/src/utils/buildUploadPlan.js +325 -0
- package/src/utils/executeUploadPlan.js +437 -0
- package/src/utils/executionReducer.js +56 -0
- package/src/utils/fontHelpers.js +267 -0
- package/src/utils/generateCssFile.js +79 -77
- package/src/utils/generateFontData.js +47 -94
- package/src/utils/getEmptyFontKit.js +19 -17
- package/src/utils/parseFont.js +55 -0
- package/src/utils/planReducer.js +517 -0
- package/src/utils/planTypes.js +183 -0
- package/src/utils/processFontFiles.js +120 -78
- package/src/utils/regenerateFontData.js +2 -2
- package/src/utils/resolveExistingFont.js +87 -0
- package/src/utils/uploadFontFiles.js +405 -405
|
@@ -1,19 +1,20 @@
|
|
|
1
|
-
// Reads font files via FileReader, parses with
|
|
1
|
+
// Reads font files via FileReader, parses with lib-font, and builds the fontsObjects map — exports individual weight/style extraction helpers
|
|
2
2
|
|
|
3
|
-
import
|
|
3
|
+
import { parseFont } from './parseFont';
|
|
4
|
+
import { getNameString, getVariationAxes, getItalicAngle, getWeightClass } from './fontHelpers';
|
|
4
5
|
import { nanoid } from 'nanoid';
|
|
5
6
|
import { expandAbbreviations, removeWeightNames, reverseSpellingLookup } from './generateKeywords';
|
|
6
7
|
import { sanitizeForSanityId } from './sanitizeForSanityId';
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
|
-
* Reads a font file and returns its content as
|
|
10
|
+
* Reads a font file and returns its content as an ArrayBuffer.
|
|
10
11
|
* @param {File} file
|
|
11
|
-
* @returns {Promise<
|
|
12
|
+
* @returns {Promise<ArrayBuffer>}
|
|
12
13
|
*/
|
|
13
14
|
export const readFontFile = (file) => {
|
|
14
15
|
return new Promise((resolve, reject) => {
|
|
15
16
|
const reader = new FileReader();
|
|
16
|
-
reader.onload = (event) => { resolve(
|
|
17
|
+
reader.onload = (event) => { resolve(event.target.result); };
|
|
17
18
|
reader.onerror = (error) => { reject(error); };
|
|
18
19
|
reader.readAsArrayBuffer(file);
|
|
19
20
|
});
|
|
@@ -47,13 +48,12 @@ export const processFontFiles = async (
|
|
|
47
48
|
for (let i = 0; i < files.length; i++) {
|
|
48
49
|
const file = files[i];
|
|
49
50
|
const fontBuffer = await readFontFile(file);
|
|
50
|
-
const font =
|
|
51
|
+
const font = await parseFont(fontBuffer, file.name);
|
|
51
52
|
|
|
52
|
-
console.log('File name:
|
|
53
|
+
console.log('File name:', file.name);
|
|
53
54
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
55
|
+
// For webfonts with missing metadata, try to extract from TTF companion
|
|
56
|
+
const ttfFallbackMeta = await getWebfontFallbackMetadata(file, font, files);
|
|
57
57
|
|
|
58
58
|
let { weightName, subfamilyName, fontTitle, style, italicKW, variableFont } = extractFontMetadata(
|
|
59
59
|
font,
|
|
@@ -61,6 +61,7 @@ export const processFontFiles = async (
|
|
|
61
61
|
weightKeywordList,
|
|
62
62
|
italicKeywordList,
|
|
63
63
|
preserveShortenedNames,
|
|
64
|
+
ttfFallbackMeta,
|
|
64
65
|
);
|
|
65
66
|
|
|
66
67
|
let id;
|
|
@@ -109,72 +110,106 @@ export const processFontFiles = async (
|
|
|
109
110
|
|
|
110
111
|
console.log('Subfamilies:', subfamilies);
|
|
111
112
|
console.log('Unique subfamilies:', uniqueSubfamilies, uniqueSubfamilies.length);
|
|
112
|
-
console.log('Font objects:', fontsObjects);
|
|
113
|
+
console.log('Font objects:', Object.keys(fontsObjects));
|
|
113
114
|
|
|
114
115
|
return { fontsObjects, subfamilies, uniqueSubfamilies, newPreferredStyle, failedFiles };
|
|
115
116
|
};
|
|
116
117
|
|
|
117
118
|
/**
|
|
118
|
-
*
|
|
119
|
+
* Gets fallback metadata from a matching TTF when woff/woff2 metadata is missing.
|
|
120
|
+
* Returns null if no fallback is needed or no TTF companion exists.
|
|
121
|
+
* Unlike the old fontkit approach, this does NOT mutate the font object.
|
|
119
122
|
* @param {File} file
|
|
120
|
-
* @param {
|
|
123
|
+
* @param {object} font - lib-font parsed font
|
|
121
124
|
* @param {File[]} files
|
|
125
|
+
* @returns {Promise<{ fullName: string, familyName: string, subfamilyName: string, preferredSubfamily: string }|null>}
|
|
122
126
|
*/
|
|
123
|
-
const
|
|
124
|
-
if (
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
)
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
127
|
+
const getWebfontFallbackMetadata = async (file, font, files) => {
|
|
128
|
+
if (!file.name.endsWith('.woff2') && !file.name.endsWith('.woff')) return null;
|
|
129
|
+
|
|
130
|
+
const fullName = getNameString(font, 4);
|
|
131
|
+
// Check if name table is missing or corrupt (empty, or only uppercase hex-like garbage)
|
|
132
|
+
if (fullName && fullName !== '' && !/^[A-Z0-9]+$/.test(fullName)) return null;
|
|
133
|
+
|
|
134
|
+
const ttfFile = files.find(f => f.name === file.name.replace('.woff2', '.ttf').replace('.woff', '.ttf'));
|
|
135
|
+
if (!ttfFile) return null;
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
const ttfBuffer = await readFontFile(ttfFile);
|
|
139
|
+
const ttfFont = await parseFont(ttfBuffer, ttfFile.name);
|
|
140
|
+
return {
|
|
141
|
+
fullName: getNameString(ttfFont, 4),
|
|
142
|
+
familyName: getNameString(ttfFont, 1),
|
|
143
|
+
subfamilyName: getNameString(ttfFont, 2),
|
|
144
|
+
preferredSubfamily: getNameString(ttfFont, 17),
|
|
145
|
+
preferredFamily: getNameString(ttfFont, 16),
|
|
146
|
+
};
|
|
147
|
+
} catch (err) {
|
|
148
|
+
console.warn('Could not parse TTF companion for webfont fallback:', err.message);
|
|
149
|
+
return null;
|
|
135
150
|
}
|
|
136
151
|
};
|
|
137
152
|
|
|
138
153
|
/**
|
|
139
|
-
* Extracts and normalises metadata from a
|
|
140
|
-
* @param {
|
|
141
|
-
* @param {string} title
|
|
154
|
+
* Extracts and normalises metadata from a lib-font parsed font object.
|
|
155
|
+
* @param {object} font - lib-font parsed font
|
|
156
|
+
* @param {string} title - Typeface title
|
|
142
157
|
* @param {string[]} weightKeywordList
|
|
143
158
|
* @param {string[]} italicKeywordList
|
|
144
159
|
* @param {boolean} preserveShortenedNames
|
|
160
|
+
* @param {object|null} ttfFallbackMeta - Fallback metadata from TTF companion (for webfonts with missing names)
|
|
145
161
|
* @returns {Object}
|
|
146
162
|
*/
|
|
147
|
-
export const extractFontMetadata = (font, title, weightKeywordList, italicKeywordList, preserveShortenedNames = false) => {
|
|
148
|
-
let weightName = extractWeightName(font, italicKeywordList);
|
|
163
|
+
export const extractFontMetadata = (font, title, weightKeywordList, italicKeywordList, preserveShortenedNames = false, ttfFallbackMeta = null) => {
|
|
164
|
+
let weightName = extractWeightName(font, italicKeywordList, ttfFallbackMeta);
|
|
149
165
|
if (!preserveShortenedNames) {
|
|
150
166
|
weightName = expandAbbreviations(weightName);
|
|
151
167
|
}
|
|
152
168
|
|
|
153
|
-
|
|
154
|
-
|
|
169
|
+
const fullName = getNameString(font, 4) || ttfFallbackMeta?.fullName || '';
|
|
170
|
+
|
|
171
|
+
if ((weightName === '' || weightName.toLowerCase() === 'roman') && fullName) {
|
|
172
|
+
weightName = extractWeightFromFullName(font, title, ttfFallbackMeta);
|
|
155
173
|
if (!preserveShortenedNames) {
|
|
156
174
|
weightName = expandAbbreviations(weightName);
|
|
157
175
|
}
|
|
158
176
|
}
|
|
159
177
|
|
|
160
|
-
const
|
|
178
|
+
const axes = getVariationAxes(font);
|
|
179
|
+
const variableFont = axes !== null;
|
|
180
|
+
|
|
181
|
+
// Subfamily detection — extract width/optical variant from name table.
|
|
182
|
+
// Primary: nameId4 (fullName) minus typeface title — the most complete name record,
|
|
183
|
+
// always contains width + weight (e.g. "Gear XXNarrow Regular" → "XXNarrow Regular").
|
|
184
|
+
// Fallback: nameId1 (familyName) minus typeface title — contains width but not always weight.
|
|
185
|
+
// processSubfamilyName then strips weight/italic keywords, leaving just the width variant.
|
|
186
|
+
// This matches the production logic that has been reliable across all foundry sites.
|
|
187
|
+
const trimmedTitle = title.trim();
|
|
161
188
|
|
|
162
|
-
|
|
163
|
-
|
|
189
|
+
const nameId4Remainder = fullName ? fullName.replace(trimmedTitle, '').trim() : '';
|
|
190
|
+
const nameId1 = getNameString(font, 1) || ttfFallbackMeta?.familyName || '';
|
|
191
|
+
const nameId1Remainder = nameId1 ? nameId1.replace(trimmedTitle, '').trim() : '';
|
|
192
|
+
|
|
193
|
+
let subfamilyName = nameId4Remainder || nameId1Remainder;
|
|
164
194
|
|
|
165
195
|
if (!preserveShortenedNames) {
|
|
166
196
|
subfamilyName = expandAbbreviations(subfamilyName);
|
|
167
197
|
}
|
|
168
198
|
|
|
169
|
-
let fontTitle =
|
|
170
|
-
|
|
199
|
+
let fontTitle = fullName.trim() || '';
|
|
200
|
+
const italicAngle = getItalicAngle(font);
|
|
201
|
+
let style = (italicAngle !== 0 || fullName.toLowerCase().includes('italic')) ? 'Italic' : 'Regular';
|
|
171
202
|
|
|
172
203
|
const italicKW = processItalicKeywords(font, fontTitle, italicKeywordList);
|
|
173
204
|
|
|
174
205
|
subfamilyName = processSubfamilyName(subfamilyName, weightKeywordList, italicKW, preserveShortenedNames);
|
|
175
206
|
fontTitle = formatFontTitle(fontTitle, preserveShortenedNames);
|
|
176
207
|
|
|
177
|
-
|
|
208
|
+
// Style-only names are not subfamilies — strip them
|
|
209
|
+
subfamilyName = subfamilyName
|
|
210
|
+
.replace(/\b(Italic|Slant|Slanted|Oblique|Backslant|Roman|Upright)\b/gi, '')
|
|
211
|
+
.replace(/\s+/g, ' ')
|
|
212
|
+
.trim();
|
|
178
213
|
|
|
179
214
|
if (subfamilyName !== '') {
|
|
180
215
|
weightName = weightName
|
|
@@ -187,6 +222,7 @@ export const extractFontMetadata = (font, title, weightKeywordList, italicKeywor
|
|
|
187
222
|
if (!fontTitle.toLowerCase().includes('vf')) {
|
|
188
223
|
fontTitle = fontTitle + ' VF';
|
|
189
224
|
}
|
|
225
|
+
// Variable fonts are not placed in subfamilies — they go in the separate variableFont array
|
|
190
226
|
subfamilyName = '';
|
|
191
227
|
}
|
|
192
228
|
|
|
@@ -200,20 +236,18 @@ export const extractFontMetadata = (font, title, weightKeywordList, italicKeywor
|
|
|
200
236
|
/**
|
|
201
237
|
* Extracts the weight name from a font's preferred subfamily or subfamily record.
|
|
202
238
|
* Returns "Variable" for variable fonts.
|
|
203
|
-
* @param {
|
|
239
|
+
* @param {object} font - lib-font parsed font
|
|
204
240
|
* @param {string[]} italicKW
|
|
241
|
+
* @param {object|null} ttfFallbackMeta
|
|
205
242
|
* @returns {string}
|
|
206
243
|
*/
|
|
207
|
-
export const extractWeightName = (font, italicKW) => {
|
|
208
|
-
let weightName = font
|
|
209
|
-
|
|
210
|
-
if (typeof weightName === 'object') {
|
|
211
|
-
weightName = weightName?.en ||
|
|
212
|
-
(weightName.constructor === Object ? weightName[Object.keys(weightName)[0]] : null);
|
|
213
|
-
}
|
|
244
|
+
export const extractWeightName = (font, italicKW, ttfFallbackMeta = null) => {
|
|
245
|
+
let weightName = getNameString(font, 17) || getNameString(font, 2) ||
|
|
246
|
+
ttfFallbackMeta?.preferredSubfamily || ttfFallbackMeta?.subfamilyName || '';
|
|
214
247
|
|
|
215
|
-
|
|
216
|
-
|
|
248
|
+
const axes = getVariationAxes(font);
|
|
249
|
+
if (axes !== null) {
|
|
250
|
+
return '';
|
|
217
251
|
}
|
|
218
252
|
|
|
219
253
|
if (italicKW) {
|
|
@@ -236,17 +270,15 @@ export const extractWeightName = (font, italicKW) => {
|
|
|
236
270
|
|
|
237
271
|
/**
|
|
238
272
|
* Extracts a weight name from the font's full name record when subfamily is empty or "Roman".
|
|
239
|
-
* @param {
|
|
273
|
+
* @param {object} font - lib-font parsed font
|
|
240
274
|
* @param {string} title
|
|
275
|
+
* @param {object|null} ttfFallbackMeta
|
|
241
276
|
* @returns {string}
|
|
242
277
|
*/
|
|
243
|
-
export const extractWeightFromFullName = (font, title) => {
|
|
244
|
-
let weightName = font?.
|
|
245
|
-
weightName = weightName
|
|
246
|
-
|
|
247
|
-
: (weightName?.constructor === Object ? weightName[Object.keys(weightName)[0]] : weightName);
|
|
248
|
-
weightName = weightName?.replace(title + ' ', '').replace(title, '').trim();
|
|
249
|
-
weightName = weightName?.replace('Italic', '').replace('It', '').replace('Slanted', '').replace('Slant', '').trim();
|
|
278
|
+
export const extractWeightFromFullName = (font, title, ttfFallbackMeta = null) => {
|
|
279
|
+
let weightName = getNameString(font, 4) || ttfFallbackMeta?.fullName || '';
|
|
280
|
+
weightName = weightName.replace(title + ' ', '').replace(title, '').trim();
|
|
281
|
+
weightName = weightName.replace('Italic', '').replace('It', '').replace('Slanted', '').replace('Slant', '').trim();
|
|
250
282
|
return weightName;
|
|
251
283
|
};
|
|
252
284
|
|
|
@@ -282,13 +314,14 @@ export const processSubfamilyName = (subfamilyName, weightKeywordList, italicKey
|
|
|
282
314
|
|
|
283
315
|
/**
|
|
284
316
|
* Collects italic keywords present in a font's full name.
|
|
285
|
-
* @param {
|
|
317
|
+
* @param {object} font - lib-font parsed font
|
|
286
318
|
* @param {string} fontTitle
|
|
287
319
|
* @param {string[]} italicKeywordList
|
|
288
320
|
* @returns {string[]}
|
|
289
321
|
*/
|
|
290
322
|
export const processItalicKeywords = (font, fontTitle, italicKeywordList) => {
|
|
291
323
|
let italicKW = [];
|
|
324
|
+
const fullName = getNameString(font, 4);
|
|
292
325
|
|
|
293
326
|
italicKeywordList.forEach(keyword => {
|
|
294
327
|
const kw = keyword.trim();
|
|
@@ -297,7 +330,7 @@ export const processItalicKeywords = (font, fontTitle, italicKeywordList) => {
|
|
|
297
330
|
fontTitle = fontTitle.replace(kwRegex, '').trim();
|
|
298
331
|
italicKW.push(kw);
|
|
299
332
|
}
|
|
300
|
-
if (
|
|
333
|
+
if (fullName && fullName.toLowerCase().includes(kw.toLowerCase())) {
|
|
301
334
|
if (!italicKW.includes(kw)) italicKW.push(kw);
|
|
302
335
|
}
|
|
303
336
|
});
|
|
@@ -327,7 +360,7 @@ export const formatFontTitle = (fontTitle, preserveShortenedNames = false) => {
|
|
|
327
360
|
|
|
328
361
|
/**
|
|
329
362
|
* Appends any italic keywords to the font title that aren't already present.
|
|
330
|
-
* @param {
|
|
363
|
+
* @param {object} font - lib-font parsed font
|
|
331
364
|
* @param {string} fontTitle
|
|
332
365
|
* @param {string[]} italicKW
|
|
333
366
|
* @param {string} style
|
|
@@ -335,8 +368,9 @@ export const formatFontTitle = (fontTitle, preserveShortenedNames = false) => {
|
|
|
335
368
|
* @returns {string}
|
|
336
369
|
*/
|
|
337
370
|
export const addItalicToFontTitle = (font, fontTitle, italicKW, style, preserveShortenedNames = false) => {
|
|
338
|
-
const hasItalicAngle = font
|
|
339
|
-
const
|
|
371
|
+
const hasItalicAngle = getItalicAngle(font) !== 0;
|
|
372
|
+
const fullName = getNameString(font, 4);
|
|
373
|
+
const hasItalicInName = fullName.toLowerCase().includes('italic');
|
|
340
374
|
|
|
341
375
|
if (italicKW.length > 0 || hasItalicAngle || hasItalicInName) {
|
|
342
376
|
italicKW = [...new Set(italicKW)];
|
|
@@ -379,7 +413,7 @@ export const addItalicToFontTitle = (font, fontTitle, italicKW, style, preserveS
|
|
|
379
413
|
* @param {string} id
|
|
380
414
|
* @param {string} fontTitle
|
|
381
415
|
* @param {string} title
|
|
382
|
-
* @param {
|
|
416
|
+
* @param {object} font - lib-font parsed font
|
|
383
417
|
* @param {boolean} variableFont
|
|
384
418
|
* @param {string} weightName
|
|
385
419
|
* @param {string} subfamilyName
|
|
@@ -388,13 +422,16 @@ export const addItalicToFontTitle = (font, fontTitle, italicKW, style, preserveS
|
|
|
388
422
|
* @returns {Object}
|
|
389
423
|
*/
|
|
390
424
|
export const createFontObject = (id, fontTitle, title, font, variableFont, weightName, subfamilyName, file, originalFilename = null) => {
|
|
425
|
+
const italicAngle = getItalicAngle(font);
|
|
426
|
+
const fullName = getNameString(font, 4);
|
|
427
|
+
|
|
391
428
|
const fontObject = {
|
|
392
429
|
_key: nanoid(),
|
|
393
430
|
_id: id,
|
|
394
431
|
title: fontTitle,
|
|
395
432
|
slug: { _type: 'slug', current: id },
|
|
396
433
|
typefaceName: title,
|
|
397
|
-
style: (
|
|
434
|
+
style: (italicAngle !== 0 || fullName.toLowerCase().includes('italic')) ? 'Italic' : 'Regular',
|
|
398
435
|
variableFont: variableFont,
|
|
399
436
|
weightName: weightName,
|
|
400
437
|
subfamily: subfamilyName,
|
|
@@ -414,13 +451,14 @@ export const createFontObject = (id, fontTitle, title, font, variableFont, weigh
|
|
|
414
451
|
|
|
415
452
|
/**
|
|
416
453
|
* Determines a numeric CSS weight value for a font.
|
|
417
|
-
* @param {
|
|
454
|
+
* @param {object} font - lib-font parsed font
|
|
418
455
|
* @param {string} weightName
|
|
419
456
|
* @returns {number}
|
|
420
457
|
*/
|
|
421
458
|
export const determineWeight = (font, weightName) => {
|
|
422
|
-
|
|
423
|
-
|
|
459
|
+
const usWeightClass = getWeightClass(font);
|
|
460
|
+
if (usWeightClass) {
|
|
461
|
+
return Number(usWeightClass);
|
|
424
462
|
}
|
|
425
463
|
|
|
426
464
|
const wn = weightName?.toLowerCase() || '';
|
|
@@ -462,7 +500,7 @@ export const sortFontObjects = (fontsObjects) => {
|
|
|
462
500
|
* Logs font metadata to the console for debugging.
|
|
463
501
|
* @param {string} id
|
|
464
502
|
* @param {string} fontTitle
|
|
465
|
-
* @param {
|
|
503
|
+
* @param {object} font - lib-font parsed font
|
|
466
504
|
* @param {string} fileName
|
|
467
505
|
* @param {string} subfamilyName
|
|
468
506
|
* @param {string} style
|
|
@@ -471,17 +509,21 @@ export const sortFontObjects = (fontsObjects) => {
|
|
|
471
509
|
* @param {string[]} italicKW
|
|
472
510
|
*/
|
|
473
511
|
export const logFontInfo = (id, fontTitle, font, fileName, subfamilyName, style, weightName, variableFont, italicKW) => {
|
|
512
|
+
const fullName = getNameString(font, 4);
|
|
513
|
+
const familyName = getNameString(font, 1);
|
|
514
|
+
const italicAngle = getItalicAngle(font);
|
|
515
|
+
|
|
474
516
|
console.log('=== Font Info ====');
|
|
475
|
-
console.log('Font id:
|
|
476
|
-
console.log('Font title:
|
|
477
|
-
console.log('
|
|
478
|
-
console.log('
|
|
479
|
-
console.log('File name:
|
|
480
|
-
console.log('Subfamily:
|
|
481
|
-
console.log('Style:
|
|
482
|
-
console.log('Weight:
|
|
483
|
-
console.log('Variable:
|
|
484
|
-
console.log('
|
|
485
|
-
console.log('
|
|
517
|
+
console.log('Font id:', id);
|
|
518
|
+
console.log('Font title:', fontTitle);
|
|
519
|
+
console.log('Full name:', fullName);
|
|
520
|
+
console.log('Family name:', familyName);
|
|
521
|
+
console.log('File name:', fileName);
|
|
522
|
+
console.log('Subfamily:', subfamilyName);
|
|
523
|
+
console.log('Style:', style);
|
|
524
|
+
console.log('Weight:', weightName);
|
|
525
|
+
console.log('Variable:', variableFont);
|
|
526
|
+
console.log('ItalicKW:', italicKW);
|
|
527
|
+
console.log('Italic detection:', (italicAngle !== 0 || fullName.toLowerCase().includes('italic')) ? 'Italic' : 'Regular');
|
|
486
528
|
console.log('=======');
|
|
487
529
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Renames font document IDs across a typeface when a typeface slug changes
|
|
2
2
|
|
|
3
|
-
import
|
|
3
|
+
import { parseFont } from './parseFont';
|
|
4
4
|
import {
|
|
5
5
|
readFontFile,
|
|
6
6
|
extractFontMetadata,
|
|
@@ -100,7 +100,7 @@ export const renameFontDocuments = async ({
|
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
const fontBuffer = await readFontFile(file);
|
|
103
|
-
const font =
|
|
103
|
+
const font = await parseFont(fontBuffer, `${fontDoc._id}.ttf`);
|
|
104
104
|
|
|
105
105
|
const { weightName, subfamilyName, fontTitle } = extractFontMetadata(
|
|
106
106
|
font,
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// Standalone document resolution — determines if a font already exists in Sanity
|
|
2
|
+
|
|
3
|
+
import { RECOMMENDATION } from './planTypes';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Resolves whether a font document already exists in Sanity, returning match details
|
|
7
|
+
* and a recommendation for how to proceed.
|
|
8
|
+
*
|
|
9
|
+
* Resolution strategies (in priority order):
|
|
10
|
+
* 1. Exact _id match or draft _id match or slug.current match
|
|
11
|
+
* 2. Content match by typefaceName + weightName + style + subfamily + variableFont
|
|
12
|
+
*
|
|
13
|
+
* @param {object} font - { _id, typefaceName, weightName, style, subfamily, variableFont, title }
|
|
14
|
+
* @param {object} client - Sanity client (parameterized queries only)
|
|
15
|
+
* @returns {Promise<{ exact: object|null, candidates: object[], recommendation: string, lookupFailed: boolean }>}
|
|
16
|
+
*/
|
|
17
|
+
export const resolveExistingFont = async (font, client) => {
|
|
18
|
+
const result = {
|
|
19
|
+
exact: null,
|
|
20
|
+
candidates: [],
|
|
21
|
+
recommendation: RECOMMENDATION.CREATE,
|
|
22
|
+
lookupFailed: false,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
// Strategy 1: ID / slug match
|
|
27
|
+
const idMatches = await client.fetch(
|
|
28
|
+
`*[_type == 'font' && (_id == $id || _id == $draftId || slug.current == $id)]{
|
|
29
|
+
_id, title, weight, style, weightName, typefaceName, subfamily, variableFont,
|
|
30
|
+
fileInput, description, metaData, metrics, opentypeFeatures, characterSet,
|
|
31
|
+
scriptFileInput, variableInstanceReferences
|
|
32
|
+
}`,
|
|
33
|
+
{ id: font._id, draftId: `drafts.${font._id}` }
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
if (idMatches.length > 0) {
|
|
37
|
+
result.exact = idMatches[0];
|
|
38
|
+
result.recommendation = RECOMMENDATION.USE_EXACT;
|
|
39
|
+
return result;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Strategy 2: Content match (only when ID query returns nothing)
|
|
43
|
+
const subfamily = font.subfamily || '';
|
|
44
|
+
const contentMatches = await client.fetch(
|
|
45
|
+
`*[_type == 'font'
|
|
46
|
+
&& lower(typefaceName) == lower($typefaceName)
|
|
47
|
+
&& lower(weightName) == lower($weightName)
|
|
48
|
+
&& lower(style) == lower($style)
|
|
49
|
+
&& (variableFont == $variableFont || (!defined(variableFont) && $variableFont == false))
|
|
50
|
+
&& (
|
|
51
|
+
lower(coalesce(subfamily, '')) == lower($subfamily)
|
|
52
|
+
|| (lower(coalesce(subfamily, '')) in ['', 'regular'] && lower($subfamily) in ['', 'regular'])
|
|
53
|
+
)
|
|
54
|
+
]{
|
|
55
|
+
_id, title, weight, style, weightName, typefaceName, subfamily, variableFont,
|
|
56
|
+
fileInput, description, metaData, metrics, opentypeFeatures, characterSet,
|
|
57
|
+
scriptFileInput, variableInstanceReferences
|
|
58
|
+
}`,
|
|
59
|
+
{
|
|
60
|
+
typefaceName: font.typefaceName,
|
|
61
|
+
weightName: font.weightName || '',
|
|
62
|
+
style: font.style || 'Regular',
|
|
63
|
+
variableFont: font.variableFont || false,
|
|
64
|
+
subfamily: subfamily === '' ? 'regular' : subfamily,
|
|
65
|
+
}
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
if (contentMatches.length === 1) {
|
|
69
|
+
result.candidates = contentMatches;
|
|
70
|
+
result.recommendation = RECOMMENDATION.USE_CANDIDATE;
|
|
71
|
+
return result;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (contentMatches.length > 1) {
|
|
75
|
+
result.candidates = contentMatches;
|
|
76
|
+
result.recommendation = RECOMMENDATION.AMBIGUOUS;
|
|
77
|
+
console.warn(`Ambiguous font match for "${font.title}" — ${contentMatches.length} candidates found:`,
|
|
78
|
+
contentMatches.map(c => c._id));
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
81
|
+
} catch (err) {
|
|
82
|
+
console.error('Error resolving existing font:', font._id, err.message);
|
|
83
|
+
result.lookupFailed = true;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return result;
|
|
87
|
+
};
|