@lightnet/sveltia-admin 4.0.15 → 4.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/CHANGELOG.md CHANGED
@@ -1,5 +1,45 @@
1
1
  # @lightnet/sveltia-admin
2
2
 
3
+ ## 4.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#421](https://github.com/LightNetDev/LightNet/pull/421) [`9935fa9`](https://github.com/LightNetDev/LightNet/commit/9935fa94b22a4f70d09ac7118ee9abc1c63bfad3) - Automatically generate new entry slugs from entry name or title.
8
+
9
+ - [#421](https://github.com/LightNetDev/LightNet/pull/421) [`9935fa9`](https://github.com/LightNetDev/LightNet/commit/9935fa94b22a4f70d09ac7118ee9abc1c63bfad3) - Improve edit button handling
10
+
11
+ - header icon menu will now show admin button
12
+ - details page will show edit button for all admin ui paths
13
+
14
+ - [#421](https://github.com/LightNetDev/LightNet/pull/421) [`9935fa9`](https://github.com/LightNetDev/LightNet/commit/9935fa94b22a4f70d09ac7118ee9abc1c63bfad3) - Support gif uploads through admin ui.
15
+
16
+ - [#421](https://github.com/LightNetDev/LightNet/pull/421) [`9935fa9`](https://github.com/LightNetDev/LightNet/commit/9935fa94b22a4f70d09ac7118ee9abc1c63bfad3) - Add experimental options to hide/show admin UI collections and fields:
17
+
18
+ Hiding:
19
+
20
+ - `experimental.hideCategoriesCollection` (default: `false`): Hide `categories` collection.
21
+ - `experimental.hideMediaTypesCollection` (default: `false`): Hide `media-types` collection.
22
+ - `experimental.hideMediaCollectionsCollection` (default: `false`): Hide `media-collections` collection.
23
+ - `experimental.hideAuthorsField` (default: `false`): Hide authors field in the media item editor.
24
+ - `experimental.hideCategoriesField` (default: `false`): Hide categories field in the media item editor.
25
+
26
+ Showing:
27
+
28
+ - `experimental.showContentLabelField` (default: `true`): Show `content[].label` field in the media item editor.
29
+ - `experimental.showDateCreatedField` (default: `true`): Show `dateCreated` field in the media item editor.
30
+ - `experimental.showCommonIdField` (default: `true`): Show `commonId` field in the media item editor.
31
+ - `experimental.showSlugField` (default: `true`): Show `slug` field editor for new collection entries.
32
+
33
+ ### Patch Changes
34
+
35
+ - [#421](https://github.com/LightNetDev/LightNet/pull/421) [`9935fa9`](https://github.com/LightNetDev/LightNet/commit/9935fa94b22a4f70d09ac7118ee9abc1c63bfad3) - Update dependencies.
36
+
37
+ - [#421](https://github.com/LightNetDev/LightNet/pull/421) [`9935fa9`](https://github.com/LightNetDev/LightNet/commit/9935fa94b22a4f70d09ac7118ee9abc1c63bfad3) - Reduce max image dimension to 1024px for sveltia image uploads.
38
+
39
+ - [#421](https://github.com/LightNetDev/LightNet/pull/421) [`9935fa9`](https://github.com/LightNetDev/LightNet/commit/9935fa94b22a4f70d09ac7118ee9abc1c63bfad3) - Disable image transforms for content file uploads.
40
+
41
+ - [#421](https://github.com/LightNetDev/LightNet/pull/421) [`9935fa9`](https://github.com/LightNetDev/LightNet/commit/9935fa94b22a4f70d09ac7118ee9abc1c63bfad3) - Improve hints and descriptions in Sveltia based Admin UI.
42
+
3
43
  ## 4.0.15
4
44
 
5
45
  ### Patch Changes
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "Admin UI for LightNet based on Sveltia CMS.",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
- "version": "4.0.15",
6
+ "version": "4.1.0",
7
7
  "publishConfig": {
8
8
  "access": "public"
9
9
  },
@@ -34,15 +34,15 @@
34
34
  "lightnet": "^4.0.0"
35
35
  },
36
36
  "dependencies": {
37
- "@sveltia/cms": "0.167.3"
37
+ "@sveltia/cms": "0.169.0"
38
38
  },
39
39
  "devDependencies": {
40
- "@playwright/test": "^1.61.0",
40
+ "@playwright/test": "^1.61.1",
41
41
  "astro": "^6.4.8",
42
42
  "typescript": "^6.0.3",
43
43
  "vitest": "^4.1.9",
44
44
  "@internal/e2e-test-utils": "^0.0.1",
45
- "lightnet": "^4.5.0"
45
+ "lightnet": "^4.5.1"
46
46
  },
47
47
  "engines": {
48
48
  "node": ">=22"
package/src/Admin.astro CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  import config from "virtual:lightnet/sveltiaAdminConfig"
3
3
 
4
- import { pathWithBase } from "./utils/paths"
4
+ import { pathWithBase } from "./utils/path-with-base"
5
5
  ---
6
6
 
7
7
  <!doctype html>
@@ -20,6 +20,15 @@ import { pathWithBase } from "./utils/paths"
20
20
  <script>
21
21
  import { init } from "@sveltia/cms"
22
22
  init({})
23
+ window.localStorage.setItem(
24
+ "ln.adminPaths",
25
+ JSON.stringify({
26
+ rootPath: window.location.pathname,
27
+ mediaItemPath:
28
+ window.location.pathname +
29
+ "#/collections/media/entries/{{mediaId}}",
30
+ }),
31
+ )
23
32
  </script>
24
33
  </body>
25
34
  </html>
@@ -113,12 +113,79 @@ export const adminConfigSchema = z.object({
113
113
  * migration path.
114
114
  */
115
115
  useLanguagesCollection: z.boolean().default(false),
116
+ /**
117
+ * Hide `categories` collection.
118
+ *
119
+ * @default false
120
+ */
121
+ hideCategoriesCollection: z.boolean().default(false),
122
+ /**
123
+ * Hide `media-collections` collection.
124
+ *
125
+ * @default false
126
+ */
127
+ hideMediaCollectionsCollection: z.boolean().default(false),
128
+ /**
129
+ * Hide `media-types` collection.
130
+ *
131
+ * @default false
132
+ */
133
+ hideMediaTypesCollection: z.boolean().default(false),
134
+ /**
135
+ * Hide `authors` field in the media item editor.
136
+ *
137
+ * @default false
138
+ */
139
+ hideAuthorsField: z.boolean().default(false),
140
+ /**
141
+ * Hide `categories` field in the media item editor.
142
+ *
143
+ * @default false
144
+ */
145
+ hideCategoriesField: z.boolean().default(false),
146
+ /**
147
+ * Show `content[].label` field in the media item editor.
148
+ *
149
+ * @default true
150
+ */
151
+ showContentLabelField: z.boolean().default(true),
152
+ /**
153
+ * Show `dateCreated` field in the media item editor.
154
+ *
155
+ * @default true
156
+ */
157
+ showDateCreatedField: z.boolean().default(true),
158
+ /**
159
+ * Show `commonI`d field in the media item editor.
160
+ *
161
+ * @default true
162
+ */
163
+ showCommonIdField: z.boolean().default(true),
164
+ /**
165
+ * Show `slug` field editor for new collection entries.
166
+ *
167
+ * @default true
168
+ */
169
+ showSlugField: z.boolean().default(true),
116
170
  /**
117
171
  * Use Cloudflare r2 for content uploads.
118
172
  */
119
173
  fileStorage: r2Storage.optional(),
120
174
  })
121
- .optional(),
175
+ // we need to provide defaults because we cannot short circuit flags that default to `true`.
176
+ // experimental object can be made optional when we move flags out or change all defaults to `false`.
177
+ .default({
178
+ useLanguagesCollection: false,
179
+ hideCategoriesCollection: false,
180
+ hideMediaCollectionsCollection: false,
181
+ hideMediaTypesCollection: false,
182
+ hideAuthorsField: false,
183
+ hideCategoriesField: false,
184
+ showCommonIdField: true,
185
+ showContentLabelField: true,
186
+ showDateCreatedField: true,
187
+ showSlugField: true,
188
+ }),
122
189
  })
123
190
 
124
191
  export type SveltiaAdminConfig = z.input<typeof adminConfigSchema>
@@ -1,5 +1,3 @@
1
- import { fileURLToPath } from "node:url"
2
-
3
1
  import type { AstroIntegration, ViteUserConfig } from "astro"
4
2
  import { AstroError } from "astro/errors"
5
3
 
@@ -56,14 +54,7 @@ export default function lightnetSveltiaAdmin(
56
54
  }
57
55
 
58
56
  const CONFIG = "virtual:lightnet/sveltiaAdminConfig"
59
- const MEDIA_ITEM_EDIT_BUTTON_CONTROLLER =
60
- "virtual:lightnet/components/media-item-edit-button-controller"
61
- const MEDIA_ITEM_EDIT_BUTTON_CONTROLLER_PATH = JSON.stringify(
62
- fileURLToPath(
63
- new URL("./media-item-edit-button-controller.ts", import.meta.url),
64
- ),
65
- )
66
- const VIRTUAL_MODULES = [CONFIG, MEDIA_ITEM_EDIT_BUTTON_CONTROLLER] as const
57
+ const VIRTUAL_MODULES = [CONFIG] as const
67
58
 
68
59
  function vitePluginSveltiaAdminConfig(
69
60
  userConfig: ExtendedSveltiaAdminConfig,
@@ -80,10 +71,6 @@ function vitePluginSveltiaAdminConfig(
80
71
  switch (module) {
81
72
  case CONFIG:
82
73
  return `export default ${JSON.stringify(userConfig)};`
83
- case MEDIA_ITEM_EDIT_BUTTON_CONTROLLER:
84
- return userConfig.path === "admin"
85
- ? `export { default } from ${MEDIA_ITEM_EDIT_BUTTON_CONTROLLER_PATH};`
86
- : "export default undefined;"
87
74
  }
88
75
  },
89
76
  }
@@ -0,0 +1,35 @@
1
+ import type { Collection } from "@sveltia/cms"
2
+ import config from "virtual:lightnet/config"
3
+ import adminConfig from "virtual:lightnet/sveltiaAdminConfig"
4
+
5
+ import { projectPath } from "../utils/paths"
6
+ import { inlineTranslation } from "./fields/inline-translation"
7
+
8
+ export const categoriesCollection: Collection = {
9
+ name: "categories",
10
+ label: "Categories",
11
+ description:
12
+ "Use categories to group related media items by topic. Examples: discipleship, youth, prayer. [Read documentation](https://docs.lightnet.community/content/categories/)",
13
+ label_singular: "Category",
14
+ folder: projectPath("src/content/categories"),
15
+ create: true,
16
+ hide: adminConfig.experimental.hideCategoriesCollection,
17
+ format: "json",
18
+ slug: adminConfig.experimental.showSlugField
19
+ ? "{{fields._slug}}"
20
+ : `{{label.${config.defaultLocale}}}`,
21
+ summary: `{{label.${config.defaultLocale}}}`,
22
+ fields: [
23
+ inlineTranslation({ name: "label", label: "Name" }),
24
+ {
25
+ name: "image",
26
+ label: "Image",
27
+ required: false,
28
+ choose_url: false,
29
+ widget: "image",
30
+ media_folder: "./images",
31
+ accept: "image/png, image/jpeg, image/webp, image/gif",
32
+ hint: "Upload an image for this category. A square image works best. LightNet may resize it and change the file format.",
33
+ },
34
+ ],
35
+ }
@@ -1,15 +1,20 @@
1
1
  import type { CollectionFile } from "@sveltia/cms"
2
2
  import config from "virtual:lightnet/config"
3
- import sveltiaAdminConfig from "virtual:lightnet/sveltiaAdminConfig"
3
+ import adminConfig from "virtual:lightnet/sveltiaAdminConfig"
4
4
 
5
- import { inlineTranslation } from "../../utils/inline-translation"
6
- import { projectPath } from "../../utils/path"
5
+ import { projectPath } from "../utils/paths"
6
+ import { inlineTranslation } from "./fields/inline-translation"
7
7
 
8
8
  export const languagesSelect = () => {
9
- if (sveltiaAdminConfig.experimental?.useLanguagesCollection) {
9
+ const commonProperties = {
10
+ name: "language",
11
+ label: "Content Language",
12
+ hint: "Choose the content language for this item. It should match the content you add.",
13
+ }
14
+
15
+ if (adminConfig.experimental.useLanguagesCollection) {
10
16
  return {
11
- name: "language",
12
- label: "Language",
17
+ ...commonProperties,
13
18
  widget: "relation",
14
19
  collection: "_singletons",
15
20
  file: "languages",
@@ -20,8 +25,7 @@ export const languagesSelect = () => {
20
25
  }
21
26
  } else {
22
27
  return {
23
- name: "language",
24
- label: "Language",
28
+ ...commonProperties,
25
29
  widget: "select",
26
30
  options: config.languages.map(({ code, label }) => {
27
31
  return {
@@ -34,7 +38,7 @@ export const languagesSelect = () => {
34
38
  }
35
39
 
36
40
  export const defineLanguagesCollection = () => {
37
- if (!sveltiaAdminConfig.experimental?.useLanguagesCollection) {
41
+ if (!adminConfig.experimental.useLanguagesCollection) {
38
42
  return
39
43
  }
40
44
  return languagesCollection
@@ -1,31 +1,36 @@
1
1
  import type { Collection } from "@sveltia/cms"
2
2
  import config from "virtual:lightnet/config"
3
+ import adminConfig from "virtual:lightnet/sveltiaAdminConfig"
3
4
 
4
- import { inlineTranslation } from "../../utils/inline-translation"
5
- import { projectPath } from "../../utils/path"
5
+ import { projectPath } from "../utils/paths"
6
+ import { inlineTranslation } from "./fields/inline-translation"
6
7
 
7
8
  export const mediaCollectionCollection: Collection = {
8
9
  name: "media-collections",
9
10
  label: "Media Collections",
10
11
  description:
11
- "Group related media items in a specific order. Examples: a course, a series, a study path. [Read documentation](https://docs.lightnet.community/content/media-collections/)",
12
+ "Use media collections to group related media items in order. Examples: a course, a series, a study path. [Read documentation](https://docs.lightnet.community/content/media-collections/)",
12
13
  label_singular: "Media Collection",
13
14
  create: true,
15
+ hide: adminConfig.experimental.hideMediaCollectionsCollection,
14
16
  folder: projectPath("src/content/media-collections"),
15
17
  format: "json",
16
- slug: "{{fields._slug}}",
17
- summary: `{{label.${config.defaultLocale}}} ({{slug}})`,
18
+ slug: adminConfig.experimental.showSlugField
19
+ ? "{{fields._slug}}"
20
+ : `{{label.${config.defaultLocale}}}`,
21
+ summary: `{{label.${config.defaultLocale}}}`,
18
22
  fields: [
19
23
  inlineTranslation({
20
24
  name: "label",
21
25
  label: "Name",
26
+ hint: "Enter the name people should see for this collection.",
22
27
  }),
23
28
  {
24
29
  name: "mediaItems",
25
30
  label: "Media Items",
26
31
  label_singular: "Media Item",
27
32
  widget: "list",
28
- hint: "The list order defines the item order in this collection.",
33
+ hint: "Add the media items in the order people should see them.",
29
34
  summary: "{{fields.mediaItem}}",
30
35
  collapsed: true,
31
36
  field: {
@@ -33,7 +38,6 @@ export const mediaCollectionCollection: Collection = {
33
38
  label: "Media Item",
34
39
  widget: "relation",
35
40
  collection: "media",
36
- value_field: "{{slug}}",
37
41
  display_fields: ["{{title}} ({{slug}})"],
38
42
  search_fields: ["{{title}}", "{{slug}}"],
39
43
  dropdown_threshold: 1,
@@ -0,0 +1,203 @@
1
+ import type { Collection } from "@sveltia/cms"
2
+ import config from "virtual:lightnet/config"
3
+ import adminConfig from "virtual:lightnet/sveltiaAdminConfig"
4
+
5
+ import { isDefined } from "../utils/is-defined"
6
+ import { projectPath } from "../utils/paths"
7
+ import { inlineTranslation } from "./fields/inline-translation"
8
+ import { languagesSelect } from "./languages"
9
+
10
+ export const mediaItemCollection: Collection = {
11
+ name: "media",
12
+ label: "Media Items",
13
+ description:
14
+ "Use media items to add resources people can open or download. Examples: a book PDF, a YouTube link. [Read documentation](https://docs.lightnet.community/content/media-items/)",
15
+ label_singular: "Media Item",
16
+ folder: projectPath("src/content/media"),
17
+ create: true,
18
+ preview_path: `${config.defaultLocale}/media/{{filename}}`,
19
+ format: "json",
20
+ sortable_fields: ["title", "dateCreated", "language"],
21
+ slug: adminConfig.experimental.showSlugField
22
+ ? "{{fields._slug}}"
23
+ : "{{title}}-{{language}}",
24
+ view_groups: [
25
+ { label: "Language", field: "language", pattern: ".*" },
26
+ { label: "Type", field: "type", pattern: ".*" },
27
+ ],
28
+ fields: [
29
+ {
30
+ name: "title",
31
+ label: "Title",
32
+ widget: "string",
33
+ hint: "Enter the title in the content language.",
34
+ },
35
+ {
36
+ name: "type",
37
+ label: "Media Type",
38
+ widget: "relation",
39
+ hint: "Choose the kind of media this content represents.",
40
+ collection: "media-types",
41
+ display_fields: [`{{label.${config.defaultLocale}}} ({{slug}})`],
42
+ },
43
+ languagesSelect(),
44
+ {
45
+ name: "image",
46
+ label: "Image",
47
+ widget: "image",
48
+ choose_url: false,
49
+ media_folder: "./images",
50
+ accept: "image/png, image/jpeg, image/webp, image/gif",
51
+ hint: "Upload a cover image for this item. LightNet may resize it and change the file format.",
52
+ },
53
+ {
54
+ name: "content",
55
+ label: "Content",
56
+ widget: "list",
57
+ hint: "Add one or more files or links. The first item becomes the main resource people will open or download.",
58
+ min: 1,
59
+ summary: "{{types.url}}",
60
+ types: [
61
+ {
62
+ name: "upload",
63
+ label: "File Upload",
64
+ fields: [
65
+ {
66
+ name: "url",
67
+ label: "File",
68
+ widget: "file",
69
+ choose_url: false,
70
+ ...getFileStorage(),
71
+ },
72
+ adminConfig.experimental.showContentLabelField &&
73
+ inlineTranslation({
74
+ name: "label",
75
+ label: "Visible Name",
76
+ hint: "Optional. Add a clearer label if you do not want to show the file name.",
77
+ required: false,
78
+ collapsed: "auto",
79
+ }),
80
+ ].filter(isDefined),
81
+ },
82
+ {
83
+ name: "link",
84
+ label: "Link",
85
+ summary: "{{url}}",
86
+ fields: [
87
+ {
88
+ name: "url",
89
+ label: "Url",
90
+ widget: "string",
91
+ type: "url",
92
+ pattern: ["^https?://", "Link must start with http(s)://"],
93
+ },
94
+ adminConfig.experimental.showContentLabelField &&
95
+ inlineTranslation({
96
+ name: "label",
97
+ label: "Visible Name",
98
+ required: false,
99
+ hint: "Optional. Add a clearer label if you do not want to show the website name.",
100
+ collapsed: "auto",
101
+ }),
102
+ ].filter(isDefined),
103
+ },
104
+ ],
105
+ },
106
+ adminConfig.experimental.showDateCreatedField
107
+ ? {
108
+ name: "dateCreated",
109
+ label: "Created On",
110
+ widget: "datetime",
111
+ time_format: false,
112
+ required: true,
113
+ default: "{{now}}",
114
+ picker_utc: true,
115
+ hint: "Choose when this item was added to the media library.",
116
+ }
117
+ : {
118
+ name: "dateCreated",
119
+ widget: "hidden",
120
+ default: "{{datetime | date('YYYY-MM-DD')}}",
121
+ },
122
+ !adminConfig.experimental.hideAuthorsField && {
123
+ name: "authors",
124
+ label: "Authors",
125
+ label_singular: "Author",
126
+ required: false,
127
+ collapsed: true,
128
+ default: [],
129
+ widget: "list",
130
+ summary: "{{fields.name}}",
131
+ hint: "Add the author names in the content language.",
132
+ field: { label: "Name", name: "name", widget: "string" },
133
+ },
134
+ adminConfig.experimental.showCommonIdField && {
135
+ name: "commonId",
136
+ label: "Translation Group (Common ID)",
137
+ widget: "string",
138
+ required: false,
139
+ hint: "Optional. Use the same value for matching items in different languages so LightNet can treat them as translations of each other.",
140
+ },
141
+ !adminConfig.experimental.hideCategoriesField && {
142
+ name: "categories",
143
+ label: "Categories",
144
+ required: false,
145
+ widget: "relation",
146
+ multiple: true,
147
+ collection: "categories",
148
+ display_fields: [`{{label.${config.defaultLocale}}}`],
149
+ },
150
+ {
151
+ name: "description",
152
+ label: "Description",
153
+ widget: "markdown",
154
+ hint: "Write a short description in the content language.",
155
+ required: false,
156
+ editor_components: [],
157
+ buttons: [
158
+ "heading-two",
159
+ "heading-three",
160
+ "heading-four",
161
+ "heading-five",
162
+ "heading-six",
163
+ "bold",
164
+ "italic",
165
+ "bulleted-list",
166
+ "numbered-list",
167
+ "quote",
168
+ "link",
169
+ ],
170
+ },
171
+ ].filter(isDefined),
172
+ }
173
+
174
+ function getFileStorage() {
175
+ const externalFileStorage = adminConfig.experimental.fileStorage
176
+ if (!externalFileStorage) {
177
+ return {
178
+ media_folder: projectPath("public/files"),
179
+ public_folder: "/files",
180
+ hint: `Upload a file up to ${adminConfig.maxFileSize} MB.`,
181
+ media_libraries: {
182
+ default: {
183
+ config: {
184
+ max_file_size: adminConfig.maxFileSize * 1_000_000,
185
+ transformations: {},
186
+ },
187
+ },
188
+ },
189
+ }
190
+ }
191
+
192
+ return {
193
+ media_libraries: {
194
+ default: false,
195
+ cloudflare_r2: {
196
+ ...externalFileStorage,
197
+ access_key_id: externalFileStorage.accessKeyId,
198
+ account_id: externalFileStorage.accountId,
199
+ public_url: externalFileStorage.publicUrl,
200
+ },
201
+ },
202
+ }
203
+ }
@@ -1,19 +1,23 @@
1
1
  import type { Collection } from "@sveltia/cms"
2
2
  import config from "virtual:lightnet/config"
3
+ import adminConfig from "virtual:lightnet/sveltiaAdminConfig"
3
4
 
4
- import { inlineTranslation } from "../../utils/inline-translation"
5
- import { projectPath } from "../../utils/path"
5
+ import { projectPath } from "../utils/paths"
6
+ import { inlineTranslation } from "./fields/inline-translation"
6
7
 
7
8
  export const mediaTypeCollection: Collection = {
8
9
  name: "media-types",
9
10
  label: "Media Types",
10
11
  description:
11
- "Define different content formats. Examples: books, videos, audio. [Read documentation](https://docs.lightnet.community/content/media-types/)",
12
+ "Use media types to organize media items by format. Examples: books, videos, audio. [Read documentation](https://docs.lightnet.community/content/media-types/)",
12
13
  label_singular: "Media Type",
13
14
  folder: projectPath("src/content/media-types"),
14
15
  format: "json",
15
- slug: "{{fields._slug}}",
16
- summary: `{{label.${config.defaultLocale}}} ({{slug}})`,
16
+ hide: adminConfig.experimental.hideMediaTypesCollection,
17
+ slug: adminConfig.experimental.showSlugField
18
+ ? "{{fields._slug}}"
19
+ : `{{label.${config.defaultLocale}}}`,
20
+ summary: `{{label.${config.defaultLocale}}}`,
17
21
  fields: [
18
22
  inlineTranslation({ name: "label", label: "Name" }),
19
23
  {
@@ -24,7 +28,7 @@ export const mediaTypeCollection: Collection = {
24
28
  "Icon name must start with mdi-- or lucide--",
25
29
  ],
26
30
  widget: "string",
27
- hint: "Browse Lucide icons at https://lucide.dev/icons/ and enter the icon name with the 'lucide--' prefix, for example 'lucide--book-open'.",
31
+ hint: "Enter an Lucide icon name such as 'lucide--book-open'. Browse Lucide icons at https://lucide.dev/icons/.",
28
32
  },
29
33
  {
30
34
  name: "coverImageStyle",
@@ -1,7 +1,7 @@
1
1
  import type { APIRoute } from "astro"
2
2
 
3
- import { getConfig } from "./sveltia/sveltia.config"
3
+ import { createConfig } from "./sveltia.config"
4
4
 
5
5
  export const GET: APIRoute = () => {
6
- return new Response(JSON.stringify(getConfig()))
6
+ return new Response(JSON.stringify(createConfig()))
7
7
  }
@@ -1,13 +1,17 @@
1
1
  import type { Backend, CmsConfig } from "@sveltia/cms"
2
2
  import { site } from "astro:config/server"
3
- import sveltiaAdminConfig from "virtual:lightnet/sveltiaAdminConfig"
3
+ import adminConfig from "virtual:lightnet/sveltiaAdminConfig"
4
4
 
5
- import lightnetLogo from "../assets/lightnet-logo.svg?url"
6
- import { contentCollections } from "./collections/content"
7
- import { defineLanguagesCollection } from "./collections/content/languages"
8
- import { projectPath } from "./utils/path"
5
+ import lightnetLogo from "./assets/lightnet-logo.svg?url"
6
+ import { categoriesCollection } from "./collections/categories"
7
+ import { defineLanguagesCollection } from "./collections/languages"
8
+ import { mediaCollectionCollection } from "./collections/media-collections"
9
+ import { mediaItemCollection } from "./collections/media-items"
10
+ import { mediaTypeCollection } from "./collections/media-types"
11
+ import { isDefined } from "./utils/is-defined"
12
+ import { projectPath } from "./utils/paths"
9
13
 
10
- export function getConfig(
14
+ export function createConfig(
11
15
  siteUrl = process.env.LIGHTNET_DEV_SITE_URL ?? site,
12
16
  ): CmsConfig {
13
17
  return {
@@ -27,14 +31,11 @@ export function getConfig(
27
31
  },
28
32
  default: {
29
33
  config: {
30
- max_file_size: sveltiaAdminConfig.maxFileSize * 1_000_000,
34
+ max_file_size: adminConfig.maxFileSize * 1_000_000,
31
35
  transformations: {
32
- raster_image: {
33
- format: "webp",
34
- quality: 85,
35
- width: 2048,
36
- height: 2048,
37
- },
36
+ jpeg: optimizedImage,
37
+ png: optimizedImage,
38
+ webp: optimizedImage,
38
39
  svg: {
39
40
  optimize: true,
40
41
  },
@@ -52,13 +53,26 @@ export function getConfig(
52
53
  encoding: "ascii",
53
54
  maxlength: 60,
54
55
  },
55
- collections: [...contentCollections],
56
- singletons: [defineLanguagesCollection()].filter((c) => !!c),
56
+ collections: [
57
+ mediaItemCollection,
58
+ { divider: true },
59
+ categoriesCollection,
60
+ mediaCollectionCollection,
61
+ mediaTypeCollection,
62
+ ],
63
+ singletons: [defineLanguagesCollection()].filter(isDefined),
57
64
  }
58
65
  }
59
66
 
67
+ const optimizedImage = {
68
+ format: "webp",
69
+ quality: 85,
70
+ width: 1024,
71
+ height: 1024,
72
+ } as const
73
+
60
74
  function getBackend(): Backend {
61
- const { backend } = sveltiaAdminConfig
75
+ const { backend } = adminConfig
62
76
 
63
77
  if (!backend) {
64
78
  return {
@@ -97,5 +111,3 @@ function createLocalRepoPath() {
97
111
  .replaceAll(".", "-") + "/local-repository"
98
112
  )
99
113
  }
100
-
101
- export const config = getConfig()
@@ -0,0 +1,5 @@
1
+ export const isDefined = <TInput>(
2
+ input: TInput | undefined | false,
3
+ ): input is TInput => {
4
+ return input !== false && input !== undefined
5
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Prefix a site-internal path with Astro's configured base path.
3
+ *
4
+ * This helper trims any trailing slash from `BASE_URL`, ensures the input
5
+ * path starts with a leading slash, and concatenates the two values.
6
+ * Absolute URLs are out of scope for this helper.
7
+ *
8
+ * @param path internal path such as "/en/media", "/api/internal/search.json", or "/"
9
+ * @returns base-aware internal path
10
+ */
11
+ export function pathWithBase(path: string) {
12
+ const normalizedBase = import.meta.env.BASE_URL.replace(/\/+$/, "")
13
+ const normalizedPath = path.startsWith("/") ? path : `/${path}`
14
+ return `${normalizedBase}${normalizedPath}`
15
+ }
@@ -1,15 +1,6 @@
1
- /**
2
- * Prefix a site-internal path with Astro's configured base path.
3
- *
4
- * This helper trims any trailing slash from `BASE_URL`, ensures the input
5
- * path starts with a leading slash, and concatenates the two values.
6
- * Absolute URLs are out of scope for this helper.
7
- *
8
- * @param path internal path such as "/en/media", "/api/internal/search.json", or "/"
9
- * @returns base-aware internal path
10
- */
11
- export function pathWithBase(path: string) {
12
- const normalizedBase = import.meta.env.BASE_URL.replace(/\/+$/, "")
13
- const normalizedPath = path.startsWith("/") ? path : `/${path}`
14
- return `${normalizedBase}${normalizedPath}`
15
- }
1
+ import sveltiaAdminConfig from "virtual:lightnet/sveltiaAdminConfig"
2
+
3
+ const basePath: string = sveltiaAdminConfig.siteRootInRepo
4
+
5
+ export const projectPath = (path: string) =>
6
+ `${basePath}${basePath.endsWith("/") || path.startsWith("/") ? "" : "/"}${path}`
@@ -1,27 +0,0 @@
1
- import { pathWithBase } from "../utils/paths"
2
-
3
- const parseCachedUser = () => {
4
- try {
5
- const cachedUser = localStorage.getItem("sveltia-cms.user")
6
- return cachedUser ? JSON.parse(cachedUser) : undefined
7
- } catch {
8
- return undefined
9
- }
10
- }
11
-
12
- export default {
13
- shouldShow: () => {
14
- if (import.meta.env.DEV) {
15
- return true
16
- }
17
-
18
- const cachedUser = parseCachedUser()
19
- return (
20
- typeof cachedUser === "object" &&
21
- cachedUser !== null &&
22
- typeof cachedUser.backendName === "string"
23
- )
24
- },
25
- createHref: (mediaId: string) =>
26
- `${pathWithBase("/admin")}#/collections/media/entries/${encodeURIComponent(mediaId)}`,
27
- }
@@ -1,31 +0,0 @@
1
- import type { Collection } from "@sveltia/cms"
2
- import config from "virtual:lightnet/config"
3
-
4
- import { inlineTranslation } from "../../utils/inline-translation"
5
- import { projectPath } from "../../utils/path"
6
-
7
- export const categoriesCollection: Collection = {
8
- name: "categories",
9
- label: "Categories",
10
- description:
11
- "Organize and filter media items by topic. Examples: discipleship, youth, prayer. [Read documentation](https://docs.lightnet.community/content/categories/)",
12
- label_singular: "Category",
13
- folder: projectPath("src/content/categories"),
14
- create: true,
15
- format: "json",
16
- slug: "{{fields._slug}}",
17
- summary: `{{label.${config.defaultLocale}}} ({{slug}})`,
18
- fields: [
19
- inlineTranslation({ name: "label", label: "Name" }),
20
- {
21
- name: "image",
22
- label: "Image",
23
- required: false,
24
- choose_url: false,
25
- widget: "image",
26
- media_folder: "./images",
27
- accept: "image/png, image/jpeg, image/webp",
28
- hint: "When you upload an image, it is automatically resized (up to 2048 pixels) and saved in a web-friendly format.",
29
- },
30
- ],
31
- }
@@ -1,12 +0,0 @@
1
- import { categoriesCollection } from "./categories"
2
- import { mediaCollectionCollection } from "./media-collections"
3
- import { mediaItemCollection } from "./media-items"
4
- import { mediaTypeCollection } from "./media-types"
5
-
6
- export const contentCollections = [
7
- mediaItemCollection,
8
- { divider: true },
9
- categoriesCollection,
10
- mediaCollectionCollection,
11
- mediaTypeCollection,
12
- ]
@@ -1,180 +0,0 @@
1
- import type { Collection } from "@sveltia/cms"
2
- import config from "virtual:lightnet/config"
3
- import sveltiaAdminConfig from "virtual:lightnet/sveltiaAdminConfig"
4
-
5
- import { inlineTranslation } from "../../utils/inline-translation"
6
- import { projectPath } from "../../utils/path"
7
- import { languagesSelect } from "./languages"
8
-
9
- export const mediaItemCollection: Collection = {
10
- name: "media",
11
- label: "Media Items",
12
- description:
13
- "Add content entries to the media library. Examples: a book PDF, a YouTube link. [Read documentation](https://docs.lightnet.community/content/media-items/)",
14
- label_singular: "Media Item",
15
- folder: projectPath("src/content/media"),
16
- create: true,
17
- preview_path: `${config.defaultLocale}/media/{{filename}}`,
18
- format: "json",
19
- slug: "{{fields._slug}}",
20
- sortable_fields: ["slug", "dateCreated", "language"],
21
- summary: "{{title}} ({{slug}})",
22
- view_groups: [
23
- { label: "Language", field: "language", pattern: ".*" },
24
- { label: "Type", field: "type", pattern: ".*" },
25
- ],
26
- fields: [
27
- { name: "title", label: "Title", widget: "string" },
28
- {
29
- name: "type",
30
- label: "Type",
31
- widget: "relation",
32
- collection: "media-types",
33
- value_field: "{{slug}}",
34
- display_fields: [`{{label.${config.defaultLocale}}} ({{slug}})`],
35
- },
36
- languagesSelect(),
37
- {
38
- name: "image",
39
- label: "Image",
40
- widget: "image",
41
- choose_url: false,
42
- media_folder: "./images",
43
- accept: "image/png, image/jpeg, image/webp",
44
- hint: "When you upload an image, it is automatically resized (up to 2048 pixels) and saved in a web-friendly format.",
45
- },
46
- {
47
- name: "content",
48
- label: "Content",
49
- widget: "list",
50
- hint: "Add files or weblinks. First item in the list is the main content.",
51
- min: 1,
52
- summary: "{{types.url}}",
53
- types: [
54
- {
55
- name: "upload",
56
- label: "File Upload",
57
- fields: [
58
- {
59
- name: "url",
60
- label: "File",
61
- widget: "file",
62
- choose_url: false,
63
- ...getFileStorage(),
64
- },
65
- inlineTranslation({
66
- name: "label",
67
- label: "Label",
68
- hint: "Optional. Defaults to the file name, for example 'bible' from 'bible.pdf'.",
69
- required: false,
70
- collapsed: "auto",
71
- }),
72
- ],
73
- },
74
- {
75
- name: "link",
76
- label: "Link",
77
- summary: "{{url}}",
78
- fields: [
79
- {
80
- name: "url",
81
- label: "Url",
82
- widget: "string",
83
- type: "url",
84
- pattern: ["^https?://", "Link must start with http(s)://"],
85
- },
86
- inlineTranslation({
87
- name: "label",
88
- label: "Label",
89
- required: false,
90
- hint: "Optional. Defaults to the file name or link domain, for example 'youtube.com'.",
91
- collapsed: "auto",
92
- }),
93
- ],
94
- },
95
- ],
96
- },
97
- {
98
- name: "dateCreated",
99
- label: "Date Created",
100
- widget: "datetime",
101
- time_format: false,
102
- required: true,
103
- default: "{{now}}",
104
- picker_utc: true,
105
- hint: "The date this item was added to this media library.",
106
- },
107
- {
108
- name: "authors",
109
- label: "Authors",
110
- label_singular: "Author",
111
- required: false,
112
- collapsed: true,
113
- default: [],
114
- widget: "list",
115
- summary: "{{fields.name}}",
116
- field: { label: "Name", name: "name", widget: "string" },
117
- },
118
- {
119
- name: "commonId",
120
- label: "Common ID",
121
- widget: "string",
122
- required: false,
123
- hint: "Optional: Use a shared Common ID to link translated versions of a media item.",
124
- },
125
- {
126
- name: "categories",
127
- label: "Categories",
128
- required: false,
129
- widget: "relation",
130
- multiple: true,
131
- collection: "categories",
132
- display_fields: ["{{slug}}"],
133
- search_fields: ["{{slug}}"],
134
- },
135
- {
136
- name: "description",
137
- label: "Description",
138
- widget: "markdown",
139
- required: false,
140
- editor_components: [],
141
- buttons: [
142
- "heading-one",
143
- "heading-two",
144
- "heading-three",
145
- "heading-four",
146
- "heading-five",
147
- "heading-six",
148
- "bold",
149
- "italic",
150
- "bulleted-list",
151
- "numbered-list",
152
- "quote",
153
- "link",
154
- ],
155
- },
156
- ],
157
- }
158
-
159
- function getFileStorage() {
160
- const externalFileStorage = sveltiaAdminConfig.experimental?.fileStorage
161
- if (!externalFileStorage) {
162
- return {
163
- media_folder: projectPath("public/files"),
164
- public_folder: "/files",
165
- hint: `Maximum file size is ${sveltiaAdminConfig.maxFileSize} MB.`,
166
- }
167
- }
168
-
169
- return {
170
- media_libraries: {
171
- default: false,
172
- cloudflare_r2: {
173
- ...externalFileStorage,
174
- access_key_id: externalFileStorage.accessKeyId,
175
- account_id: externalFileStorage.accountId,
176
- public_url: externalFileStorage.publicUrl,
177
- },
178
- },
179
- }
180
- }
@@ -1,6 +0,0 @@
1
- import sveltiaAdminConfig from "virtual:lightnet/sveltiaAdminConfig"
2
-
3
- const basePath: string = sveltiaAdminConfig.siteRootInRepo
4
-
5
- export const projectPath = (path: string) =>
6
- `${basePath}${basePath.endsWith("/") || path.startsWith("/") ? "" : "/"}${path}`