@liiift-studio/sanity-font-manager 2.7.0 → 2.7.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.
Files changed (62) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +695 -437
  3. package/dist/index.js +1634 -17839
  4. package/dist/index.mjs +1551 -17745
  5. package/package.json +83 -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,146 +1,146 @@
1
- // Renames font document IDs across a typeface when a typeface slug changes
2
-
3
- import { parseFont } from './parseFont';
4
- import {
5
- readFontFile,
6
- extractFontMetadata,
7
- } from './processFontFiles';
8
-
9
- /**
10
- * Re-downloads TTF/OTF files for all fonts in a typeface and re-extracts their metadata,
11
- * patching each font document's title, slug, weightName, and subfamily.
12
- *
13
- * @param {Object} params
14
- * @param {Object} params.client - Sanity client
15
- * @param {string} params.typefaceName
16
- * @param {Object} params.slug - Typeface slug object
17
- * @param {string[]} params.weightKeywordList
18
- * @param {string[]} params.italicKeywordList
19
- * @param {boolean} params.preserveShortenedNames
20
- * @param {Function} params.setStatus
21
- * @param {Function} params.setError
22
- * @returns {Promise<Object>}
23
- */
24
- export const renameFontDocuments = async ({
25
- client,
26
- typefaceName,
27
- slug,
28
- weightKeywordList,
29
- italicKeywordList,
30
- preserveShortenedNames = false,
31
- setStatus,
32
- setError,
33
- }) => {
34
- try {
35
- if (!typefaceName) {
36
- setStatus('Typeface needs a title');
37
- setError(true);
38
- console.error('Typeface needs title');
39
- return { success: false, message: 'Typeface needs title' };
40
- }
41
-
42
- setStatus('Fetching font documents...');
43
- const slugCurrent = slug?.current || typefaceName;
44
-
45
- const query = await client.fetch(
46
- `*[_type == "typeface" && slug.current == $slugCurrent][0]{
47
- "fonts": styles.fonts[]->{ _id, title, subfamily, fileInput, style, weight, _key }
48
- }`,
49
- { slugCurrent }
50
- );
51
- const fontDocuments = query?.fonts || [];
52
-
53
- if (fontDocuments.length === 0) {
54
- setStatus('No font documents found for this typeface');
55
- setError(true);
56
- return { success: false, message: 'No font documents found for this typeface' };
57
- }
58
-
59
- setStatus(`Found ${fontDocuments.length} font documents to process...`);
60
-
61
- let updatedCount = 0;
62
- let skippedCount = 0;
63
- let errorCount = 0;
64
-
65
- for (let i = 0; i < fontDocuments.length; i++) {
66
- const fontDoc = fontDocuments[i];
67
- setStatus(`Processing font ${i + 1}/${fontDocuments.length}: ${fontDoc.title}`);
68
-
69
- if (!fontDoc.fileInput?.ttf?.asset?._ref && !fontDoc.fileInput?.otf?.asset?._ref) {
70
- console.log(`Skipping ${fontDoc.title} — no TTF or OTF file found`);
71
- skippedCount++;
72
- continue;
73
- }
74
-
75
- try {
76
- let file;
77
- let ttfAsset;
78
- let otfAsset;
79
-
80
- if (fontDoc.fileInput?.ttf?.asset?._ref) {
81
- ttfAsset = await client.getDocument(fontDoc.fileInput.ttf.asset._ref);
82
- }
83
-
84
- if (!ttfAsset?.url) {
85
- if (fontDoc.fileInput?.otf?.asset?._ref) {
86
- otfAsset = await client.getDocument(fontDoc.fileInput.otf.asset._ref);
87
- }
88
- if (!otfAsset?.url) {
89
- console.log(`Skipping ${fontDoc.title} — no TTF or OTF URL`);
90
- skippedCount++;
91
- continue;
92
- }
93
- setStatus(`Fetching OTF file for ${fontDoc.title}...`);
94
- const res = await fetch(otfAsset.url);
95
- file = await res.blob();
96
- } else {
97
- setStatus(`Fetching TTF file for ${fontDoc.title}...`);
98
- const res = await fetch(ttfAsset.url);
99
- file = await res.blob();
100
- }
101
-
102
- const fontBuffer = await readFontFile(file);
103
- const font = await parseFont(fontBuffer, `${fontDoc._id}.ttf`);
104
-
105
- const { weightName, subfamilyName, fontTitle } = extractFontMetadata(
106
- font,
107
- typefaceName,
108
- weightKeywordList,
109
- italicKeywordList,
110
- preserveShortenedNames,
111
- );
112
-
113
- const slugValue = fontTitle.toLowerCase().replace(/\s+/g, '-');
114
-
115
- setStatus(`Updating font ${i + 1}/${fontDocuments.length}: ${fontTitle}`);
116
- await client.patch(fontDoc._id)
117
- .set({
118
- title: fontTitle,
119
- slug: { _type: 'slug', current: slugValue },
120
- weightName: weightName,
121
- subfamily: subfamilyName,
122
- })
123
- .commit();
124
-
125
- updatedCount++;
126
- } catch (err) {
127
- console.error(`Error processing ${fontDoc.title}:`, err);
128
- errorCount++;
129
- }
130
- }
131
-
132
- const resultMessage = `Renamed ${updatedCount} of ${fontDocuments.length} font documents (${skippedCount} skipped, ${errorCount} errors)`;
133
- setStatus(resultMessage);
134
-
135
- return {
136
- success: true,
137
- message: resultMessage,
138
- stats: { total: fontDocuments.length, updated: updatedCount, skipped: skippedCount, errors: errorCount },
139
- };
140
- } catch (err) {
141
- console.error('Error renaming font documents:', err);
142
- setError(true);
143
- setStatus(`Error: ${err.message}`);
144
- return { success: false, message: `Error: ${err.message}` };
145
- }
146
- };
1
+ // Renames font document IDs across a typeface when a typeface slug changes
2
+
3
+ import { parseFont } from './parseFont';
4
+ import {
5
+ readFontFile,
6
+ extractFontMetadata,
7
+ } from './processFontFiles';
8
+
9
+ /**
10
+ * Re-downloads TTF/OTF files for all fonts in a typeface and re-extracts their metadata,
11
+ * patching each font document's title, slug, weightName, and subfamily.
12
+ *
13
+ * @param {Object} params
14
+ * @param {Object} params.client - Sanity client
15
+ * @param {string} params.typefaceName
16
+ * @param {Object} params.slug - Typeface slug object
17
+ * @param {string[]} params.weightKeywordList
18
+ * @param {string[]} params.italicKeywordList
19
+ * @param {boolean} params.preserveShortenedNames
20
+ * @param {Function} params.setStatus
21
+ * @param {Function} params.setError
22
+ * @returns {Promise<Object>}
23
+ */
24
+ export const renameFontDocuments = async ({
25
+ client,
26
+ typefaceName,
27
+ slug,
28
+ weightKeywordList,
29
+ italicKeywordList,
30
+ preserveShortenedNames = false,
31
+ setStatus,
32
+ setError,
33
+ }) => {
34
+ try {
35
+ if (!typefaceName) {
36
+ setStatus('Typeface needs a title');
37
+ setError(true);
38
+ console.error('Typeface needs title');
39
+ return { success: false, message: 'Typeface needs title' };
40
+ }
41
+
42
+ setStatus('Fetching font documents...');
43
+ const slugCurrent = slug?.current || typefaceName;
44
+
45
+ const query = await client.fetch(
46
+ `*[_type == "typeface" && slug.current == $slugCurrent][0]{
47
+ "fonts": styles.fonts[]->{ _id, title, subfamily, fileInput, style, weight, _key }
48
+ }`,
49
+ { slugCurrent }
50
+ );
51
+ const fontDocuments = query?.fonts || [];
52
+
53
+ if (fontDocuments.length === 0) {
54
+ setStatus('No font documents found for this typeface');
55
+ setError(true);
56
+ return { success: false, message: 'No font documents found for this typeface' };
57
+ }
58
+
59
+ setStatus(`Found ${fontDocuments.length} font documents to process...`);
60
+
61
+ let updatedCount = 0;
62
+ let skippedCount = 0;
63
+ let errorCount = 0;
64
+
65
+ for (let i = 0; i < fontDocuments.length; i++) {
66
+ const fontDoc = fontDocuments[i];
67
+ setStatus(`Processing font ${i + 1}/${fontDocuments.length}: ${fontDoc.title}`);
68
+
69
+ if (!fontDoc.fileInput?.ttf?.asset?._ref && !fontDoc.fileInput?.otf?.asset?._ref) {
70
+ console.log(`Skipping ${fontDoc.title} — no TTF or OTF file found`);
71
+ skippedCount++;
72
+ continue;
73
+ }
74
+
75
+ try {
76
+ let file;
77
+ let ttfAsset;
78
+ let otfAsset;
79
+
80
+ if (fontDoc.fileInput?.ttf?.asset?._ref) {
81
+ ttfAsset = await client.getDocument(fontDoc.fileInput.ttf.asset._ref);
82
+ }
83
+
84
+ if (!ttfAsset?.url) {
85
+ if (fontDoc.fileInput?.otf?.asset?._ref) {
86
+ otfAsset = await client.getDocument(fontDoc.fileInput.otf.asset._ref);
87
+ }
88
+ if (!otfAsset?.url) {
89
+ console.log(`Skipping ${fontDoc.title} — no TTF or OTF URL`);
90
+ skippedCount++;
91
+ continue;
92
+ }
93
+ setStatus(`Fetching OTF file for ${fontDoc.title}...`);
94
+ const res = await fetch(otfAsset.url);
95
+ file = await res.blob();
96
+ } else {
97
+ setStatus(`Fetching TTF file for ${fontDoc.title}...`);
98
+ const res = await fetch(ttfAsset.url);
99
+ file = await res.blob();
100
+ }
101
+
102
+ const fontBuffer = await readFontFile(file);
103
+ const font = await parseFont(fontBuffer, `${fontDoc._id}.ttf`);
104
+
105
+ const { weightName, subfamilyName, fontTitle } = extractFontMetadata(
106
+ font,
107
+ typefaceName,
108
+ weightKeywordList,
109
+ italicKeywordList,
110
+ preserveShortenedNames,
111
+ );
112
+
113
+ const slugValue = fontTitle.toLowerCase().replace(/\s+/g, '-');
114
+
115
+ setStatus(`Updating font ${i + 1}/${fontDocuments.length}: ${fontTitle}`);
116
+ await client.patch(fontDoc._id)
117
+ .set({
118
+ title: fontTitle,
119
+ slug: { _type: 'slug', current: slugValue },
120
+ weightName: weightName,
121
+ subfamily: subfamilyName,
122
+ })
123
+ .commit();
124
+
125
+ updatedCount++;
126
+ } catch (err) {
127
+ console.error(`Error processing ${fontDoc.title}:`, err);
128
+ errorCount++;
129
+ }
130
+ }
131
+
132
+ const resultMessage = `Renamed ${updatedCount} of ${fontDocuments.length} font documents (${skippedCount} skipped, ${errorCount} errors)`;
133
+ setStatus(resultMessage);
134
+
135
+ return {
136
+ success: true,
137
+ message: resultMessage,
138
+ stats: { total: fontDocuments.length, updated: updatedCount, skipped: skippedCount, errors: errorCount },
139
+ };
140
+ } catch (err) {
141
+ console.error('Error renaming font documents:', err);
142
+ setError(true);
143
+ setStatus(`Error: ${err.message}`);
144
+ return { success: false, message: `Error: ${err.message}` };
145
+ }
146
+ };
@@ -1,87 +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
- };
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
+ };