@liiift-studio/sanity-font-manager 2.4.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-ADNRGQUI.mjs +6 -0
- package/dist/UploadModal-WPK2CXLR.js +6 -0
- package/dist/chunk-JCDZ7SWZ.js +7711 -0
- package/dist/chunk-TMDE4A54.mjs +7711 -0
- package/dist/index.js +666 -1647
- package/dist/index.mjs +319 -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 +455 -0
- package/src/components/SingleUploaderTool.jsx +3 -4
- package/src/components/UploadModal.jsx +304 -0
- package/src/components/UploadScriptsComponent.jsx +23 -21
- package/src/components/UploadStep1Settings.jsx +272 -0
- package/src/components/UploadStep2Review.jsx +474 -0
- package/src/components/UploadStep3Execute.jsx +234 -0
- package/src/components/UploadStep3bInstances.jsx +396 -0
- package/src/components/UploadSummary.jsx +196 -0
- package/src/index.js +46 -0
- package/src/utils/buildUploadPlan.js +326 -0
- package/src/utils/executeUploadPlan.js +430 -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/parseVariableFontInstances.js +237 -147
- package/src/utils/planReducer.js +517 -0
- package/src/utils/planTypes.js +183 -0
- package/src/utils/processFontFiles.js +121 -78
- package/src/utils/regenerateFontData.js +2 -2
- package/src/utils/resolveExistingFont.js +87 -0
- package/src/utils/updateTypefaceDocument.js +15 -2
- 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,107 @@ 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
|
+
const axes = getVariationAxes(font);
|
|
172
|
+
const variableFont = axes !== null;
|
|
173
|
+
|
|
174
|
+
// For non-VF fonts, fall back to extracting weight from fullName when weightName is empty
|
|
175
|
+
if (!variableFont && (weightName === '' || weightName.toLowerCase() === 'roman') && fullName) {
|
|
176
|
+
weightName = extractWeightFromFullName(font, title, ttfFallbackMeta);
|
|
155
177
|
if (!preserveShortenedNames) {
|
|
156
178
|
weightName = expandAbbreviations(weightName);
|
|
157
179
|
}
|
|
158
180
|
}
|
|
159
181
|
|
|
160
|
-
|
|
182
|
+
// Subfamily detection — extract width/optical variant from name table.
|
|
183
|
+
// Primary: nameId4 (fullName) minus typeface title — the most complete name record,
|
|
184
|
+
// always contains width + weight (e.g. "Gear XXNarrow Regular" → "XXNarrow Regular").
|
|
185
|
+
// Fallback: nameId1 (familyName) minus typeface title — contains width but not always weight.
|
|
186
|
+
// processSubfamilyName then strips weight/italic keywords, leaving just the width variant.
|
|
187
|
+
// This matches the production logic that has been reliable across all foundry sites.
|
|
188
|
+
const trimmedTitle = title.trim();
|
|
161
189
|
|
|
162
|
-
|
|
163
|
-
|
|
190
|
+
const nameId4Remainder = fullName ? fullName.replace(trimmedTitle, '').trim() : '';
|
|
191
|
+
const nameId1 = getNameString(font, 1) || ttfFallbackMeta?.familyName || '';
|
|
192
|
+
const nameId1Remainder = nameId1 ? nameId1.replace(trimmedTitle, '').trim() : '';
|
|
193
|
+
|
|
194
|
+
let subfamilyName = nameId4Remainder || nameId1Remainder;
|
|
164
195
|
|
|
165
196
|
if (!preserveShortenedNames) {
|
|
166
197
|
subfamilyName = expandAbbreviations(subfamilyName);
|
|
167
198
|
}
|
|
168
199
|
|
|
169
|
-
let fontTitle =
|
|
170
|
-
|
|
200
|
+
let fontTitle = fullName.trim() || '';
|
|
201
|
+
const italicAngle = getItalicAngle(font);
|
|
202
|
+
let style = (italicAngle !== 0 || fullName.toLowerCase().includes('italic')) ? 'Italic' : 'Regular';
|
|
171
203
|
|
|
172
204
|
const italicKW = processItalicKeywords(font, fontTitle, italicKeywordList);
|
|
173
205
|
|
|
174
206
|
subfamilyName = processSubfamilyName(subfamilyName, weightKeywordList, italicKW, preserveShortenedNames);
|
|
175
207
|
fontTitle = formatFontTitle(fontTitle, preserveShortenedNames);
|
|
176
208
|
|
|
177
|
-
|
|
209
|
+
// Style-only names are not subfamilies — strip them
|
|
210
|
+
subfamilyName = subfamilyName
|
|
211
|
+
.replace(/\b(Italic|Slant|Slanted|Oblique|Backslant|Roman|Upright)\b/gi, '')
|
|
212
|
+
.replace(/\s+/g, ' ')
|
|
213
|
+
.trim();
|
|
178
214
|
|
|
179
215
|
if (subfamilyName !== '') {
|
|
180
216
|
weightName = weightName
|
|
@@ -187,6 +223,7 @@ export const extractFontMetadata = (font, title, weightKeywordList, italicKeywor
|
|
|
187
223
|
if (!fontTitle.toLowerCase().includes('vf')) {
|
|
188
224
|
fontTitle = fontTitle + ' VF';
|
|
189
225
|
}
|
|
226
|
+
// Variable fonts are not placed in subfamilies — they go in the separate variableFont array
|
|
190
227
|
subfamilyName = '';
|
|
191
228
|
}
|
|
192
229
|
|
|
@@ -200,20 +237,18 @@ export const extractFontMetadata = (font, title, weightKeywordList, italicKeywor
|
|
|
200
237
|
/**
|
|
201
238
|
* Extracts the weight name from a font's preferred subfamily or subfamily record.
|
|
202
239
|
* Returns "Variable" for variable fonts.
|
|
203
|
-
* @param {
|
|
240
|
+
* @param {object} font - lib-font parsed font
|
|
204
241
|
* @param {string[]} italicKW
|
|
242
|
+
* @param {object|null} ttfFallbackMeta
|
|
205
243
|
* @returns {string}
|
|
206
244
|
*/
|
|
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
|
-
}
|
|
245
|
+
export const extractWeightName = (font, italicKW, ttfFallbackMeta = null) => {
|
|
246
|
+
let weightName = getNameString(font, 17) || getNameString(font, 2) ||
|
|
247
|
+
ttfFallbackMeta?.preferredSubfamily || ttfFallbackMeta?.subfamilyName || '';
|
|
214
248
|
|
|
215
|
-
|
|
216
|
-
|
|
249
|
+
const axes = getVariationAxes(font);
|
|
250
|
+
if (axes !== null) {
|
|
251
|
+
return '';
|
|
217
252
|
}
|
|
218
253
|
|
|
219
254
|
if (italicKW) {
|
|
@@ -236,17 +271,15 @@ export const extractWeightName = (font, italicKW) => {
|
|
|
236
271
|
|
|
237
272
|
/**
|
|
238
273
|
* Extracts a weight name from the font's full name record when subfamily is empty or "Roman".
|
|
239
|
-
* @param {
|
|
274
|
+
* @param {object} font - lib-font parsed font
|
|
240
275
|
* @param {string} title
|
|
276
|
+
* @param {object|null} ttfFallbackMeta
|
|
241
277
|
* @returns {string}
|
|
242
278
|
*/
|
|
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();
|
|
279
|
+
export const extractWeightFromFullName = (font, title, ttfFallbackMeta = null) => {
|
|
280
|
+
let weightName = getNameString(font, 4) || ttfFallbackMeta?.fullName || '';
|
|
281
|
+
weightName = weightName.replace(title + ' ', '').replace(title, '').trim();
|
|
282
|
+
weightName = weightName.replace('Italic', '').replace('It', '').replace('Slanted', '').replace('Slant', '').trim();
|
|
250
283
|
return weightName;
|
|
251
284
|
};
|
|
252
285
|
|
|
@@ -282,13 +315,14 @@ export const processSubfamilyName = (subfamilyName, weightKeywordList, italicKey
|
|
|
282
315
|
|
|
283
316
|
/**
|
|
284
317
|
* Collects italic keywords present in a font's full name.
|
|
285
|
-
* @param {
|
|
318
|
+
* @param {object} font - lib-font parsed font
|
|
286
319
|
* @param {string} fontTitle
|
|
287
320
|
* @param {string[]} italicKeywordList
|
|
288
321
|
* @returns {string[]}
|
|
289
322
|
*/
|
|
290
323
|
export const processItalicKeywords = (font, fontTitle, italicKeywordList) => {
|
|
291
324
|
let italicKW = [];
|
|
325
|
+
const fullName = getNameString(font, 4);
|
|
292
326
|
|
|
293
327
|
italicKeywordList.forEach(keyword => {
|
|
294
328
|
const kw = keyword.trim();
|
|
@@ -297,7 +331,7 @@ export const processItalicKeywords = (font, fontTitle, italicKeywordList) => {
|
|
|
297
331
|
fontTitle = fontTitle.replace(kwRegex, '').trim();
|
|
298
332
|
italicKW.push(kw);
|
|
299
333
|
}
|
|
300
|
-
if (
|
|
334
|
+
if (fullName && fullName.toLowerCase().includes(kw.toLowerCase())) {
|
|
301
335
|
if (!italicKW.includes(kw)) italicKW.push(kw);
|
|
302
336
|
}
|
|
303
337
|
});
|
|
@@ -327,7 +361,7 @@ export const formatFontTitle = (fontTitle, preserveShortenedNames = false) => {
|
|
|
327
361
|
|
|
328
362
|
/**
|
|
329
363
|
* Appends any italic keywords to the font title that aren't already present.
|
|
330
|
-
* @param {
|
|
364
|
+
* @param {object} font - lib-font parsed font
|
|
331
365
|
* @param {string} fontTitle
|
|
332
366
|
* @param {string[]} italicKW
|
|
333
367
|
* @param {string} style
|
|
@@ -335,8 +369,9 @@ export const formatFontTitle = (fontTitle, preserveShortenedNames = false) => {
|
|
|
335
369
|
* @returns {string}
|
|
336
370
|
*/
|
|
337
371
|
export const addItalicToFontTitle = (font, fontTitle, italicKW, style, preserveShortenedNames = false) => {
|
|
338
|
-
const hasItalicAngle = font
|
|
339
|
-
const
|
|
372
|
+
const hasItalicAngle = getItalicAngle(font) !== 0;
|
|
373
|
+
const fullName = getNameString(font, 4);
|
|
374
|
+
const hasItalicInName = fullName.toLowerCase().includes('italic');
|
|
340
375
|
|
|
341
376
|
if (italicKW.length > 0 || hasItalicAngle || hasItalicInName) {
|
|
342
377
|
italicKW = [...new Set(italicKW)];
|
|
@@ -379,7 +414,7 @@ export const addItalicToFontTitle = (font, fontTitle, italicKW, style, preserveS
|
|
|
379
414
|
* @param {string} id
|
|
380
415
|
* @param {string} fontTitle
|
|
381
416
|
* @param {string} title
|
|
382
|
-
* @param {
|
|
417
|
+
* @param {object} font - lib-font parsed font
|
|
383
418
|
* @param {boolean} variableFont
|
|
384
419
|
* @param {string} weightName
|
|
385
420
|
* @param {string} subfamilyName
|
|
@@ -388,13 +423,16 @@ export const addItalicToFontTitle = (font, fontTitle, italicKW, style, preserveS
|
|
|
388
423
|
* @returns {Object}
|
|
389
424
|
*/
|
|
390
425
|
export const createFontObject = (id, fontTitle, title, font, variableFont, weightName, subfamilyName, file, originalFilename = null) => {
|
|
426
|
+
const italicAngle = getItalicAngle(font);
|
|
427
|
+
const fullName = getNameString(font, 4);
|
|
428
|
+
|
|
391
429
|
const fontObject = {
|
|
392
430
|
_key: nanoid(),
|
|
393
431
|
_id: id,
|
|
394
432
|
title: fontTitle,
|
|
395
433
|
slug: { _type: 'slug', current: id },
|
|
396
434
|
typefaceName: title,
|
|
397
|
-
style: (
|
|
435
|
+
style: (italicAngle !== 0 || fullName.toLowerCase().includes('italic')) ? 'Italic' : 'Regular',
|
|
398
436
|
variableFont: variableFont,
|
|
399
437
|
weightName: weightName,
|
|
400
438
|
subfamily: subfamilyName,
|
|
@@ -414,13 +452,14 @@ export const createFontObject = (id, fontTitle, title, font, variableFont, weigh
|
|
|
414
452
|
|
|
415
453
|
/**
|
|
416
454
|
* Determines a numeric CSS weight value for a font.
|
|
417
|
-
* @param {
|
|
455
|
+
* @param {object} font - lib-font parsed font
|
|
418
456
|
* @param {string} weightName
|
|
419
457
|
* @returns {number}
|
|
420
458
|
*/
|
|
421
459
|
export const determineWeight = (font, weightName) => {
|
|
422
|
-
|
|
423
|
-
|
|
460
|
+
const usWeightClass = getWeightClass(font);
|
|
461
|
+
if (usWeightClass) {
|
|
462
|
+
return Number(usWeightClass);
|
|
424
463
|
}
|
|
425
464
|
|
|
426
465
|
const wn = weightName?.toLowerCase() || '';
|
|
@@ -462,7 +501,7 @@ export const sortFontObjects = (fontsObjects) => {
|
|
|
462
501
|
* Logs font metadata to the console for debugging.
|
|
463
502
|
* @param {string} id
|
|
464
503
|
* @param {string} fontTitle
|
|
465
|
-
* @param {
|
|
504
|
+
* @param {object} font - lib-font parsed font
|
|
466
505
|
* @param {string} fileName
|
|
467
506
|
* @param {string} subfamilyName
|
|
468
507
|
* @param {string} style
|
|
@@ -471,17 +510,21 @@ export const sortFontObjects = (fontsObjects) => {
|
|
|
471
510
|
* @param {string[]} italicKW
|
|
472
511
|
*/
|
|
473
512
|
export const logFontInfo = (id, fontTitle, font, fileName, subfamilyName, style, weightName, variableFont, italicKW) => {
|
|
513
|
+
const fullName = getNameString(font, 4);
|
|
514
|
+
const familyName = getNameString(font, 1);
|
|
515
|
+
const italicAngle = getItalicAngle(font);
|
|
516
|
+
|
|
474
517
|
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('
|
|
518
|
+
console.log('Font id:', id);
|
|
519
|
+
console.log('Font title:', fontTitle);
|
|
520
|
+
console.log('Full name:', fullName);
|
|
521
|
+
console.log('Family name:', familyName);
|
|
522
|
+
console.log('File name:', fileName);
|
|
523
|
+
console.log('Subfamily:', subfamilyName);
|
|
524
|
+
console.log('Style:', style);
|
|
525
|
+
console.log('Weight:', weightName);
|
|
526
|
+
console.log('Variable:', variableFont);
|
|
527
|
+
console.log('ItalicKW:', italicKW);
|
|
528
|
+
console.log('Italic detection:', (italicAngle !== 0 || fullName.toLowerCase().includes('italic')) ? 'Italic' : 'Regular');
|
|
486
529
|
console.log('=======');
|
|
487
530
|
};
|
|
@@ -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
|
+
};
|
|
@@ -38,9 +38,22 @@ export const updateTypefaceDocument = async (
|
|
|
38
38
|
|
|
39
39
|
// Use dot-path keys so .set() does not clobber sibling fields
|
|
40
40
|
// (styles.collections, styles.pairs, styles.free, styles.displayStyles)
|
|
41
|
+
// Deduplicate by _ref to prevent duplicate entries on re-upload
|
|
42
|
+
const dedupeRefs = (existing, incoming) => {
|
|
43
|
+
const merged = [...(existing || [])];
|
|
44
|
+
const existingRefs = new Set(merged.map(r => r._ref).filter(Boolean));
|
|
45
|
+
incoming.forEach(ref => {
|
|
46
|
+
if (ref._ref && !existingRefs.has(ref._ref)) {
|
|
47
|
+
merged.push(ref);
|
|
48
|
+
existingRefs.add(ref._ref);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
return merged;
|
|
52
|
+
};
|
|
53
|
+
|
|
41
54
|
let patch = {
|
|
42
|
-
'styles.fonts': stylesObject.fonts
|
|
43
|
-
'styles.variableFont': stylesObject?.variableFont
|
|
55
|
+
'styles.fonts': dedupeRefs(stylesObject.fonts, fontRefs),
|
|
56
|
+
'styles.variableFont': dedupeRefs(stylesObject?.variableFont, variableRefs),
|
|
44
57
|
};
|
|
45
58
|
|
|
46
59
|
setStatus('Organising font subfamilies...');
|