@team-monolith/cds 1.49.1 → 1.49.4

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.
@@ -33,14 +33,14 @@ function validateValue(value) {
33
33
  return true;
34
34
  }
35
35
  /** undefined value를 제외한 객체 일치 여부를 판단합니다. */
36
- function isCleanEqualObjects(oldObj, newObj) {
36
+ function isCleanlyEqual(oldObj, newObj) {
37
37
  const cleanedOldObj = getCleanObject(oldObj);
38
38
  const cleanedNewObj = getCleanObject(newObj);
39
39
  return _.isEqual(cleanedOldObj, cleanedNewObj);
40
40
  }
41
41
  /** 객체 내에 undefined value를 갖는 key를 재귀적으로 모두 제거합니다. */
42
42
  function getCleanObject(obj) {
43
- const newObj = _.cloneDeep(obj);
43
+ const newObj = Object.assign({}, obj);
44
44
  for (const key in newObj) {
45
45
  if (newObj[key] === undefined) {
46
46
  delete newObj[key];
@@ -83,7 +83,8 @@ export function LexicalEditor(props) {
83
83
  },
84
84
  },
85
85
  ],
86
- theme: getTheme(theme),
86
+ // 마운트 된 이후 editable 수정해도 theme 반영되지 않음 유의
87
+ theme: getTheme(theme, editable),
87
88
  editorState: validateValue(value) ? JSON.stringify(value) : undefined,
88
89
  editable: editable,
89
90
  };
@@ -94,7 +95,7 @@ export function LexicalEditor(props) {
94
95
  */
95
96
  const onChangeHandler = onChange
96
97
  ? (blocks) => {
97
- if (!isCleanEqualObjects(value, blocks)) {
98
+ if (!isCleanlyEqual(value, blocks)) {
98
99
  onChange(blocks);
99
100
  }
100
101
  }
@@ -29,8 +29,6 @@ export class LayoutContainerNode extends ElementNode {
29
29
  }
30
30
  createDOM(config) {
31
31
  const dom = document.createElement("div");
32
- dom.style.display = "grid";
33
- dom.style.gap = "24px";
34
32
  dom.style.gridTemplateColumns = this.__templateColumns;
35
33
  if (typeof config.theme.layoutContainer === "string") {
36
34
  addClassNamesToElement(dom, config.theme.layoutContainer);
@@ -4,43 +4,48 @@ export interface ImageProps {
4
4
  src: string;
5
5
  altText: string;
6
6
  }
7
- export interface Selection {
8
- /** 해당 선택지의 정답 유무는 채점하기 전의 학생에게는 노출하지 않습니다. */
9
- isAnswer?: boolean;
7
+ export interface SelectionWithoutSolution {
10
8
  show: {
11
9
  image: ImageProps | null;
12
10
  text: string;
13
11
  };
14
12
  value: string;
15
13
  }
16
- export interface ProblemSelectPayload {
17
- selections: Selection[];
14
+ export type Selection = SelectionWithoutSolution & {
15
+ isAnswer: boolean;
16
+ };
17
+ /** 정답이 공개되지 않은 객관식 문제는 다중정답 여부를 전달받기 위해서 서버에서 hasMultipleSolutions를 보내줍니다. */
18
+ export type ProblemSelectPayload = {
18
19
  selected: string[];
19
- hasMultipleAnswers: boolean;
20
20
  key?: NodeKey;
21
- }
21
+ } & ({
22
+ selections: Selection[];
23
+ } | {
24
+ selections: SelectionWithoutSolution[];
25
+ hasMultipleSolutions: boolean;
26
+ });
22
27
  export type SerializedProblemSelectNode = Spread<ProblemSelectPayload, SerializedLexicalNode>;
23
28
  /**
24
29
  * selections는 Selection타입의 배열로서 객관식 정보를 담고 있습니다. (교사용)
25
30
  * selected는 학생이 선택한 답의 value를 담고 있습니다.(학생용)
26
31
  */
27
32
  export declare class ProblemSelectNode extends DecoratorNode<ReactNode> {
28
- __selections: Selection[];
33
+ __selections: Selection[] | SelectionWithoutSolution[];
29
34
  __selected: string[];
30
- __hasMultipleAnswers: boolean;
35
+ __hasMultipleSolutions: boolean | undefined;
31
36
  isInline(): boolean;
32
37
  static getType(): string;
33
- getSelections(): Selection[];
38
+ getSelections(): Selection[] | SelectionWithoutSolution[];
34
39
  getSelected(): string[];
35
- setSelections(selections: Selection[]): void;
40
+ setSelections(selections: Selection[] | SelectionWithoutSolution[]): void;
36
41
  setSelected(selected: string[]): void;
37
42
  static clone(node: ProblemSelectNode): ProblemSelectNode;
38
- constructor(selections: Selection[], selected: string[], hasMultipleAnswers: boolean, key?: NodeKey);
43
+ constructor(selections: Selection[] | SelectionWithoutSolution[], selected: string[], hasMultipleSolutions?: boolean, key?: NodeKey);
39
44
  createDOM(config: EditorConfig): HTMLElement;
40
45
  updateDOM(): boolean;
41
46
  static importJSON(serializedNode: SerializedProblemSelectNode): ProblemSelectNode;
42
47
  exportJSON(): SerializedProblemSelectNode;
43
48
  decorate(): ReactNode;
44
49
  }
45
- export declare function $createProblemSelectNode({ selections, selected, hasMultipleAnswers, key, }: ProblemSelectPayload): ProblemSelectNode;
50
+ export declare function $createProblemSelectNode(payload: ProblemSelectPayload): ProblemSelectNode;
46
51
  export declare function $isProblemSelectNode(node: LexicalNode | null | undefined): node is ProblemSelectNode;
@@ -28,13 +28,13 @@ export class ProblemSelectNode extends DecoratorNode {
28
28
  self.__selected = selected;
29
29
  }
30
30
  static clone(node) {
31
- return new ProblemSelectNode(node.__selections, node.__selected, node.__hasMultipleAnswers, node.__key);
31
+ return new ProblemSelectNode(node.__selections, node.__selected, node.__hasMultipleSolutions, node.__key);
32
32
  }
33
- constructor(selections, selected, hasMultipleAnswers, key) {
33
+ constructor(selections, selected, hasMultipleSolutions, key) {
34
34
  super(key);
35
35
  this.__selections = selections;
36
36
  this.__selected = selected;
37
- this.__hasMultipleAnswers = hasMultipleAnswers;
37
+ this.__hasMultipleSolutions = hasMultipleSolutions;
38
38
  }
39
39
  createDOM(config) {
40
40
  // Define the DOM element here
@@ -46,29 +46,43 @@ export class ProblemSelectNode extends DecoratorNode {
46
46
  return false;
47
47
  }
48
48
  static importJSON(serializedNode) {
49
- const node = $createProblemSelectNode({
50
- key: serializedNode.key,
51
- selections: serializedNode.selections,
52
- selected: serializedNode.selected,
53
- hasMultipleAnswers: serializedNode.hasMultipleAnswers,
54
- });
55
- return node;
49
+ if ("hasMultipleSolutions" in serializedNode) {
50
+ return $createProblemSelectNode({
51
+ key: serializedNode.key,
52
+ selections: serializedNode.selections,
53
+ selected: serializedNode.selected,
54
+ hasMultipleSolutions: serializedNode.hasMultipleSolutions,
55
+ });
56
+ }
57
+ else {
58
+ return $createProblemSelectNode({
59
+ key: serializedNode.key,
60
+ selections: serializedNode.selections,
61
+ selected: serializedNode.selected,
62
+ });
63
+ }
56
64
  }
57
65
  exportJSON() {
58
66
  return {
59
- version: 1,
60
67
  type: "problem-select",
68
+ version: 1,
61
69
  selections: this.__selections,
62
70
  selected: this.__selected,
63
- hasMultipleAnswers: this.__hasMultipleAnswers,
64
71
  };
65
72
  }
66
73
  decorate() {
67
- return (_jsx(SelectComponent, { selections: this.__selections, selected: this.__selected, hasMultipleAnswers: this.__hasMultipleAnswers, nodeKey: this.getKey() }));
74
+ return (_jsx(SelectComponent, { selections: this.__selections, selected: this.__selected, hasMultipleSolutions: this.__hasMultipleSolutions, nodeKey: this.getKey() }));
68
75
  }
69
76
  }
70
- export function $createProblemSelectNode({ selections, selected, hasMultipleAnswers, key, }) {
71
- return $applyNodeReplacement(new ProblemSelectNode(selections, selected, hasMultipleAnswers, key));
77
+ export function $createProblemSelectNode(payload) {
78
+ if ("hasMultipleSolutions" in payload) {
79
+ const { selections, selected, key, hasMultipleSolutions } = payload;
80
+ return $applyNodeReplacement(new ProblemSelectNode(selections, selected, hasMultipleSolutions, key));
81
+ }
82
+ else {
83
+ const { selections, selected, key } = payload;
84
+ return $applyNodeReplacement(new ProblemSelectNode(selections, selected, undefined, key));
85
+ }
72
86
  }
73
87
  export function $isProblemSelectNode(node) {
74
88
  return node instanceof ProblemSelectNode;
@@ -1,9 +1,9 @@
1
1
  /** @jsxImportSource @emotion/react */
2
2
  import { NodeKey } from "lexical";
3
- import { Selection } from "./ProblemSelectNode";
3
+ import { Selection, SelectionWithoutSolution } from "./ProblemSelectNode";
4
4
  export declare function SelectComponent(props: {
5
- selections: Selection[];
5
+ selections: Selection[] | SelectionWithoutSolution[];
6
6
  selected: string[];
7
- hasMultipleAnswers: boolean;
7
+ hasMultipleSolutions: boolean | undefined;
8
8
  nodeKey: NodeKey;
9
9
  }): import("@emotion/react/jsx-runtime").JSX.Element;
@@ -1,18 +1,7 @@
1
- var __rest = (this && this.__rest) || function (s, e) {
2
- var t = {};
3
- for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
4
- t[p] = s[p];
5
- if (s != null && typeof Object.getOwnPropertySymbols === "function")
6
- for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
7
- if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
8
- t[p[i]] = s[p[i]];
9
- }
10
- return t;
11
- };
12
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "@emotion/react/jsx-runtime";
13
2
  /** @jsxImportSource @emotion/react */
14
3
  import { $getNodeByKey } from "lexical";
15
- import { $isProblemSelectNode } from "./ProblemSelectNode";
4
+ import { $isProblemSelectNode, } from "./ProblemSelectNode";
16
5
  import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
17
6
  import { useContext, useState } from "react";
18
7
  import useLexicalEditable from "@lexical/react/useLexicalEditable";
@@ -24,18 +13,22 @@ import SettingForm from "./SettingForm";
24
13
  import styled from "@emotion/styled";
25
14
  import { LexicalCustomConfigContext } from "../../LexicalCustomConfigContext";
26
15
  export function SelectComponent(props) {
27
- const { selected, hasMultipleAnswers } = props, settingFormProps = __rest(props, ["selected", "hasMultipleAnswers"]);
28
- const { selections, nodeKey } = settingFormProps;
16
+ const { selected, hasMultipleSolutions, selections, nodeKey } = props;
29
17
  const [editor] = useLexicalComposerContext();
30
18
  const [settingOpen, setSettingOpen] = useState(false);
31
19
  const isEditable = useLexicalEditable();
32
20
  const { freezeProblemNode, showQuizSolution } = useContext(LexicalCustomConfigContext);
21
+ const showMultipleSolutions = hasMultipleSolutions ||
22
+ (hasMultipleSolutions === undefined &&
23
+ selections.filter((s) => "isAnswer" in s && s.isAnswer).length > 1);
33
24
  // view
34
25
  if (!isEditable) {
35
- return (_jsxs(_Fragment, { children: [hasMultipleAnswers && (_jsxs(Alert, { children: [_jsx(AlarmWarningFillIcon, { css: css `
26
+ return (_jsxs(_Fragment, { children: [showMultipleSolutions && (_jsxs(Alert, { children: [_jsx(AlarmWarningFillIcon, { css: css `
36
27
  width: 14px;
37
28
  height: 14px;
38
- ` }), "\uC9C8\uBB38\uC5D0 \uD574\uB2F9\uD558\uB294 \uB2F5\uC744 \uBAA8\uB450 \uACE0\uB974\uB294 \uBB38\uC81C\uC785\uB2C8\uB2E4."] })), selections.map((selection, index) => (_jsx(SelectBoxView, { index: index + 1, isAnswer: showQuizSolution ? selection.isAnswer : undefined, isSelected: selected.includes(selection.value), image: selection.show.image, text: selection.show.text, onClick: freezeProblemNode
29
+ ` }), "\uC9C8\uBB38\uC5D0 \uD574\uB2F9\uD558\uB294 \uB2F5\uC744 \uBAA8\uB450 \uACE0\uB974\uB294 \uBB38\uC81C\uC785\uB2C8\uB2E4."] })), selections.map((selection, index) => (_jsx(SelectBoxView, { index: index + 1, isAnswer: showQuizSolution && "isAnswer" in selection
30
+ ? selection.isAnswer
31
+ : undefined, isSelected: selected.includes(selection.value), image: selection.show.image, text: selection.show.text, onClick: freezeProblemNode
39
32
  ? undefined
40
33
  : () => {
41
34
  const isSelected = selected.includes(selection.value);
@@ -54,7 +47,7 @@ export function SelectComponent(props) {
54
47
  if (!$isProblemSelectNode(node)) {
55
48
  return;
56
49
  }
57
- if (hasMultipleAnswers) {
50
+ if (showMultipleSolutions) {
58
51
  node.setSelected([...selected, selection.value]);
59
52
  }
60
53
  else {
@@ -72,9 +65,9 @@ export function SelectComponent(props) {
72
65
  display: flex;
73
66
  flex-direction: column;
74
67
  gap: 4px;
75
- ` }, { children: selections.map((selection, index) => (_jsx(SelectBoxEdit, { index: index + 1, isAnswer: Boolean(selection.isAnswer), image: selection.show.image, text: selection.show.text || `${index + 1}번 선택지`, onClick: () => setSettingOpen(true) }, index))) })), _jsx(SquareButton, { size: "small", color: "icon", icon: _jsx(Settings3FillIcon, {}), onClick: () => {
68
+ ` }, { children: selections.map((selection, index) => (_jsx(SelectBoxEdit, { index: index + 1, isAnswer: "isAnswer" in selection && selection.isAnswer, image: selection.show.image, text: selection.show.text || `${index + 1}번 선택지`, onClick: () => setSettingOpen(true) }, index))) })), _jsx(SquareButton, { size: "small", color: "icon", icon: _jsx(Settings3FillIcon, {}), onClick: () => {
76
69
  setSettingOpen(true);
77
- } })] })), settingOpen && (_jsx(SettingForm, Object.assign({}, settingFormProps, { onClose: () => setSettingOpen(false) })))] }));
70
+ } })] })), settingOpen && (_jsx(SettingForm, { selections: selections, nodeKey: nodeKey, onClose: () => setSettingOpen(false) }))] }));
78
71
  }
79
72
  const Alert = styled.div(({ theme }) => css `
80
73
  display: flex;
@@ -100,7 +100,6 @@ function getQuizContextOptions(editor, theme) {
100
100
  },
101
101
  ],
102
102
  selected: [],
103
- hasMultipleAnswers: false,
104
103
  });
105
104
  },
106
105
  }),
@@ -197,8 +196,13 @@ export function getBaseOptions(props) {
197
196
  onSelect: () => setImageOpen(true),
198
197
  }),
199
198
  ];
200
- // 로컬스토리지 devMode true이면 칼럼 컨텍스트 메뉴를 추가합니다.
199
+ // devMode 여부를 로컬스토리지에서 가져옵니다.
201
200
  const isDevMode = localStorage.getItem("devMode") === "true";
201
+ // isQuizEnabled이거나 devMode이면 퀴즈 컨텍스트 메뉴를 꼭대기에 추가합니다.
202
+ if (isQuizEnabled || isDevMode) {
203
+ baseOptions.unshift(...getQuizContextOptions(editor, theme));
204
+ }
205
+ // devMode이면 칼럼 컨텍스트 메뉴를 추가합니다.
202
206
  if (isDevMode) {
203
207
  baseOptions.push(new ComponentPickerOption("칼럼", {
204
208
  icon: _jsx(LayoutColumnLineIcon, {}),
@@ -206,10 +210,6 @@ export function getBaseOptions(props) {
206
210
  onSelect: () => editor.dispatchCommand(INSERT_LAYOUT_COMMAND, "1fr 1fr"),
207
211
  }));
208
212
  }
209
- // isQuizEnabled이거나 로컬스토리지 devMode가 true이면 퀴즈 컨텍스트 메뉴를 추가합니다.
210
- if (isQuizEnabled || isDevMode) {
211
- return [...getQuizContextOptions(editor, theme), ...baseOptions];
212
- }
213
213
  return baseOptions;
214
214
  }
215
215
  export function ComponentPickerMenuPlugin(props) {
@@ -1,5 +1,5 @@
1
1
  import { Theme } from "@emotion/react";
2
- export declare function getTheme(theme: Theme): {
2
+ export declare function getTheme(theme: Theme, editable: boolean): {
3
3
  paragraph: string;
4
4
  quote: string;
5
5
  coloredQuote: {
@@ -30,4 +30,6 @@ export declare function getTheme(theme: Theme): {
30
30
  };
31
31
  problemInput: string;
32
32
  problemSelect: string;
33
+ layoutContainer: string;
34
+ layoutItem: string | false;
33
35
  };
@@ -1,5 +1,5 @@
1
1
  import { css } from "@emotion/css";
2
- export function getTheme(theme) {
2
+ export function getTheme(theme, editable) {
3
3
  return {
4
4
  paragraph: css `
5
5
  color: ${theme.color.foreground.neutralBase};
@@ -260,5 +260,13 @@ export function getTheme(theme) {
260
260
  gap: 4px;
261
261
  margin-bottom: 8px;
262
262
  `,
263
+ layoutContainer: css `
264
+ display: grid;
265
+ gap: 24px;
266
+ `,
267
+ layoutItem: editable &&
268
+ css `
269
+ border: 1px dashed ${theme.color.background.neutralAltActive};
270
+ `,
263
271
  };
264
272
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@team-monolith/cds",
3
- "version": "1.49.1",
3
+ "version": "1.49.4",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "sideEffects": false,