@liiift-studio/sanity-utilities 1.0.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 +102 -0
- package/SETUP.md +166 -0
- package/components/ConvertIdsToSlug.jsx +151 -0
- package/components/ConvertToWeakReferences.jsx +230 -0
- package/components/DangerModeWarning.jsx +132 -0
- package/components/DeleteUnusedAssets.jsx +217 -0
- package/components/DuplicateAndRename.jsx +236 -0
- package/components/ExportData.jsx +373 -0
- package/components/GetFontData.jsx +240 -0
- package/components/SearchAddData.jsx +444 -0
- package/components/SearchAndDelete.jsx +236 -0
- package/components/utilities.jsx +54 -0
- package/index.d.ts +19 -0
- package/index.js +13 -0
- package/package.json +54 -0
package/README.md
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# @liiift-studio/sanity-utilities
|
|
2
|
+
|
|
3
|
+
Centralized Sanity utilities package for Liiift Studio foundry projects.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
This package provides a collection of powerful utilities for Sanity Studio that help manage and manipulate content across foundry projects. All utilities are designed with safety features including danger mode locks and confirmation dialogs for destructive operations.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install @liiift-studio/sanity-utilities
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
### Basic Usage
|
|
18
|
+
|
|
19
|
+
Import and use the utilities desk in your Sanity config:
|
|
20
|
+
|
|
21
|
+
```js
|
|
22
|
+
import { UtilitiesDesk } from '@liiift-studio/sanity-utilities';
|
|
23
|
+
|
|
24
|
+
// In your sanity.config.js desk structure
|
|
25
|
+
S.listItem()
|
|
26
|
+
.title('Utilities')
|
|
27
|
+
.child(
|
|
28
|
+
S.component(UtilitiesDesk).title('Utilities')
|
|
29
|
+
)
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Available Components
|
|
33
|
+
|
|
34
|
+
All utility components are exported individually if you need custom implementations:
|
|
35
|
+
|
|
36
|
+
```js
|
|
37
|
+
import {
|
|
38
|
+
ConvertIdsToSlug,
|
|
39
|
+
ConvertToWeakReferences,
|
|
40
|
+
DangerModeWarning,
|
|
41
|
+
DeleteUnusedAssets,
|
|
42
|
+
DuplicateAndRename,
|
|
43
|
+
ExportData,
|
|
44
|
+
GetFontData,
|
|
45
|
+
SearchAddData,
|
|
46
|
+
SearchAndDelete,
|
|
47
|
+
UtilitiesDesk
|
|
48
|
+
} from '@liiift-studio/sanity-utilities';
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Utilities Included
|
|
52
|
+
|
|
53
|
+
### 1. Convert IDs to Slug
|
|
54
|
+
Converts font document IDs from auto-generated to slug-based IDs, updating all references across your content.
|
|
55
|
+
|
|
56
|
+
### 2. Convert to Weak References
|
|
57
|
+
Transforms strong document references into weak references across matching documents.
|
|
58
|
+
|
|
59
|
+
### 3. Delete Unused Assets
|
|
60
|
+
Scans for unreferenced image and file assets and allows bulk deletion with preview thumbnails.
|
|
61
|
+
|
|
62
|
+
### 4. Duplicate and Rename
|
|
63
|
+
Copies field values to new field names across all documents of a specific type.
|
|
64
|
+
|
|
65
|
+
### 5. Export Data
|
|
66
|
+
Exports documents to CSV or JSON formats with optional reference population.
|
|
67
|
+
|
|
68
|
+
### 6. Get Font Data
|
|
69
|
+
Analyzes uploaded font files using fontkit, displaying metadata and OpenType properties.
|
|
70
|
+
|
|
71
|
+
### 7. Search & Add Data
|
|
72
|
+
Bulk add or replace field data with multiple modes:
|
|
73
|
+
- Full Replace
|
|
74
|
+
- Find & Replace
|
|
75
|
+
- Prepend
|
|
76
|
+
- Append
|
|
77
|
+
- Transform (toLowerCase, toUpperCase, trim)
|
|
78
|
+
|
|
79
|
+
### 8. Search & Delete
|
|
80
|
+
Searches for and deletes documents matching specific criteria with confirmation dialogs.
|
|
81
|
+
|
|
82
|
+
## Safety Features
|
|
83
|
+
|
|
84
|
+
- **Danger Mode Locks**: Destructive operations require unlocking danger mode
|
|
85
|
+
- **Warning Modals**: 48-hour suppression option for danger mode warnings
|
|
86
|
+
- **Confirmation Dialogs**: Double confirmation for bulk delete operations
|
|
87
|
+
- **Preview Features**: See what will be affected before taking action
|
|
88
|
+
|
|
89
|
+
## Requirements
|
|
90
|
+
|
|
91
|
+
- Sanity Studio v3
|
|
92
|
+
- React 18+
|
|
93
|
+
- @sanity/ui v1 or v2
|
|
94
|
+
- @sanity/icons v2 or v3
|
|
95
|
+
|
|
96
|
+
## License
|
|
97
|
+
|
|
98
|
+
UNLICENSED - Internal use only for Liiift Studio projects
|
|
99
|
+
|
|
100
|
+
## Support
|
|
101
|
+
|
|
102
|
+
For issues or questions, please contact the Liiift Studio development team.
|
package/SETUP.md
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# Setup & Publishing Guide
|
|
2
|
+
|
|
3
|
+
## Initial Setup
|
|
4
|
+
|
|
5
|
+
### 1. Install Dependencies
|
|
6
|
+
|
|
7
|
+
This package has no production dependencies (only peer dependencies). No installation needed.
|
|
8
|
+
|
|
9
|
+
### 2. Configure NPM Authentication
|
|
10
|
+
|
|
11
|
+
Create or update `.npmrc` in your project root:
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
//registry.npmjs.org/:_authToken=${NPM_TOKEN}
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Set the NPM_TOKEN environment variable with your npm authentication token.
|
|
18
|
+
|
|
19
|
+
### 3. Verify Package Contents
|
|
20
|
+
|
|
21
|
+
Check what will be published:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm pack --dry-run
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Publishing
|
|
28
|
+
|
|
29
|
+
### Version Bump
|
|
30
|
+
|
|
31
|
+
Follow semantic versioning:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
# Patch release (bug fixes)
|
|
35
|
+
npm version patch
|
|
36
|
+
|
|
37
|
+
# Minor release (new features, backward compatible)
|
|
38
|
+
npm version minor
|
|
39
|
+
|
|
40
|
+
# Major release (breaking changes)
|
|
41
|
+
npm version major
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Publish to NPM
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
npm publish
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Integration in Consumer Projects
|
|
51
|
+
|
|
52
|
+
### 1. Install the Package
|
|
53
|
+
|
|
54
|
+
In your Sanity project (e.g., Darden-Studio):
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
npm install @liiift-studio/sanity-utilities
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### 2. Update sanity.config.js
|
|
61
|
+
|
|
62
|
+
Import and use the utilities desk:
|
|
63
|
+
|
|
64
|
+
```js
|
|
65
|
+
import { UtilitiesDesk } from '@liiift-studio/sanity-utilities';
|
|
66
|
+
|
|
67
|
+
export default defineConfig({
|
|
68
|
+
// ... other config
|
|
69
|
+
|
|
70
|
+
structure: (S) =>
|
|
71
|
+
S.list()
|
|
72
|
+
.title('Content')
|
|
73
|
+
.items([
|
|
74
|
+
// ... other list items
|
|
75
|
+
|
|
76
|
+
S.listItem()
|
|
77
|
+
.title('Utilities')
|
|
78
|
+
.child(
|
|
79
|
+
S.component(UtilitiesDesk).title('Utilities')
|
|
80
|
+
),
|
|
81
|
+
]),
|
|
82
|
+
});
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### 3. Remove Old Utility Files (if migrating)
|
|
86
|
+
|
|
87
|
+
After confirming the package works, remove the old utility files:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
rm -rf sanity/schemas/components/utilities/
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Update any imports that referenced the old location.
|
|
94
|
+
|
|
95
|
+
## Updating the Package
|
|
96
|
+
|
|
97
|
+
### Making Changes
|
|
98
|
+
|
|
99
|
+
1. Make your changes to the component files
|
|
100
|
+
2. Test in a consumer project (use `npm link` for local testing)
|
|
101
|
+
3. Bump the version: `npm version patch|minor|major`
|
|
102
|
+
4. Publish: `npm publish`
|
|
103
|
+
|
|
104
|
+
### Testing Locally Before Publishing
|
|
105
|
+
|
|
106
|
+
In the package directory:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
npm link
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
In the consumer project:
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
npm link @liiift-studio/sanity-utilities
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Test your changes, then unlink:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
npm unlink @liiift-studio/sanity-utilities
|
|
122
|
+
npm install @liiift-studio/sanity-utilities
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Updating Consumer Projects
|
|
126
|
+
|
|
127
|
+
After publishing a new version:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
npm install @liiift-studio/sanity-utilities@latest
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Or update the version in package.json and run:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
npm install
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Troubleshooting
|
|
140
|
+
|
|
141
|
+
### Package Not Found
|
|
142
|
+
|
|
143
|
+
Ensure you're authenticated with npm and have access to the @liiift-studio scope.
|
|
144
|
+
|
|
145
|
+
### Import Errors
|
|
146
|
+
|
|
147
|
+
Check that peer dependencies are installed in the consumer project:
|
|
148
|
+
- @sanity/icons
|
|
149
|
+
- @sanity/ui
|
|
150
|
+
- react
|
|
151
|
+
- sanity
|
|
152
|
+
|
|
153
|
+
### Component Not Rendering
|
|
154
|
+
|
|
155
|
+
Verify the imports match the exports in index.js. Use named imports:
|
|
156
|
+
|
|
157
|
+
```js
|
|
158
|
+
import { UtilitiesDesk } from '@liiift-studio/sanity-utilities';
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Not default imports:
|
|
162
|
+
|
|
163
|
+
```js
|
|
164
|
+
// ❌ Wrong
|
|
165
|
+
import UtilitiesDesk from '@liiift-studio/sanity-utilities';
|
|
166
|
+
```
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
// Component for converting document IDs to slug-based IDs
|
|
2
|
+
import { Stack, Grid, Heading, Text, Button, Select } from '@sanity/ui'
|
|
3
|
+
import { LockIcon, UnlockIcon } from '@sanity/icons'
|
|
4
|
+
import { useState, useEffect } from 'react'
|
|
5
|
+
import DangerModeWarning, { shouldShowDangerWarning } from './DangerModeWarning'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Convert IDs to Slug Component
|
|
9
|
+
* Converts document IDs to slug-based IDs for better URL handling
|
|
10
|
+
* @param {Object} props - Component props
|
|
11
|
+
* @param {SanityClient} props.client - Sanity client instance
|
|
12
|
+
*/
|
|
13
|
+
const ConvertIdsToSlug = (props) => {
|
|
14
|
+
const {client} = props;
|
|
15
|
+
const [typefaces, setTypefaces] = useState([]);
|
|
16
|
+
const [targetTypeface, setTargetTypeface] = useState('');
|
|
17
|
+
const [dangerMode, setDangerMode] = useState(false);
|
|
18
|
+
const [convertMessage, setConvertMessage] = useState('');
|
|
19
|
+
const [showWarningModal, setShowWarningModal] = useState(false);
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Handle danger mode toggle with warning modal
|
|
23
|
+
*/
|
|
24
|
+
const handleDangerModeToggle = () => {
|
|
25
|
+
if (!dangerMode && shouldShowDangerWarning()) {
|
|
26
|
+
// Trying to enable danger mode, show warning
|
|
27
|
+
setShowWarningModal(true);
|
|
28
|
+
} else {
|
|
29
|
+
// Either disabling danger mode or warning is suppressed
|
|
30
|
+
setDangerMode(!dangerMode);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const handleWarningConfirm = () => {
|
|
35
|
+
setShowWarningModal(false);
|
|
36
|
+
setDangerMode(true);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const handleWarningCancel = () => {
|
|
40
|
+
setShowWarningModal(false);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
async function getTypefaces(){
|
|
44
|
+
let typefaces = await client.fetch(`*[_type == "typeface" && !(_id in path('drafts.**'))]`);
|
|
45
|
+
setTypefaces(typefaces);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
getTypefaces();
|
|
50
|
+
}, [])
|
|
51
|
+
|
|
52
|
+
async function updateIdsToSlug(){
|
|
53
|
+
console.log(`Scanning ${targetTypeface}`)
|
|
54
|
+
|
|
55
|
+
let typeface = await client.fetch(`*[_type == "typeface" && title match "${targetTypeface}*"][0]`);
|
|
56
|
+
let typefaceIds = [];
|
|
57
|
+
typeface.styles.fonts.forEach(font => {
|
|
58
|
+
typefaceIds.push(font._ref);
|
|
59
|
+
});
|
|
60
|
+
console.log('Target Ids:', typefaceIds);
|
|
61
|
+
|
|
62
|
+
for await (let [index, id] of typefaceIds.entries()) {
|
|
63
|
+
const rootDoc = await client.fetch(`*[_id == "${id}"][0]`);
|
|
64
|
+
const refDocs = await client.fetch(`*[references("${id}")]`);
|
|
65
|
+
const slug = rootDoc?.slug?.current;
|
|
66
|
+
|
|
67
|
+
if (slug) {
|
|
68
|
+
console.log(`[${index}/${typefaceIds.length}] Creating new document: `, slug);
|
|
69
|
+
const newDoc = await client.createOrReplace({ ...rootDoc, _id: slug })
|
|
70
|
+
console.log("New document: ", newDoc);
|
|
71
|
+
|
|
72
|
+
for await (let [refIndex, ref] of refDocs.entries()) {
|
|
73
|
+
let refString = JSON.stringify(ref);
|
|
74
|
+
refString = refString.replaceAll(id, slug);
|
|
75
|
+
let refObject = JSON.parse(refString);
|
|
76
|
+
|
|
77
|
+
console.log(`[${index}/${typefaceIds.length}][${refIndex}/${refDocs.length}] Updating document: `, ref._id);
|
|
78
|
+
await client
|
|
79
|
+
.patch(ref._id)
|
|
80
|
+
.set(refObject)
|
|
81
|
+
.commit();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Delete the old document
|
|
85
|
+
console.log("Deleting old document: ", id);
|
|
86
|
+
await client.delete(id)
|
|
87
|
+
|
|
88
|
+
console.log("Updated all instances of ", id, " to ", slug);
|
|
89
|
+
|
|
90
|
+
} else {
|
|
91
|
+
console.log('No Slug Found', rootDoc);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
<>
|
|
98
|
+
<DangerModeWarning
|
|
99
|
+
isOpen={showWarningModal}
|
|
100
|
+
onConfirm={handleWarningConfirm}
|
|
101
|
+
onCancel={handleWarningCancel}
|
|
102
|
+
utilityName="Convert IDs to Slug"
|
|
103
|
+
/>
|
|
104
|
+
|
|
105
|
+
<Stack style={{paddingTop: "4em", paddingBottom: "2em", position: "relative"}}>
|
|
106
|
+
<Heading as="h3" size={3}>{dangerMode ? "Convert " : ""}Typeface's Fonts IDs to their own Slugs</Heading>
|
|
107
|
+
<Text muted size={1} style={{paddingTop: "2em", maxWidth: "calc(100% - 100px)"}}>
|
|
108
|
+
Migrate font document IDs from auto-generated IDs to slug-based IDs for cleaner URLs and better content management. Automatically updates all references.
|
|
109
|
+
</Text>
|
|
110
|
+
<Button
|
|
111
|
+
mode={dangerMode?"ghost":"bleed"}
|
|
112
|
+
tone="critical"
|
|
113
|
+
icon={dangerMode?UnlockIcon:LockIcon}
|
|
114
|
+
onClick={handleDangerModeToggle}
|
|
115
|
+
style={{cursor: "pointer", position: "absolute", bottom: "1.5em", right: "0"}}
|
|
116
|
+
/>
|
|
117
|
+
</Stack>
|
|
118
|
+
|
|
119
|
+
{dangerMode && (
|
|
120
|
+
<Stack style={{ position: "relative" }} >
|
|
121
|
+
<Select
|
|
122
|
+
style={{
|
|
123
|
+
borderRadius: "3px",
|
|
124
|
+
}}
|
|
125
|
+
onChange={(event) => { setTargetTypeface(event.currentTarget.value) }}
|
|
126
|
+
value={typefaces ? typefaces[0] : ""}
|
|
127
|
+
>
|
|
128
|
+
{typefaces && typefaces.map((typeface, index) => (
|
|
129
|
+
<option key={`typeface-${index}`} value={typeface.title}>{typeface.title}</option>
|
|
130
|
+
))}
|
|
131
|
+
</Select>
|
|
132
|
+
<p style={{opacity: "0.5"}}>Make sure you publish your updates first!<br/><br/></p>
|
|
133
|
+
<Button
|
|
134
|
+
flex={12}
|
|
135
|
+
tone="critical"
|
|
136
|
+
onClick={updateIdsToSlug}
|
|
137
|
+
text={"Convert"}
|
|
138
|
+
/>
|
|
139
|
+
</Stack>
|
|
140
|
+
)}
|
|
141
|
+
|
|
142
|
+
{convertMessage !== "" && (
|
|
143
|
+
<Stack>
|
|
144
|
+
<p style={{padding: ".5em 0em 1em", opacity: "0.75"}} dangerouslySetInnerHTML={{__html: convertMessage}}></p>
|
|
145
|
+
</Stack>
|
|
146
|
+
)}
|
|
147
|
+
</>
|
|
148
|
+
)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export default ConvertIdsToSlug
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
// Component for converting strong references to weak references
|
|
2
|
+
import { Stack, Grid, Heading, Text, Button, TextInput, Select } from '@sanity/ui'
|
|
3
|
+
import { CollapseIcon, ExpandIcon, LockIcon, UnlockIcon } from '@sanity/icons'
|
|
4
|
+
import { useState, useEffect } from 'react'
|
|
5
|
+
import DangerModeWarning, { shouldShowDangerWarning } from './DangerModeWarning'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Convert to Weak References Component
|
|
9
|
+
* Converts strong references to weak references across documents
|
|
10
|
+
* @param {Object} props - Component props
|
|
11
|
+
* @param {SanityClient} props.client - Sanity client instance
|
|
12
|
+
*/
|
|
13
|
+
const ConvertToWeakReferences = (props) => {
|
|
14
|
+
const { client } = props;
|
|
15
|
+
const [ convertValue, setConvertValue ] = useState('');
|
|
16
|
+
const [ convertible, setConvertible ] = useState([]);
|
|
17
|
+
const [ convertibleMessage, setConvertibleMessage ] = useState('');
|
|
18
|
+
const [ convertType, setConvertType ] = useState('typeface');
|
|
19
|
+
const [ excludeValue, setExcludeValue ] = useState('');
|
|
20
|
+
const [ exclude, setExclude ] = useState(false);
|
|
21
|
+
const [ dangerMode, setDangerMode ] = useState(false);
|
|
22
|
+
const [showWarningModal, setShowWarningModal] = useState(false);
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Handle danger mode toggle with warning modal
|
|
26
|
+
*/
|
|
27
|
+
const handleDangerModeToggle = () => {
|
|
28
|
+
if (!dangerMode && shouldShowDangerWarning()) {
|
|
29
|
+
// Trying to enable danger mode, show warning
|
|
30
|
+
setShowWarningModal(true);
|
|
31
|
+
} else {
|
|
32
|
+
// Either disabling danger mode or warning is suppressed
|
|
33
|
+
setDangerMode(!dangerMode);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const handleWarningConfirm = () => {
|
|
38
|
+
setShowWarningModal(false);
|
|
39
|
+
setDangerMode(true);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const handleWarningCancel = () => {
|
|
43
|
+
setShowWarningModal(false);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
if (!exclude) setExcludeValue("");
|
|
48
|
+
}, [exclude]);
|
|
49
|
+
|
|
50
|
+
async function searchFor(value) {
|
|
51
|
+
const items = await client.fetch(`
|
|
52
|
+
*[
|
|
53
|
+
_type == "${convertType}"
|
|
54
|
+
&& title match "${value}*"
|
|
55
|
+
${excludeValue !== "" ? ` && !(title match "*${excludeValue}*")` : ""}
|
|
56
|
+
]
|
|
57
|
+
`)
|
|
58
|
+
setConvertible(items)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
searchFor(convertValue)
|
|
63
|
+
}, [convertValue, convertType, excludeValue])
|
|
64
|
+
|
|
65
|
+
function convert(){
|
|
66
|
+
setConvertibleMessage('Updating data...');
|
|
67
|
+
client
|
|
68
|
+
.fetch(`
|
|
69
|
+
*[
|
|
70
|
+
_type == "${convertType}"
|
|
71
|
+
&& title match "${convertValue}*"
|
|
72
|
+
${excludeValue !== "" ? ` && !(title match "*${excludeValue}*")` : ""}
|
|
73
|
+
]
|
|
74
|
+
`)
|
|
75
|
+
.then( async (items) => {
|
|
76
|
+
let updateDataCount = 0;
|
|
77
|
+
|
|
78
|
+
// convert items to string
|
|
79
|
+
let itemsString = JSON.stringify(items);
|
|
80
|
+
|
|
81
|
+
// search for all `"_type":"reference"` and replace with `"_type":"reference","_weak":true`
|
|
82
|
+
itemsString = itemsString.replace(/"_type":"reference"/g, '"_type":"reference","_weak":true');
|
|
83
|
+
|
|
84
|
+
// convert string back to object
|
|
85
|
+
let itemsObject = JSON.parse(itemsString);
|
|
86
|
+
|
|
87
|
+
for (const item of itemsObject) {
|
|
88
|
+
try {
|
|
89
|
+
setConvertibleMessage(`Updating: ${item?.title ? item.title : item._id}`);
|
|
90
|
+
client.patch(item._id).set(item).commit()
|
|
91
|
+
} catch (e) {
|
|
92
|
+
console.error(e.message)
|
|
93
|
+
setConvertibleMessage('Error: ' + e.message);
|
|
94
|
+
}
|
|
95
|
+
await new Promise(r => setTimeout(r, 50));
|
|
96
|
+
|
|
97
|
+
updateDataCount++;
|
|
98
|
+
if (updateDataCount == itemsObject.length - 1) {
|
|
99
|
+
setConvertibleMessage('All Updated!');
|
|
100
|
+
setTimeout(()=>{
|
|
101
|
+
setConvertibleMessage("");
|
|
102
|
+
}, 2000)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
})
|
|
106
|
+
.catch( (err)=>{ console.error(err) })
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return (
|
|
110
|
+
<>
|
|
111
|
+
<DangerModeWarning
|
|
112
|
+
isOpen={showWarningModal}
|
|
113
|
+
onConfirm={handleWarningConfirm}
|
|
114
|
+
onCancel={handleWarningCancel}
|
|
115
|
+
utilityName="Convert to Weak References"
|
|
116
|
+
/>
|
|
117
|
+
|
|
118
|
+
<Stack style={{paddingTop: "4em", paddingBottom: "2em", position: "relative"}}>
|
|
119
|
+
<Heading as="h3" size={3}>{dangerMode ? "Convert to " : ""}Weak References</Heading>
|
|
120
|
+
<Text muted size={1} style={{paddingTop: "2em", maxWidth: "calc(100% - 100px)"}}>
|
|
121
|
+
Transform strong document references into weak references across matching documents. Weak references don't prevent deletion and are useful for non-critical relationships.
|
|
122
|
+
</Text>
|
|
123
|
+
<div style={{position: "absolute", bottom: "1.5em", right: "0"}}>
|
|
124
|
+
<Button
|
|
125
|
+
mode={exclude?"ghost":"bleed"}
|
|
126
|
+
tone="positive"
|
|
127
|
+
icon={exclude?CollapseIcon:ExpandIcon}
|
|
128
|
+
onClick={() => { setExclude(!exclude) }}
|
|
129
|
+
style={{cursor: "pointer", marginLeft: ".5em"}}
|
|
130
|
+
/>
|
|
131
|
+
<Button
|
|
132
|
+
mode={dangerMode?"ghost":"bleed"}
|
|
133
|
+
tone="critical"
|
|
134
|
+
icon={dangerMode?UnlockIcon:LockIcon}
|
|
135
|
+
onClick={handleDangerModeToggle}
|
|
136
|
+
style={{cursor: "pointer", marginLeft: ".5em"}}
|
|
137
|
+
/>
|
|
138
|
+
</div>
|
|
139
|
+
</Stack>
|
|
140
|
+
|
|
141
|
+
<Stack style={{ position: "relative" }} >
|
|
142
|
+
<Grid columns={exclude ? [3] : [2]} gap={0}
|
|
143
|
+
style={{
|
|
144
|
+
position: "relative",
|
|
145
|
+
}}
|
|
146
|
+
>
|
|
147
|
+
<TextInput
|
|
148
|
+
style={{
|
|
149
|
+
borderRadius: "3px 0 0 0",
|
|
150
|
+
}}
|
|
151
|
+
onChange={(event) => { setConvertValue(event.currentTarget.value) }}
|
|
152
|
+
placeholder="Name"
|
|
153
|
+
value={convertValue}
|
|
154
|
+
/>
|
|
155
|
+
{!!exclude &&
|
|
156
|
+
<TextInput
|
|
157
|
+
style={{
|
|
158
|
+
display: exclude ? "" : "none",
|
|
159
|
+
}}
|
|
160
|
+
onChange={(event) => { setExcludeValue(event.currentTarget.value) }}
|
|
161
|
+
placeholder="Excluding"
|
|
162
|
+
value={excludeValue}
|
|
163
|
+
/>
|
|
164
|
+
}
|
|
165
|
+
<Select
|
|
166
|
+
style={{
|
|
167
|
+
borderRadius: "0 3px 0 0",
|
|
168
|
+
}}
|
|
169
|
+
onChange={(event) => { setConvertType(event.currentTarget.value) }}
|
|
170
|
+
value={convertType}
|
|
171
|
+
>
|
|
172
|
+
<option value="typeface">Typeface</option>
|
|
173
|
+
<option value="collection">Collection</option>
|
|
174
|
+
<option value="pair">Pair</option>
|
|
175
|
+
<option value="font">Font</option>
|
|
176
|
+
<option value="license">License</option>
|
|
177
|
+
<option value="order">Order</option>
|
|
178
|
+
<option value="account">Account</option>
|
|
179
|
+
<option value="cart">Cart</option>
|
|
180
|
+
<option value="page">Page</option>
|
|
181
|
+
<option value="blogpost">Blogpost</option>
|
|
182
|
+
</Select>
|
|
183
|
+
</Grid>
|
|
184
|
+
</Stack>
|
|
185
|
+
|
|
186
|
+
{ convertibleMessage!="" && (
|
|
187
|
+
<Stack>
|
|
188
|
+
<p style={{padding: ".5em 0em 1em", opacity: "0.75"}} dangerouslySetInnerHTML={{__html: convertibleMessage}}></p>
|
|
189
|
+
</Stack>
|
|
190
|
+
)}
|
|
191
|
+
|
|
192
|
+
{ convertible.length > 0 && (
|
|
193
|
+
<>
|
|
194
|
+
<div
|
|
195
|
+
style={{
|
|
196
|
+
maxHeight: "400px",
|
|
197
|
+
marginTop: "5px",
|
|
198
|
+
border: "1px solid rgba(255,255,255,0.1)",
|
|
199
|
+
overflow: "auto",
|
|
200
|
+
paddingBottom: "1rem",
|
|
201
|
+
borderRadius: "3px",
|
|
202
|
+
}}
|
|
203
|
+
>
|
|
204
|
+
{ convertible.map((item, index) => (
|
|
205
|
+
<a
|
|
206
|
+
target="_blank"
|
|
207
|
+
key={`item-${index}`}
|
|
208
|
+
className="link"
|
|
209
|
+
href={`${window.location.origin}/desk/${(convertType === "typeface" || convertType === "licenseGroup") ? "orderable-" : ""}${convertType};${item._id}`}
|
|
210
|
+
>
|
|
211
|
+
<Stack>
|
|
212
|
+
<Text size={1} style={{padding: "1em 1em .5em"}}>{item.title}</Text>
|
|
213
|
+
</Stack>
|
|
214
|
+
</a>
|
|
215
|
+
))}
|
|
216
|
+
</div>
|
|
217
|
+
<div style={{pointerEvents: "none", textAlign: "right", top: "-30px", paddingRight: "10px", position: "relative", height: "30px"}}>{ convertible.length} items</div>
|
|
218
|
+
|
|
219
|
+
{dangerMode && (
|
|
220
|
+
<Stack>
|
|
221
|
+
<Button text="Convert" tone="critical" onClick={() => { convert() }}/>
|
|
222
|
+
</Stack>
|
|
223
|
+
)}
|
|
224
|
+
</>
|
|
225
|
+
)}
|
|
226
|
+
</>
|
|
227
|
+
)
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export default ConvertToWeakReferences
|