@fils/sanity-components 0.0.9 → 0.1.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/lib/components/video/CreateVideoInput.d.ts +8 -0
- package/lib/components/video/CreateVideoInput.js +8 -0
- package/lib/components/video/VideoAndThumb.d.ts +7 -0
- package/lib/components/video/VideoAndThumb.js +101 -0
- package/lib/components/video/VideoSchemas.d.ts +58 -0
- package/lib/components/video/VideoSchemas.js +197 -0
- package/lib/main.d.ts +1 -0
- package/lib/main.js +1 -0
- package/package.json +13 -6
- package/src/components/core/SEO.ts +36 -0
- package/src/components/core/SEOImage.ts +8 -0
- package/src/components/ui/DeployButton.tsx +424 -0
- package/src/components/video/CreateVideoInput.tsx +26 -0
- package/src/components/video/VideoAndThumb.tsx +187 -0
- package/src/components/video/VideoSchemas.ts +205 -0
- package/src/config/utils.ts +86 -0
- package/src/main.ts +6 -0
- package/src/validators/utils.ts +17 -0
- package/tsconfig.json +20 -0
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { LinkIcon, PlayIcon, VideoIcon } from "@sanity/icons";
|
|
2
|
+
import { defineField, defineType } from "sanity";
|
|
3
|
+
import { createVideoInput } from "./CreateVideoInput";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Video File Schema
|
|
7
|
+
* Document type for uploaded video files with thumbnail generation
|
|
8
|
+
*/
|
|
9
|
+
export const VideoFile = defineType({
|
|
10
|
+
name: 'videoFile',
|
|
11
|
+
type: 'document',
|
|
12
|
+
title: 'Video File',
|
|
13
|
+
description: 'Video File + Thumbnail',
|
|
14
|
+
icon: VideoIcon,
|
|
15
|
+
fields: [
|
|
16
|
+
defineField({
|
|
17
|
+
name: 'video',
|
|
18
|
+
type: 'file',
|
|
19
|
+
options: {
|
|
20
|
+
accept: "video/mp4, video/webm"
|
|
21
|
+
},
|
|
22
|
+
icon: PlayIcon,
|
|
23
|
+
description: 'Video File. Supported formats: MP4 & WebM',
|
|
24
|
+
validation: Rule => Rule.required()
|
|
25
|
+
}),
|
|
26
|
+
defineField({
|
|
27
|
+
name: 'image',
|
|
28
|
+
title: 'Video Thumbnail',
|
|
29
|
+
type: 'image',
|
|
30
|
+
validation: Rule => Rule.required()
|
|
31
|
+
})
|
|
32
|
+
],
|
|
33
|
+
components: {
|
|
34
|
+
input: createVideoInput({
|
|
35
|
+
inputType: 'file',
|
|
36
|
+
enableThumbnailGeneration: true
|
|
37
|
+
})
|
|
38
|
+
},
|
|
39
|
+
preview: {
|
|
40
|
+
select: {
|
|
41
|
+
video: 'video',
|
|
42
|
+
media: 'image'
|
|
43
|
+
},
|
|
44
|
+
prepare(selected) {
|
|
45
|
+
const { video, media } = selected;
|
|
46
|
+
|
|
47
|
+
// Just use the original filename from the asset if available
|
|
48
|
+
const filename = video?.asset?._ref
|
|
49
|
+
? video.asset._ref.split('-').slice(1, -1).join('-')
|
|
50
|
+
: 'No video';
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
title: filename,
|
|
54
|
+
media
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Video URL Schema
|
|
62
|
+
* Document type for external video URLs (e.g., Vimeo, YouTube)
|
|
63
|
+
*/
|
|
64
|
+
export const VideoURL = defineType({
|
|
65
|
+
name: 'videoUrl',
|
|
66
|
+
type: 'document',
|
|
67
|
+
title: 'Video URL',
|
|
68
|
+
description: 'External video URL + Thumbnail',
|
|
69
|
+
icon: VideoIcon,
|
|
70
|
+
fields: [
|
|
71
|
+
defineField({
|
|
72
|
+
name: 'video',
|
|
73
|
+
type: 'url',
|
|
74
|
+
icon: LinkIcon,
|
|
75
|
+
description: 'Video URL (e.g., Vimeo, YouTube)',
|
|
76
|
+
validation: Rule => Rule.required().uri({
|
|
77
|
+
scheme: ['http', 'https']
|
|
78
|
+
})
|
|
79
|
+
}),
|
|
80
|
+
defineField({
|
|
81
|
+
name: 'image',
|
|
82
|
+
title: 'Video Thumbnail',
|
|
83
|
+
type: 'image',
|
|
84
|
+
validation: Rule => Rule.required()
|
|
85
|
+
})
|
|
86
|
+
],
|
|
87
|
+
components: {
|
|
88
|
+
input: createVideoInput({
|
|
89
|
+
inputType: 'url',
|
|
90
|
+
enableThumbnailGeneration: false // Can't generate from external URLs
|
|
91
|
+
})
|
|
92
|
+
},
|
|
93
|
+
preview: {
|
|
94
|
+
select: {
|
|
95
|
+
video: 'video',
|
|
96
|
+
media: 'image'
|
|
97
|
+
},
|
|
98
|
+
prepare(selected) {
|
|
99
|
+
const { video, media } = selected;
|
|
100
|
+
return {
|
|
101
|
+
title: video || 'No URL',
|
|
102
|
+
media
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Video File Object Schema
|
|
110
|
+
* Object type for use as a field in other schemas
|
|
111
|
+
*/
|
|
112
|
+
export const VideoFileObject = defineType({
|
|
113
|
+
name: 'videoFileObject',
|
|
114
|
+
type: 'object',
|
|
115
|
+
title: 'Video with Thumbnail',
|
|
116
|
+
fields: [
|
|
117
|
+
defineField({
|
|
118
|
+
name: 'video',
|
|
119
|
+
type: 'file',
|
|
120
|
+
options: {
|
|
121
|
+
accept: "video/mp4, video/webm"
|
|
122
|
+
}
|
|
123
|
+
}),
|
|
124
|
+
defineField({
|
|
125
|
+
name: 'image',
|
|
126
|
+
title: 'Thumbnail',
|
|
127
|
+
type: 'image'
|
|
128
|
+
})
|
|
129
|
+
],
|
|
130
|
+
components: {
|
|
131
|
+
input: createVideoInput({
|
|
132
|
+
inputType: 'file',
|
|
133
|
+
enableThumbnailGeneration: true
|
|
134
|
+
})
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Video URL Object Schema
|
|
140
|
+
* Object type for use as a field in other schemas (URL version)
|
|
141
|
+
*/
|
|
142
|
+
export const VideoURLObject = defineType({
|
|
143
|
+
name: 'videoUrlObject',
|
|
144
|
+
type: 'object',
|
|
145
|
+
title: 'Video URL with Thumbnail',
|
|
146
|
+
fields: [
|
|
147
|
+
defineField({
|
|
148
|
+
name: 'video',
|
|
149
|
+
type: 'url'
|
|
150
|
+
}),
|
|
151
|
+
defineField({
|
|
152
|
+
name: 'image',
|
|
153
|
+
title: 'Thumbnail',
|
|
154
|
+
type: 'image'
|
|
155
|
+
})
|
|
156
|
+
],
|
|
157
|
+
components: {
|
|
158
|
+
input: createVideoInput({
|
|
159
|
+
inputType: 'url',
|
|
160
|
+
enableThumbnailGeneration: false
|
|
161
|
+
})
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
export const VideoFileNoThumb = defineType({
|
|
166
|
+
name: 'videoFileNoThumb',
|
|
167
|
+
type: 'object',
|
|
168
|
+
title: 'Video File',
|
|
169
|
+
fields: [
|
|
170
|
+
defineField({
|
|
171
|
+
name: 'video',
|
|
172
|
+
type: 'file',
|
|
173
|
+
options: {
|
|
174
|
+
accept: 'video/mp4, video/webm'
|
|
175
|
+
}
|
|
176
|
+
})
|
|
177
|
+
],
|
|
178
|
+
components: {
|
|
179
|
+
input: createVideoInput({
|
|
180
|
+
inputType: 'file',
|
|
181
|
+
enableThumbnailGeneration: false
|
|
182
|
+
})
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
export const VideoURLNoThumb = defineType({
|
|
187
|
+
name: 'videoFileNoThumb',
|
|
188
|
+
type: 'object',
|
|
189
|
+
title: 'Video URL',
|
|
190
|
+
fields: [
|
|
191
|
+
defineField({
|
|
192
|
+
name: 'video',
|
|
193
|
+
type: 'file',
|
|
194
|
+
options: {
|
|
195
|
+
accept: 'video/mp4, video/webm'
|
|
196
|
+
}
|
|
197
|
+
})
|
|
198
|
+
],
|
|
199
|
+
components: {
|
|
200
|
+
input: createVideoInput({
|
|
201
|
+
inputType: 'url',
|
|
202
|
+
enableThumbnailGeneration: false
|
|
203
|
+
})
|
|
204
|
+
}
|
|
205
|
+
});
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { defineConfig, DocumentActionComponent, DocumentActionsContext } from "sanity"
|
|
2
|
+
import {StructureResolver, structureTool} from 'sanity/structure'
|
|
3
|
+
import { netlifyWidget, SiteWidgetOption } from "sanity-plugin-dashboard-widget-netlify";
|
|
4
|
+
import { dashboardTool } from '@sanity/dashboard';
|
|
5
|
+
|
|
6
|
+
// Define the actions that should be available for singleton documents
|
|
7
|
+
const singletonActions = new Set(["publish", "discardChanges", "restore"])
|
|
8
|
+
|
|
9
|
+
export function getActions(singletonTypes:Set<string>) {
|
|
10
|
+
return (input: DocumentActionComponent[], context: DocumentActionsContext) =>
|
|
11
|
+
singletonTypes.has(context.schemaType)
|
|
12
|
+
? input.filter(({ action }) => action && singletonActions.has(action))
|
|
13
|
+
: input;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function getDefaultConfig(projectId:string, title:string, structure:StructureResolver, schemaTypes:any, singletonTypes:Set<string>) {
|
|
17
|
+
return defineConfig({
|
|
18
|
+
name: 'default',
|
|
19
|
+
title,
|
|
20
|
+
|
|
21
|
+
projectId,
|
|
22
|
+
dataset: 'production',
|
|
23
|
+
|
|
24
|
+
scheduledPublishing: {
|
|
25
|
+
enabled: false
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
plugins: [structureTool({
|
|
29
|
+
structure
|
|
30
|
+
})],
|
|
31
|
+
|
|
32
|
+
schema: {
|
|
33
|
+
types: schemaTypes,
|
|
34
|
+
// Filter out singleton types from the global “New document” menu options
|
|
35
|
+
templates: (templates) =>
|
|
36
|
+
templates.filter(({ schemaType }) => !singletonTypes.has(schemaType)),
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
document: {
|
|
40
|
+
// For singleton types, filter out actions that are not explicitly included
|
|
41
|
+
// in the `singletonActions` list defined above
|
|
42
|
+
actions: getActions(singletonTypes),
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function getConfigWithNetlify(projectId:string, title:string, structure:StructureResolver, schemaTypes:any, singletonTypes:Set<string>, sites:SiteWidgetOption[]) {
|
|
48
|
+
return defineConfig({
|
|
49
|
+
name: 'default',
|
|
50
|
+
title,
|
|
51
|
+
|
|
52
|
+
projectId,
|
|
53
|
+
dataset: 'production',
|
|
54
|
+
|
|
55
|
+
scheduledPublishing: {
|
|
56
|
+
enabled: false
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
plugins: [
|
|
60
|
+
structureTool({
|
|
61
|
+
structure
|
|
62
|
+
}),
|
|
63
|
+
dashboardTool({
|
|
64
|
+
widgets: [
|
|
65
|
+
netlifyWidget({
|
|
66
|
+
title: 'Deploy Site',
|
|
67
|
+
sites
|
|
68
|
+
})
|
|
69
|
+
]
|
|
70
|
+
})
|
|
71
|
+
],
|
|
72
|
+
|
|
73
|
+
schema: {
|
|
74
|
+
types: schemaTypes,
|
|
75
|
+
// Filter out singleton types from the global “New document” menu options
|
|
76
|
+
templates: (templates) =>
|
|
77
|
+
templates.filter(({ schemaType }) => !singletonTypes.has(schemaType)),
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
document: {
|
|
81
|
+
// For singleton types, filter out actions that are not explicitly included
|
|
82
|
+
// in the `singletonActions` list defined above
|
|
83
|
+
actions: getActions(singletonTypes),
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
}
|
package/src/main.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Rule } from "sanity";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Deprecated use Sanity's Rule.uri instead!!
|
|
5
|
+
* @returns regexp url validator
|
|
6
|
+
*/
|
|
7
|
+
export function urlValidation() {
|
|
8
|
+
return (rule:Rule) =>
|
|
9
|
+
rule.custom((url:string) => {
|
|
10
|
+
if (typeof url === 'undefined' || url === null) {
|
|
11
|
+
return true; // Allow undefined values
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const regex = /^https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)$/;
|
|
15
|
+
return regex.test(url) ? true : 'Not a valid URL';
|
|
16
|
+
});
|
|
17
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"lib": ["DOM", "DOM.Iterable", "ESNext"],
|
|
5
|
+
"module": "ESNext",
|
|
6
|
+
"declaration": true,
|
|
7
|
+
"outDir": "./lib",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
"resolveJsonModule": true,
|
|
11
|
+
"moduleResolution": "node",
|
|
12
|
+
"jsx": "react-jsx"
|
|
13
|
+
},
|
|
14
|
+
"include": [
|
|
15
|
+
"src",
|
|
16
|
+
],
|
|
17
|
+
"exclude": [
|
|
18
|
+
"node_modules"
|
|
19
|
+
]
|
|
20
|
+
}
|