@team-monolith/cds 1.117.12 → 1.117.14
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/dist/components/AlertDialog/AlertDialogTitle.js +3 -1
- package/dist/components/FileTypeAlertDialog.js +3 -1
- package/dist/components/Pagination.js +7 -3
- package/dist/patterns/LexicalEditor/components/FileSelectInput.js +22 -9
- package/dist/patterns/LexicalEditor/components/InsertImageDialog/ImagePreview.js +3 -1
- package/dist/patterns/LexicalEditor/components/InsertImageDialog/InsertImageDialog.js +3 -1
- package/dist/patterns/LexicalEditor/components/UploadFileDialog/UploadFileDialog.js +2 -1
- package/dist/patterns/LexicalEditor/nodes/FileNode/FileDownloadButton.js +5 -3
- package/dist/patterns/LexicalEditor/nodes/ImageNode/ImageComponent.js +4 -2
- package/dist/patterns/LexicalEditor/nodes/ProblemInputNode/InputComponent.js +7 -4
- package/dist/patterns/LexicalEditor/nodes/ProblemInputNode/SettingForm/FormPlaceholder.js +2 -1
- package/dist/patterns/LexicalEditor/nodes/ProblemInputNode/SettingForm/FormSolution.js +4 -2
- package/dist/patterns/LexicalEditor/nodes/ProblemInputNode/SettingForm/SettingForm.js +19 -15
- package/dist/patterns/LexicalEditor/nodes/ProblemInputNode/SettingForm/TextTypeDropdown.js +6 -2
- package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SelectBox/SelectBoxView.js +18 -9
- package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SelectComponent.js +8 -3
- package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SettingForm/FormSelection.js +23 -4
- package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SettingForm/SettingForm.js +9 -8
- package/dist/patterns/LexicalEditor/nodes/SelfEvaluationNode/EvaluationComponent/Evaluation/SettingForm/FormIconAndLabel/FormIconAndLabel.js +6 -5
- package/dist/patterns/LexicalEditor/nodes/SelfEvaluationNode/EvaluationComponent/Evaluation/SettingForm/FormIconAndLabel/FormLabel.js +7 -5
- package/dist/patterns/LexicalEditor/nodes/SelfEvaluationNode/EvaluationComponent/Evaluation/SettingForm/FormQuestion.js +7 -2
- package/dist/patterns/LexicalEditor/nodes/SelfEvaluationNode/EvaluationComponent/Evaluation/SettingForm/SettingForm.js +7 -4
- package/dist/patterns/LexicalEditor/nodes/SelfEvaluationNode/EvaluationComponent/EvaluationComponent.js +5 -3
- package/dist/patterns/LexicalEditor/nodes/SelfEvaluationNode/iconData.d.ts +1 -1
- package/dist/patterns/LexicalEditor/nodes/SelfEvaluationNode/iconData.js +6 -5
- package/dist/patterns/LexicalEditor/nodes/SheetInputNode/InputComponent.js +9 -2
- package/dist/patterns/LexicalEditor/nodes/SheetInputNode/SettingForm.js +7 -2
- package/dist/patterns/LexicalEditor/nodes/SheetSelectNode/SelectComponent/SelectBox/SelectBoxView.js +10 -2
- package/dist/patterns/LexicalEditor/nodes/SheetSelectNode/SelectComponent/SelectComponent.js +8 -3
- package/dist/patterns/LexicalEditor/nodes/SheetSelectNode/SelectComponent/SettingForm/FormAllowMultipleAnswers.js +3 -1
- package/dist/patterns/LexicalEditor/nodes/SheetSelectNode/SelectComponent/SettingForm/FormSelection.js +20 -3
- package/dist/patterns/LexicalEditor/nodes/SheetSelectNode/SelectComponent/SettingForm/SettingForm.js +10 -9
- package/dist/patterns/LexicalEditor/plugins/ActionsPlugin/index.js +3 -1
- package/dist/patterns/LexicalEditor/plugins/ComponentAdderPlugin/ComponentAdderPlugin.js +8 -2
- package/dist/patterns/LexicalEditor/plugins/ComponentAdderPlugin/useContextMenuOptions.js +33 -31
- package/dist/patterns/LexicalEditor/plugins/ComponentPickerMenuPlugin/ComponentPickerMenuPlugin.js +44 -25
- package/dist/patterns/LexicalEditor/plugins/DragDropPastePlugin/index.js +2 -1
- package/dist/patterns/LexicalEditor/plugins/FloatingLinkEditorPlugin/FloatingLinkEditor.js +3 -1
- package/dist/patterns/ToggleButtonGroup/ToggleButton.js +4 -2
- package/dist/texts.d.ts +12 -0
- package/dist/texts.js +14 -0
- package/package.json +1 -1
|
@@ -17,6 +17,7 @@ import { SquareButton } from "../SquareButton";
|
|
|
17
17
|
import { CloseFillIcon } from "../../icons";
|
|
18
18
|
import { AlertDialogContext } from "./AlertDialogContext";
|
|
19
19
|
import { MOBILE } from "../../foundation/breakpoints";
|
|
20
|
+
import { useTranslation } from "react-i18next";
|
|
20
21
|
export const alertDialogTitleClasses = {
|
|
21
22
|
root: "AlertDialogTitle",
|
|
22
23
|
content: "AlertDialogTitle-content",
|
|
@@ -26,6 +27,7 @@ export const alertDialogTitleClasses = {
|
|
|
26
27
|
export const AlertDialogTitle = React.forwardRef(function AlertDialogTitle(props, ref) {
|
|
27
28
|
const { className, component: Component = "div", onClose, children } = props, other = __rest(props, ["className", "component", "onClose", "children"]);
|
|
28
29
|
const { icon } = React.useContext(AlertDialogContext);
|
|
30
|
+
const { t } = useTranslation();
|
|
29
31
|
const theme = useTheme();
|
|
30
32
|
return (_jsx(_Fragment, { children: _jsxs(Component, Object.assign({}, other, { ref: ref, className: [alertDialogTitleClasses.root, className]
|
|
31
33
|
.filter(Boolean)
|
|
@@ -63,5 +65,5 @@ export const AlertDialogTitle = React.forwardRef(function AlertDialogTitle(props
|
|
|
63
65
|
height: 32px;
|
|
64
66
|
}
|
|
65
67
|
`)}
|
|
66
|
-
`, children: icon })), children] }), onClose && (_jsx(SquareButton, { className: alertDialogTitleClasses.closeButton, color: "icon", size: "small", icon: _jsx(CloseFillIcon, {}), onClick: onClose, "aria-label": "
|
|
68
|
+
`, children: icon })), children] }), onClose && (_jsx(SquareButton, { className: alertDialogTitleClasses.closeButton, color: "icon", size: "small", icon: _jsx(CloseFillIcon, {}), onClick: onClose, "aria-label": t("닫기", { context: "다이얼로그" }) }))] })) }));
|
|
67
69
|
});
|
|
@@ -4,6 +4,7 @@ import { css, useTheme } from "@emotion/react";
|
|
|
4
4
|
import { AlertDialog, AlertDialogActions, AlertDialogContent, AlertDialogTitle, } from "./AlertDialog";
|
|
5
5
|
import { AlertFillIcon } from "../icons";
|
|
6
6
|
import { Button } from "./Button";
|
|
7
|
+
import { useTranslation } from "react-i18next";
|
|
7
8
|
export const fileTypeAlertDialogClasses = {
|
|
8
9
|
root: "FileTypeAlertDialog",
|
|
9
10
|
title: "FileTypeAlertDialog-title",
|
|
@@ -13,11 +14,12 @@ export const fileTypeAlertDialogClasses = {
|
|
|
13
14
|
export function FileTypeAlertDialog(props) {
|
|
14
15
|
const { className, open, onClose, content, openFileBrowser } = props;
|
|
15
16
|
const theme = useTheme();
|
|
17
|
+
const { t } = useTranslation();
|
|
16
18
|
return (_jsxs(AlertDialog, { className: [fileTypeAlertDialogClasses.root, className].filter(Boolean).join(" "), open: open, onClose: onClose, icon: _jsx(AlertFillIcon, { css: css `
|
|
17
19
|
color: ${theme.color.background.danger};
|
|
18
20
|
` }), children: [_jsx(AlertDialogTitle, { className: fileTypeAlertDialogClasses.title, css: css `
|
|
19
21
|
font-weight: 700;
|
|
20
|
-
`, onClose: onClose, children: "
|
|
22
|
+
`, onClose: onClose, children: t("파일 형식을 확인해주세요.") }), _jsx(AlertDialogContent, { className: fileTypeAlertDialogClasses.content, children: content }), _jsxs(AlertDialogActions, { className: fileTypeAlertDialogClasses.actions, children: [_jsx(Button, { color: "grey", label: t("닫기", { context: "다이얼로그" }), size: "medium", onClick: onClose, bold: true }), _jsx(Button, { color: "primary", label: t("다시 업로드"), size: "medium", onClick: () => {
|
|
21
23
|
onClose();
|
|
22
24
|
openFileBrowser();
|
|
23
25
|
}, bold: true })] })] }));
|
|
@@ -16,6 +16,7 @@ import * as React from "react";
|
|
|
16
16
|
import styled from "@emotion/styled";
|
|
17
17
|
import { Pagination as MuiPagination } from "@mui/material";
|
|
18
18
|
import { HOVER } from "../utils/hover";
|
|
19
|
+
import { useTranslation } from "react-i18next";
|
|
19
20
|
export const paginationClasses = {
|
|
20
21
|
root: "Pagination",
|
|
21
22
|
};
|
|
@@ -24,12 +25,15 @@ export const paginationClasses = {
|
|
|
24
25
|
*/
|
|
25
26
|
export const Pagination = React.forwardRef(function Pagination(props, ref) {
|
|
26
27
|
const { className } = props, other = __rest(props, ["className"]);
|
|
28
|
+
const { t } = useTranslation();
|
|
27
29
|
return (_jsx(StyledPagination, Object.assign({ siblingCount: 2, shape: "rounded" }, other, { className: [paginationClasses.root, className].filter(Boolean).join(" "), ref: ref, getItemAriaLabel: (type, page, selected) => {
|
|
28
30
|
if (type === "previous")
|
|
29
|
-
return "이전 페이지로 이동";
|
|
31
|
+
return t("이전 페이지로 이동");
|
|
30
32
|
if (type === "next")
|
|
31
|
-
return "다음 페이지로 이동";
|
|
32
|
-
return
|
|
33
|
+
return t("다음 페이지로 이동");
|
|
34
|
+
return selected
|
|
35
|
+
? t("{{page}}페이지, 선택됨", { page })
|
|
36
|
+
: t("{{page}}페이지", { page });
|
|
33
37
|
} })));
|
|
34
38
|
});
|
|
35
39
|
const StyledPagination = styled(MuiPagination)(({ theme }) => css `
|
|
@@ -15,11 +15,23 @@ import { FileWarningLineIcon, UploadLineIcon } from "../../../icons";
|
|
|
15
15
|
import { css, useTheme } from "@emotion/react";
|
|
16
16
|
import styled from "@emotion/styled";
|
|
17
17
|
import moment from "moment";
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
import { useTranslation } from "react-i18next";
|
|
19
|
+
import { i18n } from "../../../i18n/i18n";
|
|
20
|
+
import { getTexts } from "../../../texts";
|
|
21
|
+
const getFileType = (fileType) => ({
|
|
22
|
+
image: {
|
|
23
|
+
accept: "image/*",
|
|
24
|
+
errorStr: i18n.t("업로드할 수 없는 파일입니다. 이미지 파일을 업로드해주세요."),
|
|
25
|
+
},
|
|
26
|
+
pdf: {
|
|
27
|
+
accept: ".pdf",
|
|
28
|
+
errorStr: i18n.t("업로드할 수 없는 파일입니다. PDF 파일을 업로드해주세요."),
|
|
29
|
+
},
|
|
30
|
+
file: {
|
|
31
|
+
accept: "*",
|
|
32
|
+
errorStr: i18n.t("업로드할 수 없는 파일입니다. 모든 파일을 업로드해주세요."),
|
|
33
|
+
},
|
|
34
|
+
})[fileType];
|
|
23
35
|
const MAX_FILE_SIZE = 1 * 1024 * 1024 * 1024; // 1GB
|
|
24
36
|
/**
|
|
25
37
|
* 파일 탐색기를 열어 특정 파일을 업로드 한 후, 그 경로를 onChange로 전달합니다.
|
|
@@ -31,15 +43,16 @@ export function FileSelectInput(props) {
|
|
|
31
43
|
const [errorMessage, setErrorMessage] = useState(null);
|
|
32
44
|
const inputRef = useRef(null);
|
|
33
45
|
const cdsContext = useContext(CdsContext);
|
|
34
|
-
const { accept,
|
|
46
|
+
const { accept, errorStr } = getFileType(fileType);
|
|
35
47
|
const theme = useTheme();
|
|
48
|
+
const { t } = useTranslation();
|
|
36
49
|
const handleFileChange = (event) => __awaiter(this, void 0, void 0, function* () {
|
|
37
50
|
var _a, _b, _c;
|
|
38
51
|
const file = (_a = event.target.files) === null || _a === void 0 ? void 0 : _a[0];
|
|
39
52
|
if (!file)
|
|
40
53
|
return;
|
|
41
54
|
if (file.size >= MAX_FILE_SIZE) {
|
|
42
|
-
setErrorMessage("
|
|
55
|
+
setErrorMessage(getTexts("errorFileTooLarge"));
|
|
43
56
|
event.target.value = "";
|
|
44
57
|
return;
|
|
45
58
|
}
|
|
@@ -49,7 +62,7 @@ export function FileSelectInput(props) {
|
|
|
49
62
|
? accept.slice(1) === ((_b = file.name.split(".").pop()) === null || _b === void 0 ? void 0 : _b.toLowerCase())
|
|
50
63
|
: new RegExp(accept.replace("*", ".*")).test(file.type));
|
|
51
64
|
if (!isTypeValid) {
|
|
52
|
-
setErrorMessage(
|
|
65
|
+
setErrorMessage(errorStr);
|
|
53
66
|
event.target.value = "";
|
|
54
67
|
return;
|
|
55
68
|
}
|
|
@@ -67,7 +80,7 @@ export function FileSelectInput(props) {
|
|
|
67
80
|
}
|
|
68
81
|
setErrorMessage(null);
|
|
69
82
|
});
|
|
70
|
-
return (_jsxs(_Fragment, { children: [_jsxs(Container, { children: [_jsx(Button, { fullWidth: true, color: "grey", size: "medium", label: "
|
|
83
|
+
return (_jsxs(_Fragment, { children: [_jsxs(Container, { children: [_jsx(Button, { fullWidth: true, color: "grey", size: "medium", label: t("파일 선택하기"), onClick: () => {
|
|
71
84
|
var _a;
|
|
72
85
|
(_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.click();
|
|
73
86
|
}, startIcon: _jsx(UploadLineIcon, {}), bold: bold }), _jsx(IconAndErrorMessage, { children: errorMessage && (_jsxs(_Fragment, { children: [_jsx(FileWarningLineIcon, { css: css `
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import styled from "@emotion/styled";
|
|
3
3
|
import { useState } from "react";
|
|
4
|
+
import { useTranslation } from "react-i18next";
|
|
4
5
|
export function ImagePreview(props) {
|
|
5
6
|
const { src, alt, fallback } = props;
|
|
6
7
|
const [erroredSrc, setErroredSrc] = useState(null);
|
|
7
|
-
|
|
8
|
+
const { t } = useTranslation();
|
|
9
|
+
return (_jsxs(Container, { children: [t("이미지 미리보기"), erroredSrc === src ? (fallback) : (_jsx("img", { src: src, alt: alt, onError: () => setErroredSrc(src) }))] }));
|
|
8
10
|
}
|
|
9
11
|
const Container = styled.div `
|
|
10
12
|
display: flex;
|
|
@@ -12,9 +12,11 @@ import { ImagePreview } from "./ImagePreview";
|
|
|
12
12
|
import { ImageNotAvailable } from "../../nodes/ImageNode/ImageNotAvailable";
|
|
13
13
|
import styled from "@emotion/styled";
|
|
14
14
|
import { debounce } from "lodash";
|
|
15
|
+
import { useTranslation } from "react-i18next";
|
|
15
16
|
export function InsertImageDialog(props) {
|
|
16
17
|
const { title, open, onClose, imageProps, onChange, onDelete, shouldReset } = props;
|
|
17
18
|
const theme = useTheme();
|
|
19
|
+
const { t } = useTranslation();
|
|
18
20
|
const { control, setValue, watch, reset, handleSubmit, subscribe } = useForm({
|
|
19
21
|
defaultValues: imageProps !== null && imageProps !== void 0 ? imageProps : { src: "", altText: "" },
|
|
20
22
|
});
|
|
@@ -49,7 +51,7 @@ export function InsertImageDialog(props) {
|
|
|
49
51
|
handleSubmit(onSubmit)();
|
|
50
52
|
}, disableIconPadding: true, children: [_jsx(StyledAlertDialogTitle, { onClose: handleOnClose, children: title }), _jsx(AlertDialogContent, { children: _jsxs(Inputs, { children: [_jsx(FileSelectInput, { onChange: (value) => {
|
|
51
53
|
setValue("src", value);
|
|
52
|
-
}, fileType: "image" }), _jsx(FormInput, { name: "src", control: control, placeholder: "https://www.pexels.com/photo/n-2848492", size: "medium", label: "URL", fullWidth: true, startIcon: _jsx(LinkIcon, {}) }), previewSrc && (_jsx(ImagePreview, { src: previewSrc, alt: watch("altText"), fallback: _jsx(ImageNotAvailable, {}) })), _jsx(FormInput, { name: "altText", control: control, placeholder: "
|
|
54
|
+
}, fileType: "image" }), _jsx(FormInput, { name: "src", control: control, placeholder: "https://www.pexels.com/photo/n-2848492", size: "medium", label: "URL", fullWidth: true, startIcon: _jsx(LinkIcon, {}) }), previewSrc && (_jsx(ImagePreview, { src: previewSrc, alt: watch("altText"), fallback: _jsx(ImageNotAvailable, {}) })), _jsx(FormInput, { name: "altText", control: control, placeholder: t("삽입하는 이미지에 관한 설명"), size: "medium", label: t("대체 텍스트"), fullWidth: true })] }) }), _jsxs(AlertDialogActions, { children: [_jsx(Button, { type: "submit", fullWidth: true, label: t("삽입하기"), size: "medium", color: "primary", disabled: isDisabled }), onDelete && (_jsx(Button, { color: "danger", size: "medium", fullWidth: true, label: t("삭제하기"), onClick: onDelete }))] })] }));
|
|
53
55
|
}
|
|
54
56
|
const StyledAlertDialog = styled(AlertDialog) `
|
|
55
57
|
gap: 16px;
|
|
@@ -13,6 +13,7 @@ import { useContext, useEffect, useRef } from "react";
|
|
|
13
13
|
import moment from "moment";
|
|
14
14
|
import { CdsContext } from "../../../../CdsProvider";
|
|
15
15
|
import styled from "@emotion/styled";
|
|
16
|
+
import { getTexts } from "../../../../texts";
|
|
16
17
|
/**
|
|
17
18
|
* OS 파일 다이얼로그를 열어 파일을 선택하고 업로드하는 컴포넌트입니다.
|
|
18
19
|
*/
|
|
@@ -29,7 +30,7 @@ export function UploadFileDialog({ open, onClose, onChange, }) {
|
|
|
29
30
|
}
|
|
30
31
|
const MAX_FILE_SIZE = 1 * 1024 * 1024 * 1024; // 1GB
|
|
31
32
|
if (file.size >= MAX_FILE_SIZE) {
|
|
32
|
-
showFileError === null || showFileError === void 0 ? void 0 : showFileError("upload", "
|
|
33
|
+
showFileError === null || showFileError === void 0 ? void 0 : showFileError("upload", getTexts("errorFileTooLarge"));
|
|
33
34
|
event.target.value = "";
|
|
34
35
|
onClose();
|
|
35
36
|
return;
|
|
@@ -15,6 +15,7 @@ import { OverflowTooltip } from "../../../../components/OverflowTooltip";
|
|
|
15
15
|
import { useContext } from "react";
|
|
16
16
|
import { CdsContext } from "../../../../CdsProvider";
|
|
17
17
|
import { HOVER } from "../../../../utils/hover";
|
|
18
|
+
import { useTranslation } from "react-i18next";
|
|
18
19
|
/**
|
|
19
20
|
* 파일 업로드 블록 내에서 삽입하기를 통해 렉시컬에 삽입된 파일블록을 표시하는 컴포넌트입니다. (ReadOnly)
|
|
20
21
|
* images/2025-02-05-10-53-19.png
|
|
@@ -22,18 +23,19 @@ import { HOVER } from "../../../../utils/hover";
|
|
|
22
23
|
export function FileDownloadButton({ fileName, fileSize, fileUrl, }) {
|
|
23
24
|
const cdsContext = useContext(CdsContext);
|
|
24
25
|
const theme = useTheme();
|
|
26
|
+
const { t } = useTranslation();
|
|
25
27
|
const handleDownload = () => __awaiter(this, void 0, void 0, function* () {
|
|
26
28
|
var _a;
|
|
27
29
|
const showFileError = (_a = cdsContext.lexical) === null || _a === void 0 ? void 0 : _a.showFileError;
|
|
28
30
|
try {
|
|
29
31
|
if (!fileUrl) {
|
|
30
|
-
showFileError === null || showFileError === void 0 ? void 0 : showFileError("download", "다운로드 중 오류가 발생했어요");
|
|
32
|
+
showFileError === null || showFileError === void 0 ? void 0 : showFileError("download", t("다운로드 중 오류가 발생했어요"));
|
|
31
33
|
return;
|
|
32
34
|
}
|
|
33
35
|
// fileUrl에 대해 GET 요청을 보냅니다.
|
|
34
36
|
const response = yield fetch(fileUrl, { method: "GET" });
|
|
35
37
|
if (!response.ok) {
|
|
36
|
-
showFileError === null || showFileError === void 0 ? void 0 : showFileError("download", "파일이 삭제되어 다운로드할 수 없어요.");
|
|
38
|
+
showFileError === null || showFileError === void 0 ? void 0 : showFileError("download", t("파일이 삭제되어 다운로드할 수 없어요."));
|
|
37
39
|
return;
|
|
38
40
|
}
|
|
39
41
|
// 요청이 성공하면 응답 데이터를 Blob(파일 데이터)로 변환
|
|
@@ -52,7 +54,7 @@ export function FileDownloadButton({ fileName, fileSize, fileUrl, }) {
|
|
|
52
54
|
URL.revokeObjectURL(blobUrl);
|
|
53
55
|
}
|
|
54
56
|
catch (_b) {
|
|
55
|
-
showFileError === null || showFileError === void 0 ? void 0 : showFileError("download", "다운로드 중 오류가 발생했어요");
|
|
57
|
+
showFileError === null || showFileError === void 0 ? void 0 : showFileError("download", t("다운로드 중 오류가 발생했어요"));
|
|
56
58
|
}
|
|
57
59
|
});
|
|
58
60
|
return (_jsxs(ButtonContainer, { onClick: handleDownload, theme: theme, children: [_jsx(IconContainer, { children: getFileIcon(fileName) }), _jsx(FileNameLabel, { children: _jsx(OverflowTooltip, { text: fileName }) }), _jsxs(FileSize, { children: [(fileSize / (1024 * 1024)).toFixed(1), " MB"] })] }));
|
|
@@ -15,6 +15,7 @@ import { ImageResizer } from "./ImageResizer";
|
|
|
15
15
|
import { css } from "@emotion/react";
|
|
16
16
|
import { RESET_BUTTON } from "../../../../utils/reset";
|
|
17
17
|
import { VISUALLY_HIDDEN } from "../../../../utils/visuallyHidden";
|
|
18
|
+
import { useTranslation } from "react-i18next";
|
|
18
19
|
const imageCache = new Set();
|
|
19
20
|
export const RIGHT_CLICK_IMAGE_COMMAND = createCommand("RIGHT_CLICK_IMAGE_COMMAND");
|
|
20
21
|
function useSuspenseImage(src) {
|
|
@@ -61,6 +62,7 @@ export function ImageComponent({ src, altText, nodeKey, width, height, maxWidth,
|
|
|
61
62
|
const [selection, setSelection] = useState(null);
|
|
62
63
|
const activeEditorRef = useRef(null);
|
|
63
64
|
const [isLoadError, setIsLoadError] = useState(false);
|
|
65
|
+
const { t } = useTranslation();
|
|
64
66
|
const $onDelete = useCallback((payload) => {
|
|
65
67
|
const deleteSelection = $getSelection();
|
|
66
68
|
if (isSelected && $isNodeSelection(deleteSelection)) {
|
|
@@ -196,7 +198,7 @@ export function ImageComponent({ src, altText, nodeKey, width, height, maxWidth,
|
|
|
196
198
|
cursor: pointer;
|
|
197
199
|
`, src: src, altText: altText, imageRef: imageRef, width: width, height: height, maxWidth: maxWidth, onError: () => setIsLoadError(true), buttonProps: {
|
|
198
200
|
onClick: () => window.open(src, "_blank"),
|
|
199
|
-
}, title: "
|
|
201
|
+
}, title: t("클릭해서 원본 이미지 보기."), addtionalInfoLabel: _jsx("span", { css: VISUALLY_HIDDEN, children: t("(새 창)", { context: "렉시컬 이미지 도구" }) }) })) }) }));
|
|
200
202
|
}
|
|
201
203
|
return (_jsxs(_Fragment, { children: [_jsxs(EditContainer, { children: [_jsx(Suspense, { fallback: null, children: _jsx("div", { draggable: draggable, css: css `
|
|
202
204
|
// ImageResizer를 위한 relative 설정입니다.
|
|
@@ -208,7 +210,7 @@ export function ImageComponent({ src, altText, nodeKey, width, height, maxWidth,
|
|
|
208
210
|
// maxWidth={maxWidth}
|
|
209
211
|
onResizeStart: onResizeStart, onResizeEnd: onResizeEnd, captionsEnabled: false }))] })) }) }), _jsx(SquareButton, { size: "small", color: "icon", icon: _jsx(Settings3FillIcon, {}), onClick: () => {
|
|
210
212
|
setOpen(true);
|
|
211
|
-
}, "aria-label": "
|
|
213
|
+
}, "aria-label": t("이미지 수정하기") })] }), _jsx(InsertImageDialog, { open: open, title: t("이미지 수정하기"), onClose: () => setOpen(false), imageProps: {
|
|
212
214
|
src,
|
|
213
215
|
altText,
|
|
214
216
|
}, onChange: (props) => {
|
|
@@ -24,10 +24,13 @@ import { $getNodeByKey } from "lexical";
|
|
|
24
24
|
import { LexicalCustomConfigContext } from "../../LexicalCustomConfigContext";
|
|
25
25
|
import { TextInput } from "./TextInput";
|
|
26
26
|
import { SegmentedInput } from "./SegmentedInput";
|
|
27
|
+
import { useTranslation } from "react-i18next";
|
|
28
|
+
import { getTexts } from "../../../../texts";
|
|
27
29
|
export function InputComponent(props) {
|
|
28
30
|
const { answer, isCorrect } = props, settingFormProps = __rest(props, ["answer", "isCorrect"]);
|
|
29
31
|
const { solutions, showCharacterNumber, placeholder, nodeKey } = settingFormProps;
|
|
30
32
|
const [editor] = useLexicalComposerContext();
|
|
33
|
+
const { t } = useTranslation();
|
|
31
34
|
const [settingOpen, setSettingOpen] = useState(false);
|
|
32
35
|
const isEditable = useLexicalEditable();
|
|
33
36
|
// SoT를 EditorState로 설정하는 경우 한글 입력시 문제가 생김.
|
|
@@ -51,9 +54,9 @@ export function InputComponent(props) {
|
|
|
51
54
|
// 학생 view
|
|
52
55
|
if (!isEditable) {
|
|
53
56
|
if (showCharacterNumber) {
|
|
54
|
-
return (_jsx(SegmentedInput, { readOnly: freezeProblemNode, isCorrect: isCorrect, answerFormat: answerFormat, placeholder: placeholder || "
|
|
57
|
+
return (_jsx(SegmentedInput, { readOnly: freezeProblemNode, isCorrect: isCorrect, answerFormat: answerFormat, placeholder: placeholder || getTexts("placeholderEnterHere"), value: answerInput, onChange: handleChange }));
|
|
55
58
|
}
|
|
56
|
-
return (_jsx(TextInput, { readOnly: freezeProblemNode, isCorrect: isCorrect, size: "small", color: "default", placeholder: placeholder || "
|
|
59
|
+
return (_jsx(TextInput, { readOnly: freezeProblemNode, isCorrect: isCorrect, size: "small", color: "default", placeholder: placeholder || getTexts("placeholderEnterHere"), value: answerInput, onChange: (e) => {
|
|
57
60
|
handleChange(e.target.value);
|
|
58
61
|
}, fullWidth: true }));
|
|
59
62
|
}
|
|
@@ -62,9 +65,9 @@ export function InputComponent(props) {
|
|
|
62
65
|
display: flex;
|
|
63
66
|
align-items: center;
|
|
64
67
|
gap: 4px;
|
|
65
|
-
`, children: [_jsx(VirtualInput, { onClick: () => setSettingOpen(true), children: "
|
|
68
|
+
`, children: [_jsx(VirtualInput, { onClick: () => setSettingOpen(true), children: t("주관식 입력 칸", { context: "렉시컬 주관식 입력 도구" }) }), _jsx(SquareButton, { size: "small", color: "icon", icon: _jsx(Settings3FillIcon, {}), onClick: () => {
|
|
66
69
|
setSettingOpen(true);
|
|
67
|
-
}, "aria-label": "
|
|
70
|
+
}, "aria-label": t("입력 칸 설정", { context: "렉시컬 주관식 입력 도구" }) })] }), settingOpen && (_jsx(SettingForm, Object.assign({}, settingFormProps, { onClose: () => setSettingOpen(false) })))] }));
|
|
68
71
|
}
|
|
69
72
|
const VirtualInput = styled.div(({ theme }) => css `
|
|
70
73
|
box-sizing: border-box;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { Controller } from "react-hook-form";
|
|
3
3
|
import { Input } from "../../../../../components/Input";
|
|
4
|
+
import { getTexts } from "../../../../../texts";
|
|
4
5
|
export function FormPlaceholder(props) {
|
|
5
6
|
const { control } = props;
|
|
6
|
-
return (_jsx(Controller, { name: "placeholder", control: control, render: ({ field: { value, onChange } }) => (_jsx(Input, { size: "small", color: "default", value: value, onChange: onChange, placeholder: "
|
|
7
|
+
return (_jsx(Controller, { name: "placeholder", control: control, render: ({ field: { value, onChange } }) => (_jsx(Input, { size: "small", color: "default", value: value, onChange: onChange, placeholder: getTexts("exampleEnterHere") })) }));
|
|
7
8
|
}
|
|
@@ -7,18 +7,20 @@ import { AlertFillIcon, DeleteBinLineIcon, ErrorWarningFillIcon, } from "../../.
|
|
|
7
7
|
import { SquareButton } from "../../../../../components/SquareButton";
|
|
8
8
|
import { Tooltip } from "../../../../../components/Tooltip";
|
|
9
9
|
import { TextTypeDropdown } from "./TextTypeDropdown";
|
|
10
|
+
import { Trans, useTranslation } from "react-i18next";
|
|
10
11
|
export function FormSolution(props) {
|
|
11
12
|
const { index, control, rules, onDelete } = props;
|
|
12
13
|
const theme = useTheme();
|
|
14
|
+
const { t } = useTranslation();
|
|
13
15
|
return (_jsx(Controller, { name: `solutions.${index}.value`, control: control, rules: rules, render: ({ field: { value, onChange }, fieldState: { invalid, error }, }) => {
|
|
14
16
|
const disabled = (error === null || error === void 0 ? void 0 : error.type) === "enabled";
|
|
15
|
-
return (_jsx(Input, { size: "small", color: invalid ? "activeDanger" : "default", onChange: onChange, disabled: disabled, value: value, hintIcon: !disabled && invalid ? _jsx(ErrorWarningFillIcon, {}) : undefined, hintText: !disabled ? error === null || error === void 0 ? void 0 : error.message : undefined, placeholder: "
|
|
17
|
+
return (_jsx(Input, { size: "small", color: invalid ? "activeDanger" : "default", onChange: onChange, disabled: disabled, value: value, hintIcon: !disabled && invalid ? _jsx(ErrorWarningFillIcon, {}) : undefined, hintText: !disabled ? error === null || error === void 0 ? void 0 : error.message : undefined, placeholder: t("안녕하세요"), fullWidth: true, css: css `
|
|
16
18
|
> div {
|
|
17
19
|
padding: 4px 12px;
|
|
18
20
|
}
|
|
19
21
|
`, startIcon: _jsx(TextTypeDropdown, { index: index, control: control, disabled: disabled }), endIcon: _jsxs("div", { css: css `
|
|
20
22
|
display: flex;
|
|
21
23
|
gap: 4px;
|
|
22
|
-
`, children: [onDelete && (_jsx(SquareButton, { color: "white", size: "xsmall", icon: _jsx(DeleteBinLineIcon, {}), onClick: onDelete, "aria-label": "
|
|
24
|
+
`, children: [onDelete && (_jsx(SquareButton, { color: "white", size: "xsmall", icon: _jsx(DeleteBinLineIcon, {}), onClick: onDelete, "aria-label": t("삭제", { context: "스퀘어버튼, 렉시컬 주관식 설정창" }) })), disabled && (_jsx(Tooltip, { text: _jsx(Trans, { i18nKey: "\uC785\uB825 \uCE78 \uC124\uC815\uC774 '\uAE00\uC790 \uC218\uB300\uB85C'\uC778 \uACBD\uC6B0<br/>\uC815\uB2F5\uC744 \uD558\uB098\uB9CC \uB4F1\uB85D\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4." }), children: _jsx(SquareButton, { color: "danger", size: "xsmall", icon: _jsx(AlertFillIcon, { color: theme.color.foreground.neutralAlt }), disabled: true, "aria-label": t("정답 등록 불가") }) }))] }) }));
|
|
23
25
|
} }));
|
|
24
26
|
}
|
|
@@ -13,9 +13,13 @@ import { FormSegmentedControl } from "./FormSegmentedControl";
|
|
|
13
13
|
import { FormPlaceholder } from "./FormPlaceholder";
|
|
14
14
|
import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
|
|
15
15
|
import { $getNodeByKey } from "lexical";
|
|
16
|
+
import { Trans, useTranslation } from "react-i18next";
|
|
17
|
+
import { getTexts } from "../../../../../texts";
|
|
16
18
|
export function SettingForm(props) {
|
|
17
19
|
const { solutions, showCharacterNumber, placeholder, nodeKey, onClose, caseSensitive, ignoreWhitespace, } = props;
|
|
18
20
|
const [editor] = useLexicalComposerContext();
|
|
21
|
+
const { t } = useTranslation();
|
|
22
|
+
const numCharOptionStr = t("글자 수대로");
|
|
19
23
|
const { control, handleSubmit, watch, trigger } = useForm({
|
|
20
24
|
mode: "all",
|
|
21
25
|
defaultValues: {
|
|
@@ -50,19 +54,19 @@ export function SettingForm(props) {
|
|
|
50
54
|
return (_jsxs(Form, { onSubmit: handleSubmit(onSettingSubmit), children: [_jsxs(Title, { children: [_jsx(InputMethodLineIcon, { css: css `
|
|
51
55
|
width: 12px;
|
|
52
56
|
height: 12px;
|
|
53
|
-
` }), "
|
|
57
|
+
` }), t("주관식 입력 칸", { context: "렉시컬 주관식 설정창" })] }), _jsxs(Content, { children: [_jsxs(Left, { children: [_jsxs(FormArea, { children: [_jsx(Label, { children: t("정답", { context: "소제목, 렉시컬 주관식 설정창" }) }), fields.map((field, index) => (_jsx(FormSolution, { index: index, control: control, rules: {
|
|
54
58
|
validate: {
|
|
55
59
|
// required 옵션보다 먼저 검증되어야 하는데 priority 옵션이 없어서 validate에서 통합해서 검증합니다.
|
|
56
60
|
enabled: () => index === 0 ||
|
|
57
61
|
!multipleSolutionsDisabled ||
|
|
58
|
-
"복수 정답이 불가능합니다.",
|
|
59
|
-
required: (value) => value !== "" || "정답을 입력해주세요.",
|
|
62
|
+
t("복수 정답이 불가능합니다."),
|
|
63
|
+
required: (value) => value !== "" || t("정답을 입력해주세요."),
|
|
60
64
|
},
|
|
61
65
|
}, onDelete: index !== 0
|
|
62
66
|
? () => {
|
|
63
67
|
remove(index);
|
|
64
68
|
}
|
|
65
|
-
: undefined }, field.uid)))] }), _jsx(Button, { color: "grey", size: "small", startIcon: _jsx(AddFillIcon, {}), label: "
|
|
69
|
+
: undefined }, field.uid)))] }), _jsx(Button, { color: "grey", size: "small", startIcon: _jsx(AddFillIcon, {}), label: t("복수 정답 추가"), disabled: multipleSolutionsDisabled, onClick: () => {
|
|
66
70
|
append({
|
|
67
71
|
textType: "normal",
|
|
68
72
|
value: "",
|
|
@@ -73,22 +77,22 @@ export function SettingForm(props) {
|
|
|
73
77
|
`, children: [_jsx(AlarmWarningFillIcon, { color: theme.color.foreground.neutralBaseDisabled, css: css `
|
|
74
78
|
width: 14px;
|
|
75
79
|
height: 14px;
|
|
76
|
-
` }), _jsx(Label, { children: "
|
|
80
|
+
` }), _jsx(Label, { children: t("가능한 정답을 모두 추가해야 원활하게 자동 채점할 수 있어요.") })] })] }), _jsxs(Right, { children: [_jsxs(FormArea, { children: [_jsxs(Label, { children: [t("입력 칸"), _jsx(Tooltip, { text: _jsx(Trans, { i18nKey: "\uC608\uB97C \uB4E4\uC5B4 \uC815\uB2F5\uC774 '\uAE00\uC790 \uC218'\uC774\uACE0<br /><strong>{{numCharOptionStr}}</strong> \uC635\uC158\uC744 \uC120\uD0DD\uD588\uB2E4\uBA74<br />\uC785\uB825 \uCE78\uC774 '\u2610\u2610 \u2610' \uCC98\uB7FC \uD45C\uC2DC\uB429\uB2C8\uB2E4.", value: { numCharOptionStr } }), placement: "top", children: _jsx(QuestionFillIcon, { css: css `
|
|
77
81
|
width: 12px;
|
|
78
82
|
height: 12px;
|
|
79
83
|
` }) })] }), _jsx(FormSegmentedControl, { control: control, trigger: trigger, name: "showCharacterNumber", items: [
|
|
80
|
-
{ value: false, label: "한 칸으로" },
|
|
81
|
-
{ value: true, label:
|
|
82
|
-
] })] }), _jsxs(FormArea, { children: [_jsxs(Label, { children: ["
|
|
84
|
+
{ value: false, label: t("한 칸으로") },
|
|
85
|
+
{ value: true, label: numCharOptionStr },
|
|
86
|
+
] })] }), _jsxs(FormArea, { children: [_jsxs(Label, { children: [t("자리 표시자", { context: "렉시컬 주관식 설정창" }), _jsx(Tooltip, { text: getTexts("descriptionDefaultInputText"), placement: "top", children: _jsx(QuestionFillIcon, { css: css `
|
|
83
87
|
width: 12px;
|
|
84
88
|
height: 12px;
|
|
85
|
-
` }) })] }), _jsx(FormPlaceholder, { control: control })] }), _jsxs(FormArea, { children: [_jsx(Label, { children: "
|
|
86
|
-
{ value: true, label: "무시하기" },
|
|
87
|
-
{ value: false, label: "포함하기" },
|
|
88
|
-
] })] }), _jsxs(FormArea, { children: [_jsx(Label, { children: "
|
|
89
|
-
{ value: false, label: "무시하기" },
|
|
90
|
-
{ value: true, label: "포함하기" },
|
|
91
|
-
] })] })] })] }), _jsxs(Buttons, { children: [_jsx(Button, { color: "grey", size: "xsmall", label: "
|
|
89
|
+
` }) })] }), _jsx(FormPlaceholder, { control: control })] }), _jsxs(FormArea, { children: [_jsx(Label, { children: t("띄어쓰기") }), _jsx(FormSegmentedControl, { control: control, trigger: trigger, name: "ignoreWhitespace", items: [
|
|
90
|
+
{ value: true, label: t("무시하기") },
|
|
91
|
+
{ value: false, label: t("포함하기") },
|
|
92
|
+
] })] }), _jsxs(FormArea, { children: [_jsx(Label, { children: t("대소문자") }), _jsx(FormSegmentedControl, { control: control, trigger: trigger, name: "caseSensitive", items: [
|
|
93
|
+
{ value: false, label: t("무시하기") },
|
|
94
|
+
{ value: true, label: t("포함하기") },
|
|
95
|
+
] })] })] })] }), _jsxs(Buttons, { children: [_jsx(Button, { color: "grey", size: "xsmall", label: t("닫기", { context: "렉시컬 도구 설정창" }), onClick: onClose }), _jsx(Button, { color: "primary", size: "xsmall", label: t("이대로 넣기", { context: "렉시컬 도구 설정창" }), type: "submit" })] })] }));
|
|
92
96
|
}
|
|
93
97
|
const Form = styled.form(({ theme }) => css `
|
|
94
98
|
display: flex;
|
|
@@ -2,10 +2,14 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import { Controller } from "react-hook-form";
|
|
3
3
|
import { css, useTheme } from "@emotion/react";
|
|
4
4
|
import { Dropdown, DropdownItem } from "../../../../..";
|
|
5
|
+
import { useTranslation } from "react-i18next";
|
|
5
6
|
export function TextTypeDropdown(props) {
|
|
6
7
|
const theme = useTheme();
|
|
7
8
|
const { index, control, disabled } = props;
|
|
8
|
-
|
|
9
|
+
const { t } = useTranslation();
|
|
10
|
+
const normalLabel = t("일반 텍스트");
|
|
11
|
+
const codeLabel = t("코드 텍스트");
|
|
12
|
+
return (_jsx(Controller, { name: `solutions.${index}.textType`, control: control, render: ({ field: { value, onChange } }) => (_jsx(Dropdown, { label: value === "normal" ? normalLabel : codeLabel, size: "xsmall", color: "textNeutral", closeOnItemClick: true, disabled: disabled, buttonCss: css `
|
|
9
13
|
${disabled && `color: ${theme.color.foreground.neutralAlt};`}
|
|
10
14
|
> span {
|
|
11
15
|
font-weight: 700;
|
|
@@ -19,7 +23,7 @@ export function TextTypeDropdown(props) {
|
|
|
19
23
|
vertical: "top",
|
|
20
24
|
horizontal: "center",
|
|
21
25
|
},
|
|
22
|
-
}, children: _jsx(DropdownItem, { index: 0, label: value === "normal" ?
|
|
26
|
+
}, children: _jsx(DropdownItem, { index: 0, label: value === "normal" ? codeLabel : normalLabel, onClick: () => {
|
|
23
27
|
onChange(value === "normal" ? "code" : "normal");
|
|
24
28
|
} }) })) }));
|
|
25
29
|
}
|
|
@@ -6,6 +6,8 @@ import { SelectBoxComponent } from "./SelectBoxComponent";
|
|
|
6
6
|
import { Tag } from "../../../../../components/Tag";
|
|
7
7
|
import { useRef } from "react";
|
|
8
8
|
import { useEventListener } from "usehooks-ts";
|
|
9
|
+
import { i18n } from "../../../../../i18n/i18n";
|
|
10
|
+
import { useTranslation } from "react-i18next";
|
|
9
11
|
const TYPE_TO_INDEX_ICON = (type) => ({
|
|
10
12
|
primary: (_jsx(CheckFillIcon, { css: css `
|
|
11
13
|
width: 12px;
|
|
@@ -28,30 +30,35 @@ function getTypeAndDescription(isSelected, isAnswer, onClick) {
|
|
|
28
30
|
if (isAnswer === undefined) {
|
|
29
31
|
return {
|
|
30
32
|
type: isSelected ? "primary" : undefined,
|
|
31
|
-
description: isSelected
|
|
33
|
+
description: isSelected
|
|
34
|
+
? i18n.t("선택됨")
|
|
35
|
+
: onClick
|
|
36
|
+
? i18n.t("선택하기")
|
|
37
|
+
: "",
|
|
32
38
|
};
|
|
33
39
|
}
|
|
34
40
|
if (isAnswer) {
|
|
35
41
|
return {
|
|
36
42
|
type: isSelected ? "success" : undefined,
|
|
37
43
|
description: isSelected
|
|
38
|
-
? "선택됨, 정답"
|
|
44
|
+
? i18n.t("선택됨, 정답", { context: "렉시컬 선택지 하나의 상태" })
|
|
39
45
|
: onClick
|
|
40
|
-
? "선택하기, 정답"
|
|
41
|
-
: "정답",
|
|
46
|
+
? i18n.t("선택하기, 정답", { context: "렉시컬 선택지 하나의 상태" })
|
|
47
|
+
: i18n.t("정답", { context: "렉시컬 선택지 하나의 상태" }),
|
|
42
48
|
};
|
|
43
49
|
}
|
|
44
50
|
return {
|
|
45
51
|
type: isSelected ? "danger" : undefined,
|
|
46
52
|
description: isSelected
|
|
47
|
-
? "선택됨, 오답"
|
|
53
|
+
? i18n.t("선택됨, 오답", { context: "렉시컬 선택지 하나의 상태" })
|
|
48
54
|
: onClick
|
|
49
|
-
? "선택하기, 오답"
|
|
50
|
-
: "오답",
|
|
55
|
+
? i18n.t("선택하기, 오답", { context: "렉시컬 선택지 하나의 상태" })
|
|
56
|
+
: i18n.t("오답", { context: "렉시컬 선택지 하나의 상태" }),
|
|
51
57
|
};
|
|
52
58
|
}
|
|
53
59
|
export function SelectBoxView(props) {
|
|
54
60
|
const { multipleSelectionsEnabled, index, isSelected, isAnswer, image, text, onClick, } = props;
|
|
61
|
+
const { t } = useTranslation();
|
|
55
62
|
const { type, description } = getTypeAndDescription(isSelected, isAnswer, onClick);
|
|
56
63
|
const boxRef = useRef(null);
|
|
57
64
|
const handleKeyDown = (e) => {
|
|
@@ -71,13 +78,15 @@ export function SelectBoxView(props) {
|
|
|
71
78
|
};
|
|
72
79
|
useEventListener("keydown", handleKeyDown);
|
|
73
80
|
// text가 있으면 text, 없으면 image의 altText, 없으면 index를 aria-label로 사용
|
|
74
|
-
const ariaText = text ||
|
|
81
|
+
const ariaText = text ||
|
|
82
|
+
(image && image.altText) ||
|
|
83
|
+
t("{{index}}번 선택지", { index, context: "렉시컬 선택지 도구" });
|
|
75
84
|
return (_jsx(SelectBoxComponent, { css: css `
|
|
76
85
|
width: 100%;
|
|
77
86
|
`, ref: boxRef, role: multipleSelectionsEnabled ? "checkbox" : "radio", "aria-checked": isSelected, "aria-label": [ariaText, description].filter(Boolean).join(" "), tabIndex: onClick ? 0 : -1, type: type, index: type ? TYPE_TO_INDEX_ICON(type) : index, image: image, text: text, onClick: onClick,
|
|
78
87
|
// 선택되지 않았으나 정답일 때 정답 태그를 표시
|
|
79
88
|
endIcon: isAnswer &&
|
|
80
|
-
!isSelected && (_jsx(Tag, { label: "
|
|
89
|
+
!isSelected && (_jsx(Tag, { label: t("정답", { context: "태그, 렉시컬 선택지" }), icon: _jsx(CheckboxCircleFillIcon, {}), color: "green", css: css `
|
|
81
90
|
span {
|
|
82
91
|
font-weight: 700;
|
|
83
92
|
}
|
|
@@ -12,9 +12,11 @@ import { AlarmWarningFillIcon, Settings3FillIcon } from "../../../../icons";
|
|
|
12
12
|
import { SettingForm } from "./SettingForm";
|
|
13
13
|
import styled from "@emotion/styled";
|
|
14
14
|
import { LexicalCustomConfigContext } from "../../LexicalCustomConfigContext";
|
|
15
|
+
import { useTranslation } from "react-i18next";
|
|
15
16
|
export function SelectComponent(props) {
|
|
16
17
|
const { selected, hasMultipleSolutions, selections, nodeKey } = props;
|
|
17
18
|
const [editor] = useLexicalComposerContext();
|
|
19
|
+
const { t } = useTranslation();
|
|
18
20
|
const [settingOpen, setSettingOpen] = useState(false);
|
|
19
21
|
const isEditable = useLexicalEditable();
|
|
20
22
|
const { freezeProblemNode, showQuizSolution } = useContext(LexicalCustomConfigContext);
|
|
@@ -33,7 +35,7 @@ export function SelectComponent(props) {
|
|
|
33
35
|
`, children: [multipleSelectionsEnabled && (_jsxs(Alert, { children: [_jsx(AlarmWarningFillIcon, { css: css `
|
|
34
36
|
width: 14px;
|
|
35
37
|
height: 14px;
|
|
36
|
-
` }), "
|
|
38
|
+
` }), t("질문에 해당하는 답을 모두 고르는 문제입니다.")] })), selections.map((selection, index) => (_jsx(SelectBoxView, { multipleSelectionsEnabled: multipleSelectionsEnabled, index: index + 1, isAnswer: showQuizSolution && "isAnswer" in selection
|
|
37
39
|
? selection.isAnswer
|
|
38
40
|
: undefined, isSelected: selected.includes(selection.value), image: selection.show.image, text: selection.show.text, onClick: freezeProblemNode
|
|
39
41
|
? undefined
|
|
@@ -73,10 +75,13 @@ export function SelectComponent(props) {
|
|
|
73
75
|
flex-direction: column;
|
|
74
76
|
gap: 4px;
|
|
75
77
|
`, children: selections.map((selection, index) => (_jsx(SelectBoxEdit, { index: index + 1, isAnswer: "isAnswer" in selection && selection.isAnswer, image: selection.show.image, text: selection.show.text === "" && selection.show.image === null
|
|
76
|
-
?
|
|
78
|
+
? t("{{index}}번 선택지", {
|
|
79
|
+
index: index + 1,
|
|
80
|
+
context: "렉시컬 선택지 도구",
|
|
81
|
+
})
|
|
77
82
|
: selection.show.text, onClick: () => setSettingOpen(true) }, index))) }), _jsx(SquareButton, { size: "small", color: "icon", icon: _jsx(Settings3FillIcon, {}), onClick: () => {
|
|
78
83
|
setSettingOpen(true);
|
|
79
|
-
}, "aria-label": "
|
|
84
|
+
}, "aria-label": t("선택지 설정", { context: "렉시컬 선택지 도구" }) })] }), settingOpen && (_jsx(SettingForm, { selections: selections, nodeKey: nodeKey, onClose: () => setSettingOpen(false) }))] }));
|
|
80
85
|
}
|
|
81
86
|
const Alert = styled.div(({ theme }) => css `
|
|
82
87
|
display: flex;
|