@liiift-studio/sanity-font-manager 2.7.0 → 2.8.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/LICENSE +21 -0
  2. package/README.md +695 -437
  3. package/dist/index.js +2330 -7084
  4. package/dist/index.mjs +2331 -7085
  5. package/package.json +84 -83
  6. package/src/components/BatchUploadFonts.jsx +655 -655
  7. package/src/components/BulkActions.jsx +99 -99
  8. package/src/components/ExistingDocumentResolver.jsx +152 -152
  9. package/src/components/FontReviewCard.jsx +455 -455
  10. package/src/components/FontScriptUploaderComponent.jsx +463 -463
  11. package/src/components/GenerateCollectionsPairsComponent.jsx +259 -259
  12. package/src/components/KeyValueInput.jsx +95 -95
  13. package/src/components/KeyValueReferenceInput.jsx +267 -267
  14. package/src/components/NestedObjectArraySelector.jsx +146 -146
  15. package/src/components/PriceInput.jsx +26 -26
  16. package/src/components/PrimaryCollectionGeneratorTypeface.jsx +116 -116
  17. package/src/components/RegenerateSubfamiliesComponent.jsx +185 -185
  18. package/src/components/SetOTF.jsx +87 -87
  19. package/src/components/SingleUploaderTool.jsx +674 -674
  20. package/src/components/StatusDisplay.jsx +26 -26
  21. package/src/components/StyleCountInput.jsx +16 -16
  22. package/src/components/UpdateScriptsComponent.jsx +76 -76
  23. package/src/components/UploadButton.jsx +43 -43
  24. package/src/components/UploadModal.jsx +309 -309
  25. package/src/components/UploadScriptsComponent.jsx +539 -539
  26. package/src/components/UploadStep1Settings.jsx +272 -272
  27. package/src/components/UploadStep2Review.jsx +478 -478
  28. package/src/components/UploadStep3Execute.jsx +234 -234
  29. package/src/components/UploadStep3bInstances.jsx +396 -396
  30. package/src/components/UploadSummary.jsx +196 -196
  31. package/src/components/VariableInstanceReferencesInput.jsx +190 -190
  32. package/src/hooks/useNestedObjects.js +92 -92
  33. package/src/hooks/useSanityClient.js +9 -9
  34. package/src/index.js +120 -120
  35. package/src/schema/openTypeField.js +1995 -1995
  36. package/src/schema/styleCountField.js +12 -12
  37. package/src/schema/stylesField.js +302 -302
  38. package/src/schema/stylisticSetField.js +301 -301
  39. package/src/utils/buildUploadPlan.js +326 -326
  40. package/src/utils/executeUploadPlan.js +430 -430
  41. package/src/utils/executionReducer.js +56 -56
  42. package/src/utils/fontHelpers.js +281 -281
  43. package/src/utils/generateCssFile.js +207 -207
  44. package/src/utils/generateFontData.js +98 -98
  45. package/src/utils/generateFontFile.js +38 -38
  46. package/src/utils/generateKeywords.js +185 -185
  47. package/src/utils/generateSubset.js +45 -45
  48. package/src/utils/getEmptyFontKit.js +101 -101
  49. package/src/utils/parseFont.js +56 -56
  50. package/src/utils/parseVariableFontInstances.js +301 -301
  51. package/src/utils/planReducer.js +531 -531
  52. package/src/utils/planTypes.js +183 -183
  53. package/src/utils/processFontFiles.js +530 -530
  54. package/src/utils/regenerateFontData.js +146 -146
  55. package/src/utils/resolveExistingFont.js +87 -87
  56. package/src/utils/retitleFontEntries.js +154 -154
  57. package/src/utils/sanitizeForSanityId.js +65 -65
  58. package/src/utils/setupDecompressors.js +27 -27
  59. package/src/utils/updateFontPrices.js +94 -94
  60. package/src/utils/updateTypefaceDocument.js +162 -162
  61. package/src/utils/uploadFontFiles.js +405 -405
  62. package/src/utils/utils.js +24 -24
@@ -1,94 +1,94 @@
1
- // Bulk-updates the price field across all font documents linked to a typeface
2
-
3
- /**
4
- * Sets the same price on every font document referenced by a typeface.
5
- *
6
- * @param {Object} params
7
- * @param {Object} params.client - Sanity client
8
- * @param {string} params.title - Typeface title
9
- * @param {Object} params.slug - Typeface slug object
10
- * @param {string} params.inputPrice - New price value (will be coerced to Number)
11
- * @param {string} params.doc_id - Document ID (used to detect draft state)
12
- * @param {Function} params.setStatus
13
- * @param {Function} params.setError
14
- * @returns {Promise<Object>}
15
- */
16
- export const updateFontPrices = async ({
17
- client,
18
- title,
19
- slug,
20
- inputPrice,
21
- doc_id,
22
- setStatus,
23
- setError,
24
- }) => {
25
- try {
26
- if (!title) {
27
- setStatus('Typeface needs a title');
28
- setError(true);
29
- console.error('Typeface needs title');
30
- return { success: false, message: 'Typeface needs title' };
31
- }
32
-
33
- if (!slug?.current) {
34
- setStatus('Typeface needs a slug');
35
- setError(true);
36
- console.error('Typeface needs slug');
37
- return { success: false, message: 'Typeface needs slug' };
38
- }
39
-
40
- const price = Number(inputPrice);
41
- if (isNaN(price)) {
42
- setStatus('Invalid price value');
43
- setError(true);
44
- console.error('Invalid price value');
45
- return { success: false, message: 'Invalid price value' };
46
- }
47
-
48
- setStatus('Fetching typeface document...');
49
- const typeface = await client.fetch(
50
- `*[_type == "typeface" && slug.current == $slug][0]`,
51
- { slug: slug.current }
52
- );
53
-
54
- if (!typeface) {
55
- setStatus('Typeface not found');
56
- setError(true);
57
- console.error('Typeface not found');
58
- return { success: false, message: 'Typeface not found' };
59
- }
60
-
61
- if (!typeface.styles?.fonts?.length) {
62
- setStatus('No fonts found in typeface');
63
- setError(true);
64
- console.error('No fonts found in typeface');
65
- return { success: false, message: 'No fonts found in typeface' };
66
- }
67
-
68
- const fontRefs = typeface.styles.fonts;
69
- setStatus(`Updating prices for ${fontRefs.length} fonts...`);
70
-
71
- let updatedCount = 0;
72
- for (let i = 0; i < fontRefs.length; i++) {
73
- try {
74
- await client.patch(fontRefs[i]._ref).set({ price, sell: price > 0 }).commit();
75
- updatedCount++;
76
- setStatus(`Updated ${updatedCount}/${fontRefs.length} fonts...`);
77
- } catch (err) {
78
- console.error(`Error updating font ${fontRefs[i]._ref}:`, err);
79
- }
80
- }
81
-
82
- const successMessage = `Successfully updated prices for ${updatedCount} fonts to $${price}`;
83
- setStatus(successMessage);
84
- console.log(successMessage);
85
-
86
- return { success: true, message: successMessage, updatedCount };
87
- } catch (err) {
88
- const errorMessage = `Error: ${err.message}`;
89
- console.error('Error updating font prices:', err);
90
- setError(true);
91
- setStatus(errorMessage);
92
- return { success: false, message: errorMessage };
93
- }
94
- };
1
+ // Bulk-updates the price field across all font documents linked to a typeface
2
+
3
+ /**
4
+ * Sets the same price on every font document referenced by a typeface.
5
+ *
6
+ * @param {Object} params
7
+ * @param {Object} params.client - Sanity client
8
+ * @param {string} params.title - Typeface title
9
+ * @param {Object} params.slug - Typeface slug object
10
+ * @param {string} params.inputPrice - New price value (will be coerced to Number)
11
+ * @param {string} params.doc_id - Document ID (used to detect draft state)
12
+ * @param {Function} params.setStatus
13
+ * @param {Function} params.setError
14
+ * @returns {Promise<Object>}
15
+ */
16
+ export const updateFontPrices = async ({
17
+ client,
18
+ title,
19
+ slug,
20
+ inputPrice,
21
+ doc_id,
22
+ setStatus,
23
+ setError,
24
+ }) => {
25
+ try {
26
+ if (!title) {
27
+ setStatus('Typeface needs a title');
28
+ setError(true);
29
+ console.error('Typeface needs title');
30
+ return { success: false, message: 'Typeface needs title' };
31
+ }
32
+
33
+ if (!slug?.current) {
34
+ setStatus('Typeface needs a slug');
35
+ setError(true);
36
+ console.error('Typeface needs slug');
37
+ return { success: false, message: 'Typeface needs slug' };
38
+ }
39
+
40
+ const price = Number(inputPrice);
41
+ if (isNaN(price)) {
42
+ setStatus('Invalid price value');
43
+ setError(true);
44
+ console.error('Invalid price value');
45
+ return { success: false, message: 'Invalid price value' };
46
+ }
47
+
48
+ setStatus('Fetching typeface document...');
49
+ const typeface = await client.fetch(
50
+ `*[_type == "typeface" && slug.current == $slug][0]`,
51
+ { slug: slug.current }
52
+ );
53
+
54
+ if (!typeface) {
55
+ setStatus('Typeface not found');
56
+ setError(true);
57
+ console.error('Typeface not found');
58
+ return { success: false, message: 'Typeface not found' };
59
+ }
60
+
61
+ if (!typeface.styles?.fonts?.length) {
62
+ setStatus('No fonts found in typeface');
63
+ setError(true);
64
+ console.error('No fonts found in typeface');
65
+ return { success: false, message: 'No fonts found in typeface' };
66
+ }
67
+
68
+ const fontRefs = typeface.styles.fonts;
69
+ setStatus(`Updating prices for ${fontRefs.length} fonts...`);
70
+
71
+ let updatedCount = 0;
72
+ for (let i = 0; i < fontRefs.length; i++) {
73
+ try {
74
+ await client.patch(fontRefs[i]._ref).set({ price, sell: price > 0 }).commit();
75
+ updatedCount++;
76
+ setStatus(`Updated ${updatedCount}/${fontRefs.length} fonts...`);
77
+ } catch (err) {
78
+ console.error(`Error updating font ${fontRefs[i]._ref}:`, err);
79
+ }
80
+ }
81
+
82
+ const successMessage = `Successfully updated prices for ${updatedCount} fonts to $${price}`;
83
+ setStatus(successMessage);
84
+ console.log(successMessage);
85
+
86
+ return { success: true, message: successMessage, updatedCount };
87
+ } catch (err) {
88
+ const errorMessage = `Error: ${err.message}`;
89
+ console.error('Error updating font prices:', err);
90
+ setError(true);
91
+ setStatus(errorMessage);
92
+ return { success: false, message: errorMessage };
93
+ }
94
+ };
@@ -1,162 +1,162 @@
1
- // Patches the parent typeface document's styles.fonts array with newly uploaded font references
2
-
3
- import { nanoid } from 'nanoid';
4
-
5
- /**
6
- * Patches a typeface document (draft and published) with the new font references,
7
- * subfamily structure, and preferred style derived from the upload batch.
8
- *
9
- * @param {string} doc_id - The Sanity document ID (may be a draft)
10
- * @param {Object[]} fontRefs - New regular font references
11
- * @param {Object[]} variableRefs - New variable font references
12
- * @param {Object} subfamilies - Map of font ID → subfamily name
13
- * @param {string[]} uniqueSubfamilies
14
- * @param {Object[]} subfamiliesArray - Existing subfamilies array from the typeface
15
- * @param {Object} preferredStyleRef - Existing preferred style reference
16
- * @param {Object} newPreferredStyle - Candidate preferred style from the upload
17
- * @param {Object} stylesObject - Existing typeface styles object
18
- * @param {Object} client - Sanity client
19
- * @param {Function} setStatus
20
- * @param {Function} setError
21
- */
22
- export const updateTypefaceDocument = async (
23
- doc_id,
24
- fontRefs,
25
- variableRefs,
26
- subfamilies,
27
- uniqueSubfamilies,
28
- subfamiliesArray,
29
- preferredStyleRef,
30
- newPreferredStyle,
31
- stylesObject,
32
- client,
33
- setStatus,
34
- setError,
35
- ) => {
36
- console.log('Updating typeface document with new fonts:', { fontRefs, variableRefs, subfamilies, uniqueSubfamilies });
37
- setStatus('Updating typeface references...');
38
-
39
- // Use dot-path keys so .set() does not clobber sibling fields
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
-
54
- let patch = {
55
- 'styles.fonts': dedupeRefs(stylesObject.fonts, fontRefs),
56
- 'styles.variableFont': dedupeRefs(stylesObject?.variableFont, variableRefs),
57
- };
58
-
59
- setStatus('Organising font subfamilies...');
60
- subfamiliesArray = subfamiliesArray || [];
61
-
62
- // Create any missing subfamily groups
63
- uniqueSubfamilies.forEach(subfamilyName => {
64
- if (!subfamiliesArray.find(sf => sf.title === subfamilyName)) {
65
- subfamiliesArray.push({
66
- title: subfamilyName,
67
- _key: nanoid(),
68
- _type: 'object',
69
- fonts: [],
70
- });
71
- }
72
- });
73
-
74
- // Associate fonts with their subfamily groups (skip VF fonts)
75
- if (subfamiliesArray.length > 0) {
76
- Object.entries(subfamilies).forEach(([id, subfamilyName]) => {
77
- if (id.toLowerCase().includes('vf')) return;
78
-
79
- const subfamilyIndex = subfamiliesArray.findIndex(sf => sf.title === subfamilyName);
80
- if (subfamilyIndex !== -1) {
81
- subfamiliesArray[subfamilyIndex].fonts.push({
82
- _ref: id,
83
- _key: nanoid(),
84
- _type: 'reference',
85
- _weak: true,
86
- });
87
- }
88
- });
89
-
90
- // Deduplicate references within each subfamily
91
- subfamiliesArray = subfamiliesArray.map(subfamily => ({
92
- ...subfamily,
93
- fonts: subfamily.fonts.filter((font, index, self) =>
94
- index === self.findIndex(f => f._ref === font._ref)
95
- ),
96
- }));
97
- }
98
-
99
- patch['styles.subfamilies'] = subfamiliesArray;
100
-
101
- // Optionally update preferred style
102
- await updatePreferredStyle(doc_id, preferredStyleRef, newPreferredStyle, patch, client);
103
-
104
- console.log('doc_id: ', doc_id);
105
- console.log('Typeface patch: ', patch);
106
- console.log('New preferred style: ', newPreferredStyle);
107
- console.log('SubfamiliesArray:', subfamiliesArray);
108
-
109
- try {
110
- await client.patch(doc_id).set(patch).commit();
111
- console.log(`Updated document: ${doc_id}`);
112
-
113
- if (doc_id.startsWith('drafts.')) {
114
- await updatePublishedDocument(doc_id, patch, client);
115
- }
116
- } catch (err) {
117
- console.error('Error updating document:', err.message);
118
- setStatus('Error updating typeface');
119
- setError(true);
120
- }
121
- };
122
-
123
- /**
124
- * Sets preferredStyle on the patch only when currently empty.
125
- * Does not overwrite an existing preferredStyle — the user's choice is sticky.
126
- * @param {string} doc_id
127
- * @param {Object} preferredStyleRef
128
- * @param {Object} newPreferredStyle
129
- * @param {Object} patch
130
- * @param {Object} client
131
- */
132
- const updatePreferredStyle = async (doc_id, preferredStyleRef, newPreferredStyle, patch, client) => {
133
- const isCurrentlyEmpty = !preferredStyleRef?._ref || preferredStyleRef._ref === '' || preferredStyleRef._ref === null;
134
- const hasCandidate = newPreferredStyle?._ref && newPreferredStyle._ref !== '';
135
-
136
- if (isCurrentlyEmpty && hasCandidate) {
137
- patch.preferredStyle = {
138
- _type: 'reference',
139
- _ref: newPreferredStyle._ref,
140
- _weak: true,
141
- };
142
- }
143
- };
144
-
145
- /**
146
- * Applies the same patch to the published document if it exists.
147
- * @param {string} doc_id - Draft document ID
148
- * @param {Object} patch
149
- * @param {Object} client
150
- */
151
- const updatePublishedDocument = async (doc_id, patch, client) => {
152
- const publishedId = doc_id.replace('drafts.', '');
153
- // Parameterized to prevent injection from any draft ID edge cases
154
- const publishedDoc = await client.fetch(`*[_id == $publishedId]`, { publishedId }).then(res => res[0]);
155
-
156
- if (publishedDoc) {
157
- await client.patch(publishedId).set(patch).commit();
158
- console.log(`Updated published document: ${publishedId}`);
159
- } else {
160
- console.log(`No published document found for ${publishedId}, skipping`);
161
- }
162
- };
1
+ // Patches the parent typeface document's styles.fonts array with newly uploaded font references
2
+
3
+ import { nanoid } from 'nanoid';
4
+
5
+ /**
6
+ * Patches a typeface document (draft and published) with the new font references,
7
+ * subfamily structure, and preferred style derived from the upload batch.
8
+ *
9
+ * @param {string} doc_id - The Sanity document ID (may be a draft)
10
+ * @param {Object[]} fontRefs - New regular font references
11
+ * @param {Object[]} variableRefs - New variable font references
12
+ * @param {Object} subfamilies - Map of font ID → subfamily name
13
+ * @param {string[]} uniqueSubfamilies
14
+ * @param {Object[]} subfamiliesArray - Existing subfamilies array from the typeface
15
+ * @param {Object} preferredStyleRef - Existing preferred style reference
16
+ * @param {Object} newPreferredStyle - Candidate preferred style from the upload
17
+ * @param {Object} stylesObject - Existing typeface styles object
18
+ * @param {Object} client - Sanity client
19
+ * @param {Function} setStatus
20
+ * @param {Function} setError
21
+ */
22
+ export const updateTypefaceDocument = async (
23
+ doc_id,
24
+ fontRefs,
25
+ variableRefs,
26
+ subfamilies,
27
+ uniqueSubfamilies,
28
+ subfamiliesArray,
29
+ preferredStyleRef,
30
+ newPreferredStyle,
31
+ stylesObject,
32
+ client,
33
+ setStatus,
34
+ setError,
35
+ ) => {
36
+ console.log('Updating typeface document with new fonts:', { fontRefs, variableRefs, subfamilies, uniqueSubfamilies });
37
+ setStatus('Updating typeface references...');
38
+
39
+ // Use dot-path keys so .set() does not clobber sibling fields
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
+
54
+ let patch = {
55
+ 'styles.fonts': dedupeRefs(stylesObject.fonts, fontRefs),
56
+ 'styles.variableFont': dedupeRefs(stylesObject?.variableFont, variableRefs),
57
+ };
58
+
59
+ setStatus('Organising font subfamilies...');
60
+ subfamiliesArray = subfamiliesArray || [];
61
+
62
+ // Create any missing subfamily groups
63
+ uniqueSubfamilies.forEach(subfamilyName => {
64
+ if (!subfamiliesArray.find(sf => sf.title === subfamilyName)) {
65
+ subfamiliesArray.push({
66
+ title: subfamilyName,
67
+ _key: nanoid(),
68
+ _type: 'object',
69
+ fonts: [],
70
+ });
71
+ }
72
+ });
73
+
74
+ // Associate fonts with their subfamily groups (skip VF fonts)
75
+ if (subfamiliesArray.length > 0) {
76
+ Object.entries(subfamilies).forEach(([id, subfamilyName]) => {
77
+ if (id.toLowerCase().includes('vf')) return;
78
+
79
+ const subfamilyIndex = subfamiliesArray.findIndex(sf => sf.title === subfamilyName);
80
+ if (subfamilyIndex !== -1) {
81
+ subfamiliesArray[subfamilyIndex].fonts.push({
82
+ _ref: id,
83
+ _key: nanoid(),
84
+ _type: 'reference',
85
+ _weak: true,
86
+ });
87
+ }
88
+ });
89
+
90
+ // Deduplicate references within each subfamily
91
+ subfamiliesArray = subfamiliesArray.map(subfamily => ({
92
+ ...subfamily,
93
+ fonts: subfamily.fonts.filter((font, index, self) =>
94
+ index === self.findIndex(f => f._ref === font._ref)
95
+ ),
96
+ }));
97
+ }
98
+
99
+ patch['styles.subfamilies'] = subfamiliesArray;
100
+
101
+ // Optionally update preferred style
102
+ await updatePreferredStyle(doc_id, preferredStyleRef, newPreferredStyle, patch, client);
103
+
104
+ console.log('doc_id: ', doc_id);
105
+ console.log('Typeface patch: ', patch);
106
+ console.log('New preferred style: ', newPreferredStyle);
107
+ console.log('SubfamiliesArray:', subfamiliesArray);
108
+
109
+ try {
110
+ await client.patch(doc_id).set(patch).commit();
111
+ console.log(`Updated document: ${doc_id}`);
112
+
113
+ if (doc_id.startsWith('drafts.')) {
114
+ await updatePublishedDocument(doc_id, patch, client);
115
+ }
116
+ } catch (err) {
117
+ console.error('Error updating document:', err.message);
118
+ setStatus('Error updating typeface');
119
+ setError(true);
120
+ }
121
+ };
122
+
123
+ /**
124
+ * Sets preferredStyle on the patch only when currently empty.
125
+ * Does not overwrite an existing preferredStyle — the user's choice is sticky.
126
+ * @param {string} doc_id
127
+ * @param {Object} preferredStyleRef
128
+ * @param {Object} newPreferredStyle
129
+ * @param {Object} patch
130
+ * @param {Object} client
131
+ */
132
+ const updatePreferredStyle = async (doc_id, preferredStyleRef, newPreferredStyle, patch, client) => {
133
+ const isCurrentlyEmpty = !preferredStyleRef?._ref || preferredStyleRef._ref === '' || preferredStyleRef._ref === null;
134
+ const hasCandidate = newPreferredStyle?._ref && newPreferredStyle._ref !== '';
135
+
136
+ if (isCurrentlyEmpty && hasCandidate) {
137
+ patch.preferredStyle = {
138
+ _type: 'reference',
139
+ _ref: newPreferredStyle._ref,
140
+ _weak: true,
141
+ };
142
+ }
143
+ };
144
+
145
+ /**
146
+ * Applies the same patch to the published document if it exists.
147
+ * @param {string} doc_id - Draft document ID
148
+ * @param {Object} patch
149
+ * @param {Object} client
150
+ */
151
+ const updatePublishedDocument = async (doc_id, patch, client) => {
152
+ const publishedId = doc_id.replace('drafts.', '');
153
+ // Parameterized to prevent injection from any draft ID edge cases
154
+ const publishedDoc = await client.fetch(`*[_id == $publishedId]`, { publishedId }).then(res => res[0]);
155
+
156
+ if (publishedDoc) {
157
+ await client.patch(publishedId).set(patch).commit();
158
+ console.log(`Updated published document: ${publishedId}`);
159
+ } else {
160
+ console.log(`No published document found for ${publishedId}, skipping`);
161
+ }
162
+ };