@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,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,160 +1,149 @@
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
- let patch = {
40
- styles: {
41
- fonts: stylesObject.fonts ? [...stylesObject.fonts, ...fontRefs] : [...fontRefs],
42
- subfamilies: uniqueSubfamilies.length > 1 ? uniqueSubfamilies : [],
43
- variableFont: stylesObject?.variableFont ? [...stylesObject.variableFont, ...variableRefs] : [...variableRefs],
44
- },
45
- };
46
-
47
- setStatus('Organising font subfamilies...');
48
- subfamiliesArray = subfamiliesArray || [];
49
-
50
- // Create any missing subfamily groups
51
- uniqueSubfamilies.forEach(subfamilyName => {
52
- if (!subfamiliesArray.find(sf => sf.title === subfamilyName)) {
53
- subfamiliesArray.push({
54
- title: subfamilyName,
55
- _key: nanoid(),
56
- _type: 'object',
57
- fonts: [],
58
- });
59
- }
60
- });
61
-
62
- // Associate fonts with their subfamily groups (skip VF fonts)
63
- if (subfamiliesArray.length > 0) {
64
- Object.entries(subfamilies).forEach(([id, subfamilyName]) => {
65
- if (id.toLowerCase().includes('vf')) return;
66
-
67
- const subfamilyIndex = subfamiliesArray.findIndex(sf => sf.title === subfamilyName);
68
- if (subfamilyIndex !== -1) {
69
- subfamiliesArray[subfamilyIndex].fonts.push({
70
- _ref: id,
71
- _key: nanoid(),
72
- _type: 'reference',
73
- _weak: true,
74
- });
75
- }
76
- });
77
-
78
- // Deduplicate references within each subfamily
79
- subfamiliesArray = subfamiliesArray.map(subfamily => ({
80
- ...subfamily,
81
- fonts: subfamily.fonts.filter((font, index, self) =>
82
- index === self.findIndex(f => f._ref === font._ref)
83
- ),
84
- }));
85
- }
86
-
87
- patch.styles.subfamilies = subfamiliesArray;
88
-
89
- // Optionally update preferred style
90
- await updatePreferredStyle(doc_id, preferredStyleRef, newPreferredStyle, patch, client);
91
-
92
- console.log('doc_id: ', doc_id);
93
- console.log('Typeface patch: ', patch);
94
- console.log('New preferred style: ', newPreferredStyle);
95
- console.log('SubfamiliesArray:', subfamiliesArray);
96
-
97
- try {
98
- await client.patch(doc_id).set(patch).commit();
99
- console.log(`Updated document: ${doc_id}`);
100
-
101
- if (doc_id.startsWith('drafts.')) {
102
- await updatePublishedDocument(doc_id, patch, client);
103
- }
104
- } catch (err) {
105
- console.error('Error updating document:', err.message);
106
- setStatus('Error updating typeface');
107
- setError(true);
108
- }
109
- };
110
-
111
- /**
112
- * Conditionally sets a new preferredStyle on the patch when the candidate has a higher weight.
113
- * @param {string} doc_id
114
- * @param {Object} preferredStyleRef
115
- * @param {Object} newPreferredStyle
116
- * @param {Object} patch
117
- * @param {Object} client
118
- */
119
- const updatePreferredStyle = async (doc_id, preferredStyleRef, newPreferredStyle, patch, client) => {
120
- if (
121
- preferredStyleRef?._ref &&
122
- preferredStyleRef._ref !== '' &&
123
- preferredStyleRef._ref !== null &&
124
- newPreferredStyle._ref !== preferredStyleRef._ref
125
- ) {
126
- // Parameterized — doc_id comes from Sanity's useFormValue but we parameterize defensively
127
- const prefStyleResult = await client.fetch(
128
- `*[_id == $docId]{ preferredStyle->{ weight, style, _id } }`,
129
- { docId: doc_id }
130
- );
131
- const prefStyle = prefStyleResult[0]?.preferredStyle;
132
-
133
- if (!prefStyle?.weight || prefStyle === null || prefStyle.weight < newPreferredStyle.weight) {
134
- patch.preferredStyle = {
135
- _type: 'reference',
136
- _ref: newPreferredStyle._ref,
137
- _weak: true,
138
- };
139
- }
140
- }
141
- };
142
-
143
- /**
144
- * Applies the same patch to the published document if it exists.
145
- * @param {string} doc_id - Draft document ID
146
- * @param {Object} patch
147
- * @param {Object} client
148
- */
149
- const updatePublishedDocument = async (doc_id, patch, client) => {
150
- const publishedId = doc_id.replace('drafts.', '');
151
- // Parameterized to prevent injection from any draft ID edge cases
152
- const publishedDoc = await client.fetch(`*[_id == $publishedId]`, { publishedId }).then(res => res[0]);
153
-
154
- if (publishedDoc) {
155
- await client.patch(publishedId).set(patch).commit();
156
- console.log(`Updated published document: ${publishedId}`);
157
- } else {
158
- console.log(`No published document found for ${publishedId}, skipping`);
159
- }
160
- };
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
+ let patch = {
42
+ 'styles.fonts': stylesObject.fonts ? [...stylesObject.fonts, ...fontRefs] : [...fontRefs],
43
+ 'styles.variableFont': stylesObject?.variableFont ? [...stylesObject.variableFont, ...variableRefs] : [...variableRefs],
44
+ };
45
+
46
+ setStatus('Organising font subfamilies...');
47
+ subfamiliesArray = subfamiliesArray || [];
48
+
49
+ // Create any missing subfamily groups
50
+ uniqueSubfamilies.forEach(subfamilyName => {
51
+ if (!subfamiliesArray.find(sf => sf.title === subfamilyName)) {
52
+ subfamiliesArray.push({
53
+ title: subfamilyName,
54
+ _key: nanoid(),
55
+ _type: 'object',
56
+ fonts: [],
57
+ });
58
+ }
59
+ });
60
+
61
+ // Associate fonts with their subfamily groups (skip VF fonts)
62
+ if (subfamiliesArray.length > 0) {
63
+ Object.entries(subfamilies).forEach(([id, subfamilyName]) => {
64
+ if (id.toLowerCase().includes('vf')) return;
65
+
66
+ const subfamilyIndex = subfamiliesArray.findIndex(sf => sf.title === subfamilyName);
67
+ if (subfamilyIndex !== -1) {
68
+ subfamiliesArray[subfamilyIndex].fonts.push({
69
+ _ref: id,
70
+ _key: nanoid(),
71
+ _type: 'reference',
72
+ _weak: true,
73
+ });
74
+ }
75
+ });
76
+
77
+ // Deduplicate references within each subfamily
78
+ subfamiliesArray = subfamiliesArray.map(subfamily => ({
79
+ ...subfamily,
80
+ fonts: subfamily.fonts.filter((font, index, self) =>
81
+ index === self.findIndex(f => f._ref === font._ref)
82
+ ),
83
+ }));
84
+ }
85
+
86
+ patch['styles.subfamilies'] = subfamiliesArray;
87
+
88
+ // Optionally update preferred style
89
+ await updatePreferredStyle(doc_id, preferredStyleRef, newPreferredStyle, patch, client);
90
+
91
+ console.log('doc_id: ', doc_id);
92
+ console.log('Typeface patch: ', patch);
93
+ console.log('New preferred style: ', newPreferredStyle);
94
+ console.log('SubfamiliesArray:', subfamiliesArray);
95
+
96
+ try {
97
+ await client.patch(doc_id).set(patch).commit();
98
+ console.log(`Updated document: ${doc_id}`);
99
+
100
+ if (doc_id.startsWith('drafts.')) {
101
+ await updatePublishedDocument(doc_id, patch, client);
102
+ }
103
+ } catch (err) {
104
+ console.error('Error updating document:', err.message);
105
+ setStatus('Error updating typeface');
106
+ setError(true);
107
+ }
108
+ };
109
+
110
+ /**
111
+ * Sets preferredStyle on the patch only when currently empty.
112
+ * Does not overwrite an existing preferredStyle the user's choice is sticky.
113
+ * @param {string} doc_id
114
+ * @param {Object} preferredStyleRef
115
+ * @param {Object} newPreferredStyle
116
+ * @param {Object} patch
117
+ * @param {Object} client
118
+ */
119
+ const updatePreferredStyle = async (doc_id, preferredStyleRef, newPreferredStyle, patch, client) => {
120
+ const isCurrentlyEmpty = !preferredStyleRef?._ref || preferredStyleRef._ref === '' || preferredStyleRef._ref === null;
121
+ const hasCandidate = newPreferredStyle?._ref && newPreferredStyle._ref !== '';
122
+
123
+ if (isCurrentlyEmpty && hasCandidate) {
124
+ patch.preferredStyle = {
125
+ _type: 'reference',
126
+ _ref: newPreferredStyle._ref,
127
+ _weak: true,
128
+ };
129
+ }
130
+ };
131
+
132
+ /**
133
+ * Applies the same patch to the published document if it exists.
134
+ * @param {string} doc_id - Draft document ID
135
+ * @param {Object} patch
136
+ * @param {Object} client
137
+ */
138
+ const updatePublishedDocument = async (doc_id, patch, client) => {
139
+ const publishedId = doc_id.replace('drafts.', '');
140
+ // Parameterized to prevent injection from any draft ID edge cases
141
+ const publishedDoc = await client.fetch(`*[_id == $publishedId]`, { publishedId }).then(res => res[0]);
142
+
143
+ if (publishedDoc) {
144
+ await client.patch(publishedId).set(patch).commit();
145
+ console.log(`Updated published document: ${publishedId}`);
146
+ } else {
147
+ console.log(`No published document found for ${publishedId}, skipping`);
148
+ }
149
+ };