lightnet 3.12.0 → 3.12.2
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 +22 -0
- package/__e2e__/admin.spec.ts +1 -365
- package/__e2e__/fixtures/basics/node_modules/.bin/astro +2 -2
- package/__e2e__/fixtures/basics/package.json +3 -3
- package/package.json +8 -15
- package/src/astro-integration/config.ts +1 -22
- package/src/astro-integration/integration.ts +4 -39
- package/src/content/content-schema.ts +0 -7
- package/src/content/get-media-items.ts +1 -47
- package/src/i18n/translate.ts +1 -1
- package/src/i18n/translations/TRANSLATION-STATUS.md +37 -12
- package/src/i18n/translations/en.yml +5 -0
- package/src/i18n/translations.ts +0 -7
- package/src/layouts/Page.astro +2 -0
- package/src/layouts/global.css +3 -0
- package/src/pages/details-page/components/main-details/EditButton.astro +2 -4
- package/src/pages/search-page/components/SearchFilter.tsx +1 -1
- package/src/admin/api/fs/write-file.ts +0 -53
- package/src/admin/components/form/DynamicArray.tsx +0 -129
- package/src/admin/components/form/Input.tsx +0 -68
- package/src/admin/components/form/LazyLoadedMarkdownEditor.tsx +0 -109
- package/src/admin/components/form/MarkdownEditor.tsx +0 -62
- package/src/admin/components/form/Select.tsx +0 -71
- package/src/admin/components/form/SubmitButton.tsx +0 -86
- package/src/admin/components/form/atoms/Button.tsx +0 -27
- package/src/admin/components/form/atoms/ErrorMessage.tsx +0 -13
- package/src/admin/components/form/atoms/FileUpload.tsx +0 -190
- package/src/admin/components/form/atoms/Hint.tsx +0 -19
- package/src/admin/components/form/atoms/Label.tsx +0 -37
- package/src/admin/components/form/hooks/use-field-dirty.tsx +0 -12
- package/src/admin/components/form/hooks/use-field-error.tsx +0 -34
- package/src/admin/components/form/utils/get-border-class.ts +0 -22
- package/src/admin/i18n/admin-i18n.ts +0 -21
- package/src/admin/i18n/translations/en.yml +0 -50
- package/src/admin/i18n/translations.ts +0 -5
- package/src/admin/pages/AdminRoute.astro +0 -14
- package/src/admin/pages/media/EditForm.tsx +0 -136
- package/src/admin/pages/media/EditRoute.astro +0 -100
- package/src/admin/pages/media/fields/Authors.tsx +0 -44
- package/src/admin/pages/media/fields/Categories.tsx +0 -49
- package/src/admin/pages/media/fields/Collections.tsx +0 -68
- package/src/admin/pages/media/fields/Content.tsx +0 -150
- package/src/admin/pages/media/fields/Image.tsx +0 -119
- package/src/admin/pages/media/file-system.ts +0 -41
- package/src/admin/pages/media/media-item-store.ts +0 -61
- package/src/admin/types/media-item.ts +0 -81
- package/src/api/media/[mediaId].ts +0 -16
|
@@ -4,15 +4,21 @@ This autogenerated report shows all built-in languages and the current status of
|
|
|
4
4
|
|
|
5
5
|
## **AR** ([ar.yml](./ar.yml))
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Missing keys:
|
|
8
|
+
|
|
9
|
+
- ln.details.edit
|
|
8
10
|
|
|
9
11
|
## **BN** ([bn.yml](./bn.yml))
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
Missing keys:
|
|
14
|
+
|
|
15
|
+
- ln.details.edit
|
|
12
16
|
|
|
13
17
|
## **DE** ([de.yml](./de.yml))
|
|
14
18
|
|
|
15
|
-
|
|
19
|
+
Missing keys:
|
|
20
|
+
|
|
21
|
+
- ln.details.edit
|
|
16
22
|
|
|
17
23
|
## **EN** ([en.yml](./en.yml))
|
|
18
24
|
|
|
@@ -20,19 +26,27 @@ All keys have been translated. ✅
|
|
|
20
26
|
|
|
21
27
|
## **ES** ([es.yml](./es.yml))
|
|
22
28
|
|
|
23
|
-
|
|
29
|
+
Missing keys:
|
|
30
|
+
|
|
31
|
+
- ln.details.edit
|
|
24
32
|
|
|
25
33
|
## **FI** ([fi.yml](./fi.yml))
|
|
26
34
|
|
|
27
|
-
|
|
35
|
+
Missing keys:
|
|
36
|
+
|
|
37
|
+
- ln.details.edit
|
|
28
38
|
|
|
29
39
|
## **FR** ([fr.yml](./fr.yml))
|
|
30
40
|
|
|
31
|
-
|
|
41
|
+
Missing keys:
|
|
42
|
+
|
|
43
|
+
- ln.details.edit
|
|
32
44
|
|
|
33
45
|
## **HI** ([hi.yml](./hi.yml))
|
|
34
46
|
|
|
35
|
-
|
|
47
|
+
Missing keys:
|
|
48
|
+
|
|
49
|
+
- ln.details.edit
|
|
36
50
|
|
|
37
51
|
## **KK** ([kk.yml](./kk.yml))
|
|
38
52
|
|
|
@@ -40,23 +54,34 @@ Missing keys:
|
|
|
40
54
|
|
|
41
55
|
- ln.previous
|
|
42
56
|
- ln.next
|
|
57
|
+
- ln.details.edit
|
|
43
58
|
|
|
44
59
|
## **PT** ([pt.yml](./pt.yml))
|
|
45
60
|
|
|
46
|
-
|
|
61
|
+
Missing keys:
|
|
62
|
+
|
|
63
|
+
- ln.details.edit
|
|
47
64
|
|
|
48
65
|
## **RU** ([ru.yml](./ru.yml))
|
|
49
66
|
|
|
50
|
-
|
|
67
|
+
Missing keys:
|
|
68
|
+
|
|
69
|
+
- ln.details.edit
|
|
51
70
|
|
|
52
71
|
## **UK** ([uk.yml](./uk.yml))
|
|
53
72
|
|
|
54
|
-
|
|
73
|
+
Missing keys:
|
|
74
|
+
|
|
75
|
+
- ln.details.edit
|
|
55
76
|
|
|
56
77
|
## **UR** ([ur.yml](./ur.yml))
|
|
57
78
|
|
|
58
|
-
|
|
79
|
+
Missing keys:
|
|
80
|
+
|
|
81
|
+
- ln.details.edit
|
|
59
82
|
|
|
60
83
|
## **ZH** ([zh.yml](./zh.yml))
|
|
61
84
|
|
|
62
|
-
|
|
85
|
+
Missing keys:
|
|
86
|
+
|
|
87
|
+
- ln.details.edit
|
|
@@ -113,6 +113,11 @@ ln.details.open: Open
|
|
|
113
113
|
# Used on: https://sk8-ministries.pages.dev/en/media/ollie-with-integrity--en/
|
|
114
114
|
ln.details.share: Share
|
|
115
115
|
|
|
116
|
+
# Button label to start editing an item
|
|
117
|
+
#
|
|
118
|
+
# English: Edit
|
|
119
|
+
ln.details.edit: Edit
|
|
120
|
+
|
|
116
121
|
# Label for indicating that the media item belongs to a collection.
|
|
117
122
|
#
|
|
118
123
|
# English: Part of collection
|
package/src/i18n/translations.ts
CHANGED
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
import YAML from "yaml"
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
type AdminTranslationKey,
|
|
5
|
-
builtInAdminTranslations,
|
|
6
|
-
} from "../admin/i18n/translations"
|
|
7
|
-
|
|
8
3
|
const builtInTranslations = {
|
|
9
4
|
ar: () => import("./translations/ar.yml?raw"),
|
|
10
5
|
bn: () => import("./translations/bn.yml?raw"),
|
|
@@ -39,7 +34,6 @@ const userTranslations = Object.fromEntries(
|
|
|
39
34
|
|
|
40
35
|
export const loadTranslations = async (bcp47: string) => ({
|
|
41
36
|
...(await loadBuiltInTranslations(builtInTranslations, bcp47)),
|
|
42
|
-
...(await loadBuiltInTranslations(builtInAdminTranslations, bcp47)),
|
|
43
37
|
...(await loadUserTranslations(bcp47)),
|
|
44
38
|
})
|
|
45
39
|
|
|
@@ -95,4 +89,3 @@ export type LightNetTranslationKey =
|
|
|
95
89
|
| "ln.search.title"
|
|
96
90
|
| "ln.share.url-copied-to-clipboard"
|
|
97
91
|
| "ln.footer.powered-by-lightnet"
|
|
98
|
-
| AdminTranslationKey
|
package/src/layouts/Page.astro
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
---
|
|
2
|
-
import config from "virtual:lightnet/config"
|
|
3
|
-
|
|
4
2
|
import Icon from "../../../../components/Icon"
|
|
5
3
|
|
|
6
4
|
interface Props {
|
|
@@ -13,10 +11,10 @@ const { mediaId } = Astro.props
|
|
|
13
11
|
<a
|
|
14
12
|
class="hidden cursor-pointer items-center gap-2 font-bold text-gray-700 underline"
|
|
15
13
|
id="edit-btn"
|
|
16
|
-
data-admin-enabled={
|
|
14
|
+
data-admin-enabled={false}
|
|
17
15
|
href={`/admin/media/${mediaId}`}
|
|
18
16
|
><Icon className="mdi--square-edit-outline" ariaLabel="" />
|
|
19
|
-
{Astro.locals.i18n.t("ln.
|
|
17
|
+
{Astro.locals.i18n.t("ln.details.edit")}</a
|
|
20
18
|
>
|
|
21
19
|
<script>
|
|
22
20
|
const btn: HTMLAnchorElement | null = document.querySelector("#edit-btn")
|
|
@@ -48,7 +48,7 @@ export default function SearchFilter({
|
|
|
48
48
|
<label className="mb-2 flex items-center gap-2 rounded-2xl border border-gray-300 bg-white px-4 py-3 shadow-inner outline outline-2 outline-offset-2 outline-transparent transition-all ease-in-out focus-within:outline-gray-300">
|
|
49
49
|
<input
|
|
50
50
|
type="search"
|
|
51
|
-
className="grow placeholder-gray-500 focus:outline-none"
|
|
51
|
+
className="grow bg-white placeholder-gray-500 focus:outline-none"
|
|
52
52
|
name="search"
|
|
53
53
|
ref={searchInput}
|
|
54
54
|
placeholder={t("ln.search.placeholder")}
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { createWriteStream } from "node:fs"
|
|
2
|
-
import { mkdir, rename, rm } from "node:fs/promises"
|
|
3
|
-
import { dirname, isAbsolute, relative, resolve } from "node:path"
|
|
4
|
-
import { Writable } from "node:stream"
|
|
5
|
-
import { fileURLToPath } from "node:url"
|
|
6
|
-
|
|
7
|
-
import type { APIRoute } from "astro"
|
|
8
|
-
import { root } from "astro:config/server"
|
|
9
|
-
|
|
10
|
-
export const prerender = false
|
|
11
|
-
|
|
12
|
-
export const POST: APIRoute = async ({ request }) => {
|
|
13
|
-
const rootDirPath = fileURLToPath(root)
|
|
14
|
-
const requestedPath = new URL(request.url).searchParams.get("path")
|
|
15
|
-
if (!requestedPath) {
|
|
16
|
-
throw new Error("'path' search param is undefined.")
|
|
17
|
-
}
|
|
18
|
-
if (isAbsolute(requestedPath)) {
|
|
19
|
-
throw new Error("Absolute paths are not allowed.")
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const targetPath = resolve(rootDirPath, requestedPath)
|
|
23
|
-
const relativeToRoot = relative(rootDirPath, targetPath)
|
|
24
|
-
if (
|
|
25
|
-
relativeToRoot.startsWith("..") ||
|
|
26
|
-
relativeToRoot === "" ||
|
|
27
|
-
isAbsolute(relativeToRoot)
|
|
28
|
-
) {
|
|
29
|
-
throw new Error("Path escapes project root.")
|
|
30
|
-
}
|
|
31
|
-
const { body } = request
|
|
32
|
-
if (!body) {
|
|
33
|
-
throw new Error("Request body missing.")
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const targetDir = dirname(targetPath)
|
|
37
|
-
await mkdir(targetDir, { recursive: true })
|
|
38
|
-
|
|
39
|
-
const tmpPath = `${targetPath}.tmp-${Date.now()}`
|
|
40
|
-
try {
|
|
41
|
-
await body.pipeTo(Writable.toWeb(createWriteStream(tmpPath)))
|
|
42
|
-
await rename(tmpPath, targetPath)
|
|
43
|
-
} finally {
|
|
44
|
-
await rm(tmpPath, { force: true }).catch(() => {})
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
return new Response(JSON.stringify({ status: "ok" }), {
|
|
48
|
-
status: 200,
|
|
49
|
-
headers: {
|
|
50
|
-
"Content-Type": "application/json",
|
|
51
|
-
},
|
|
52
|
-
})
|
|
53
|
-
}
|
|
@@ -1,129 +0,0 @@
|
|
|
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/use-i18n"
|
|
12
|
-
import ErrorMessage from "./atoms/ErrorMessage"
|
|
13
|
-
import Hint from "./atoms/Hint"
|
|
14
|
-
import Label from "./atoms/Label"
|
|
15
|
-
import { useFieldDirty } from "./hooks/use-field-dirty"
|
|
16
|
-
import { useFieldError } from "./hooks/use-field-error"
|
|
17
|
-
import { getBorderClass } from "./utils/get-border-class"
|
|
18
|
-
|
|
19
|
-
export default function DynamicArray<TFieldValues extends FieldValues>({
|
|
20
|
-
control,
|
|
21
|
-
name,
|
|
22
|
-
label,
|
|
23
|
-
required = false,
|
|
24
|
-
hint,
|
|
25
|
-
renderElement,
|
|
26
|
-
renderElementMeta,
|
|
27
|
-
renderAddButton,
|
|
28
|
-
}: {
|
|
29
|
-
name: ArrayPath<TFieldValues>
|
|
30
|
-
required?: boolean
|
|
31
|
-
label: string
|
|
32
|
-
hint?: string
|
|
33
|
-
control: Control<TFieldValues>
|
|
34
|
-
renderElement: (index: number) => ReactNode
|
|
35
|
-
renderElementMeta?: (index: number) => ReactNode
|
|
36
|
-
renderAddButton: (args: {
|
|
37
|
-
addElement: UseFieldArrayAppend<TFieldValues, ArrayPath<TFieldValues>>
|
|
38
|
-
index: number
|
|
39
|
-
}) => ReactNode
|
|
40
|
-
}) {
|
|
41
|
-
const { fields, append, remove, swap } = useFieldArray({
|
|
42
|
-
name,
|
|
43
|
-
control,
|
|
44
|
-
})
|
|
45
|
-
const errorMessage = useFieldError({ control, name })
|
|
46
|
-
const isDirty = useFieldDirty({ control, name })
|
|
47
|
-
return (
|
|
48
|
-
<fieldset key={name}>
|
|
49
|
-
<legend>
|
|
50
|
-
<Label
|
|
51
|
-
required={required}
|
|
52
|
-
label={label}
|
|
53
|
-
isDirty={isDirty}
|
|
54
|
-
isInvalid={!!errorMessage}
|
|
55
|
-
/>
|
|
56
|
-
</legend>
|
|
57
|
-
|
|
58
|
-
<div
|
|
59
|
-
className={`flex w-full flex-col gap-1 rounded-xl rounded-ss-none ${getBorderClass({ isDirty, errorMessage })} bg-slate-200 p-1 shadow-inner`}
|
|
60
|
-
>
|
|
61
|
-
{fields.map((field, index) => (
|
|
62
|
-
<div
|
|
63
|
-
className="w-full gap-2 rounded-xl bg-slate-50 px-2 pb-4 shadow-sm"
|
|
64
|
-
key={field.id}
|
|
65
|
-
>
|
|
66
|
-
<div
|
|
67
|
-
className={`flex items-center ${renderElementMeta ? "justify-between" : "justify-end"}`}
|
|
68
|
-
>
|
|
69
|
-
{renderElementMeta && renderElementMeta(index)}
|
|
70
|
-
<div className="-me-2 flex">
|
|
71
|
-
<ItemActionButton
|
|
72
|
-
icon="mdi--arrow-up"
|
|
73
|
-
label="ln.admin.move-up"
|
|
74
|
-
disabled={index === 0}
|
|
75
|
-
onClick={() => swap(index, index - 1)}
|
|
76
|
-
/>
|
|
77
|
-
<ItemActionButton
|
|
78
|
-
icon="mdi--arrow-down"
|
|
79
|
-
label="ln.admin.move-down"
|
|
80
|
-
disabled={index === fields.length - 1}
|
|
81
|
-
onClick={() => swap(index, index + 1)}
|
|
82
|
-
/>
|
|
83
|
-
<ItemActionButton
|
|
84
|
-
icon="mdi--remove"
|
|
85
|
-
label="ln.admin.remove"
|
|
86
|
-
onClick={() => remove(index)}
|
|
87
|
-
className="hover:!text-rose-800"
|
|
88
|
-
/>
|
|
89
|
-
</div>
|
|
90
|
-
</div>
|
|
91
|
-
|
|
92
|
-
{renderElement(index)}
|
|
93
|
-
</div>
|
|
94
|
-
))}
|
|
95
|
-
<div className="flex flex-col items-center py-2">
|
|
96
|
-
{renderAddButton({ addElement: append, index: fields.length })}
|
|
97
|
-
</div>
|
|
98
|
-
</div>
|
|
99
|
-
<ErrorMessage message={errorMessage} />
|
|
100
|
-
<Hint preserveSpace={true} label={hint} />
|
|
101
|
-
</fieldset>
|
|
102
|
-
)
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
function ItemActionButton({
|
|
106
|
-
label,
|
|
107
|
-
icon,
|
|
108
|
-
onClick,
|
|
109
|
-
disabled = false,
|
|
110
|
-
className,
|
|
111
|
-
}: {
|
|
112
|
-
label: string
|
|
113
|
-
icon: string
|
|
114
|
-
disabled?: boolean
|
|
115
|
-
onClick: () => void
|
|
116
|
-
className?: string
|
|
117
|
-
}) {
|
|
118
|
-
const { t } = useI18n()
|
|
119
|
-
return (
|
|
120
|
-
<button
|
|
121
|
-
className={`flex items-center rounded-xl p-2 text-slate-600 transition-colors ease-in-out hover:text-sky-700 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-sky-700 disabled:text-slate-300 ${className}`}
|
|
122
|
-
type="button"
|
|
123
|
-
disabled={disabled}
|
|
124
|
-
onClick={onClick}
|
|
125
|
-
>
|
|
126
|
-
<Icon className={icon} ariaLabel={t(label)} />
|
|
127
|
-
</button>
|
|
128
|
-
)
|
|
129
|
-
}
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import type { InputHTMLAttributes } from "react"
|
|
2
|
-
import {
|
|
3
|
-
type Control,
|
|
4
|
-
type FieldValues,
|
|
5
|
-
type Path,
|
|
6
|
-
type RegisterOptions,
|
|
7
|
-
} from "react-hook-form"
|
|
8
|
-
|
|
9
|
-
import ErrorMessage from "./atoms/ErrorMessage"
|
|
10
|
-
import Hint from "./atoms/Hint"
|
|
11
|
-
import Label from "./atoms/Label"
|
|
12
|
-
import { useFieldDirty } from "./hooks/use-field-dirty"
|
|
13
|
-
import { useFieldError } from "./hooks/use-field-error"
|
|
14
|
-
import { getBorderClass } from "./utils/get-border-class"
|
|
15
|
-
|
|
16
|
-
type Props<TFieldValues extends FieldValues> = {
|
|
17
|
-
name: Path<TFieldValues>
|
|
18
|
-
required?: boolean
|
|
19
|
-
label?: string
|
|
20
|
-
labelSize?: "small" | "medium"
|
|
21
|
-
hint?: string
|
|
22
|
-
preserveHintSpace?: boolean
|
|
23
|
-
control: Control<TFieldValues>
|
|
24
|
-
registerOptions?: RegisterOptions<TFieldValues>
|
|
25
|
-
} & InputHTMLAttributes<HTMLInputElement>
|
|
26
|
-
|
|
27
|
-
export default function Input<TFieldValues extends FieldValues>({
|
|
28
|
-
name,
|
|
29
|
-
label,
|
|
30
|
-
labelSize,
|
|
31
|
-
hint,
|
|
32
|
-
preserveHintSpace = true,
|
|
33
|
-
control,
|
|
34
|
-
className,
|
|
35
|
-
required = false,
|
|
36
|
-
registerOptions,
|
|
37
|
-
...inputProps
|
|
38
|
-
}: Props<TFieldValues>) {
|
|
39
|
-
const isDirty = useFieldDirty({ control, name })
|
|
40
|
-
const errorMessage = useFieldError({ control, name })
|
|
41
|
-
|
|
42
|
-
return (
|
|
43
|
-
<div key={name} className="group flex w-full flex-col">
|
|
44
|
-
{label && (
|
|
45
|
-
<label htmlFor={name}>
|
|
46
|
-
<Label
|
|
47
|
-
label={label}
|
|
48
|
-
size={labelSize}
|
|
49
|
-
isDirty={isDirty}
|
|
50
|
-
isInvalid={!!errorMessage}
|
|
51
|
-
required={required}
|
|
52
|
-
/>
|
|
53
|
-
</label>
|
|
54
|
-
)}
|
|
55
|
-
|
|
56
|
-
<input
|
|
57
|
-
className={`rounded-xl ${getBorderClass({ isDirty, errorMessage })} px-4 py-3 shadow-inner disabled:text-slate-500 ${label ? "rounded-ss-none" : ""} ${className}`}
|
|
58
|
-
id={name}
|
|
59
|
-
aria-invalid={!!errorMessage}
|
|
60
|
-
aria-required={required}
|
|
61
|
-
{...control.register(name, registerOptions)}
|
|
62
|
-
{...inputProps}
|
|
63
|
-
/>
|
|
64
|
-
<ErrorMessage message={errorMessage} />
|
|
65
|
-
<Hint preserveSpace={preserveHintSpace} label={hint} />
|
|
66
|
-
</div>
|
|
67
|
-
)
|
|
68
|
-
}
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
import "@mdxeditor/editor/style.css"
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
BlockTypeSelect,
|
|
5
|
-
BoldItalicUnderlineToggles,
|
|
6
|
-
type CodeBlockEditorProps,
|
|
7
|
-
codeBlockPlugin,
|
|
8
|
-
CreateLink,
|
|
9
|
-
diffSourcePlugin,
|
|
10
|
-
DiffSourceToggleWrapper,
|
|
11
|
-
headingsPlugin,
|
|
12
|
-
linkDialogPlugin,
|
|
13
|
-
linkPlugin,
|
|
14
|
-
listsPlugin,
|
|
15
|
-
ListsToggle,
|
|
16
|
-
markdownShortcutPlugin,
|
|
17
|
-
MDXEditor,
|
|
18
|
-
quotePlugin,
|
|
19
|
-
toolbarPlugin,
|
|
20
|
-
UndoRedo,
|
|
21
|
-
useCodeBlockEditorContext,
|
|
22
|
-
} from "@mdxeditor/editor"
|
|
23
|
-
import {
|
|
24
|
-
type Control,
|
|
25
|
-
Controller,
|
|
26
|
-
type FieldValues,
|
|
27
|
-
get,
|
|
28
|
-
type Path,
|
|
29
|
-
} from "react-hook-form"
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* IMPORTANT: Do not import this component directly. It is
|
|
33
|
-
* very big. Use it with React lazy loading.
|
|
34
|
-
*/
|
|
35
|
-
export default function LazyLoadedMarkdownEditor<
|
|
36
|
-
TFieldValues extends FieldValues,
|
|
37
|
-
>({
|
|
38
|
-
control,
|
|
39
|
-
name,
|
|
40
|
-
}: {
|
|
41
|
-
name: Path<TFieldValues>
|
|
42
|
-
control: Control<TFieldValues>
|
|
43
|
-
}) {
|
|
44
|
-
return (
|
|
45
|
-
<Controller
|
|
46
|
-
control={control}
|
|
47
|
-
name={name}
|
|
48
|
-
render={({
|
|
49
|
-
field: { onBlur, onChange, value, ref },
|
|
50
|
-
formState: { defaultValues },
|
|
51
|
-
}) => (
|
|
52
|
-
<MDXEditor
|
|
53
|
-
markdown={value ?? ""}
|
|
54
|
-
onBlur={onBlur}
|
|
55
|
-
onChange={onChange}
|
|
56
|
-
contentEditableClassName="prose bg-slate-50 h-80 w-full max-w-full overflow-y-auto"
|
|
57
|
-
ref={ref}
|
|
58
|
-
onError={(error) =>
|
|
59
|
-
console.error("Error while editing markdown", error)
|
|
60
|
-
}
|
|
61
|
-
plugins={[
|
|
62
|
-
headingsPlugin(),
|
|
63
|
-
listsPlugin(),
|
|
64
|
-
linkPlugin(),
|
|
65
|
-
codeBlockPlugin({
|
|
66
|
-
codeBlockEditorDescriptors: [
|
|
67
|
-
{
|
|
68
|
-
match: () => true,
|
|
69
|
-
priority: 0,
|
|
70
|
-
Editor: TextAreaCodeEditor,
|
|
71
|
-
},
|
|
72
|
-
],
|
|
73
|
-
}),
|
|
74
|
-
linkDialogPlugin(),
|
|
75
|
-
quotePlugin(),
|
|
76
|
-
markdownShortcutPlugin(),
|
|
77
|
-
diffSourcePlugin({ diffMarkdown: get(defaultValues, name) }),
|
|
78
|
-
toolbarPlugin({
|
|
79
|
-
toolbarContents: () => (
|
|
80
|
-
<DiffSourceToggleWrapper>
|
|
81
|
-
<UndoRedo />
|
|
82
|
-
<BoldItalicUnderlineToggles />
|
|
83
|
-
<BlockTypeSelect />
|
|
84
|
-
<ListsToggle options={["bullet", "number"]} />
|
|
85
|
-
<CreateLink />
|
|
86
|
-
</DiffSourceToggleWrapper>
|
|
87
|
-
),
|
|
88
|
-
toolbarClassName: "!rounded-none !bg-slate-200",
|
|
89
|
-
}),
|
|
90
|
-
]}
|
|
91
|
-
/>
|
|
92
|
-
)}
|
|
93
|
-
/>
|
|
94
|
-
)
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
function TextAreaCodeEditor(props: CodeBlockEditorProps) {
|
|
98
|
-
const cb = useCodeBlockEditorContext()
|
|
99
|
-
return (
|
|
100
|
-
<div onKeyDown={(e) => e.nativeEvent.stopImmediatePropagation()}>
|
|
101
|
-
<textarea
|
|
102
|
-
rows={3}
|
|
103
|
-
cols={20}
|
|
104
|
-
defaultValue={props.code}
|
|
105
|
-
onChange={(e) => cb.setCode(e.target.value)}
|
|
106
|
-
/>
|
|
107
|
-
</div>
|
|
108
|
-
)
|
|
109
|
-
}
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import { lazy, Suspense } from "react"
|
|
2
|
-
import { type Control, type FieldValues, type Path } from "react-hook-form"
|
|
3
|
-
|
|
4
|
-
import ErrorMessage from "./atoms/ErrorMessage"
|
|
5
|
-
import Hint from "./atoms/Hint"
|
|
6
|
-
import Label from "./atoms/Label"
|
|
7
|
-
import { useFieldDirty } from "./hooks/use-field-dirty"
|
|
8
|
-
import { useFieldError } from "./hooks/use-field-error"
|
|
9
|
-
import { getBorderClass } from "./utils/get-border-class"
|
|
10
|
-
|
|
11
|
-
const LazyLoadedMarkdownEditor = lazy(
|
|
12
|
-
() => import("./LazyLoadedMarkdownEditor"),
|
|
13
|
-
)
|
|
14
|
-
|
|
15
|
-
export default function MarkdownEditor<TFieldValues extends FieldValues>({
|
|
16
|
-
control,
|
|
17
|
-
name,
|
|
18
|
-
required = false,
|
|
19
|
-
label,
|
|
20
|
-
hint,
|
|
21
|
-
}: {
|
|
22
|
-
name: Path<TFieldValues>
|
|
23
|
-
label: string
|
|
24
|
-
required?: boolean
|
|
25
|
-
hint?: string
|
|
26
|
-
control: Control<TFieldValues>
|
|
27
|
-
}) {
|
|
28
|
-
const isDirty = useFieldDirty({ control, name })
|
|
29
|
-
const errorMessage = useFieldError({ control, name })
|
|
30
|
-
|
|
31
|
-
return (
|
|
32
|
-
<fieldset key={name} className="group">
|
|
33
|
-
<legend>
|
|
34
|
-
<Label
|
|
35
|
-
required={required}
|
|
36
|
-
label={label}
|
|
37
|
-
isDirty={isDirty}
|
|
38
|
-
isInvalid={!!errorMessage}
|
|
39
|
-
/>
|
|
40
|
-
</legend>
|
|
41
|
-
|
|
42
|
-
<div
|
|
43
|
-
className={`overflow-hidden rounded-xl rounded-ss-none ${getBorderClass({ isDirty, errorMessage, focusWithin: true })} shadow-sm`}
|
|
44
|
-
>
|
|
45
|
-
<Suspense
|
|
46
|
-
fallback={
|
|
47
|
-
<div className="h-[22.75rem] w-full bg-slate-50">
|
|
48
|
-
<div className="h-10 bg-slate-100"></div>
|
|
49
|
-
</div>
|
|
50
|
-
}
|
|
51
|
-
>
|
|
52
|
-
<LazyLoadedMarkdownEditor
|
|
53
|
-
control={control as Control<any>}
|
|
54
|
-
name={name}
|
|
55
|
-
/>
|
|
56
|
-
</Suspense>
|
|
57
|
-
</div>
|
|
58
|
-
<ErrorMessage message={errorMessage} />
|
|
59
|
-
<Hint preserveSpace={true} label={hint} />
|
|
60
|
-
</fieldset>
|
|
61
|
-
)
|
|
62
|
-
}
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import { type Control, type FieldValues, type Path } from "react-hook-form"
|
|
2
|
-
|
|
3
|
-
import Icon from "../../../components/Icon"
|
|
4
|
-
import ErrorMessage from "./atoms/ErrorMessage"
|
|
5
|
-
import Hint from "./atoms/Hint"
|
|
6
|
-
import Label from "./atoms/Label"
|
|
7
|
-
import { useFieldDirty } from "./hooks/use-field-dirty"
|
|
8
|
-
import { useFieldError } from "./hooks/use-field-error"
|
|
9
|
-
import { getBorderClass } from "./utils/get-border-class"
|
|
10
|
-
|
|
11
|
-
export default function Select<TFieldValues extends FieldValues>({
|
|
12
|
-
name,
|
|
13
|
-
label,
|
|
14
|
-
labelSize,
|
|
15
|
-
required = false,
|
|
16
|
-
control,
|
|
17
|
-
defaultValue,
|
|
18
|
-
hint,
|
|
19
|
-
preserveHintSpace = true,
|
|
20
|
-
options,
|
|
21
|
-
}: {
|
|
22
|
-
name: Path<TFieldValues>
|
|
23
|
-
label?: string
|
|
24
|
-
labelSize?: "small" | "medium"
|
|
25
|
-
hint?: string
|
|
26
|
-
required?: boolean
|
|
27
|
-
preserveHintSpace?: boolean
|
|
28
|
-
defaultValue?: string
|
|
29
|
-
control: Control<TFieldValues>
|
|
30
|
-
options: { id: string; labelText?: string }[]
|
|
31
|
-
}) {
|
|
32
|
-
const isDirty = useFieldDirty({ control, name })
|
|
33
|
-
const errorMessage = useFieldError({ control, name })
|
|
34
|
-
return (
|
|
35
|
-
<div key={name} className="group flex w-full flex-col">
|
|
36
|
-
{label && (
|
|
37
|
-
<label htmlFor={name}>
|
|
38
|
-
<Label
|
|
39
|
-
label={label}
|
|
40
|
-
size={labelSize}
|
|
41
|
-
isDirty={isDirty}
|
|
42
|
-
required={required}
|
|
43
|
-
isInvalid={!!errorMessage}
|
|
44
|
-
/>
|
|
45
|
-
</label>
|
|
46
|
-
)}
|
|
47
|
-
<div className="relative">
|
|
48
|
-
<select
|
|
49
|
-
{...control.register(name)}
|
|
50
|
-
id={name}
|
|
51
|
-
aria-invalid={!!errorMessage}
|
|
52
|
-
aria-required={required}
|
|
53
|
-
defaultValue={defaultValue}
|
|
54
|
-
className={`w-full appearance-none rounded-xl ${getBorderClass({ isDirty, errorMessage })} bg-white px-4 py-3 pe-12 shadow-sm ${label ? "rounded-ss-none" : ""}`}
|
|
55
|
-
>
|
|
56
|
-
{options.map(({ id, labelText }) => (
|
|
57
|
-
<option key={id} value={id}>
|
|
58
|
-
{labelText ?? id}
|
|
59
|
-
</option>
|
|
60
|
-
))}
|
|
61
|
-
</select>
|
|
62
|
-
<Icon
|
|
63
|
-
className="absolute end-3 top-1/2 -translate-y-1/2 text-lg text-slate-600 mdi--chevron-down"
|
|
64
|
-
ariaLabel=""
|
|
65
|
-
/>
|
|
66
|
-
</div>
|
|
67
|
-
<ErrorMessage message={errorMessage} />
|
|
68
|
-
<Hint preserveSpace={preserveHintSpace} label={hint} />
|
|
69
|
-
</div>
|
|
70
|
-
)
|
|
71
|
-
}
|