@team-monolith/cds 1.121.2 → 1.122.1

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.
@@ -0,0 +1,8 @@
1
+ import { LexicalEditor } from "lexical";
2
+ import { ImageNode } from "../../nodes";
3
+ /**
4
+ * ImageNode의 transform 핸들러를 반환하는 커스텀 훅
5
+ * base64 data URL을 감지하고 파일로 변환하여 업로드한 후 실제 URL로 교체합니다.
6
+ * uploadByFile이 없으면 null을 반환합니다.
7
+ */
8
+ export declare function useImageNodeTransform(editor: LexicalEditor): ((node: ImageNode) => void) | null;
@@ -0,0 +1,100 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import { $getNodeByKey } from "lexical";
11
+ import { useContext, useRef } from "react";
12
+ import { useTranslation } from "react-i18next";
13
+ import { $isImageNode } from "../../nodes";
14
+ import { CdsContext } from "../../../../CdsProvider";
15
+ const DATA_URL_PATTERN = /^data:[^,]+,/i;
16
+ function isDataUrl(url) {
17
+ return DATA_URL_PATTERN.test(url);
18
+ }
19
+ function getExtensionFromMimeType(mimeType) {
20
+ var _a;
21
+ const extension = (_a = mimeType.split("/")[1]) === null || _a === void 0 ? void 0 : _a.split("+")[0];
22
+ return extension || "png";
23
+ }
24
+ function convertDataUrlToFile(dataUrl) {
25
+ return __awaiter(this, void 0, void 0, function* () {
26
+ try {
27
+ const response = yield fetch(dataUrl);
28
+ const blob = yield response.blob();
29
+ const extension = getExtensionFromMimeType(blob.type);
30
+ const filename = `pasted-image-${Date.now()}.${extension}`;
31
+ return new File([blob], filename, { type: blob.type });
32
+ }
33
+ catch (_a) {
34
+ return null;
35
+ }
36
+ });
37
+ }
38
+ /**
39
+ * ImageNode의 transform 핸들러를 반환하는 커스텀 훅
40
+ * base64 data URL을 감지하고 파일로 변환하여 업로드한 후 실제 URL로 교체합니다.
41
+ * uploadByFile이 없으면 null을 반환합니다.
42
+ */
43
+ export function useImageNodeTransform(editor) {
44
+ var _a, _b;
45
+ const cdsContext = useContext(CdsContext);
46
+ const uploadByFile = (_a = cdsContext.lexical) === null || _a === void 0 ? void 0 : _a.uploadByFile;
47
+ const showFileError = (_b = cdsContext.lexical) === null || _b === void 0 ? void 0 : _b.showFileError;
48
+ const { t } = useTranslation();
49
+ const processingNodesRef = useRef(new Set());
50
+ if (!uploadByFile) {
51
+ return null;
52
+ }
53
+ return (node) => {
54
+ const key = node.getKey();
55
+ const src = node.getSrc();
56
+ if (!isDataUrl(src)) {
57
+ processingNodesRef.current.delete(key);
58
+ return;
59
+ }
60
+ if (processingNodesRef.current.has(key)) {
61
+ return;
62
+ }
63
+ processingNodesRef.current.add(key);
64
+ (() => __awaiter(this, void 0, void 0, function* () {
65
+ const file = yield convertDataUrlToFile(src);
66
+ if (!file) {
67
+ processingNodesRef.current.delete(key);
68
+ showFileError === null || showFileError === void 0 ? void 0 : showFileError("upload", t("지원되지 않는 이미지입니다. 파일 업로드를 사용해 주세요."));
69
+ editor.update(() => {
70
+ const currentNode = $getNodeByKey(key);
71
+ if ($isImageNode(currentNode)) {
72
+ currentNode.remove();
73
+ }
74
+ });
75
+ return;
76
+ }
77
+ try {
78
+ const uploadedUrl = yield uploadByFile(file);
79
+ editor.update(() => {
80
+ const currentNode = $getNodeByKey(key);
81
+ if ($isImageNode(currentNode)) {
82
+ currentNode.setSrc(uploadedUrl);
83
+ }
84
+ });
85
+ }
86
+ catch (_a) {
87
+ showFileError === null || showFileError === void 0 ? void 0 : showFileError("upload", t("이미지를 업로드하지 못했습니다. 다시 시도해 주세요."));
88
+ editor.update(() => {
89
+ const currentNode = $getNodeByKey(key);
90
+ if ($isImageNode(currentNode)) {
91
+ currentNode.remove();
92
+ }
93
+ });
94
+ }
95
+ finally {
96
+ processingNodesRef.current.delete(key);
97
+ }
98
+ }))();
99
+ };
100
+ }
@@ -10,6 +10,7 @@ import { $insertNodeToNearestRoot, mergeRegister } from "@lexical/utils";
10
10
  import { $createRangeSelection, $getSelection, $isNodeSelection, $setSelection, COMMAND_PRIORITY_EDITOR, COMMAND_PRIORITY_HIGH, COMMAND_PRIORITY_LOW, createCommand, DRAGOVER_COMMAND, DRAGSTART_COMMAND, DROP_COMMAND, } from "lexical";
11
11
  import { useEffect } from "react";
12
12
  import { $createImageNode, $isImageNode, ImageNode, } from "../../nodes";
13
+ import { useImageNodeTransform } from "./imageNodeTransform";
13
14
  const CAN_USE_DOM = typeof window !== "undefined" &&
14
15
  typeof window.document !== "undefined" &&
15
16
  typeof window.document.createElement !== "undefined";
@@ -17,11 +18,14 @@ const getDOMSelection = (targetWindow) => CAN_USE_DOM ? (targetWindow || window)
17
18
  export const INSERT_IMAGE_COMMAND = createCommand("INSERT_IMAGE_COMMAND");
18
19
  export function ImagesPlugin({ captionsEnabled, }) {
19
20
  const [editor] = useLexicalComposerContext();
21
+ const imageNodeTransformHandler = useImageNodeTransform(editor);
20
22
  useEffect(() => {
21
23
  if (!editor.hasNodes([ImageNode])) {
22
24
  throw new Error("ImagesPlugin: ImageNode not registered on editor");
23
25
  }
24
- return mergeRegister(editor.registerCommand(INSERT_IMAGE_COMMAND, (payload) => {
26
+ return mergeRegister(...(imageNodeTransformHandler
27
+ ? [editor.registerNodeTransform(ImageNode, imageNodeTransformHandler)]
28
+ : []), editor.registerCommand(INSERT_IMAGE_COMMAND, (payload) => {
25
29
  const imageNode = $createImageNode(payload);
26
30
  // lexical의 원본코드에서는 이미지 노드를 텍스트 노드 안에 삽입하고 있었습니다.
27
31
  // 이 때문에 이미지 노드 아래에 불필요한 텍스트 라인이 생성됩니다.
@@ -31,7 +35,7 @@ export function ImagesPlugin({ captionsEnabled, }) {
31
35
  $insertNodeToNearestRoot(imageNode);
32
36
  return true;
33
37
  }, COMMAND_PRIORITY_EDITOR), editor.registerCommand(DRAGSTART_COMMAND, (event) => onDragStart(event), COMMAND_PRIORITY_HIGH), editor.registerCommand(DRAGOVER_COMMAND, (event) => onDragover(event), COMMAND_PRIORITY_LOW), editor.registerCommand(DROP_COMMAND, (event) => onDrop(event, editor), COMMAND_PRIORITY_HIGH));
34
- }, [captionsEnabled, editor]);
38
+ }, [captionsEnabled, editor, imageNodeTransformHandler]);
35
39
  return null;
36
40
  }
37
41
  const TRANSPARENT_IMAGE = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@team-monolith/cds",
3
- "version": "1.121.2",
3
+ "version": "1.122.1",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "sideEffects": false,