@liiift-studio/sanity-font-manager 2.3.19 → 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.
Files changed (62) hide show
  1. package/README.md +437 -437
  2. package/dist/UploadModal-6LIX7XOK.js +6 -0
  3. package/dist/UploadModal-NME2W53V.mjs +6 -0
  4. package/dist/chunk-646WCBRR.mjs +7276 -0
  5. package/dist/chunk-FH4QKHOH.js +7276 -0
  6. package/dist/index.js +747 -1675
  7. package/dist/index.mjs +400 -1237
  8. package/package.json +85 -85
  9. package/src/components/BatchUploadFonts.jsx +653 -639
  10. package/src/components/BulkActions.jsx +99 -0
  11. package/src/components/ExistingDocumentResolver.jsx +152 -0
  12. package/src/components/FontReviewCard.jsx +415 -0
  13. package/src/components/FontScriptUploaderComponent.jsx +463 -463
  14. package/src/components/GenerateCollectionsPairsComponent.jsx +259 -259
  15. package/src/components/KeyValueInput.jsx +95 -95
  16. package/src/components/KeyValueReferenceInput.jsx +254 -254
  17. package/src/components/NestedObjectArraySelector.jsx +146 -146
  18. package/src/components/PriceInput.jsx +26 -26
  19. package/src/components/PrimaryCollectionGeneratorTypeface.jsx +116 -116
  20. package/src/components/RegenerateSubfamiliesComponent.jsx +185 -185
  21. package/src/components/SetOTF.jsx +87 -87
  22. package/src/components/SingleUploaderTool.jsx +672 -673
  23. package/src/components/StatusDisplay.jsx +26 -26
  24. package/src/components/StyleCountInput.jsx +16 -16
  25. package/src/components/UpdateScriptsComponent.jsx +76 -76
  26. package/src/components/UploadButton.jsx +43 -43
  27. package/src/components/UploadModal.jsx +268 -0
  28. package/src/components/UploadScriptsComponent.jsx +539 -537
  29. package/src/components/UploadStep1Settings.jsx +272 -0
  30. package/src/components/UploadStep2Review.jsx +472 -0
  31. package/src/components/UploadStep3Execute.jsx +234 -0
  32. package/src/components/UploadSummary.jsx +196 -0
  33. package/src/components/VariableInstanceReferencesInput.jsx +190 -190
  34. package/src/hooks/useNestedObjects.js +92 -92
  35. package/src/hooks/useSanityClient.js +9 -9
  36. package/src/index.js +115 -70
  37. package/src/schema/openTypeField.js +1945 -1945
  38. package/src/schema/styleCountField.js +12 -12
  39. package/src/schema/stylesField.js +268 -268
  40. package/src/schema/stylisticSetField.js +301 -301
  41. package/src/utils/buildUploadPlan.js +325 -0
  42. package/src/utils/executeUploadPlan.js +437 -0
  43. package/src/utils/executionReducer.js +56 -0
  44. package/src/utils/fontHelpers.js +267 -0
  45. package/src/utils/generateCssFile.js +207 -205
  46. package/src/utils/generateFontData.js +98 -145
  47. package/src/utils/generateFontFile.js +38 -38
  48. package/src/utils/generateKeywords.js +185 -185
  49. package/src/utils/generateSubset.js +45 -45
  50. package/src/utils/getEmptyFontKit.js +101 -99
  51. package/src/utils/parseFont.js +55 -0
  52. package/src/utils/parseVariableFontInstances.js +211 -211
  53. package/src/utils/planReducer.js +517 -0
  54. package/src/utils/planTypes.js +183 -0
  55. package/src/utils/processFontFiles.js +529 -477
  56. package/src/utils/regenerateFontData.js +146 -146
  57. package/src/utils/resolveExistingFont.js +87 -0
  58. package/src/utils/sanitizeForSanityId.js +65 -65
  59. package/src/utils/updateFontPrices.js +94 -94
  60. package/src/utils/updateTypefaceDocument.js +149 -160
  61. package/src/utils/uploadFontFiles.js +405 -316
  62. package/src/utils/utils.js +24 -24
@@ -1,211 +1,211 @@
1
- // Resolves named variable font instances into Sanity font document references, creating documents for missing instances
2
-
3
- import { nanoid } from 'nanoid';
4
- import { expandAbbreviations } from './generateKeywords';
5
-
6
- /**
7
- * Parses a variable font's named instances and maps each to its corresponding static font document.
8
- * Uses 6 matching strategies in priority order:
9
- * 1. Exact title match
10
- * 2. Title normalisation (strips VF/var/variable prefixes, handles Regular/Italic suffixes)
11
- * 3. Abbreviation expansion
12
- * 4. Weight + style matching
13
- * 5. weightName comparison
14
- * 6. metaData.fullName fallback
15
- *
16
- * @param {Object} font - The variable font object (must have typefaceName and variableInstances)
17
- * @param {Object} client - Sanity client (parameterized queries only)
18
- * @returns {Promise<Array>} Array of { key, value, _key } instance mappings
19
- */
20
- export const parseVariableFontInstances = async (font, client) => {
21
- if (!font.variableFont || !font.variableInstances) return [];
22
-
23
- let variableInstances;
24
- try {
25
- variableInstances = JSON.parse(font.variableInstances);
26
- } catch (err) {
27
- console.error('Error parsing variable instances:', err);
28
- variableInstances = {};
29
- }
30
-
31
- if (Object.keys(variableInstances).length === 0) return [];
32
-
33
- // Fetch the typeface's curated font list (parameterized — no injection risk)
34
- let staticFonts;
35
- const typeface = await client.fetch(
36
- `*[_type == 'typeface' && title == $typefaceName][0]{
37
- 'fonts': styles.fonts[]-> {
38
- _id,
39
- title,
40
- subfamily,
41
- style,
42
- weight,
43
- weightName,
44
- metaData,
45
- variableFont
46
- }
47
- }`,
48
- { typefaceName: font.typefaceName }
49
- );
50
-
51
- if (typeface?.fonts && typeface.fonts.length > 0) {
52
- staticFonts = typeface.fonts.filter(f => !f.variableFont);
53
- console.log('Using curated typeface fonts list:', staticFonts.length, 'fonts');
54
- } else {
55
- console.warn('Typeface not found or no fonts in curated list, falling back to all fonts query');
56
- staticFonts = await client.fetch(
57
- `*[_type == 'font' && typefaceName == $typefaceName && variableFont != true]{
58
- _id,
59
- title,
60
- subfamily,
61
- style,
62
- weight,
63
- weightName,
64
- metaData
65
- }`,
66
- { typefaceName: font.typefaceName }
67
- );
68
- }
69
-
70
- console.log('Variable font instances:', Object.keys(variableInstances));
71
- console.log('Available static fonts:', staticFonts.map(sf => sf.title));
72
-
73
- const instanceMappings = [];
74
-
75
- Object.keys(variableInstances).forEach(instanceName => {
76
- let matchingFont = null;
77
-
78
- // Strategy 1: Exact title match
79
- matchingFont = staticFonts.find(sf => sf.title === instanceName);
80
-
81
- // Strategy 2: Title normalisation — strip VF/var/variable prefix words, handle Regular/Italic
82
- if (!matchingFont && staticFonts.some(sf => sf.metaData?.fullName)) {
83
- matchingFont = staticFonts.find(sf => {
84
- if (!sf.metaData?.fullName) return false;
85
-
86
- let fullName = sf.metaData.fullName;
87
-
88
- const WORDS_TO_REMOVE = ['VF', 'var', 'variable', 'VAR', 'vf'];
89
- const variableName = font.metaData?.familyName
90
- ?.replace(new RegExp(`\\b(${WORDS_TO_REMOVE.join('|')})\\b`, 'gi'), '')
91
- .replace(/\s{2,}/g, ' ')
92
- .trim();
93
-
94
- if (variableName && fullName.startsWith(variableName)) {
95
- fullName = fullName.substring(variableName.length).trim();
96
- }
97
-
98
- if (variableName) {
99
- const words = variableName.split(/\s+/).map(w => w.trim()).filter(Boolean);
100
- if (words.length > 0) {
101
- const regex = new RegExp(`\\b(${words.join('|')})\\b`, 'gi');
102
- const stripped = fullName.replace(regex, '').replace(/\s{2,}/g, ' ').trim();
103
- if (stripped !== '') fullName = stripped;
104
- }
105
- }
106
-
107
- if (fullName.startsWith(font.typefaceName)) {
108
- fullName = fullName.substring(font.typefaceName.length).trim();
109
- }
110
-
111
- if (sf.style?.toLowerCase() === 'italic' &&
112
- !fullName.toLowerCase().endsWith('italic') &&
113
- !fullName.toLowerCase().endsWith('slanted')) {
114
- fullName = fullName + ' Italic';
115
- }
116
-
117
- if (fullName.trim().toLowerCase().endsWith('regular')) {
118
- if (instanceName.trim().toLowerCase() + ' regular' === fullName.trim().toLowerCase()) return true;
119
- }
120
- if (fullName.trim().toLowerCase().startsWith('regular')) {
121
- if ('regular ' + instanceName.trim().toLowerCase() === fullName.trim().toLowerCase()) return true;
122
- }
123
- if (fullName.trim().toLowerCase().endsWith('italic')) {
124
- if (instanceName.trim().toLowerCase().endsWith('italic')) {
125
- const k = instanceName.trim().toLowerCase().slice(0, -6).trim() + ' regular italic';
126
- if (k === fullName.trim().toLowerCase()) return true;
127
- }
128
- }
129
-
130
- return fullName.trim().toLowerCase() === instanceName.trim().toLowerCase();
131
- });
132
- }
133
-
134
- // Strategy 3: Abbreviation expansion
135
- if (!matchingFont) {
136
- const expandedName = instanceName.split(' ').map(word => expandAbbreviations(word)).join(' ');
137
- matchingFont = staticFonts.find(sf => {
138
- const nameWithoutTypeface = sf.title.replace(font.typefaceName, '').trim();
139
- return nameWithoutTypeface === expandedName;
140
- });
141
- }
142
-
143
- // Strategy 4: Weight + style matching
144
- if (!matchingFont) {
145
- const isItalic = instanceName.toLowerCase().includes('italic');
146
- const weightTerms = [
147
- { term: 'thin', weight: '100' },
148
- { term: 'extralight', weight: '200' },
149
- { term: 'extra light', weight: '200' },
150
- { term: 'light', weight: '300' },
151
- { term: 'regular', weight: '400' },
152
- { term: 'normal', weight: '400' },
153
- { term: 'medium', weight: '500' },
154
- { term: 'semibold', weight: '600' },
155
- { term: 'semi bold', weight: '600' },
156
- { term: 'bold', weight: '700' },
157
- { term: 'extrabold', weight: '800' },
158
- { term: 'extra bold', weight: '800' },
159
- { term: 'black', weight: '900' },
160
- { term: 'heavy', weight: '900' },
161
- ];
162
-
163
- let instanceWeight = '400';
164
- for (const { term, weight } of weightTerms) {
165
- if (instanceName.toLowerCase().includes(term)) {
166
- instanceWeight = weight;
167
- break;
168
- }
169
- }
170
-
171
- matchingFont = staticFonts.find(sf =>
172
- sf.weight === instanceWeight &&
173
- ((isItalic && sf.style === 'Italic') || (!isItalic && sf.style === 'Regular'))
174
- );
175
- }
176
-
177
- // Strategy 5: weightName comparison
178
- if (!matchingFont) {
179
- matchingFont = staticFonts.find(sf => {
180
- if (!sf.weightName) return false;
181
- const cleanInstance = instanceName.toLowerCase().replace(/italic/i, '').trim();
182
- const cleanWeight = sf.weightName.toLowerCase().replace(/italic/i, '').trim();
183
- return cleanInstance === cleanWeight;
184
- });
185
- }
186
-
187
- // Strategy 6: Legacy metaData.fullName fallback
188
- if (!matchingFont && staticFonts.some(sf => sf.metaData?.fullName)) {
189
- matchingFont = staticFonts.find(sf => {
190
- if (!sf.metaData?.fullName) return false;
191
- const typefacePattern = new RegExp(`^${font.typefaceName}\\s+`, 'i');
192
- const stylePart = sf.metaData.fullName.replace(typefacePattern, '').trim();
193
- return instanceName.toLowerCase() === stylePart.toLowerCase();
194
- });
195
- }
196
-
197
- console.log(`Instance "${instanceName}" matched with:`, matchingFont ? matchingFont.title : 'No match found');
198
-
199
- instanceMappings.push({
200
- key: instanceName,
201
- value: matchingFont
202
- ? { _type: 'reference', _ref: matchingFont._id, _weak: true }
203
- : null,
204
- _key: nanoid(),
205
- });
206
- });
207
-
208
- return instanceMappings;
209
- };
210
-
211
- export default parseVariableFontInstances;
1
+ // Resolves named variable font instances into Sanity font document references, creating documents for missing instances
2
+
3
+ import { nanoid } from 'nanoid';
4
+ import { expandAbbreviations } from './generateKeywords';
5
+
6
+ /**
7
+ * Parses a variable font's named instances and maps each to its corresponding static font document.
8
+ * Uses 6 matching strategies in priority order:
9
+ * 1. Exact title match
10
+ * 2. Title normalisation (strips VF/var/variable prefixes, handles Regular/Italic suffixes)
11
+ * 3. Abbreviation expansion
12
+ * 4. Weight + style matching
13
+ * 5. weightName comparison
14
+ * 6. metaData.fullName fallback
15
+ *
16
+ * @param {Object} font - The variable font object (must have typefaceName and variableInstances)
17
+ * @param {Object} client - Sanity client (parameterized queries only)
18
+ * @returns {Promise<Array>} Array of { key, value, _key } instance mappings
19
+ */
20
+ export const parseVariableFontInstances = async (font, client) => {
21
+ if (!font.variableFont || !font.variableInstances) return [];
22
+
23
+ let variableInstances;
24
+ try {
25
+ variableInstances = JSON.parse(font.variableInstances);
26
+ } catch (err) {
27
+ console.error('Error parsing variable instances:', err);
28
+ variableInstances = {};
29
+ }
30
+
31
+ if (Object.keys(variableInstances).length === 0) return [];
32
+
33
+ // Fetch the typeface's curated font list (parameterized — no injection risk)
34
+ let staticFonts;
35
+ const typeface = await client.fetch(
36
+ `*[_type == 'typeface' && title == $typefaceName][0]{
37
+ 'fonts': styles.fonts[]-> {
38
+ _id,
39
+ title,
40
+ subfamily,
41
+ style,
42
+ weight,
43
+ weightName,
44
+ metaData,
45
+ variableFont
46
+ }
47
+ }`,
48
+ { typefaceName: font.typefaceName }
49
+ );
50
+
51
+ if (typeface?.fonts && typeface.fonts.length > 0) {
52
+ staticFonts = typeface.fonts.filter(f => !f.variableFont);
53
+ console.log('Using curated typeface fonts list:', staticFonts.length, 'fonts');
54
+ } else {
55
+ console.warn('Typeface not found or no fonts in curated list, falling back to all fonts query');
56
+ staticFonts = await client.fetch(
57
+ `*[_type == 'font' && typefaceName == $typefaceName && variableFont != true]{
58
+ _id,
59
+ title,
60
+ subfamily,
61
+ style,
62
+ weight,
63
+ weightName,
64
+ metaData
65
+ }`,
66
+ { typefaceName: font.typefaceName }
67
+ );
68
+ }
69
+
70
+ console.log('Variable font instances:', Object.keys(variableInstances));
71
+ console.log('Available static fonts:', staticFonts.map(sf => sf.title));
72
+
73
+ const instanceMappings = [];
74
+
75
+ Object.keys(variableInstances).forEach(instanceName => {
76
+ let matchingFont = null;
77
+
78
+ // Strategy 1: Exact title match
79
+ matchingFont = staticFonts.find(sf => sf.title === instanceName);
80
+
81
+ // Strategy 2: Title normalisation — strip VF/var/variable prefix words, handle Regular/Italic
82
+ if (!matchingFont && staticFonts.some(sf => sf.metaData?.fullName)) {
83
+ matchingFont = staticFonts.find(sf => {
84
+ if (!sf.metaData?.fullName) return false;
85
+
86
+ let fullName = sf.metaData.fullName;
87
+
88
+ const WORDS_TO_REMOVE = ['VF', 'var', 'variable', 'VAR', 'vf'];
89
+ const variableName = font.metaData?.familyName
90
+ ?.replace(new RegExp(`\\b(${WORDS_TO_REMOVE.join('|')})\\b`, 'gi'), '')
91
+ .replace(/\s{2,}/g, ' ')
92
+ .trim();
93
+
94
+ if (variableName && fullName.startsWith(variableName)) {
95
+ fullName = fullName.substring(variableName.length).trim();
96
+ }
97
+
98
+ if (variableName) {
99
+ const words = variableName.split(/\s+/).map(w => w.trim()).filter(Boolean);
100
+ if (words.length > 0) {
101
+ const regex = new RegExp(`\\b(${words.join('|')})\\b`, 'gi');
102
+ const stripped = fullName.replace(regex, '').replace(/\s{2,}/g, ' ').trim();
103
+ if (stripped !== '') fullName = stripped;
104
+ }
105
+ }
106
+
107
+ if (fullName.startsWith(font.typefaceName)) {
108
+ fullName = fullName.substring(font.typefaceName.length).trim();
109
+ }
110
+
111
+ if (sf.style?.toLowerCase() === 'italic' &&
112
+ !fullName.toLowerCase().endsWith('italic') &&
113
+ !fullName.toLowerCase().endsWith('slanted')) {
114
+ fullName = fullName + ' Italic';
115
+ }
116
+
117
+ if (fullName.trim().toLowerCase().endsWith('regular')) {
118
+ if (instanceName.trim().toLowerCase() + ' regular' === fullName.trim().toLowerCase()) return true;
119
+ }
120
+ if (fullName.trim().toLowerCase().startsWith('regular')) {
121
+ if ('regular ' + instanceName.trim().toLowerCase() === fullName.trim().toLowerCase()) return true;
122
+ }
123
+ if (fullName.trim().toLowerCase().endsWith('italic')) {
124
+ if (instanceName.trim().toLowerCase().endsWith('italic')) {
125
+ const k = instanceName.trim().toLowerCase().slice(0, -6).trim() + ' regular italic';
126
+ if (k === fullName.trim().toLowerCase()) return true;
127
+ }
128
+ }
129
+
130
+ return fullName.trim().toLowerCase() === instanceName.trim().toLowerCase();
131
+ });
132
+ }
133
+
134
+ // Strategy 3: Abbreviation expansion
135
+ if (!matchingFont) {
136
+ const expandedName = instanceName.split(' ').map(word => expandAbbreviations(word)).join(' ');
137
+ matchingFont = staticFonts.find(sf => {
138
+ const nameWithoutTypeface = sf.title.replace(font.typefaceName, '').trim();
139
+ return nameWithoutTypeface === expandedName;
140
+ });
141
+ }
142
+
143
+ // Strategy 4: Weight + style matching
144
+ if (!matchingFont) {
145
+ const isItalic = instanceName.toLowerCase().includes('italic');
146
+ const weightTerms = [
147
+ { term: 'thin', weight: '100' },
148
+ { term: 'extralight', weight: '200' },
149
+ { term: 'extra light', weight: '200' },
150
+ { term: 'light', weight: '300' },
151
+ { term: 'regular', weight: '400' },
152
+ { term: 'normal', weight: '400' },
153
+ { term: 'medium', weight: '500' },
154
+ { term: 'semibold', weight: '600' },
155
+ { term: 'semi bold', weight: '600' },
156
+ { term: 'bold', weight: '700' },
157
+ { term: 'extrabold', weight: '800' },
158
+ { term: 'extra bold', weight: '800' },
159
+ { term: 'black', weight: '900' },
160
+ { term: 'heavy', weight: '900' },
161
+ ];
162
+
163
+ let instanceWeight = '400';
164
+ for (const { term, weight } of weightTerms) {
165
+ if (instanceName.toLowerCase().includes(term)) {
166
+ instanceWeight = weight;
167
+ break;
168
+ }
169
+ }
170
+
171
+ matchingFont = staticFonts.find(sf =>
172
+ sf.weight === instanceWeight &&
173
+ ((isItalic && sf.style === 'Italic') || (!isItalic && sf.style === 'Regular'))
174
+ );
175
+ }
176
+
177
+ // Strategy 5: weightName comparison
178
+ if (!matchingFont) {
179
+ matchingFont = staticFonts.find(sf => {
180
+ if (!sf.weightName) return false;
181
+ const cleanInstance = instanceName.toLowerCase().replace(/italic/i, '').trim();
182
+ const cleanWeight = sf.weightName.toLowerCase().replace(/italic/i, '').trim();
183
+ return cleanInstance === cleanWeight;
184
+ });
185
+ }
186
+
187
+ // Strategy 6: Legacy metaData.fullName fallback
188
+ if (!matchingFont && staticFonts.some(sf => sf.metaData?.fullName)) {
189
+ matchingFont = staticFonts.find(sf => {
190
+ if (!sf.metaData?.fullName) return false;
191
+ const typefacePattern = new RegExp(`^${font.typefaceName}\\s+`, 'i');
192
+ const stylePart = sf.metaData.fullName.replace(typefacePattern, '').trim();
193
+ return instanceName.toLowerCase() === stylePart.toLowerCase();
194
+ });
195
+ }
196
+
197
+ console.log(`Instance "${instanceName}" matched with:`, matchingFont ? matchingFont.title : 'No match found');
198
+
199
+ instanceMappings.push({
200
+ key: instanceName,
201
+ value: matchingFont
202
+ ? { _type: 'reference', _ref: matchingFont._id, _weak: true }
203
+ : null,
204
+ _key: nanoid(),
205
+ });
206
+ });
207
+
208
+ return instanceMappings;
209
+ };
210
+
211
+ export default parseVariableFontInstances;