@team-monolith/cds 1.122.0 → 1.122.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/patterns/LexicalEditor/components/InsertImageDialog/InsertImageDialog.js +5 -14
- package/dist/patterns/LexicalEditor/plugins/ImagesPlugin/imageNodeTransform.d.ts +8 -0
- package/dist/patterns/LexicalEditor/plugins/ImagesPlugin/imageNodeTransform.js +100 -0
- package/dist/patterns/LexicalEditor/plugins/ImagesPlugin/index.js +8 -125
- package/dist/patterns/LexicalEditor/utils/url.d.ts +0 -1
- package/dist/patterns/LexicalEditor/utils/url.js +0 -4
- package/dist/texts.d.ts +0 -2
- package/dist/texts.js +0 -2
- package/package.json +1 -1
|
@@ -13,15 +13,13 @@ import { ImageNotAvailable } from "../../nodes/ImageNode/ImageNotAvailable";
|
|
|
13
13
|
import styled from "@emotion/styled";
|
|
14
14
|
import { debounce } from "lodash";
|
|
15
15
|
import { useTranslation } from "react-i18next";
|
|
16
|
-
import { getTexts } from "../../../../texts";
|
|
17
|
-
import { isDataUrl } from "../../utils/url";
|
|
18
16
|
export function InsertImageDialog(props) {
|
|
17
|
+
var _a;
|
|
19
18
|
const { title, open, onClose, imageProps, onChange, onDelete, shouldReset } = props;
|
|
20
19
|
const theme = useTheme();
|
|
21
20
|
const { t } = useTranslation();
|
|
22
|
-
const { control, setValue, watch, reset, handleSubmit, subscribe
|
|
21
|
+
const { control, setValue, watch, reset, handleSubmit, subscribe } = useForm({
|
|
23
22
|
defaultValues: imageProps !== null && imageProps !== void 0 ? imageProps : { src: "", altText: "" },
|
|
24
|
-
mode: "all",
|
|
25
23
|
});
|
|
26
24
|
const handleOnClose = () => {
|
|
27
25
|
if (shouldReset) {
|
|
@@ -36,7 +34,7 @@ export function InsertImageDialog(props) {
|
|
|
36
34
|
// src는 입력 필드에 실시간으로 반영되는 값이고 previewSrc는 입력이 끝난 뒤 미리보기 영역에 표시할 URL입니다.
|
|
37
35
|
// 두 값은 시멘틱이 다르므로 독립적인 state로 관리합니다.
|
|
38
36
|
const [previewSrc, setPreviewSrc] = useState(watch("src"));
|
|
39
|
-
const isDisabled = watch("src") === ""
|
|
37
|
+
const isDisabled = watch("src") === "";
|
|
40
38
|
const debouncedSetPreviewSrc = useMemo(() => debounce((value) => setPreviewSrc(value), 500), []);
|
|
41
39
|
useEffect(() => {
|
|
42
40
|
const unsubscribe = subscribe({
|
|
@@ -53,15 +51,8 @@ export function InsertImageDialog(props) {
|
|
|
53
51
|
e.stopPropagation();
|
|
54
52
|
handleSubmit(onSubmit)();
|
|
55
53
|
}, disableIconPadding: true, children: [_jsx(StyledAlertDialogTitle, { onClose: handleOnClose, children: title }), _jsx(AlertDialogContent, { children: _jsxs(Inputs, { children: [_jsx(FileSelectInput, { onChange: (value) => {
|
|
56
|
-
setValue("src", value
|
|
57
|
-
|
|
58
|
-
shouldValidate: true,
|
|
59
|
-
});
|
|
60
|
-
}, 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, {}), rules: {
|
|
61
|
-
validate: (value) => value === "" || !isDataUrl(value)
|
|
62
|
-
? true
|
|
63
|
-
: getTexts(t, "errorImageDataUrlNotAllowed"),
|
|
64
|
-
} }), 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 }))] })] }));
|
|
54
|
+
setValue("src", value);
|
|
55
|
+
}, 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 }))] })] }, open ? (_a = imageProps === null || imageProps === void 0 ? void 0 : imageProps.src) !== null && _a !== void 0 ? _a : "" : undefined));
|
|
65
56
|
}
|
|
66
57
|
const StyledAlertDialog = styled(AlertDialog) `
|
|
67
58
|
gap: 16px;
|
|
@@ -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
|
+
}
|
|
@@ -1,12 +1,3 @@
|
|
|
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
1
|
/**
|
|
11
2
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
12
3
|
*
|
|
@@ -16,133 +7,25 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
16
7
|
*/
|
|
17
8
|
import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
|
|
18
9
|
import { $insertNodeToNearestRoot, mergeRegister } from "@lexical/utils";
|
|
19
|
-
import { $createRangeSelection, $
|
|
20
|
-
import {
|
|
21
|
-
import { useTranslation } from "react-i18next";
|
|
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
|
+
import { useEffect } from "react";
|
|
22
12
|
import { $createImageNode, $isImageNode, ImageNode, } from "../../nodes";
|
|
23
|
-
import {
|
|
24
|
-
import { getTexts } from "../../../../texts";
|
|
25
|
-
import { isDataUrl } from "../../utils/url";
|
|
13
|
+
import { useImageNodeTransform } from "./imageNodeTransform";
|
|
26
14
|
const CAN_USE_DOM = typeof window !== "undefined" &&
|
|
27
15
|
typeof window.document !== "undefined" &&
|
|
28
16
|
typeof window.document.createElement !== "undefined";
|
|
29
17
|
const getDOMSelection = (targetWindow) => CAN_USE_DOM ? (targetWindow || window).getSelection() : null;
|
|
30
|
-
function parseBase64Image(dataUrl) {
|
|
31
|
-
var _a;
|
|
32
|
-
const commaIndex = dataUrl.indexOf(",");
|
|
33
|
-
if (commaIndex < 0) {
|
|
34
|
-
return null;
|
|
35
|
-
}
|
|
36
|
-
const metadata = dataUrl.slice("data:".length, commaIndex);
|
|
37
|
-
const base64 = dataUrl.slice(commaIndex + 1).trim();
|
|
38
|
-
const metadataParts = metadata.split(";");
|
|
39
|
-
const mimeType = (_a = metadataParts[0]) !== null && _a !== void 0 ? _a : "";
|
|
40
|
-
const isBase64Encoded = metadataParts.includes("base64");
|
|
41
|
-
if (!isBase64Encoded || !mimeType.startsWith("image/") || base64 === "") {
|
|
42
|
-
return null;
|
|
43
|
-
}
|
|
44
|
-
return { base64, mimeType };
|
|
45
|
-
}
|
|
46
|
-
function createFileFromParsedDataUrl(parsed) {
|
|
47
|
-
if (!CAN_USE_DOM || typeof File === "undefined" || !("atob" in window)) {
|
|
48
|
-
return null;
|
|
49
|
-
}
|
|
50
|
-
try {
|
|
51
|
-
const binaryString = window.atob(parsed.base64.replace(/\s/g, ""));
|
|
52
|
-
const length = binaryString.length;
|
|
53
|
-
const bytes = new Uint8Array(length);
|
|
54
|
-
for (let index = 0; index < length; index += 1) {
|
|
55
|
-
bytes[index] = binaryString.charCodeAt(index);
|
|
56
|
-
}
|
|
57
|
-
const extension = getExtensionFromMimeType(parsed.mimeType);
|
|
58
|
-
const filename = `pasted-image-${Date.now()}.${extension}`;
|
|
59
|
-
return new File([bytes], filename, { type: parsed.mimeType });
|
|
60
|
-
}
|
|
61
|
-
catch (_a) {
|
|
62
|
-
return null;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
function getExtensionFromMimeType(mimeType) {
|
|
66
|
-
const match = mimeType.match(/\/([a-zA-Z0-9.+-]+)/);
|
|
67
|
-
if (!match || match.length < 2) {
|
|
68
|
-
return "png";
|
|
69
|
-
}
|
|
70
|
-
const subtype = match[1];
|
|
71
|
-
return subtype.includes("+") ? subtype.split("+")[0] : subtype;
|
|
72
|
-
}
|
|
73
18
|
export const INSERT_IMAGE_COMMAND = createCommand("INSERT_IMAGE_COMMAND");
|
|
74
19
|
export function ImagesPlugin({ captionsEnabled, }) {
|
|
75
|
-
var _a, _b;
|
|
76
20
|
const [editor] = useLexicalComposerContext();
|
|
77
|
-
const
|
|
78
|
-
const uploadByFile = (_a = cdsContext.lexical) === null || _a === void 0 ? void 0 : _a.uploadByFile;
|
|
79
|
-
const showFileError = (_b = cdsContext.lexical) === null || _b === void 0 ? void 0 : _b.showFileError;
|
|
80
|
-
const { t } = useTranslation();
|
|
81
|
-
const processingNodesRef = useRef(new Set());
|
|
21
|
+
const imageNodeTransformHandler = useImageNodeTransform(editor);
|
|
82
22
|
useEffect(() => {
|
|
83
23
|
if (!editor.hasNodes([ImageNode])) {
|
|
84
24
|
throw new Error("ImagesPlugin: ImageNode not registered on editor");
|
|
85
25
|
}
|
|
86
|
-
return mergeRegister(
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
if (!isDataUrl(src)) {
|
|
90
|
-
processingNodesRef.current.delete(key);
|
|
91
|
-
return;
|
|
92
|
-
}
|
|
93
|
-
if (processingNodesRef.current.has(key)) {
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
processingNodesRef.current.add(key);
|
|
97
|
-
const parsedDataUrl = parseBase64Image(src);
|
|
98
|
-
if (!parsedDataUrl) {
|
|
99
|
-
processingNodesRef.current.delete(key);
|
|
100
|
-
node.remove();
|
|
101
|
-
showFileError === null || showFileError === void 0 ? void 0 : showFileError("upload", getTexts(t, "errorImageDataUrlNotAllowed"));
|
|
102
|
-
return;
|
|
103
|
-
}
|
|
104
|
-
if (!uploadByFile) {
|
|
105
|
-
processingNodesRef.current.delete(key);
|
|
106
|
-
node.remove();
|
|
107
|
-
showFileError === null || showFileError === void 0 ? void 0 : showFileError("upload", getTexts(t, "errorImageDataUrlNotAllowed"));
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
void (() => __awaiter(this, void 0, void 0, function* () {
|
|
111
|
-
const file = createFileFromParsedDataUrl(parsedDataUrl);
|
|
112
|
-
if (!file) {
|
|
113
|
-
processingNodesRef.current.delete(key);
|
|
114
|
-
showFileError === null || showFileError === void 0 ? void 0 : showFileError("upload", getTexts(t, "errorImageDataUrlNotAllowed"));
|
|
115
|
-
editor.update(() => {
|
|
116
|
-
const currentNode = $getNodeByKey(key);
|
|
117
|
-
if ($isImageNode(currentNode)) {
|
|
118
|
-
currentNode.remove();
|
|
119
|
-
}
|
|
120
|
-
});
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
try {
|
|
124
|
-
const uploadedUrl = yield uploadByFile(file);
|
|
125
|
-
editor.update(() => {
|
|
126
|
-
const currentNode = $getNodeByKey(key);
|
|
127
|
-
if ($isImageNode(currentNode)) {
|
|
128
|
-
currentNode.setSrc(uploadedUrl);
|
|
129
|
-
}
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
catch (_a) {
|
|
133
|
-
showFileError === null || showFileError === void 0 ? void 0 : showFileError("upload", getTexts(t, "errorImageBase64UploadFailed"));
|
|
134
|
-
editor.update(() => {
|
|
135
|
-
const currentNode = $getNodeByKey(key);
|
|
136
|
-
if ($isImageNode(currentNode)) {
|
|
137
|
-
currentNode.remove();
|
|
138
|
-
}
|
|
139
|
-
});
|
|
140
|
-
}
|
|
141
|
-
finally {
|
|
142
|
-
processingNodesRef.current.delete(key);
|
|
143
|
-
}
|
|
144
|
-
}))();
|
|
145
|
-
}), editor.registerCommand(INSERT_IMAGE_COMMAND, (payload) => {
|
|
26
|
+
return mergeRegister(...(imageNodeTransformHandler
|
|
27
|
+
? [editor.registerNodeTransform(ImageNode, imageNodeTransformHandler)]
|
|
28
|
+
: []), editor.registerCommand(INSERT_IMAGE_COMMAND, (payload) => {
|
|
146
29
|
const imageNode = $createImageNode(payload);
|
|
147
30
|
// lexical의 원본코드에서는 이미지 노드를 텍스트 노드 안에 삽입하고 있었습니다.
|
|
148
31
|
// 이 때문에 이미지 노드 아래에 불필요한 텍스트 라인이 생성됩니다.
|
|
@@ -152,7 +35,7 @@ export function ImagesPlugin({ captionsEnabled, }) {
|
|
|
152
35
|
$insertNodeToNearestRoot(imageNode);
|
|
153
36
|
return true;
|
|
154
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));
|
|
155
|
-
}, [captionsEnabled, editor,
|
|
38
|
+
}, [captionsEnabled, editor, imageNodeTransformHandler]);
|
|
156
39
|
return null;
|
|
157
40
|
}
|
|
158
41
|
const TRANSPARENT_IMAGE = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
|
|
@@ -12,7 +12,6 @@ const SUPPORTED_URL_PROTOCOLS = new Set([
|
|
|
12
12
|
"sms:",
|
|
13
13
|
"tel:",
|
|
14
14
|
]);
|
|
15
|
-
const DATA_URL_PATTERN = /^data:[^,]+,/i;
|
|
16
15
|
export function sanitizeUrl(url) {
|
|
17
16
|
try {
|
|
18
17
|
const parsedUrl = new URL(url);
|
|
@@ -26,9 +25,6 @@ export function sanitizeUrl(url) {
|
|
|
26
25
|
}
|
|
27
26
|
return url;
|
|
28
27
|
}
|
|
29
|
-
export function isDataUrl(url) {
|
|
30
|
-
return DATA_URL_PATTERN.test(url);
|
|
31
|
-
}
|
|
32
28
|
// Source: https://stackoverflow.com/a/8234912/2013580
|
|
33
29
|
const urlRegExp = new RegExp(/((([A-Za-z]{3,9}:(?:\/\/)?)(?:[-;:&=+$,\w]+@)?[A-Za-z0-9.-]+|(?:www.|[-;:&=+$,\w]+@)[A-Za-z0-9.-]+)((?:\/[+~%/.\w-_]*)?\??(?:[-+=&;%@.\w_]*)#?(?:[\w]*))?)/);
|
|
34
30
|
export function validateUrl(url) {
|
package/dist/texts.d.ts
CHANGED
|
@@ -2,8 +2,6 @@ import { TFunction } from "i18next";
|
|
|
2
2
|
type TranslationMap = {
|
|
3
3
|
placeholderEnterHere: string;
|
|
4
4
|
errorFileTooLarge: string;
|
|
5
|
-
errorImageDataUrlNotAllowed: string;
|
|
6
|
-
errorImageBase64UploadFailed: string;
|
|
7
5
|
descriptionDefaultInputText: string;
|
|
8
6
|
exampleEnterHere: string;
|
|
9
7
|
placeholderEvaluationItem: string;
|
package/dist/texts.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
const TRANSLATION_TEXT = {
|
|
2
2
|
placeholderEnterHere: (t) => t("여기에 입력하세요."),
|
|
3
3
|
errorFileTooLarge: (t) => t("용량이 너무 큽니다. 1GB 이하의 파일을 선택해 주세요."),
|
|
4
|
-
errorImageDataUrlNotAllowed: (t) => t("데이터 URL 기반 이미지는 지원되지 않습니다. 파일 업로드를 사용해 주세요."),
|
|
5
|
-
errorImageBase64UploadFailed: (t) => t("이미지를 업로드하지 못했습니다. 다시 시도해 주세요."),
|
|
6
4
|
descriptionDefaultInputText: (t) => t("입력 칸에 기본으로 노출되는 텍스트입니다."),
|
|
7
5
|
exampleEnterHere: (t) => t("예) 여기에 입력하세요."),
|
|
8
6
|
placeholderEvaluationItem: (t) => t("평가 항목을 입력하세요."),
|