lightnet 3.10.4 → 3.10.6
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 +12 -0
- package/__e2e__/admin.spec.ts +93 -53
- package/__e2e__/{test-utils.ts → basics-fixture.ts} +20 -23
- package/__e2e__/fixtures/basics/node_modules/.bin/astro +2 -2
- package/__e2e__/fixtures/basics/package.json +2 -2
- package/__e2e__/fixtures/basics/src/content/media/faithful-freestyle--en.json +1 -1
- package/__e2e__/fixtures/basics/src/content/media/skate-sounds--en.json +1 -1
- package/__e2e__/global.teardown.ts +5 -0
- package/__e2e__/homepage.spec.ts +17 -19
- package/__e2e__/search.spec.ts +3 -5
- package/package.json +6 -6
- package/playwright.config.ts +1 -0
- package/src/admin/components/form/DynamicArray.tsx +74 -0
- package/src/admin/components/form/Input.tsx +8 -5
- package/src/admin/components/form/LazyLoadedMarkdownEditor.tsx +78 -0
- package/src/admin/components/form/MarkdownEditor.tsx +52 -0
- package/src/admin/components/form/Select.tsx +11 -10
- package/src/admin/components/form/SubmitButton.tsx +9 -12
- package/src/admin/components/form/atoms/ErrorMessage.tsx +7 -28
- package/src/admin/components/form/atoms/Hint.tsx +2 -2
- package/src/admin/components/form/atoms/Label.tsx +5 -1
- package/src/admin/components/form/atoms/Legend.tsx +12 -2
- package/src/admin/components/form/hooks/use-field-error.tsx +3 -11
- package/src/admin/i18n/translations/en.yml +16 -5
- package/src/admin/pages/media/EditForm.tsx +48 -6
- package/src/admin/pages/media/EditRoute.astro +29 -10
- package/src/admin/pages/media/fields/Authors.tsx +51 -51
- package/src/admin/pages/media/fields/Categories.tsx +70 -0
- package/src/admin/pages/media/fields/Collections.tsx +117 -0
- package/src/admin/pages/media/media-item-store.ts +6 -1
- package/src/admin/types/media-item.ts +35 -2
- package/src/components/CategoriesSection.astro +2 -2
- package/src/components/MediaGallerySection.astro +3 -3
- package/src/components/MediaList.astro +2 -2
- package/src/content/get-categories.ts +18 -3
- package/src/i18n/resolve-language.ts +1 -1
- package/src/layouts/Page.astro +3 -2
- package/src/layouts/components/LanguagePicker.astro +1 -1
- package/src/pages/details-page/components/more-details/Languages.astro +2 -2
- package/src/pages/search-page/components/SearchFilter.astro +7 -7
- package/src/pages/search-page/components/SearchFilter.tsx +4 -4
- package/src/pages/search-page/components/SearchList.astro +4 -4
- package/src/pages/search-page/components/SearchListItem.tsx +4 -4
- package/src/pages/search-page/components/Select.tsx +3 -3
- package/src/pages/search-page/hooks/use-search.ts +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# lightnet
|
|
2
2
|
|
|
3
|
+
## 3.10.6
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#333](https://github.com/LightNetDev/LightNet/pull/333) [`539a377`](https://github.com/LightNetDev/LightNet/commit/539a377702df9213d0869ae63646f569c10b1867) Thanks [@smn-cds](https://github.com/smn-cds)! - Update dependencies
|
|
8
|
+
|
|
9
|
+
## 3.10.5
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- [#331](https://github.com/LightNetDev/LightNet/pull/331) [`cdacfdf`](https://github.com/LightNetDev/LightNet/commit/cdacfdfca311469248cb7d1ac0b46f92d9fd5521) Thanks [@smn-cds](https://github.com/smn-cds)! - Experimenatl Admin UI: support editing categories and collections
|
|
14
|
+
|
|
3
15
|
## 3.10.4
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/__e2e__/admin.spec.ts
CHANGED
|
@@ -2,9 +2,8 @@ import { readFile } from "node:fs/promises"
|
|
|
2
2
|
|
|
3
3
|
import { expect, type Page } from "@playwright/test"
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { test } from "./basics-fixture"
|
|
6
6
|
|
|
7
|
-
const test = lightnetTest("./fixtures/basics/")
|
|
8
7
|
const faithfulFreestyleMediaUrl = new URL(
|
|
9
8
|
"./fixtures/basics/src/content/media/faithful-freestyle--en.json",
|
|
10
9
|
import.meta.url,
|
|
@@ -13,9 +12,9 @@ const faithfulFreestyleMediaUrl = new URL(
|
|
|
13
12
|
test.describe("Edit button on details page", () => {
|
|
14
13
|
test("Should not show `Edit` button on details page by default.", async ({
|
|
15
14
|
page,
|
|
16
|
-
|
|
15
|
+
lightnet,
|
|
17
16
|
}) => {
|
|
18
|
-
await
|
|
17
|
+
await lightnet()
|
|
19
18
|
|
|
20
19
|
await page.getByRole("link", { name: "Faithful Freestyle" }).click()
|
|
21
20
|
await expect(
|
|
@@ -28,9 +27,9 @@ test.describe("Edit button on details page", () => {
|
|
|
28
27
|
|
|
29
28
|
test("Should show `Edit` button on book details page after visiting `/en/admin/` path.", async ({
|
|
30
29
|
page,
|
|
31
|
-
|
|
30
|
+
lightnet,
|
|
32
31
|
}) => {
|
|
33
|
-
const ln = await
|
|
32
|
+
const ln = await lightnet()
|
|
34
33
|
|
|
35
34
|
await page.goto(ln.resolveURL("/en/admin/"))
|
|
36
35
|
await expect(
|
|
@@ -52,11 +51,10 @@ test.describe("Edit button on details page", () => {
|
|
|
52
51
|
|
|
53
52
|
test("Should show `Edit` button on video details page after visiting `/en/admin/` path.", async ({
|
|
54
53
|
page,
|
|
55
|
-
|
|
54
|
+
lightnet,
|
|
56
55
|
}) => {
|
|
57
|
-
const ln = await
|
|
56
|
+
const ln = await lightnet("/en/admin/")
|
|
58
57
|
|
|
59
|
-
await page.goto(ln.resolveURL("/en/admin/"))
|
|
60
58
|
await expect(
|
|
61
59
|
page.getByText("Admin features are enabled now.", { exact: true }),
|
|
62
60
|
).toBeVisible()
|
|
@@ -76,11 +74,10 @@ test.describe("Edit button on details page", () => {
|
|
|
76
74
|
|
|
77
75
|
test("Should show `Edit` button on audio details page after visiting `/en/admin/` path.", async ({
|
|
78
76
|
page,
|
|
79
|
-
|
|
77
|
+
lightnet,
|
|
80
78
|
}) => {
|
|
81
|
-
const ln = await
|
|
79
|
+
const ln = await lightnet("/en/admin/")
|
|
82
80
|
|
|
83
|
-
await page.goto(ln.resolveURL("/en/admin/"))
|
|
84
81
|
await expect(
|
|
85
82
|
page.getByText("Admin features are enabled now.", { exact: true }),
|
|
86
83
|
).toBeVisible()
|
|
@@ -100,12 +97,10 @@ test.describe("Edit button on details page", () => {
|
|
|
100
97
|
|
|
101
98
|
test("Edit button on details page should navigate to media item edit page", async ({
|
|
102
99
|
page,
|
|
103
|
-
|
|
100
|
+
lightnet,
|
|
104
101
|
}) => {
|
|
105
|
-
const ln = await
|
|
106
|
-
|
|
107
|
-
await page.goto(ln.resolveURL("/en/admin/"))
|
|
108
|
-
await page.goto(ln.resolveURL("/en/media/faithful-freestyle--en"))
|
|
102
|
+
const ln = await lightnet("/en/admin/")
|
|
103
|
+
await ln.goto("/en/media/faithful-freestyle--en")
|
|
109
104
|
|
|
110
105
|
const editButton = page.locator("#edit-btn")
|
|
111
106
|
await expect(editButton).toBeVisible()
|
|
@@ -143,19 +138,24 @@ test.describe("Media item edit page", () => {
|
|
|
143
138
|
return () => writeFileRequestPromise
|
|
144
139
|
}
|
|
145
140
|
|
|
146
|
-
|
|
147
|
-
|
|
141
|
+
const getPublishButton = (page: Page) =>
|
|
142
|
+
page.getByRole("button", { name: "Publish Changes" }).first()
|
|
148
143
|
|
|
149
|
-
|
|
144
|
+
const expectPublishedMessage = (page: Page) =>
|
|
145
|
+
expect(
|
|
146
|
+
page.getByRole("button", { name: "Published" }).first(),
|
|
147
|
+
).toBeVisible()
|
|
150
148
|
|
|
151
|
-
|
|
149
|
+
test("should edit title", async ({ page, lightnet }) => {
|
|
150
|
+
await lightnet("/en/admin/media/faithful-freestyle--en")
|
|
151
|
+
const writeFileRequest = await recordWriteFile(page)
|
|
152
152
|
|
|
153
153
|
const updatedTitle = "Faithful Freestyle (Edited)"
|
|
154
154
|
const titleInput = page.getByLabel("Title")
|
|
155
155
|
await expect(titleInput).toHaveValue("Faithful Freestyle")
|
|
156
156
|
await titleInput.fill(updatedTitle)
|
|
157
157
|
|
|
158
|
-
const saveButton = page
|
|
158
|
+
const saveButton = getPublishButton(page)
|
|
159
159
|
await expect(saveButton).toBeEnabled()
|
|
160
160
|
await saveButton.click()
|
|
161
161
|
|
|
@@ -171,20 +171,18 @@ test.describe("Media item edit page", () => {
|
|
|
171
171
|
...expectedMediaItem,
|
|
172
172
|
title: updatedTitle,
|
|
173
173
|
})
|
|
174
|
-
await
|
|
174
|
+
await expectPublishedMessage(page)
|
|
175
175
|
})
|
|
176
176
|
|
|
177
|
-
test("Should update media type", async ({ page,
|
|
178
|
-
|
|
177
|
+
test("Should update media type", async ({ page, lightnet }) => {
|
|
178
|
+
await lightnet("/en/admin/media/faithful-freestyle--en")
|
|
179
179
|
const writeFileRequest = await recordWriteFile(page)
|
|
180
180
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
const typeSelect = page.getByLabel("Type")
|
|
181
|
+
const typeSelect = page.getByLabel("Type").first()
|
|
184
182
|
await expect(typeSelect).toHaveValue("book")
|
|
185
183
|
await typeSelect.selectOption("video")
|
|
186
184
|
|
|
187
|
-
const saveButton = page
|
|
185
|
+
const saveButton = getPublishButton(page)
|
|
188
186
|
await expect(saveButton).toBeEnabled()
|
|
189
187
|
await saveButton.click()
|
|
190
188
|
|
|
@@ -196,22 +194,20 @@ test.describe("Media item edit page", () => {
|
|
|
196
194
|
...expectedMediaItem,
|
|
197
195
|
type: "video",
|
|
198
196
|
})
|
|
199
|
-
await
|
|
197
|
+
await expectPublishedMessage(page)
|
|
200
198
|
})
|
|
201
199
|
|
|
202
|
-
test("Should update author name", async ({ page,
|
|
203
|
-
|
|
200
|
+
test("Should update author name", async ({ page, lightnet }) => {
|
|
201
|
+
await lightnet("/en/admin/media/faithful-freestyle--en")
|
|
204
202
|
const writeFileRequest = await recordWriteFile(page)
|
|
205
203
|
|
|
206
|
-
await page.goto(ln.resolveURL("/en/admin/media/faithful-freestyle--en"))
|
|
207
|
-
|
|
208
204
|
const authorsFieldset = page.getByRole("group", { name: "Authors" })
|
|
209
205
|
const firstAuthorInput = authorsFieldset.getByRole("textbox").first()
|
|
210
206
|
const updatedAuthor = "Sk8 Ministries International"
|
|
211
207
|
await expect(firstAuthorInput).toHaveValue("Sk8 Ministries")
|
|
212
208
|
await firstAuthorInput.fill(updatedAuthor)
|
|
213
209
|
|
|
214
|
-
const saveButton = page
|
|
210
|
+
const saveButton = getPublishButton(page)
|
|
215
211
|
await expect(saveButton).toBeEnabled()
|
|
216
212
|
await saveButton.click()
|
|
217
213
|
|
|
@@ -223,15 +219,13 @@ test.describe("Media item edit page", () => {
|
|
|
223
219
|
...expectedMediaItem,
|
|
224
220
|
authors: [updatedAuthor],
|
|
225
221
|
})
|
|
226
|
-
await
|
|
222
|
+
await expectPublishedMessage(page)
|
|
227
223
|
})
|
|
228
224
|
|
|
229
|
-
test("Should add author", async ({ page,
|
|
230
|
-
|
|
225
|
+
test("Should add author", async ({ page, lightnet }) => {
|
|
226
|
+
await lightnet("/en/admin/media/faithful-freestyle--en")
|
|
231
227
|
const writeFileRequest = await recordWriteFile(page)
|
|
232
228
|
|
|
233
|
-
await page.goto(ln.resolveURL("/en/admin/media/faithful-freestyle--en"))
|
|
234
|
-
|
|
235
229
|
const authorsFieldset = page.getByRole("group", { name: "Authors" })
|
|
236
230
|
const addAuthorButton = page.getByRole("button", { name: "Add Author" })
|
|
237
231
|
await addAuthorButton.click()
|
|
@@ -239,7 +233,7 @@ test.describe("Media item edit page", () => {
|
|
|
239
233
|
const additionalAuthor = "Tony Hawk"
|
|
240
234
|
await newAuthorInput.fill(additionalAuthor)
|
|
241
235
|
|
|
242
|
-
const saveButton = page
|
|
236
|
+
const saveButton = getPublishButton(page)
|
|
243
237
|
await expect(saveButton).toBeEnabled()
|
|
244
238
|
await saveButton.click()
|
|
245
239
|
|
|
@@ -251,15 +245,13 @@ test.describe("Media item edit page", () => {
|
|
|
251
245
|
...expectedMediaItem,
|
|
252
246
|
authors: ["Sk8 Ministries", additionalAuthor],
|
|
253
247
|
})
|
|
254
|
-
await
|
|
248
|
+
await expectPublishedMessage(page)
|
|
255
249
|
})
|
|
256
250
|
|
|
257
|
-
test("Should remove author", async ({ page,
|
|
258
|
-
|
|
251
|
+
test("Should remove author", async ({ page, lightnet }) => {
|
|
252
|
+
await lightnet("/en/admin/media/faithful-freestyle--en")
|
|
259
253
|
const writeFileRequest = await recordWriteFile(page)
|
|
260
254
|
|
|
261
|
-
await page.goto(ln.resolveURL("/en/admin/media/faithful-freestyle--en"))
|
|
262
|
-
|
|
263
255
|
const authorsFieldset = page.getByRole("group", { name: "Authors" })
|
|
264
256
|
const addAuthorButton = page.getByRole("button", { name: "Add Author" })
|
|
265
257
|
const replacementAuthor = "Skate Evangelists"
|
|
@@ -272,7 +264,7 @@ test.describe("Media item edit page", () => {
|
|
|
272
264
|
})
|
|
273
265
|
await removeButtons.first().click()
|
|
274
266
|
|
|
275
|
-
const saveButton = page
|
|
267
|
+
const saveButton = getPublishButton(page)
|
|
276
268
|
await expect(saveButton).toBeEnabled()
|
|
277
269
|
await saveButton.click()
|
|
278
270
|
|
|
@@ -284,15 +276,14 @@ test.describe("Media item edit page", () => {
|
|
|
284
276
|
...expectedMediaItem,
|
|
285
277
|
authors: [replacementAuthor],
|
|
286
278
|
})
|
|
287
|
-
await
|
|
279
|
+
await expectPublishedMessage(page)
|
|
288
280
|
})
|
|
289
281
|
|
|
290
282
|
test("should show error message if common id is set empty", async ({
|
|
291
283
|
page,
|
|
292
|
-
|
|
284
|
+
lightnet,
|
|
293
285
|
}) => {
|
|
294
|
-
|
|
295
|
-
await page.goto(ln.resolveURL("/en/admin/media/faithful-freestyle--en"))
|
|
286
|
+
await lightnet("/en/admin/media/faithful-freestyle--en")
|
|
296
287
|
|
|
297
288
|
const commonIdInput = page.getByLabel("Common ID")
|
|
298
289
|
await expect(commonIdInput).toHaveValue("faithful-freestyle")
|
|
@@ -303,8 +294,57 @@ test.describe("Media item edit page", () => {
|
|
|
303
294
|
await expect(
|
|
304
295
|
page
|
|
305
296
|
.getByRole("alert")
|
|
306
|
-
.filter({ hasText: "
|
|
297
|
+
.filter({ hasText: "Please enter at least one character." }),
|
|
298
|
+
).toBeVisible()
|
|
299
|
+
})
|
|
300
|
+
|
|
301
|
+
test("should focus invalid field when submitting invalid form data", async ({
|
|
302
|
+
page,
|
|
303
|
+
lightnet,
|
|
304
|
+
}) => {
|
|
305
|
+
await lightnet("/en/admin/media/faithful-freestyle--en")
|
|
306
|
+
|
|
307
|
+
const categoriesFieldset = page.getByRole("group", { name: "Categories" })
|
|
308
|
+
await page.getByRole("button", { name: "Add Category" }).click()
|
|
309
|
+
const newCategorySelect = categoriesFieldset.getByRole("combobox").last()
|
|
310
|
+
await expect(newCategorySelect).toHaveValue("")
|
|
311
|
+
|
|
312
|
+
// move focus away so the submission handler needs to return focus
|
|
313
|
+
await page.getByLabel("Title").click()
|
|
314
|
+
|
|
315
|
+
const saveButton = getPublishButton(page)
|
|
316
|
+
await saveButton.click()
|
|
317
|
+
|
|
318
|
+
await expect(
|
|
319
|
+
page.getByRole("alert").filter({ hasText: "This field is required." }),
|
|
307
320
|
).toBeVisible()
|
|
308
|
-
await expect(
|
|
321
|
+
await expect(newCategorySelect).toBeFocused()
|
|
322
|
+
})
|
|
323
|
+
|
|
324
|
+
test("should not allow assigning duplicate categories", async ({
|
|
325
|
+
page,
|
|
326
|
+
lightnet,
|
|
327
|
+
}) => {
|
|
328
|
+
await lightnet("/en/admin/media/faithful-freestyle--en")
|
|
329
|
+
|
|
330
|
+
const categoriesFieldset = page.getByRole("group", { name: "Categories" })
|
|
331
|
+
await page.getByRole("button", { name: "Add Category" }).click()
|
|
332
|
+
|
|
333
|
+
const categorySelects = categoriesFieldset.getByRole("combobox")
|
|
334
|
+
const firstCategoryValue = await categorySelects.first().inputValue()
|
|
335
|
+
const duplicateCategorySelect = categorySelects.last()
|
|
336
|
+
await duplicateCategorySelect.selectOption(firstCategoryValue)
|
|
337
|
+
|
|
338
|
+
const publishButton = getPublishButton(page)
|
|
339
|
+
await publishButton.click()
|
|
340
|
+
|
|
341
|
+
const duplicateCategoryError = categoriesFieldset
|
|
342
|
+
.getByRole("alert")
|
|
343
|
+
.filter({ hasText: "Please choose a different value for each entry." })
|
|
344
|
+
await expect(duplicateCategoryError).toBeVisible()
|
|
345
|
+
await expect(duplicateCategorySelect).toHaveAttribute(
|
|
346
|
+
"aria-invalid",
|
|
347
|
+
"true",
|
|
348
|
+
)
|
|
309
349
|
})
|
|
310
350
|
})
|
|
@@ -24,7 +24,6 @@
|
|
|
24
24
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
25
25
|
* SOFTWARE.
|
|
26
26
|
*/
|
|
27
|
-
|
|
28
27
|
import { fileURLToPath } from "node:url"
|
|
29
28
|
|
|
30
29
|
import { type Page, test as baseTest } from "@playwright/test"
|
|
@@ -34,30 +33,26 @@ export { expect, type Locator } from "@playwright/test"
|
|
|
34
33
|
|
|
35
34
|
process.env.ASTRO_TELEMETRY_DISABLED = "true"
|
|
36
35
|
process.env.ASTRO_DISABLE_UPDATE_CHECK = "true"
|
|
37
|
-
|
|
38
|
-
const root = fileURLToPath(new URL(fixturePath, import.meta.url))
|
|
39
|
-
|
|
40
|
-
let server: Server | null = null
|
|
41
|
-
const test = baseTest.extend<{
|
|
42
|
-
startLightnet: (path?: string) => Promise<LightNetPage>
|
|
43
|
-
}>({
|
|
44
|
-
startLightnet: ({ page }, use) =>
|
|
45
|
-
use(async (path) => {
|
|
46
|
-
if (!server) {
|
|
47
|
-
await build({ logLevel: "error", root })
|
|
48
|
-
server = await preview({ logLevel: "error", root })
|
|
49
|
-
}
|
|
50
|
-
const ln = new LightNetPage(server, page)
|
|
51
|
-
await ln.goto(path ?? "/")
|
|
52
|
-
return ln
|
|
53
|
-
}),
|
|
54
|
-
})
|
|
36
|
+
const root = fileURLToPath(new URL("./fixtures/basics/", import.meta.url))
|
|
55
37
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
38
|
+
let server: Server | null = null
|
|
39
|
+
const test = baseTest.extend<{
|
|
40
|
+
lightnet: (path?: string) => Promise<LightNetPage>
|
|
41
|
+
}>({
|
|
42
|
+
lightnet: ({ page }, use) =>
|
|
43
|
+
use(async (path) => {
|
|
44
|
+
if (!server) {
|
|
45
|
+
await build({ logLevel: "error", root })
|
|
46
|
+
server = await preview({ logLevel: "error", root })
|
|
47
|
+
}
|
|
48
|
+
const ln = new LightNetPage(server, page)
|
|
49
|
+
await ln.goto(path ?? "/")
|
|
50
|
+
return ln
|
|
51
|
+
}),
|
|
52
|
+
})
|
|
59
53
|
|
|
60
|
-
|
|
54
|
+
const teardown = async () => {
|
|
55
|
+
await server?.stop()
|
|
61
56
|
}
|
|
62
57
|
|
|
63
58
|
// A Playwright test fixture accessible from within all tests.
|
|
@@ -78,3 +73,5 @@ class LightNetPage {
|
|
|
78
73
|
}
|
|
79
74
|
|
|
80
75
|
type Server = Awaited<ReturnType<typeof preview>>
|
|
76
|
+
|
|
77
|
+
export { teardown, test }
|
|
@@ -10,9 +10,9 @@ case `uname` in
|
|
|
10
10
|
esac
|
|
11
11
|
|
|
12
12
|
if [ -z "$NODE_PATH" ]; then
|
|
13
|
-
export NODE_PATH="/home/runner/work/LightNet/LightNet/node_modules/.pnpm/astro@5.15.
|
|
13
|
+
export NODE_PATH="/home/runner/work/LightNet/LightNet/node_modules/.pnpm/astro@5.15.8_@types+node@24.10.1_jiti@2.4.2_lightningcss@1.29.1_rollup@4.53.2_terser@5.39.0_typescript@5.9.3_yaml@2.8.1/node_modules/astro/node_modules:/home/runner/work/LightNet/LightNet/node_modules/.pnpm/astro@5.15.8_@types+node@24.10.1_jiti@2.4.2_lightningcss@1.29.1_rollup@4.53.2_terser@5.39.0_typescript@5.9.3_yaml@2.8.1/node_modules:/home/runner/work/LightNet/LightNet/node_modules/.pnpm/node_modules"
|
|
14
14
|
else
|
|
15
|
-
export NODE_PATH="/home/runner/work/LightNet/LightNet/node_modules/.pnpm/astro@5.15.
|
|
15
|
+
export NODE_PATH="/home/runner/work/LightNet/LightNet/node_modules/.pnpm/astro@5.15.8_@types+node@24.10.1_jiti@2.4.2_lightningcss@1.29.1_rollup@4.53.2_terser@5.39.0_typescript@5.9.3_yaml@2.8.1/node_modules/astro/node_modules:/home/runner/work/LightNet/LightNet/node_modules/.pnpm/astro@5.15.8_@types+node@24.10.1_jiti@2.4.2_lightningcss@1.29.1_rollup@4.53.2_terser@5.39.0_typescript@5.9.3_yaml@2.8.1/node_modules:/home/runner/work/LightNet/LightNet/node_modules/.pnpm/node_modules:$NODE_PATH"
|
|
16
16
|
fi
|
|
17
17
|
if [ -x "$basedir/node" ]; then
|
|
18
18
|
exec "$basedir/node" "$basedir/../astro/astro.js" "$@"
|
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
"@astrojs/react": "^4.4.2",
|
|
8
8
|
"@astrojs/tailwind": "^6.0.2",
|
|
9
9
|
"@lightnet/decap-admin": "^3.1.4",
|
|
10
|
-
"astro": "^5.15.
|
|
11
|
-
"lightnet": "^3.10.
|
|
10
|
+
"astro": "^5.15.8",
|
|
11
|
+
"lightnet": "^3.10.5",
|
|
12
12
|
"react": "^19.2.0",
|
|
13
13
|
"react-dom": "^19.2.0",
|
|
14
14
|
"sharp": "^0.34.5",
|
|
@@ -9,5 +9,5 @@
|
|
|
9
9
|
"language": "en",
|
|
10
10
|
"categories": ["christian-living"],
|
|
11
11
|
"collections": [{ "collection": "how-to-articles" }],
|
|
12
|
-
"description": "*How to: Faithful Freestyle* empowers you to express your Christianity through the unique and creative outlet of skateboarding. This book includes:\n\n
|
|
12
|
+
"description": "*How to: Faithful Freestyle* empowers you to express your Christianity through the unique and creative outlet of skateboarding. This book includes:\n\n* **Creative ways to incorporate faith** into your skating routines\n* **Stories of freestyle skaters** who honor God through their sport\n* **Practical advice on witnessing** to others in the skateboarding scene\n* **Inspirational devotions** designed for skaters\n\nEmbrace a faithful freestyle and let every trick and turn reflect your devotion to Christ."
|
|
13
13
|
}
|
|
@@ -11,5 +11,5 @@
|
|
|
11
11
|
"image": "./images/cover.jpg",
|
|
12
12
|
"language": "en",
|
|
13
13
|
"categories": ["christian-living"],
|
|
14
|
-
"description": "A vibrant collection of authentic skate park sounds to energize your projects and playlists.\n\n**Highlights:**\n
|
|
14
|
+
"description": "A vibrant collection of authentic skate park sounds to energize your projects and playlists.\n\n**Highlights:**\n* Real-world skating ambience\n* Perfect for creative mixes\n* Inspiring background audio for worship gatherings"
|
|
15
15
|
}
|
package/__e2e__/homepage.spec.ts
CHANGED
|
@@ -1,34 +1,32 @@
|
|
|
1
1
|
import { expect } from "@playwright/test"
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { test } from "./basics-fixture"
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
test("Should have title set", async ({ page, startLightnet }) => {
|
|
8
|
-
await startLightnet()
|
|
5
|
+
test("Should have title set", async ({ page, lightnet }) => {
|
|
6
|
+
await lightnet()
|
|
9
7
|
await expect(page).toHaveTitle("Basic Test")
|
|
10
8
|
})
|
|
11
9
|
|
|
12
10
|
test("Should have header title that navigates to home page", async ({
|
|
13
11
|
page,
|
|
14
|
-
|
|
12
|
+
lightnet,
|
|
15
13
|
}) => {
|
|
16
|
-
const ln = await
|
|
14
|
+
const ln = await lightnet()
|
|
17
15
|
await page.getByRole("link", { name: "Basic Test" }).click()
|
|
18
16
|
|
|
19
17
|
await expect(page).toHaveURL(ln.resolveURL("/en/"))
|
|
20
18
|
})
|
|
21
19
|
|
|
22
|
-
test("Should have item section", async ({ page,
|
|
23
|
-
await
|
|
20
|
+
test("Should have item section", async ({ page, lightnet }) => {
|
|
21
|
+
await lightnet()
|
|
24
22
|
await expect(page.getByRole("heading", { name: "All items" })).toBeVisible()
|
|
25
23
|
})
|
|
26
24
|
|
|
27
25
|
test("Should navigate to search page from main menu", async ({
|
|
28
26
|
page,
|
|
29
|
-
|
|
27
|
+
lightnet,
|
|
30
28
|
}) => {
|
|
31
|
-
const ln = await
|
|
29
|
+
const ln = await lightnet()
|
|
32
30
|
await expect(
|
|
33
31
|
page.getByRole("button", { name: "Open Main Menu" }),
|
|
34
32
|
).toBeVisible()
|
|
@@ -42,8 +40,8 @@ test("Should navigate to search page from main menu", async ({
|
|
|
42
40
|
await expect(page.getByRole("heading", { name: "Search" })).toBeVisible()
|
|
43
41
|
})
|
|
44
42
|
|
|
45
|
-
test("Should switch languages", async ({ page,
|
|
46
|
-
const ln = await
|
|
43
|
+
test("Should switch languages", async ({ page, lightnet }) => {
|
|
44
|
+
const ln = await lightnet()
|
|
47
45
|
|
|
48
46
|
await page.getByLabel("Select language").click()
|
|
49
47
|
await page.getByRole("link", { name: "Deutsch" }).click()
|
|
@@ -57,9 +55,9 @@ test("Should switch languages", async ({ page, startLightnet }) => {
|
|
|
57
55
|
|
|
58
56
|
test("Should verify EN Detail media page url and title", async ({
|
|
59
57
|
page,
|
|
60
|
-
|
|
58
|
+
lightnet,
|
|
61
59
|
}) => {
|
|
62
|
-
const ln = await
|
|
60
|
+
const ln = await lightnet()
|
|
63
61
|
|
|
64
62
|
await page.getByRole("link", { name: "Faithful Freestyle" }).click()
|
|
65
63
|
await expect(
|
|
@@ -80,9 +78,9 @@ test("Should verify EN Detail media page url and title", async ({
|
|
|
80
78
|
|
|
81
79
|
test("Should verify DE Detail media page url and title", async ({
|
|
82
80
|
page,
|
|
83
|
-
|
|
81
|
+
lightnet,
|
|
84
82
|
}) => {
|
|
85
|
-
const ln = await
|
|
83
|
+
const ln = await lightnet()
|
|
86
84
|
|
|
87
85
|
await page.getByLabel("Select language").click()
|
|
88
86
|
await page.getByRole("link", { name: "Deutsch" }).click()
|
|
@@ -105,9 +103,9 @@ test("Should verify DE Detail media page url and title", async ({
|
|
|
105
103
|
|
|
106
104
|
test("Should show `Powered by LightNet` in footer", async ({
|
|
107
105
|
page,
|
|
108
|
-
|
|
106
|
+
lightnet,
|
|
109
107
|
}) => {
|
|
110
|
-
await
|
|
108
|
+
await lightnet()
|
|
111
109
|
|
|
112
110
|
const footerLink = page
|
|
113
111
|
.getByRole("contentinfo")
|
package/__e2e__/search.spec.ts
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
import { expect } from "@playwright/test"
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
const test = lightnetTest("./fixtures/basics/")
|
|
3
|
+
import { test } from "./basics-fixture"
|
|
6
4
|
|
|
7
5
|
test("Search should have heading section and URL", async ({
|
|
8
6
|
page,
|
|
9
|
-
|
|
7
|
+
lightnet,
|
|
10
8
|
}) => {
|
|
11
|
-
const ln = await
|
|
9
|
+
const ln = await lightnet()
|
|
12
10
|
|
|
13
11
|
await page.getByLabel("Search").click()
|
|
14
12
|
await expect(page.getByRole("heading", { name: "Search" })).toBeVisible()
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"description": "LightNet makes it easy to run your own digital media library.",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
|
-
"version": "3.10.
|
|
6
|
+
"version": "3.10.6",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
9
9
|
"url": "https://github.com/LightNetDev/lightnet",
|
|
@@ -47,27 +47,27 @@
|
|
|
47
47
|
"dependencies": {
|
|
48
48
|
"@astrojs/react": "^4.4.2",
|
|
49
49
|
"@astrojs/tailwind": "^6.0.2",
|
|
50
|
-
"@hookform/error-message": "^2.0.1",
|
|
51
50
|
"@hookform/resolvers": "^5.2.2",
|
|
52
51
|
"@iconify-json/mdi": "^1.2.3",
|
|
53
52
|
"@iconify/tailwind": "^1.2.0",
|
|
53
|
+
"@mdxeditor/editor": "^3.49.1",
|
|
54
54
|
"@tailwindcss/typography": "^0.5.19",
|
|
55
55
|
"@tanstack/react-virtual": "^3.13.12",
|
|
56
56
|
"daisyui": "^4.12.24",
|
|
57
57
|
"embla-carousel": "^8.6.0",
|
|
58
58
|
"embla-carousel-wheel-gestures": "^8.1.0",
|
|
59
59
|
"fuse.js": "^7.1.0",
|
|
60
|
-
"i18next": "^25.6.
|
|
60
|
+
"i18next": "^25.6.2",
|
|
61
61
|
"marked": "^16.4.2",
|
|
62
62
|
"react-hook-form": "^7.66.0",
|
|
63
63
|
"yaml": "^2.8.1"
|
|
64
64
|
},
|
|
65
65
|
"devDependencies": {
|
|
66
66
|
"@playwright/test": "^1.56.1",
|
|
67
|
-
"@types/node": "^22.19.
|
|
68
|
-
"@types/react": "^19.2.
|
|
67
|
+
"@types/node": "^22.19.1",
|
|
68
|
+
"@types/react": "^19.2.5",
|
|
69
69
|
"typescript": "^5.9.3",
|
|
70
|
-
"vitest": "^4.0.
|
|
70
|
+
"vitest": "^4.0.9"
|
|
71
71
|
},
|
|
72
72
|
"engines": {
|
|
73
73
|
"node": ">=22"
|
package/playwright.config.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { defineConfig, devices } from "@playwright/test"
|
|
|
5
5
|
*/
|
|
6
6
|
export default defineConfig({
|
|
7
7
|
testDir: "./__e2e__",
|
|
8
|
+
globalTeardown: "./__e2e__/global.teardown.ts",
|
|
8
9
|
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
|
9
10
|
forbidOnly: !!process.env.CI,
|
|
10
11
|
/* Retry on CI only */
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { ReactNode } from "react"
|
|
2
|
+
import {
|
|
3
|
+
type ArrayPath,
|
|
4
|
+
type Control,
|
|
5
|
+
type FieldValues,
|
|
6
|
+
useFieldArray,
|
|
7
|
+
type UseFieldArrayAppend,
|
|
8
|
+
} from "react-hook-form"
|
|
9
|
+
|
|
10
|
+
import Icon from "../../../components/Icon"
|
|
11
|
+
import { useI18n } from "../../../i18n/react/useI18n"
|
|
12
|
+
import ErrorMessage from "./atoms/ErrorMessage"
|
|
13
|
+
import Hint from "./atoms/Hint"
|
|
14
|
+
import Legend from "./atoms/Legend"
|
|
15
|
+
import { useFieldError } from "./hooks/use-field-error"
|
|
16
|
+
|
|
17
|
+
export default function DynamicArray<TFieldValues extends FieldValues>({
|
|
18
|
+
control,
|
|
19
|
+
name,
|
|
20
|
+
label,
|
|
21
|
+
hint,
|
|
22
|
+
renderElement,
|
|
23
|
+
addButton,
|
|
24
|
+
}: {
|
|
25
|
+
name: ArrayPath<TFieldValues>
|
|
26
|
+
label: string
|
|
27
|
+
hint?: string
|
|
28
|
+
control: Control<TFieldValues>
|
|
29
|
+
renderElement: (index: number) => ReactNode
|
|
30
|
+
addButton: {
|
|
31
|
+
label: string
|
|
32
|
+
onClick: (
|
|
33
|
+
append: UseFieldArrayAppend<TFieldValues, ArrayPath<TFieldValues>>,
|
|
34
|
+
elementIndex: number,
|
|
35
|
+
) => void
|
|
36
|
+
}
|
|
37
|
+
}) {
|
|
38
|
+
const { fields, append, remove } = useFieldArray({
|
|
39
|
+
name,
|
|
40
|
+
control,
|
|
41
|
+
})
|
|
42
|
+
const { t } = useI18n()
|
|
43
|
+
const errorMessage = useFieldError({ control, name })
|
|
44
|
+
return (
|
|
45
|
+
<fieldset key={name}>
|
|
46
|
+
<Legend label={label} />
|
|
47
|
+
<div className="flex w-full flex-col divide-y divide-gray-300 overflow-hidden rounded-lg border border-gray-300 bg-gray-100 shadow-sm">
|
|
48
|
+
{fields.map((field, index) => (
|
|
49
|
+
<div className="flex w-full items-center gap-2 p-2" key={field.id}>
|
|
50
|
+
<div className="flex grow flex-col">{renderElement(index)}</div>
|
|
51
|
+
<button
|
|
52
|
+
className="flex items-center p-2 text-gray-600 transition-colors ease-in-out hover:text-rose-800"
|
|
53
|
+
type="button"
|
|
54
|
+
onClick={() => remove(index)}
|
|
55
|
+
>
|
|
56
|
+
<Icon className="mdi--remove" ariaLabel={t("ln.admin.remove")} />
|
|
57
|
+
</button>
|
|
58
|
+
</div>
|
|
59
|
+
))}
|
|
60
|
+
<button
|
|
61
|
+
type="button"
|
|
62
|
+
className="p-4 text-sm font-bold text-gray-500 transition-colors ease-in-out hover:bg-gray-200"
|
|
63
|
+
onClick={() => {
|
|
64
|
+
addButton.onClick(append, fields.length)
|
|
65
|
+
}}
|
|
66
|
+
>
|
|
67
|
+
{t(addButton.label)}
|
|
68
|
+
</button>
|
|
69
|
+
</div>
|
|
70
|
+
<ErrorMessage message={errorMessage} />
|
|
71
|
+
<Hint label={hint} />
|
|
72
|
+
</fieldset>
|
|
73
|
+
)
|
|
74
|
+
}
|