@liiift-studio/sanity-font-manager 2.2.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.
- package/README.md +263 -0
- package/dist/index.js +3699 -0
- package/dist/index.mjs +3622 -0
- package/package.json +77 -0
- package/src/components/BatchUploadFonts.jsx +639 -0
- package/src/components/FontScriptUploaderComponent.jsx +463 -0
- package/src/components/GenerateCollectionsPairsComponent.jsx +259 -0
- package/src/components/PriceInput.jsx +26 -0
- package/src/components/RegenerateSubfamiliesComponent.jsx +185 -0
- package/src/components/SingleUploaderTool.jsx +673 -0
- package/src/components/StatusDisplay.jsx +26 -0
- package/src/components/UpdateScriptsComponent.jsx +76 -0
- package/src/components/UploadButton.jsx +43 -0
- package/src/components/UploadScriptsComponent.jsx +537 -0
- package/src/hooks/useSanityClient.js +9 -0
- package/src/index.js +56 -0
- package/src/utils/generateCssFile.js +197 -0
- package/src/utils/generateFontData.js +145 -0
- package/src/utils/generateFontFile.js +38 -0
- package/src/utils/generateKeywords.js +185 -0
- package/src/utils/generateSubset.js +45 -0
- package/src/utils/getEmptyFontKit.js +99 -0
- package/src/utils/parseVariableFontInstances.js +211 -0
- package/src/utils/processFontFiles.js +477 -0
- package/src/utils/regenerateFontData.js +146 -0
- package/src/utils/sanitizeForSanityId.js +65 -0
- package/src/utils/updateFontPrices.js +94 -0
- package/src/utils/updateTypefaceDocument.js +160 -0
- package/src/utils/uploadFontFiles.js +316 -0
- package/src/utils/utils.js +16 -0
|
@@ -0,0 +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
|
+
};
|
|
@@ -0,0 +1,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
|
+
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
|
+
};
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
// Core batch upload orchestrator — uploads each format to Sanity, generates CSS and metadata, then creates or updates font documents
|
|
2
|
+
|
|
3
|
+
import { nanoid } from 'nanoid';
|
|
4
|
+
import generateCssFile from './generateCssFile';
|
|
5
|
+
import generateFontData from './generateFontData';
|
|
6
|
+
import { parseVariableFontInstances } from './parseVariableFontInstances';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Uploads all font files to Sanity and creates/updates font documents.
|
|
10
|
+
* @param {Object} fontsObjects
|
|
11
|
+
* @param {Object} subfamilies
|
|
12
|
+
* @param {Object} client - Sanity client
|
|
13
|
+
* @param {string} inputPrice
|
|
14
|
+
* @param {Object} stylesObject - Existing typeface styles object
|
|
15
|
+
* @param {Function} setStatus
|
|
16
|
+
* @param {Function} setError
|
|
17
|
+
* @returns {Promise<Object>} fontRefs, variableRefs, failedFiles
|
|
18
|
+
*/
|
|
19
|
+
export const uploadFontFiles = async (
|
|
20
|
+
fontsObjects,
|
|
21
|
+
subfamilies,
|
|
22
|
+
client,
|
|
23
|
+
inputPrice,
|
|
24
|
+
stylesObject,
|
|
25
|
+
setStatus,
|
|
26
|
+
setError,
|
|
27
|
+
) => {
|
|
28
|
+
let fontRefs = [];
|
|
29
|
+
let variableRefs = [];
|
|
30
|
+
let failedFiles = [];
|
|
31
|
+
|
|
32
|
+
const fontObjectKeys = Object.keys(fontsObjects);
|
|
33
|
+
|
|
34
|
+
// Upload files for each font
|
|
35
|
+
for (let i = 0; i < fontObjectKeys.length; i++) {
|
|
36
|
+
const id = fontObjectKeys[i];
|
|
37
|
+
const fontObject = fontsObjects[id];
|
|
38
|
+
const files = fontObject.files;
|
|
39
|
+
const fontKit = fontObject.fontKit;
|
|
40
|
+
let newFileInput = fontObject.fileInput;
|
|
41
|
+
|
|
42
|
+
fontObject.subfamily = subfamilies[id] ? subfamilies[id] : '';
|
|
43
|
+
fontObject.price = Number(inputPrice) ? Number(inputPrice) : 0;
|
|
44
|
+
if (fontObject.price > 0) fontObject.sell = true;
|
|
45
|
+
|
|
46
|
+
for (let j = 0; j < files.length; j++) {
|
|
47
|
+
const file = files[j];
|
|
48
|
+
const fileType = determineFileType(file);
|
|
49
|
+
|
|
50
|
+
console.log(`[${i + 1}/${fontObjectKeys.length}][${j + 1}/${files.length}] Uploading font file: ${fontObject._id}.${fileType}`);
|
|
51
|
+
setStatus(`[${i + 1}/${fontObjectKeys.length}][${j + 1}/${files.length}] Uploading font file: ${fontObject._id}.${fileType}`);
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
const baseAsset = await client.assets.upload('file', file, { filename: fontObject._id + '.' + fileType });
|
|
55
|
+
|
|
56
|
+
newFileInput[fileType] = {
|
|
57
|
+
_type: 'file',
|
|
58
|
+
asset: { _ref: baseAsset._id, _type: 'reference' },
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
if (fileType === 'woff2') {
|
|
62
|
+
console.log(`[${i + 1}/${fontObjectKeys.length}][${j + 1}/${files.length}] Generating CSS for: ${fontObject.title}`);
|
|
63
|
+
setStatus(`[${i + 1}/${fontObjectKeys.length}][${j + 1}/${files.length}] Generating CSS for: ${fontObject.title}`);
|
|
64
|
+
newFileInput = await generateCssFile({
|
|
65
|
+
woff2File: file,
|
|
66
|
+
fileInput: newFileInput,
|
|
67
|
+
fontName: fontObject.title,
|
|
68
|
+
fileName: fontObject._id,
|
|
69
|
+
variableFont: fontObject.variableFont,
|
|
70
|
+
weight: fontObject.weight,
|
|
71
|
+
client: client,
|
|
72
|
+
style: fontObject.style,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (fileType === 'ttf') {
|
|
77
|
+
console.log(`[${i + 1}/${fontObjectKeys.length}][${j + 1}/${files.length}] Generating font data for: ${fontObject.title}`);
|
|
78
|
+
setStatus(`[${i + 1}/${fontObjectKeys.length}][${j + 1}/${files.length}] Generating font data for: ${fontObject.title}`);
|
|
79
|
+
const metadata = await generateFontData({
|
|
80
|
+
fontId: fontObject._id,
|
|
81
|
+
url: baseAsset.url,
|
|
82
|
+
fontKit: fontKit,
|
|
83
|
+
client: client,
|
|
84
|
+
commit: false,
|
|
85
|
+
});
|
|
86
|
+
Object.assign(fontObject, metadata);
|
|
87
|
+
}
|
|
88
|
+
} catch (err) {
|
|
89
|
+
console.error('Error uploading font: ', fontObject.title, err.message);
|
|
90
|
+
setStatus('Error uploading font: ' + err.message);
|
|
91
|
+
setError(true);
|
|
92
|
+
failedFiles.push({ name: file.name, fk: fontKit });
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
fontObject.fileInput = newFileInput;
|
|
97
|
+
fontsObjects[id] = fontObject;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Create or update Sanity documents
|
|
102
|
+
console.log('Creating/updating Sanity font documents:', fontsObjects);
|
|
103
|
+
|
|
104
|
+
for (let i = 0; i < fontObjectKeys.length; i++) {
|
|
105
|
+
const fontId = fontObjectKeys[i];
|
|
106
|
+
const font = fontsObjects[fontId];
|
|
107
|
+
|
|
108
|
+
const fontRef = await createOrUpdateFontDocument(font, client, setError);
|
|
109
|
+
|
|
110
|
+
if (fontRef) {
|
|
111
|
+
if (!font.variableFont) {
|
|
112
|
+
addToFontRefs(fontRef, font, fontRefs, stylesObject);
|
|
113
|
+
} else {
|
|
114
|
+
addToVariableRefs(fontRef, font, variableRefs, stylesObject);
|
|
115
|
+
}
|
|
116
|
+
console.log(`[${i + 1}/${fontObjectKeys.length}] ${fontRef._ref} created`);
|
|
117
|
+
setStatus(`[${i + 1}/${fontObjectKeys.length}] ${fontRef._ref} created`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return { fontRefs, variableRefs, failedFiles };
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Returns the file extension for a font file.
|
|
126
|
+
* @param {File} file
|
|
127
|
+
* @returns {string}
|
|
128
|
+
*/
|
|
129
|
+
const determineFileType = (file) => {
|
|
130
|
+
if (file.name.endsWith('.otf')) return 'otf';
|
|
131
|
+
if (file.name.endsWith('.ttf')) return 'ttf';
|
|
132
|
+
if (file.name.endsWith('.woff')) return 'woff';
|
|
133
|
+
if (file.name.endsWith('.woff2')) return 'woff2';
|
|
134
|
+
if (file.name.endsWith('.eot')) return 'eot';
|
|
135
|
+
if (file.name.endsWith('.svg')) return 'svg';
|
|
136
|
+
return '';
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Creates a new font document or updates an existing one, returning its reference.
|
|
141
|
+
* @param {Object} font
|
|
142
|
+
* @param {Object} client
|
|
143
|
+
* @param {Function} setError
|
|
144
|
+
* @returns {Promise<Object|null>}
|
|
145
|
+
*/
|
|
146
|
+
const createOrUpdateFontDocument = async (font, client, setError) => {
|
|
147
|
+
try {
|
|
148
|
+
// Parameterized query prevents GROQ injection via font._id
|
|
149
|
+
const existingFont = await client.fetch(
|
|
150
|
+
`*[_type == 'font' && _id == $fontId]{
|
|
151
|
+
fileInput,
|
|
152
|
+
description,
|
|
153
|
+
metaData,
|
|
154
|
+
metrics,
|
|
155
|
+
opentypeFeatures,
|
|
156
|
+
characterSet,
|
|
157
|
+
subfamily,
|
|
158
|
+
scriptFileInput,
|
|
159
|
+
variableInstanceReferences
|
|
160
|
+
}`,
|
|
161
|
+
{ fontId: font._id }
|
|
162
|
+
).then(res => res[0]);
|
|
163
|
+
|
|
164
|
+
const { files, fontKit } = font;
|
|
165
|
+
delete font.files;
|
|
166
|
+
delete font.fontKit;
|
|
167
|
+
// Remove temp field used by preserveFileNames — must not be saved to Sanity
|
|
168
|
+
delete font.originalFilename;
|
|
169
|
+
|
|
170
|
+
if (font.variableFont && font.variableInstances) {
|
|
171
|
+
const instanceMappings = await parseVariableFontInstances(font, client);
|
|
172
|
+
if (instanceMappings.length > 0) {
|
|
173
|
+
font.variableInstanceReferences = instanceMappings;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
let fontResponse;
|
|
178
|
+
if (existingFont) {
|
|
179
|
+
fontResponse = await updateExistingFont(font, existingFont, client);
|
|
180
|
+
} else {
|
|
181
|
+
fontResponse = await createNewFont(font, client);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return {
|
|
185
|
+
_key: nanoid(),
|
|
186
|
+
_type: 'reference',
|
|
187
|
+
_ref: fontResponse._id,
|
|
188
|
+
_weak: true,
|
|
189
|
+
};
|
|
190
|
+
} catch (e) {
|
|
191
|
+
console.error('Error creating font: ', font.title, font.subfamily, e);
|
|
192
|
+
setError(true);
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Patches an existing font document, merging file references and preserving stored metadata.
|
|
199
|
+
* @param {Object} font
|
|
200
|
+
* @param {Object} existingFont
|
|
201
|
+
* @param {Object} client
|
|
202
|
+
* @returns {Promise<Object>}
|
|
203
|
+
*/
|
|
204
|
+
const updateExistingFont = async (font, existingFont, client) => {
|
|
205
|
+
if (existingFont.fileInput) {
|
|
206
|
+
const newFileInput = { ...font.fileInput };
|
|
207
|
+
Object.keys(existingFont.fileInput).forEach(key => {
|
|
208
|
+
if (!newFileInput[key]) newFileInput[key] = existingFont.fileInput[key];
|
|
209
|
+
});
|
|
210
|
+
font.fileInput = newFileInput;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
font.metaData = !font?.metaData || Object.keys(font?.metaData || {}).length === 0
|
|
214
|
+
? existingFont?.metaData || {}
|
|
215
|
+
: font.metaData;
|
|
216
|
+
font.metrics = !font?.metrics || Object.keys(font?.metrics || {}).length === 0
|
|
217
|
+
? existingFont?.metrics || {}
|
|
218
|
+
: font.metrics;
|
|
219
|
+
font.opentypeFeatures = !font?.opentypeFeatures || Object.keys(font?.opentypeFeatures || {}).length === 0
|
|
220
|
+
? existingFont?.opentypeFeatures || {}
|
|
221
|
+
: font.opentypeFeatures;
|
|
222
|
+
font.characterSet = !font?.characterSet || Object.keys(font?.characterSet || {}).length === 0
|
|
223
|
+
? existingFont?.characterSet || {}
|
|
224
|
+
: font.characterSet;
|
|
225
|
+
font.scriptFileInput = existingFont?.scriptFileInput || {};
|
|
226
|
+
|
|
227
|
+
cleanMetadataValues(font);
|
|
228
|
+
|
|
229
|
+
if (font.variableFont && existingFont?.variableInstanceReferences &&
|
|
230
|
+
(!font.variableInstanceReferences || font.variableInstanceReferences.length === 0)) {
|
|
231
|
+
font.variableInstanceReferences = existingFont.variableInstanceReferences;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
console.log('Updating existing font: ', font._id, font.title);
|
|
235
|
+
|
|
236
|
+
const patchObject = {
|
|
237
|
+
fileInput: font.fileInput,
|
|
238
|
+
subfamily: font.subfamily,
|
|
239
|
+
weight: font.weight,
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
if (font.variableInstanceReferences) {
|
|
243
|
+
patchObject.variableInstanceReferences = font.variableInstanceReferences;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return await client.patch(font._id).set(patchObject).commit();
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Creates a new font document in Sanity.
|
|
251
|
+
* @param {Object} font
|
|
252
|
+
* @param {Object} client
|
|
253
|
+
* @returns {Promise<Object>}
|
|
254
|
+
*/
|
|
255
|
+
const createNewFont = async (font, client) => {
|
|
256
|
+
console.log('Creating new font: ', font._id, font.title);
|
|
257
|
+
if (font.metaData) cleanMetadataValues(font);
|
|
258
|
+
|
|
259
|
+
const newDocument = {
|
|
260
|
+
_key: nanoid(),
|
|
261
|
+
_id: font._id,
|
|
262
|
+
_type: 'font',
|
|
263
|
+
...font,
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
return await client.createOrReplace(newDocument);
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Removes null metadata values and strips control characters from string values.
|
|
271
|
+
* @param {Object} font
|
|
272
|
+
*/
|
|
273
|
+
const cleanMetadataValues = (font) => {
|
|
274
|
+
if (!font.metaData) return;
|
|
275
|
+
Object.keys(font.metaData).forEach(key => {
|
|
276
|
+
if (font.metaData[key] == null) {
|
|
277
|
+
font.metaData[key] = '';
|
|
278
|
+
} else {
|
|
279
|
+
font.metaData[key] = font.metaData[key].replace(/[-]/g, '');
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Adds a font reference to fontRefs if it's not already in the existing styles.
|
|
286
|
+
* @param {Object} fontRef
|
|
287
|
+
* @param {Object} font
|
|
288
|
+
* @param {Array} fontRefs
|
|
289
|
+
* @param {Object} stylesObject
|
|
290
|
+
*/
|
|
291
|
+
const addToFontRefs = (fontRef, font, fontRefs, stylesObject) => {
|
|
292
|
+
if (stylesObject.fonts && stylesObject.fonts.length > 0) {
|
|
293
|
+
const fontExists = stylesObject.fonts.findIndex(f => f._ref === fontRef._ref);
|
|
294
|
+
const inFontRefs = fontRefs.findIndex(f => f._ref === fontRef._ref);
|
|
295
|
+
if (fontExists === -1 && inFontRefs === -1) fontRefs.push(fontRef);
|
|
296
|
+
} else {
|
|
297
|
+
fontRefs.push(fontRef);
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Adds a variable font reference to variableRefs if it's not already in the existing styles.
|
|
303
|
+
* @param {Object} fontRef
|
|
304
|
+
* @param {Object} font
|
|
305
|
+
* @param {Array} variableRefs
|
|
306
|
+
* @param {Object} stylesObject
|
|
307
|
+
*/
|
|
308
|
+
const addToVariableRefs = (fontRef, font, variableRefs, stylesObject) => {
|
|
309
|
+
if (stylesObject?.variableFont?.length) {
|
|
310
|
+
const vfExists = stylesObject.variableFont.findIndex(f => f._ref === fontRef._ref);
|
|
311
|
+
const inVariableRefs = variableRefs.findIndex(f => f._ref === fontRef._ref);
|
|
312
|
+
if (vfExists === -1 && inVariableRefs === -1 && font.variableFont) variableRefs.push(fontRef);
|
|
313
|
+
} else {
|
|
314
|
+
variableRefs.push(fontRef);
|
|
315
|
+
}
|
|
316
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// Script variant constants (SCRIPTS, SCRIPTS_OBJECT) and HtmlDescription component for the supported script list
|
|
2
|
+
|
|
3
|
+
import React from 'react'
|
|
4
|
+
|
|
5
|
+
/** Renders children as-is; used for rich-text fields that return HTML strings */
|
|
6
|
+
export const HtmlDescription = ({ children }) => {
|
|
7
|
+
return children || ''
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** Script variants available for this studio instance — comma-separated SANITY_STUDIO_SCRIPTS env var */
|
|
11
|
+
export const SCRIPTS = (process.env.SANITY_STUDIO_SCRIPTS || '').split(',').map((script) => script.trim()).filter(Boolean);
|
|
12
|
+
|
|
13
|
+
/** SCRIPTS as Sanity select option objects */
|
|
14
|
+
export const SCRIPTS_OBJECT = SCRIPTS.map((script) => {
|
|
15
|
+
return {title: script[0].toUpperCase() + script.slice(1), value: script}
|
|
16
|
+
});
|