@team-monolith/cds 1.8.6 → 1.8.8
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/patterns/LexicalEditor/nodes/ProblemInputNode/InputComponent.js +16 -12
- package/dist/patterns/LexicalEditor/nodes/ProblemInputNode/ProblemInputNode.d.ts +0 -1
- package/dist/patterns/LexicalEditor/nodes/ProblemInputNode/SettingForm/FormCharacterCount.d.ts +2 -3
- package/dist/patterns/LexicalEditor/nodes/ProblemInputNode/SettingForm/FormCharacterCount.js +2 -2
- package/dist/patterns/LexicalEditor/nodes/ProblemInputNode/SettingForm/FormSolution.d.ts +3 -3
- package/dist/patterns/LexicalEditor/nodes/ProblemInputNode/SettingForm/FormSolution.js +13 -30
- package/dist/patterns/LexicalEditor/nodes/ProblemInputNode/SettingForm/SettingForm.js +17 -21
- package/dist/patterns/LexicalEditor/nodes/ProblemInputNode/SettingForm/TextTypeDropdown.d.ts +7 -0
- package/dist/patterns/LexicalEditor/nodes/ProblemInputNode/SettingForm/TextTypeDropdown.js +26 -0
- package/dist/patterns/LexicalEditor/plugins/ComponentAdderPlugin/ComponentAdderPlugin.js +10 -4
- package/dist/patterns/LexicalEditor/plugins/ComponentAdderPlugin/useContextMenuOptions.d.ts +2 -2
- package/dist/patterns/LexicalEditor/plugins/ComponentPickerMenuPlugin/ComponentPickerMenuList.d.ts +2 -2
- package/dist/patterns/LexicalEditor/plugins/ComponentPickerMenuPlugin/ComponentPickerMenuList.js +2 -1
- package/dist/patterns/LexicalEditor/plugins/ComponentPickerMenuPlugin/ComponentPickerMenuPlugin.d.ts +6 -4
- package/dist/patterns/LexicalEditor/plugins/ComponentPickerMenuPlugin/ComponentPickerMenuPlugin.js +19 -22
- package/dist/patterns/LexicalEditor/plugins/ProblemInputPlugin/index.d.ts +1 -1
- package/dist/patterns/LexicalEditor/plugins/ProblemInputPlugin/index.js +5 -6
- package/dist/patterns/LexicalEditor/theme.js +2 -1
- package/package.json +1 -1
|
@@ -19,7 +19,7 @@ import { Settings3FillIcon } from "../../../../icons";
|
|
|
19
19
|
import styled from "@emotion/styled";
|
|
20
20
|
import SettingForm from "./SettingForm";
|
|
21
21
|
import useLexicalEditable from "@lexical/react/useLexicalEditable";
|
|
22
|
-
import { $isProblemInputNode
|
|
22
|
+
import { $isProblemInputNode } from "./ProblemInputNode";
|
|
23
23
|
import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
|
|
24
24
|
import { $getNodeByKey } from "lexical";
|
|
25
25
|
export function InputComponent(props) {
|
|
@@ -28,20 +28,24 @@ export function InputComponent(props) {
|
|
|
28
28
|
const [editor] = useLexicalComposerContext();
|
|
29
29
|
const [settingOpen, setSettingOpen] = useState(false);
|
|
30
30
|
const isEditable = useLexicalEditable();
|
|
31
|
+
const [answerInput, setAnswerInput] = useState(answer);
|
|
31
32
|
// 학생 view
|
|
32
33
|
// TODO: "글자 수대로" 옵션시에 글자 수대로 입력칸을 표시해야 합니다.
|
|
33
34
|
if (!isEditable) {
|
|
34
|
-
return (_jsx(Input, { size: "small", placeholder: placeholder || "여기에 입력하세요.", value:
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
35
|
+
return (_jsx(Input, { size: "small", placeholder: placeholder || "여기에 입력하세요.", value: answerInput, onChange: (e) => setAnswerInput(e.target.value),
|
|
36
|
+
// 한글 입력시에 onChange마다 update가 일어나는 것을 방지하기 위해 입력 완료후 onBlur시에 update하는 전략을 사용합니다.
|
|
37
|
+
// 이를 위해 answerInput을 state로 관리합니다.
|
|
38
|
+
inputProps: {
|
|
39
|
+
onBlur: (_e) => {
|
|
40
|
+
editor.update(() => {
|
|
41
|
+
const node = $getNodeByKey(nodeKey);
|
|
42
|
+
if (!$isProblemInputNode(node)) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
node.setAnswer(answerInput);
|
|
46
|
+
});
|
|
47
|
+
},
|
|
48
|
+
}, color: "default", fullWidth: true }));
|
|
45
49
|
}
|
|
46
50
|
// 교사 edit view
|
|
47
51
|
return (_jsxs(_Fragment, { children: [_jsxs("div", Object.assign({ css: css `
|
package/dist/patterns/LexicalEditor/nodes/ProblemInputNode/SettingForm/FormCharacterCount.d.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { Control } from "react-hook-form";
|
|
2
|
-
import React from "react";
|
|
1
|
+
import { Control, UseFormTrigger } from "react-hook-form";
|
|
3
2
|
import { ProblemInputPayload } from "../ProblemInputNode";
|
|
4
3
|
export interface FormCharacterCountProps {
|
|
5
4
|
control: Control<ProblemInputPayload, any>;
|
|
6
|
-
|
|
5
|
+
trigger: UseFormTrigger<ProblemInputPayload>;
|
|
7
6
|
}
|
|
8
7
|
export declare function FormCharacterCount(props: FormCharacterCountProps): import("react/jsx-runtime").JSX.Element;
|
package/dist/patterns/LexicalEditor/nodes/ProblemInputNode/SettingForm/FormCharacterCount.js
CHANGED
|
@@ -2,11 +2,11 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { Controller } from "react-hook-form";
|
|
3
3
|
import { SegmentedControlButton, SegmentedControlGroup, } from "../../../../SegmentedControl";
|
|
4
4
|
export function FormCharacterCount(props) {
|
|
5
|
-
const { control,
|
|
5
|
+
const { control, trigger } = props;
|
|
6
6
|
return (_jsx(Controller, { name: "showCharacterCount", control: control, render: ({ field: { value, onChange } }) => {
|
|
7
7
|
return (_jsxs(SegmentedControlGroup, Object.assign({ size: "xsmall", value: value.toString(), onChange: (value) => {
|
|
8
8
|
onChange(value === "true");
|
|
9
|
-
|
|
9
|
+
trigger("solutions");
|
|
10
10
|
}, fullWidth: true }, { children: [_jsx(SegmentedControlButton, { value: "false", label: "\uD55C \uCE78\uC73C\uB85C" }), _jsx(SegmentedControlButton, { value: "true", label: "\uAE00\uC790 \uC218\uB300\uB85C" })] })));
|
|
11
11
|
} }));
|
|
12
12
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { Control } from "react-hook-form";
|
|
2
2
|
import { ProblemInputPayload } from "../ProblemInputNode";
|
|
3
|
-
export interface
|
|
3
|
+
export interface FormSolutionProps {
|
|
4
4
|
index: number;
|
|
5
5
|
control: Control<ProblemInputPayload, any>;
|
|
6
|
+
rules?: any;
|
|
6
7
|
onDelete?: () => void;
|
|
7
|
-
disabled?: boolean;
|
|
8
8
|
}
|
|
9
|
-
export declare function FormSolution(props:
|
|
9
|
+
export declare function FormSolution(props: FormSolutionProps): import("@emotion/react/jsx-runtime").JSX.Element;
|
|
@@ -2,40 +2,23 @@ import { jsx as _jsx, jsxs as _jsxs } from "@emotion/react/jsx-runtime";
|
|
|
2
2
|
/** @jsxImportSource @emotion/react */
|
|
3
3
|
import { css, useTheme } from "@emotion/react";
|
|
4
4
|
import { Controller } from "react-hook-form";
|
|
5
|
-
import Dropdown from "../../../../Dropdown";
|
|
6
|
-
import DropdownItem from "../../../../Dropdown/DropdownItem";
|
|
7
5
|
import Input from "../../../../../components/Input";
|
|
8
6
|
import { AlertFillIcon, DeleteBinLineIcon, ErrorWarningFillIcon, } from "../../../../../icons";
|
|
9
7
|
import SquareButton from "../../../../../components/SquareButton";
|
|
10
8
|
import Tooltip from "../../../../../components/Tooltip";
|
|
9
|
+
import TextTypeDropdown from "./TextTypeDropdown";
|
|
11
10
|
export function FormSolution(props) {
|
|
12
|
-
const { index, control,
|
|
11
|
+
const { index, control, rules, onDelete } = props;
|
|
13
12
|
const theme = useTheme();
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
},
|
|
24
|
-
|
|
25
|
-
vertical: "top",
|
|
26
|
-
horizontal: "center",
|
|
27
|
-
},
|
|
28
|
-
} }, { children: _jsx(DropdownItem, { index: 0, label: value === "normal" ? "코드 텍스트" : "일반 텍스트", onClick: () => {
|
|
29
|
-
onChange(value === "normal" ? "code" : "normal");
|
|
30
|
-
} }) }))) }));
|
|
31
|
-
return (_jsx(Controller, { name: `solutions.${index}.value`, control: control, rules: {
|
|
32
|
-
required: "정답을 입력해주세요.",
|
|
33
|
-
}, render: ({ field: { value, onChange }, fieldState: { invalid, error }, }) => (_jsx(Input, { size: "small", color: invalid ? "activeDanger" : "default", onChange: onChange, disabled: disabled, value: value, hintIcon: invalid ? _jsx(ErrorWarningFillIcon, {}) : undefined, hintText: error === null || error === void 0 ? void 0 : error.message, placeholder: "\uC548\uB155\uD558\uC138\uC694", fullWidth: true, css: css `
|
|
34
|
-
> div {
|
|
35
|
-
padding: 4px 12px;
|
|
36
|
-
}
|
|
37
|
-
`, startIcon: TextTypeDropdown, endIcon: _jsxs("div", Object.assign({ css: css `
|
|
38
|
-
display: flex;
|
|
39
|
-
gap: 4px;
|
|
40
|
-
` }, { children: [onDelete && (_jsx(SquareButton, { color: "white", size: "xsmall", icon: _jsx(DeleteBinLineIcon, {}), onClick: onDelete })), disabled && (_jsx(Tooltip, Object.assign({ text: _jsxs("span", { children: ["\uC785\uB825 \uCE78 \uC124\uC815\uC774 '\uAE00\uC790 \uC218\uB300\uB85C'\uC778 \uACBD\uC6B0", _jsx("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 }) })))] })) })) }));
|
|
13
|
+
return (_jsx(Controller, { name: `solutions.${index}.value`, control: control, rules: rules, render: ({ field: { value, onChange }, fieldState: { invalid, error }, }) => {
|
|
14
|
+
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: "\uC548\uB155\uD558\uC138\uC694", fullWidth: true, css: css `
|
|
16
|
+
> div {
|
|
17
|
+
padding: 4px 12px;
|
|
18
|
+
}
|
|
19
|
+
`, startIcon: _jsx(TextTypeDropdown, { index: index, control: control, disabled: disabled }), endIcon: _jsxs("div", Object.assign({ css: css `
|
|
20
|
+
display: flex;
|
|
21
|
+
gap: 4px;
|
|
22
|
+
` }, { children: [onDelete && (_jsx(SquareButton, { color: "white", size: "xsmall", icon: _jsx(DeleteBinLineIcon, {}), onClick: onDelete })), disabled && (_jsx(Tooltip, Object.assign({ text: _jsxs("span", { children: ["\uC785\uB825 \uCE78 \uC124\uC815\uC774 '\uAE00\uC790 \uC218\uB300\uB85C'\uC778 \uACBD\uC6B0", _jsx("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 }) })))] })) }));
|
|
23
|
+
} }));
|
|
41
24
|
}
|
|
@@ -4,7 +4,7 @@ import { css, useTheme } from "@emotion/react";
|
|
|
4
4
|
import styled from "@emotion/styled";
|
|
5
5
|
import shadows from "../../../../../foundation/shadows";
|
|
6
6
|
import { AddFillIcon, AlarmWarningFillIcon, InputMethodLineIcon, QuestionFillIcon, } from "../../../../../icons";
|
|
7
|
-
import { useFieldArray, useForm
|
|
7
|
+
import { useFieldArray, useForm } from "react-hook-form";
|
|
8
8
|
import { $isProblemInputNode, } from "../ProblemInputNode";
|
|
9
9
|
import { FormSolution } from "./FormSolution";
|
|
10
10
|
import Button from "../../../../../components/Button";
|
|
@@ -13,13 +13,11 @@ import { FormCharacterCount } from "./FormCharacterCount";
|
|
|
13
13
|
import { FormPlaceholder } from "./FormPlaceholder";
|
|
14
14
|
import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
|
|
15
15
|
import { $getNodeByKey } from "lexical";
|
|
16
|
-
import { useState } from "react";
|
|
17
16
|
export default function SettingForm(props) {
|
|
18
17
|
const { solutions, showCharacterCount, placeholder, nodeKey, onClose } = props;
|
|
19
18
|
const [editor] = useLexicalComposerContext();
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const { control, handleSubmit } = useForm({
|
|
19
|
+
const { control, handleSubmit, watch, trigger } = useForm({
|
|
20
|
+
mode: "all",
|
|
23
21
|
defaultValues: {
|
|
24
22
|
solutions,
|
|
25
23
|
showCharacterCount,
|
|
@@ -27,7 +25,7 @@ export default function SettingForm(props) {
|
|
|
27
25
|
},
|
|
28
26
|
});
|
|
29
27
|
const theme = useTheme();
|
|
30
|
-
const { fields, append, remove
|
|
28
|
+
const { fields, append, remove } = useFieldArray({
|
|
31
29
|
control,
|
|
32
30
|
name: "solutions",
|
|
33
31
|
keyName: "uid",
|
|
@@ -44,25 +42,23 @@ export default function SettingForm(props) {
|
|
|
44
42
|
});
|
|
45
43
|
onClose();
|
|
46
44
|
};
|
|
45
|
+
const multipleSolutionsDisabled = watch("showCharacterCount");
|
|
47
46
|
return (_jsxs(Form, Object.assign({ onSubmit: handleSubmit(onSettingSubmit) }, { children: [_jsxs(Title, { children: [_jsx(InputMethodLineIcon, { css: css `
|
|
48
47
|
width: 12px;
|
|
49
48
|
height: 12px;
|
|
50
|
-
` }), "\uC8FC\uAD00\uC2DD \uC785\uB825 \uCE78"] }), _jsxs(Content, { children: [_jsxs(Left, { children: [_jsxs(FormArea, { children: [_jsx(Label, { children: "\uC815\uB2F5" }), fields
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
49
|
+
` }), "\uC8FC\uAD00\uC2DD \uC785\uB825 \uCE78"] }), _jsxs(Content, { children: [_jsxs(Left, { children: [_jsxs(FormArea, { children: [_jsx(Label, { children: "\uC815\uB2F5" }), fields.map((field, index) => (_jsx(FormSolution, { index: index, control: control, rules: {
|
|
50
|
+
validate: {
|
|
51
|
+
// required 옵션보다 먼저 검증되어야 하는데 priority 옵션이 없어서 validate에서 통합해서 검증합니다.
|
|
52
|
+
enabled: () => index === 0 ||
|
|
53
|
+
!multipleSolutionsDisabled ||
|
|
54
|
+
"복수 정답이 불가능합니다.",
|
|
55
|
+
required: (value) => value !== "" || "정답을 입력해주세요.",
|
|
56
|
+
},
|
|
57
|
+
}, onDelete: index !== 0
|
|
57
58
|
? () => {
|
|
58
|
-
|
|
59
|
-
update(index, Object.assign(Object.assign({}, field), { destroyed: true }));
|
|
60
|
-
}
|
|
61
|
-
else {
|
|
62
|
-
remove(index);
|
|
63
|
-
}
|
|
59
|
+
remove(index);
|
|
64
60
|
}
|
|
65
|
-
: undefined }, field.uid)))] }), _jsx(Button, { color: "grey", size: "small", startIcon: _jsx(AddFillIcon, {}), label: "\uBCF5\uC218 \uC815\uB2F5 \uCD94\uAC00", disabled:
|
|
61
|
+
: undefined }, field.uid)))] }), _jsx(Button, { color: "grey", size: "small", startIcon: _jsx(AddFillIcon, {}), label: "\uBCF5\uC218 \uC815\uB2F5 \uCD94\uAC00", disabled: multipleSolutionsDisabled, onClick: () => {
|
|
66
62
|
append({
|
|
67
63
|
textType: "normal",
|
|
68
64
|
value: "",
|
|
@@ -76,7 +72,7 @@ export default function SettingForm(props) {
|
|
|
76
72
|
` }), _jsxs(Label, { children: ["\uB744\uC5B4\uC4F0\uAE30, \uC54C\uD30C\uBCB3\uC758 \uB300\uC18C\uBB38\uC790 \uAD6C\uBD84\uAE4C\uC9C0 \uC77C\uCE58\uD574\uC57C \uC815\uB2F5\uC73C\uB85C \uCC98\uB9AC\uB429\uB2C8\uB2E4.", _jsx("br", {}), "\uAC00\uB2A5\uD55C \uC815\uB2F5\uC744 \uBAA8\uB450 \uCD94\uAC00\uD574\uC57C \uC6D0\uD65C\uD558\uAC8C \uC790\uB3D9 \uCC44\uC810\uD560 \uC218 \uC788\uC5B4\uC694."] })] }))] }), _jsxs(Right, { children: [_jsxs(FormArea, { children: [_jsxs(Label, { children: ["\uC785\uB825 \uCE78", _jsx(Tooltip, Object.assign({ text: _jsxs("span", { children: ["\uC608\uB97C \uB4E4\uC5B4 \uC815\uB2F5\uC774 '\uAE00\uC790 \uC218'\uC774\uACE0", _jsx("br", {}), _jsx("b", { children: "\uAE00\uC790 \uC218\uB300\uB85C" }), " \uC635\uC158\uC744 \uC120\uD0DD\uD588\uB2E4\uBA74", _jsx("br", {}), "\uC785\uB825 \uCE78\uC774 '\u2610\u2610 \u2610' \uCC98\uB7FC \uD45C\uC2DC\uB429\uB2C8\uB2E4."] }), placement: "top" }, { children: _jsx(QuestionFillIcon, { css: css `
|
|
77
73
|
width: 12px;
|
|
78
74
|
height: 12px;
|
|
79
|
-
` }) }))] }), _jsx(FormCharacterCount, { control: control,
|
|
75
|
+
` }) }))] }), _jsx(FormCharacterCount, { control: control, trigger: trigger })] }), _jsxs(FormArea, { children: [_jsxs(Label, { children: ["\uC790\uB9AC \uD45C\uC2DC\uC790", _jsx(Tooltip, Object.assign({ text: _jsx("span", { children: "\uC785\uB825 \uCE78\uC5D0 \uAE30\uBCF8\uC73C\uB85C \uB178\uCD9C\uB418\uB294 \uD14D\uC2A4\uD2B8\uC785\uB2C8\uB2E4." }), placement: "top" }, { children: _jsx(QuestionFillIcon, { css: css `
|
|
80
76
|
width: 12px;
|
|
81
77
|
height: 12px;
|
|
82
78
|
` }) }))] }), _jsx(FormPlaceholder, { control: control })] })] })] }), _jsxs(Buttons, { children: [_jsx(Button, { color: "grey", size: "xsmall", label: "\uB2EB\uAE30", onClick: onClose }), _jsx(Button, { color: "primary", size: "xsmall", label: "\uC774\uB300\uB85C \uB123\uAE30", type: "submit" })] })] })));
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Control } from "react-hook-form";
|
|
2
|
+
import { ProblemInputPayload } from "../ProblemInputNode";
|
|
3
|
+
export default function TextTypeDropdown(props: {
|
|
4
|
+
index: number;
|
|
5
|
+
control: Control<ProblemInputPayload, any>;
|
|
6
|
+
disabled: boolean;
|
|
7
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Controller } from "react-hook-form";
|
|
3
|
+
import Dropdown from "../../../../Dropdown";
|
|
4
|
+
import { css, useTheme } from "@emotion/react";
|
|
5
|
+
import DropdownItem from "../../../../Dropdown/DropdownItem";
|
|
6
|
+
export default function TextTypeDropdown(props) {
|
|
7
|
+
const theme = useTheme();
|
|
8
|
+
const { index, control, disabled } = props;
|
|
9
|
+
return (_jsx(Controller, { name: `solutions.${index}.textType`, control: control, render: ({ field: { value, onChange } }) => (_jsx(Dropdown, Object.assign({ label: value === "normal" ? "일반 텍스트" : "코드 텍스트", size: "xsmall", color: "textNeutral", closeOnItemClick: true, disabled: disabled, buttonCss: css `
|
|
10
|
+
${disabled && `color: ${theme.color.foreground.neutralAlt};`}
|
|
11
|
+
> span {
|
|
12
|
+
font-weight: 700;
|
|
13
|
+
}
|
|
14
|
+
`, menuProps: {
|
|
15
|
+
anchorOrigin: {
|
|
16
|
+
vertical: "bottom",
|
|
17
|
+
horizontal: "center",
|
|
18
|
+
},
|
|
19
|
+
transformOrigin: {
|
|
20
|
+
vertical: "top",
|
|
21
|
+
horizontal: "center",
|
|
22
|
+
},
|
|
23
|
+
} }, { children: _jsx(DropdownItem, { index: 0, label: value === "normal" ? "코드 텍스트" : "일반 텍스트", onClick: () => {
|
|
24
|
+
onChange(value === "normal" ? "code" : "normal");
|
|
25
|
+
} }) }))) }));
|
|
26
|
+
}
|
|
@@ -22,7 +22,7 @@ import ReactDOM, { createPortal } from "react-dom";
|
|
|
22
22
|
import { LexicalNodeMenuPlugin } from "@lexical/react/LexicalNodeMenuPlugin";
|
|
23
23
|
import { useDraggableBlockMenu } from "./useDraggableBlockMenu";
|
|
24
24
|
import { css as cssToClassName } from "@emotion/css";
|
|
25
|
-
import { ComponentPickerMenuList, getBaseOptions, } from "../ComponentPickerMenuPlugin";
|
|
25
|
+
import { ComponentPickerOption, ComponentPickerMenuList, getBaseOptions, ComponentDrawerOption, } from "../ComponentPickerMenuPlugin";
|
|
26
26
|
import { useFloatingMenu } from "./useFloatingMenu";
|
|
27
27
|
import ComponentAdder from "./ComponentAdder";
|
|
28
28
|
import styled from "@emotion/styled";
|
|
@@ -115,6 +115,9 @@ export function ComponentAdderPlugin(props) {
|
|
|
115
115
|
};
|
|
116
116
|
}, [editor, nodeKey, plusOrMenu]);
|
|
117
117
|
const onSelectOption = useCallback((selectedOption, textNodeContainingQuery, closeMenu, matchingString) => {
|
|
118
|
+
if (selectedOption instanceof ComponentDrawerOption) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
118
121
|
editor.update(() => {
|
|
119
122
|
selectedOption.onSelect(matchingString);
|
|
120
123
|
setNodeKey(null);
|
|
@@ -130,10 +133,13 @@ export function ComponentAdderPlugin(props) {
|
|
|
130
133
|
});
|
|
131
134
|
const getContextMenuOptions = useContextMenuOptions();
|
|
132
135
|
const filteredOptions = options.filter((option) => {
|
|
136
|
+
if (!query) {
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
133
139
|
const regex = new RegExp(query, "i");
|
|
134
|
-
return (option
|
|
135
|
-
regex.test(option.title) ||
|
|
136
|
-
|
|
140
|
+
return (option instanceof ComponentPickerOption &&
|
|
141
|
+
(regex.test(option.title) ||
|
|
142
|
+
option.keywords.some((keyword) => regex.test(keyword))));
|
|
137
143
|
});
|
|
138
144
|
const { onDragStart, onDragEnd, targetLineRef } = useDraggableBlockMenu(editor, anchorElem, blockElem, setBlockElem);
|
|
139
145
|
return (_jsxs(_Fragment, { children: [_jsx(InsertImageDialog, { open: imageOpen, activeEditor: editor, onClose: () => setImageOpen(false) }), _jsx(LexicalNodeMenuPlugin, { nodeKey: nodeKey, anchorClassName: cssToClassName `
|
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
* Context Menu (:: 버튼)
|
|
3
3
|
*/
|
|
4
4
|
import { LexicalEditor, LexicalNode } from "lexical";
|
|
5
|
-
import { ComponentPickerOption } from "../ComponentPickerMenuPlugin";
|
|
6
|
-
export declare function useContextMenuOptions(): (editor: LexicalEditor, node: LexicalNode, setImageOpen: (open: boolean) => void) => ComponentPickerOption[];
|
|
5
|
+
import { ComponentDrawerOption, ComponentPickerOption } from "../ComponentPickerMenuPlugin";
|
|
6
|
+
export declare function useContextMenuOptions(): (editor: LexicalEditor, node: LexicalNode, setImageOpen: (open: boolean) => void) => (ComponentPickerOption | ComponentDrawerOption)[];
|
package/dist/patterns/LexicalEditor/plugins/ComponentPickerMenuPlugin/ComponentPickerMenuList.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/// <reference types="react" />
|
|
2
|
-
import { ComponentPickerOption } from "./ComponentPickerMenuPlugin";
|
|
2
|
+
import { ComponentDrawerOption, ComponentPickerOption } from "./ComponentPickerMenuPlugin";
|
|
3
3
|
export interface ComponentPickerMenuListProps {
|
|
4
|
-
options: ComponentPickerOption[];
|
|
4
|
+
options: (ComponentPickerOption | ComponentDrawerOption)[];
|
|
5
5
|
selectedIndex: number | null;
|
|
6
6
|
selectOptionAndCleanUp: (option: ComponentPickerOption) => void;
|
|
7
7
|
setHighlightedIndex: (index: number) => void;
|
package/dist/patterns/LexicalEditor/plugins/ComponentPickerMenuPlugin/ComponentPickerMenuList.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import styled from "@emotion/styled";
|
|
3
|
+
import { ComponentDrawerOption, } from "./ComponentPickerMenuPlugin";
|
|
3
4
|
import { ComponentPickerMenuItem } from "./ComponentPickerMenuItem";
|
|
4
5
|
import React from "react";
|
|
5
6
|
export function ComponentPickerMenuList(props) {
|
|
6
7
|
const { options, selectedIndex, selectOptionAndCleanUp, setHighlightedIndex, } = props;
|
|
7
8
|
return (_jsx(Container, { children: _jsx(ListContainer, { children: options.map((option, i) => {
|
|
8
|
-
if (
|
|
9
|
+
if (option instanceof ComponentDrawerOption) {
|
|
9
10
|
return (_jsx(React.Fragment, { children: option.component }, option.key));
|
|
10
11
|
}
|
|
11
12
|
return (_jsx(ComponentPickerMenuItem, { index: i, isSelected: selectedIndex === i, onClick: () => {
|
package/dist/patterns/LexicalEditor/plugins/ComponentPickerMenuPlugin/ComponentPickerMenuPlugin.d.ts
CHANGED
|
@@ -10,8 +10,12 @@ import { MenuOption } from "@lexical/react/LexicalTypeaheadMenuPlugin";
|
|
|
10
10
|
import { LexicalEditor } from "lexical";
|
|
11
11
|
import { ReactElement } from "react";
|
|
12
12
|
import { Theme } from "@emotion/react";
|
|
13
|
+
export declare class ComponentDrawerOption extends MenuOption {
|
|
14
|
+
title: string;
|
|
15
|
+
component: ReactElement;
|
|
16
|
+
constructor(title: string, component: ReactElement);
|
|
17
|
+
}
|
|
13
18
|
export declare class ComponentPickerOption extends MenuOption {
|
|
14
|
-
component?: ReactElement;
|
|
15
19
|
title: string;
|
|
16
20
|
icon?: ReactElement;
|
|
17
21
|
iconContainerClassName?: string;
|
|
@@ -24,9 +28,7 @@ export declare class ComponentPickerOption extends MenuOption {
|
|
|
24
28
|
keywords?: Array<string>;
|
|
25
29
|
keyboardShortcut?: string;
|
|
26
30
|
onSelect: (queryString: string) => void;
|
|
27
|
-
} | {
|
|
28
|
-
component: ReactElement;
|
|
29
31
|
});
|
|
30
32
|
}
|
|
31
|
-
export declare function getBaseOptions(editor: LexicalEditor, theme: Theme, setImageOpen: (open: boolean) => void): ComponentPickerOption[];
|
|
33
|
+
export declare function getBaseOptions(editor: LexicalEditor, theme: Theme, setImageOpen: (open: boolean) => void): (ComponentPickerOption | ComponentDrawerOption)[];
|
|
32
34
|
export declare function ComponentPickerMenuPlugin(): JSX.Element;
|
package/dist/patterns/LexicalEditor/plugins/ComponentPickerMenuPlugin/ComponentPickerMenuPlugin.js
CHANGED
|
@@ -24,20 +24,20 @@ import { InsertImageDialog } from "../ImagesPlugin/InsertImageDialog";
|
|
|
24
24
|
import { TextIcon, H1Icon, H2Icon, H3Icon, ListUnorderedIcon, ListOrderedIcon, DoubleQuotesLIcon, CodeViewIcon, SeparatorIcon, ImageLineIcon, InputMethodLineIcon, ListRadioIcon, } from "../../../../icons";
|
|
25
25
|
import { ZINDEX } from "../../../../utils/zIndex";
|
|
26
26
|
import { css, useTheme } from "@emotion/react";
|
|
27
|
-
import {
|
|
27
|
+
import { INSERT_PROBLEM_INPUT_COMMAND } from "../ProblemInputPlugin";
|
|
28
28
|
// import useModal from "../../hooks/useModal";
|
|
29
29
|
// import catTypingGif from "../../images/cat-typing.gif";
|
|
30
30
|
// import { INSERT_IMAGE_COMMAND, InsertImageDialog } from "../ImagesPlugin";
|
|
31
|
+
export class ComponentDrawerOption extends MenuOption {
|
|
32
|
+
constructor(title, component) {
|
|
33
|
+
super(title);
|
|
34
|
+
this.title = title;
|
|
35
|
+
this.component = component;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
31
38
|
export class ComponentPickerOption extends MenuOption {
|
|
32
39
|
constructor(title, options) {
|
|
33
40
|
super(title);
|
|
34
|
-
if ("component" in options) {
|
|
35
|
-
this.title = title;
|
|
36
|
-
this.component = options.component;
|
|
37
|
-
this.keywords = [];
|
|
38
|
-
this.onSelect = () => { };
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
41
|
this.title = title;
|
|
42
42
|
this.keywords = options.keywords || [];
|
|
43
43
|
this.icon = options.icon;
|
|
@@ -69,8 +69,8 @@ function getQuizContextOptions(editor, theme) {
|
|
|
69
69
|
return [
|
|
70
70
|
new ComponentPickerOption("주관식 입력 칸", {
|
|
71
71
|
icon: _jsx(InputMethodLineIcon, { color: theme.color.foreground.primary }),
|
|
72
|
-
keywords: ["
|
|
73
|
-
onSelect: () => editor.dispatchCommand(
|
|
72
|
+
keywords: ["problem input", "주관식 입력"],
|
|
73
|
+
onSelect: () => editor.dispatchCommand(INSERT_PROBLEM_INPUT_COMMAND, {
|
|
74
74
|
solutions: [
|
|
75
75
|
{
|
|
76
76
|
textType: "normal",
|
|
@@ -84,24 +84,17 @@ function getQuizContextOptions(editor, theme) {
|
|
|
84
84
|
}),
|
|
85
85
|
new ComponentPickerOption("객관식 입력 칸", {
|
|
86
86
|
icon: _jsx(ListRadioIcon, { color: theme.color.foreground.primary }),
|
|
87
|
-
keywords: [
|
|
88
|
-
"multiple select",
|
|
89
|
-
"multiple select input",
|
|
90
|
-
"객관식",
|
|
91
|
-
"객관식 입력",
|
|
92
|
-
],
|
|
87
|
+
keywords: ["problem select", "객관식 입력"],
|
|
93
88
|
onSelect: () => {
|
|
94
89
|
// TODO: 객관식 입력 칸 추가
|
|
95
90
|
},
|
|
96
91
|
}),
|
|
97
|
-
new
|
|
98
|
-
component: (_jsx("div", { css: css `
|
|
92
|
+
new ComponentDrawerOption("메뉴구분선", (_jsx("div", { css: css `
|
|
99
93
|
width: 100%;
|
|
100
94
|
height: 1px;
|
|
101
95
|
background: ${theme.color.background.neutralAltActive};
|
|
102
96
|
margin: 4px 0;
|
|
103
|
-
` })),
|
|
104
|
-
}),
|
|
97
|
+
` }))),
|
|
105
98
|
];
|
|
106
99
|
}
|
|
107
100
|
export function getBaseOptions(editor, theme, setImageOpen) {
|
|
@@ -211,11 +204,15 @@ export function ComponentPickerMenuPlugin() {
|
|
|
211
204
|
const regex = new RegExp(queryString, "i");
|
|
212
205
|
return [
|
|
213
206
|
...getDynamicOptions(editor, queryString),
|
|
214
|
-
...baseOptions.filter((option) =>
|
|
215
|
-
|
|
207
|
+
...baseOptions.filter((option) => option instanceof ComponentPickerOption &&
|
|
208
|
+
(regex.test(option.title) ||
|
|
209
|
+
option.keywords.some((keyword) => regex.test(keyword)))),
|
|
216
210
|
];
|
|
217
211
|
}, [editor, queryString, setOpen]);
|
|
218
212
|
const onSelectOption = useCallback((selectedOption, nodeToRemove, closeMenu, matchingString) => {
|
|
213
|
+
if (selectedOption instanceof ComponentDrawerOption) {
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
219
216
|
editor.update(() => {
|
|
220
217
|
nodeToRemove === null || nodeToRemove === void 0 ? void 0 : nodeToRemove.remove();
|
|
221
218
|
selectedOption.onSelect(matchingString);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/// <reference types="react" />
|
|
2
2
|
import { LexicalCommand } from "lexical";
|
|
3
3
|
import { ProblemInputPayload } from "../../nodes/ProblemInputNode";
|
|
4
|
-
export declare const
|
|
4
|
+
export declare const INSERT_PROBLEM_INPUT_COMMAND: LexicalCommand<ProblemInputPayload>;
|
|
5
5
|
export default function ProblemInputPlugin(): JSX.Element | null;
|
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
|
|
2
|
-
import { $
|
|
2
|
+
import { $insertNodeToNearestRoot } from "@lexical/utils";
|
|
3
|
+
import { COMMAND_PRIORITY_EDITOR, createCommand, } from "lexical";
|
|
3
4
|
import { useEffect } from "react";
|
|
4
5
|
import { $createProblemInputNode, ProblemInputNode, } from "../../nodes/ProblemInputNode";
|
|
5
|
-
export const
|
|
6
|
+
export const INSERT_PROBLEM_INPUT_COMMAND = createCommand("INSERT_PROBLEM_INPUT_COMMAND");
|
|
6
7
|
export default function ProblemInputPlugin() {
|
|
7
8
|
const [editor] = useLexicalComposerContext();
|
|
8
9
|
useEffect(() => {
|
|
9
10
|
if (!editor.hasNodes([ProblemInputNode])) {
|
|
10
11
|
throw new Error("ProblemInputNode: ProblemInputNode not registered on editor");
|
|
11
12
|
}
|
|
12
|
-
editor.registerCommand(
|
|
13
|
-
var _a;
|
|
13
|
+
editor.registerCommand(INSERT_PROBLEM_INPUT_COMMAND, (payload) => {
|
|
14
14
|
const problemInputNode = $createProblemInputNode(payload);
|
|
15
|
-
|
|
16
|
-
currentNode === null || currentNode === void 0 ? void 0 : currentNode.replace(problemInputNode);
|
|
15
|
+
$insertNodeToNearestRoot(problemInputNode);
|
|
17
16
|
return true;
|
|
18
17
|
}, COMMAND_PRIORITY_EDITOR);
|
|
19
18
|
}, [editor]);
|