|
1 | 1 | import path from 'path'; |
2 | 2 | import { fileURLToPath } from 'url'; |
3 | 3 |
|
4 | | -import type { CollectionConfig, GenerateImageName } from 'payload'; |
| 4 | +import mimeTypes from 'mime-types'; |
| 5 | +import type { |
| 6 | + CollectionBeforeValidateHook, |
| 7 | + CollectionConfig, |
| 8 | + Condition, |
| 9 | + GenerateImageName, |
| 10 | + TypeWithID |
| 11 | +} from 'payload'; |
5 | 12 |
|
6 | | -import { adminGroups } from '@codeware/app-cms/util/definitions'; |
| 13 | +import { getEnv } from '@codeware/app-cms/feature/env-loader'; |
| 14 | +import { tagsSelectField } from '@codeware/app-cms/ui/fields'; |
| 15 | +import { adminGroups, getMimeTypes } from '@codeware/app-cms/util/definitions'; |
| 16 | +import { Media } from '@codeware/shared/util/payload-types'; |
| 17 | + |
| 18 | +import { externalOrApiKeyAccess } from './access/external-or-api-key-access'; |
7 | 19 |
|
8 | 20 | const filename = fileURLToPath(import.meta.url); |
9 | 21 | const dirname = path.dirname(filename); |
| 22 | +const env = getEnv(); |
10 | 23 |
|
11 | 24 | /** Custom image name */ |
12 | 25 | const imageName: GenerateImageName = ({ extension, originalName, sizeName }) => |
13 | 26 | `${originalName}-${sizeName}.${extension}`; |
14 | 27 |
|
| 28 | +const isImageOrVideo: Condition<TypeWithID, Media> = (_, siblingData) => |
| 29 | + !!siblingData.mimeType && siblingData.mimeType.match(/image|video/) !== null; |
| 30 | + |
| 31 | +// Extracting mime type during seed has a flaky bewhavior. |
| 32 | +// The mime type is not always available when the file is uploaded. |
| 33 | +const ensureMimeType: CollectionBeforeValidateHook<Media> = ({ |
| 34 | + data, |
| 35 | + operation |
| 36 | +}) => { |
| 37 | + if (!data) { |
| 38 | + return data; |
| 39 | + } |
| 40 | + if (data.mimeType) { |
| 41 | + return data; |
| 42 | + } |
| 43 | + if (operation === 'create' || operation === 'update') { |
| 44 | + // Try to lookup the mime type from the filename |
| 45 | + data.mimeType = mimeTypes.lookup(data.filename ?? '') || undefined; |
| 46 | + } |
| 47 | + return data; |
| 48 | +}; |
| 49 | + |
15 | 50 | /** |
16 | | - * Media images collection. |
| 51 | + * Media collection for files with supported mime types. |
| 52 | + * |
| 53 | + * Uploaded images are converted to webp format. |
17 | 54 | * |
18 | | - * Upload limited to `image/*` mime types and images are converted to webp format. |
| 55 | + * **Mime types** |
| 56 | + * |
| 57 | + * `@codeware/app-cms/util/definitions` |
19 | 58 | */ |
20 | 59 | const media: CollectionConfig = { |
21 | 60 | slug: 'media', |
22 | 61 | admin: { |
23 | 62 | group: adminGroups.fileArea, |
24 | | - defaultColumns: ['filename', 'alt', 'tenant', 'updatedAt'], |
25 | | - description: { |
26 | | - en: 'Media images to use in posts and pages.', |
27 | | - sv: 'Bilder som kan användas i inlägg och sidor.' |
| 63 | + defaultColumns: ['filename', 'mimeType', 'fileSize', 'tags', 'createdAt'], |
| 64 | + components: { |
| 65 | + beforeListTable: [ |
| 66 | + { |
| 67 | + path: '@codeware/app-cms/ui/components/Callout', |
| 68 | + serverProps: { |
| 69 | + kind: 'tip', |
| 70 | + title: 'Using tags to organize media files', |
| 71 | + description: [ |
| 72 | + 'Use tags to organize your media files and easily select them in file areas.', |
| 73 | + 'Tags can be created in the "Tags" collection.', |
| 74 | + 'You can assign multiple tags to a media file.' |
| 75 | + ] |
| 76 | + } |
| 77 | + } |
| 78 | + ] |
28 | 79 | } |
29 | 80 | }, |
30 | 81 | access: { |
31 | | - // Media files like images are not fetched, hence no api key to verify. |
32 | | - // For admin access, the plugin appends proper permission filters. |
33 | | - read: () => true |
| 82 | + read: externalOrApiKeyAccess(env.SIGNATURE_SECRET) |
| 83 | + }, |
| 84 | + hooks: { |
| 85 | + beforeValidate: [ensureMimeType] |
34 | 86 | }, |
35 | 87 | labels: { |
36 | 88 | singular: { en: 'Media', sv: 'Media' }, |
37 | 89 | plural: { en: 'Media', sv: 'Media' } |
38 | 90 | }, |
39 | 91 | upload: { |
40 | | - mimeTypes: ['image/*'], |
| 92 | + mimeTypes: getMimeTypes(), |
41 | 93 | // Uploaded image is converted to a backward compatible format known by all browsers. |
42 | 94 | // This image should be used as the default image in a `<picture />` element. |
43 | 95 | formatOptions: { format: 'jpeg' }, |
@@ -102,28 +154,35 @@ const media: CollectionConfig = { |
102 | 154 | type: 'richText', |
103 | 155 | localized: true, |
104 | 156 | admin: { |
| 157 | + condition: isImageOrVideo, |
105 | 158 | description: { |
106 | | - en: 'The caption for the media.', |
107 | | - sv: 'Bildtext för media.' |
| 159 | + en: 'Caption to display below an image or video.', |
| 160 | + sv: 'Text som visas under en bild eller video.' |
108 | 161 | } |
109 | 162 | } |
110 | 163 | }, |
| 164 | + tagsSelectField({ |
| 165 | + buildIndex: true, |
| 166 | + overrides: { |
| 167 | + // TODO: Would like to use 'drawer' but it doesn't work with bulk upload media. |
| 168 | + // Better to be safe and wait for a fix. |
| 169 | + admin: { appearance: 'select' } |
| 170 | + } |
| 171 | + }), |
111 | 172 | { |
112 | | - type: 'tabs', |
113 | | - tabs: [ |
114 | | - { |
115 | | - label: { en: 'Posts', sv: 'Inlägg' }, |
116 | | - fields: [ |
117 | | - { |
118 | | - name: 'relatedPosts', |
119 | | - label: { en: 'Posts', sv: 'Inlägg' }, |
120 | | - type: 'join', |
121 | | - collection: 'posts', |
122 | | - on: 'heroImage' |
123 | | - } |
124 | | - ] |
125 | | - } |
126 | | - ] |
| 173 | + // Media files are not fetched, hence there's no api key to verify. |
| 174 | + // This property will be used as alternative access control for static file requests. |
| 175 | + name: 'external', |
| 176 | + type: 'checkbox', |
| 177 | + label: { en: 'External access', sv: 'Extern åtkomst' }, |
| 178 | + admin: { |
| 179 | + description: { |
| 180 | + en: 'Allow external access to the file without authentication. For example, this is required for file areas and document images.', |
| 181 | + sv: 'Tillåt extern åtkomst till filen utan autentisering. Detta krävs till exempel för filytor och bilder i dokument.' |
| 182 | + }, |
| 183 | + position: 'sidebar' |
| 184 | + }, |
| 185 | + defaultValue: false |
127 | 186 | } |
128 | 187 | ] |
129 | 188 | }; |
|
0 commit comments