@underverse-ui/underverse 1.0.23 → 1.0.24
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/index.cjs +225 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +35 -2
- package/dist/index.d.ts +35 -2
- package/dist/index.js +226 -7
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -166,6 +166,7 @@ __export(index_exports, {
|
|
|
166
166
|
Tooltip: () => Tooltip,
|
|
167
167
|
TranslationProvider: () => TranslationProvider,
|
|
168
168
|
UEditor: () => UEditor_default,
|
|
169
|
+
UEditorPrepareContentForSaveError: () => UEditorPrepareContentForSaveError,
|
|
169
170
|
UnderverseProvider: () => UnderverseProvider,
|
|
170
171
|
VARIANT_STYLES_ALERT: () => VARIANT_STYLES_ALERT,
|
|
171
172
|
VARIANT_STYLES_BTN: () => VARIANT_STYLES_BTN,
|
|
@@ -176,6 +177,7 @@ __export(index_exports, {
|
|
|
176
177
|
getAnimationStyles: () => getAnimationStyles,
|
|
177
178
|
getUnderverseMessages: () => getUnderverseMessages,
|
|
178
179
|
injectAnimationStyles: () => injectAnimationStyles,
|
|
180
|
+
prepareUEditorContentForSave: () => prepareUEditorContentForSave,
|
|
179
181
|
shadcnAnimationStyles: () => shadcnAnimationStyles2,
|
|
180
182
|
underverseMessages: () => underverseMessages,
|
|
181
183
|
useFormField: () => useFormField,
|
|
@@ -21461,7 +21463,7 @@ function useSmartLocale() {
|
|
|
21461
21463
|
}
|
|
21462
21464
|
|
|
21463
21465
|
// ../../components/ui/UEditor/UEditor.tsx
|
|
21464
|
-
var import_react51 = require("react");
|
|
21466
|
+
var import_react51 = __toESM(require("react"), 1);
|
|
21465
21467
|
var import_next_intl6 = require("next-intl");
|
|
21466
21468
|
var import_react52 = require("@tiptap/react");
|
|
21467
21469
|
|
|
@@ -24355,14 +24357,207 @@ var CharacterCountDisplay = ({ editor, maxCharacters }) => {
|
|
|
24355
24357
|
] });
|
|
24356
24358
|
};
|
|
24357
24359
|
|
|
24360
|
+
// ../../components/ui/UEditor/prepare-content-for-save.ts
|
|
24361
|
+
var MIME_EXTENSION_MAP = {
|
|
24362
|
+
"image/png": "png",
|
|
24363
|
+
"image/jpeg": "jpg",
|
|
24364
|
+
"image/webp": "webp",
|
|
24365
|
+
"image/gif": "gif",
|
|
24366
|
+
"image/svg+xml": "svg",
|
|
24367
|
+
"image/bmp": "bmp",
|
|
24368
|
+
"image/x-icon": "ico",
|
|
24369
|
+
"image/avif": "avif"
|
|
24370
|
+
};
|
|
24371
|
+
function isDataImageUrl(value) {
|
|
24372
|
+
return /^data:image\//i.test(value.trim());
|
|
24373
|
+
}
|
|
24374
|
+
function parseDataImageUrl(dataUrl) {
|
|
24375
|
+
const value = dataUrl.trim();
|
|
24376
|
+
if (!isDataImageUrl(value)) return null;
|
|
24377
|
+
const commaIndex = value.indexOf(",");
|
|
24378
|
+
if (commaIndex < 0) return null;
|
|
24379
|
+
const header = value.slice(5, commaIndex);
|
|
24380
|
+
const base64Data = value.slice(commaIndex + 1).trim();
|
|
24381
|
+
if (!/;base64/i.test(header)) return null;
|
|
24382
|
+
const mime = header.split(";")[0]?.trim().toLowerCase();
|
|
24383
|
+
if (!mime || !base64Data) return null;
|
|
24384
|
+
return { mime, base64Data };
|
|
24385
|
+
}
|
|
24386
|
+
function decodeBase64ToBytes(base64Data) {
|
|
24387
|
+
const normalized = base64Data.replace(/\s+/g, "");
|
|
24388
|
+
const binary = atob(normalized);
|
|
24389
|
+
const bytes = new Uint8Array(binary.length);
|
|
24390
|
+
for (let i = 0; i < binary.length; i += 1) {
|
|
24391
|
+
bytes[i] = binary.charCodeAt(i);
|
|
24392
|
+
}
|
|
24393
|
+
return bytes;
|
|
24394
|
+
}
|
|
24395
|
+
function inferFileExtension(mime) {
|
|
24396
|
+
return MIME_EXTENSION_MAP[mime] ?? "bin";
|
|
24397
|
+
}
|
|
24398
|
+
function createFileFromDataImageUrl(dataUrl, index) {
|
|
24399
|
+
const parsed = parseDataImageUrl(dataUrl);
|
|
24400
|
+
if (!parsed) {
|
|
24401
|
+
throw new Error("Invalid data image URL format.");
|
|
24402
|
+
}
|
|
24403
|
+
const bytes = decodeBase64ToBytes(parsed.base64Data);
|
|
24404
|
+
const extension = inferFileExtension(parsed.mime);
|
|
24405
|
+
const name = `ueditor-image-${index + 1}.${extension}`;
|
|
24406
|
+
return new File([bytes], name, { type: parsed.mime });
|
|
24407
|
+
}
|
|
24408
|
+
function normalizeUploadResult(result) {
|
|
24409
|
+
if (typeof result === "string") {
|
|
24410
|
+
const url2 = result.trim();
|
|
24411
|
+
if (!url2) throw new Error("Upload handler returned an empty URL.");
|
|
24412
|
+
return { url: url2 };
|
|
24413
|
+
}
|
|
24414
|
+
if (!result || typeof result !== "object") {
|
|
24415
|
+
throw new Error("Upload handler returned invalid result.");
|
|
24416
|
+
}
|
|
24417
|
+
const url = typeof result.url === "string" ? result.url.trim() : "";
|
|
24418
|
+
if (!url) throw new Error("Upload handler object result is missing `url`.");
|
|
24419
|
+
const { url: _ignoredUrl, ...rest } = result;
|
|
24420
|
+
const meta = Object.keys(rest).length > 0 ? rest : void 0;
|
|
24421
|
+
return { url, meta };
|
|
24422
|
+
}
|
|
24423
|
+
function getErrorReason(error) {
|
|
24424
|
+
if (error instanceof Error && error.message) return error.message;
|
|
24425
|
+
if (typeof error === "string" && error.trim()) return error;
|
|
24426
|
+
return "Unknown upload error.";
|
|
24427
|
+
}
|
|
24428
|
+
function replaceSrcInTag(match, nextSrc) {
|
|
24429
|
+
if (!match.srcAttr) return match.tag;
|
|
24430
|
+
const { start, end, quote } = match.srcAttr;
|
|
24431
|
+
const escaped = quote === '"' ? nextSrc.replace(/"/g, """) : quote === "'" ? nextSrc.replace(/'/g, "'") : nextSrc;
|
|
24432
|
+
const srcAttr = quote ? `src=${quote}${escaped}${quote}` : `src=${escaped}`;
|
|
24433
|
+
return `${match.tag.slice(0, start)}${srcAttr}${match.tag.slice(end)}`;
|
|
24434
|
+
}
|
|
24435
|
+
function collectImgTagMatches(html) {
|
|
24436
|
+
const matches = [];
|
|
24437
|
+
const imgRegex = /<img\b[^>]*>/gi;
|
|
24438
|
+
const srcAttrRegex = /\bsrc\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+))/i;
|
|
24439
|
+
let tagMatch = imgRegex.exec(html);
|
|
24440
|
+
while (tagMatch) {
|
|
24441
|
+
const tag = tagMatch[0];
|
|
24442
|
+
const start = tagMatch.index;
|
|
24443
|
+
const end = start + tag.length;
|
|
24444
|
+
const srcMatch = srcAttrRegex.exec(tag);
|
|
24445
|
+
let srcAttr = null;
|
|
24446
|
+
if (srcMatch) {
|
|
24447
|
+
const value = srcMatch[1] ?? srcMatch[2] ?? srcMatch[3] ?? "";
|
|
24448
|
+
const quote = srcMatch[1] !== void 0 ? '"' : srcMatch[2] !== void 0 ? "'" : "";
|
|
24449
|
+
srcAttr = {
|
|
24450
|
+
start: srcMatch.index,
|
|
24451
|
+
end: srcMatch.index + srcMatch[0].length,
|
|
24452
|
+
value,
|
|
24453
|
+
quote
|
|
24454
|
+
};
|
|
24455
|
+
}
|
|
24456
|
+
matches.push({ start, end, tag, srcAttr });
|
|
24457
|
+
tagMatch = imgRegex.exec(html);
|
|
24458
|
+
}
|
|
24459
|
+
return matches;
|
|
24460
|
+
}
|
|
24461
|
+
var UEditorPrepareContentForSaveError = class extends Error {
|
|
24462
|
+
constructor(result) {
|
|
24463
|
+
super(
|
|
24464
|
+
`Failed to upload ${result.errors.length} image(s): ${result.errors.map((item) => `#${item.index} ${item.reason}`).join("; ")}`
|
|
24465
|
+
);
|
|
24466
|
+
this.name = "UEditorPrepareContentForSaveError";
|
|
24467
|
+
this.result = result;
|
|
24468
|
+
}
|
|
24469
|
+
};
|
|
24470
|
+
async function prepareUEditorContentForSave({
|
|
24471
|
+
html,
|
|
24472
|
+
uploadImageForSave
|
|
24473
|
+
}) {
|
|
24474
|
+
if (!html || !html.includes("<img")) {
|
|
24475
|
+
return { html, uploaded: [], errors: [] };
|
|
24476
|
+
}
|
|
24477
|
+
const imgMatches = collectImgTagMatches(html);
|
|
24478
|
+
if (imgMatches.length === 0) {
|
|
24479
|
+
return { html, uploaded: [], errors: [] };
|
|
24480
|
+
}
|
|
24481
|
+
const base64Candidates = [];
|
|
24482
|
+
for (const match of imgMatches) {
|
|
24483
|
+
if (!match.srcAttr) continue;
|
|
24484
|
+
const src = match.srcAttr.value.trim();
|
|
24485
|
+
if (!isDataImageUrl(src)) continue;
|
|
24486
|
+
base64Candidates.push({
|
|
24487
|
+
id: `${match.start}:${match.end}`,
|
|
24488
|
+
match,
|
|
24489
|
+
index: base64Candidates.length,
|
|
24490
|
+
src
|
|
24491
|
+
});
|
|
24492
|
+
}
|
|
24493
|
+
if (base64Candidates.length === 0) {
|
|
24494
|
+
return { html, uploaded: [], errors: [] };
|
|
24495
|
+
}
|
|
24496
|
+
if (!uploadImageForSave) {
|
|
24497
|
+
return {
|
|
24498
|
+
html,
|
|
24499
|
+
uploaded: [],
|
|
24500
|
+
errors: base64Candidates.map((item) => ({
|
|
24501
|
+
index: item.index,
|
|
24502
|
+
reason: "`uploadImageForSave` is required to transform base64 images before save."
|
|
24503
|
+
}))
|
|
24504
|
+
};
|
|
24505
|
+
}
|
|
24506
|
+
const uploaded = [];
|
|
24507
|
+
const errors = [];
|
|
24508
|
+
const replacements = /* @__PURE__ */ new Map();
|
|
24509
|
+
const uploadResults = await Promise.all(
|
|
24510
|
+
base64Candidates.map(async (candidate) => {
|
|
24511
|
+
try {
|
|
24512
|
+
const file = createFileFromDataImageUrl(candidate.src, candidate.index);
|
|
24513
|
+
const uploadResult = await uploadImageForSave(file);
|
|
24514
|
+
const normalized = normalizeUploadResult(uploadResult);
|
|
24515
|
+
return { candidate, file, ...normalized };
|
|
24516
|
+
} catch (error) {
|
|
24517
|
+
return { candidate, error: getErrorReason(error) };
|
|
24518
|
+
}
|
|
24519
|
+
})
|
|
24520
|
+
);
|
|
24521
|
+
for (const item of uploadResults) {
|
|
24522
|
+
if ("error" in item) {
|
|
24523
|
+
errors.push({
|
|
24524
|
+
index: item.candidate.index,
|
|
24525
|
+
reason: item.error ?? "Unknown upload error."
|
|
24526
|
+
});
|
|
24527
|
+
continue;
|
|
24528
|
+
}
|
|
24529
|
+
replacements.set(item.candidate.id, item.url);
|
|
24530
|
+
uploaded.push({
|
|
24531
|
+
url: item.url,
|
|
24532
|
+
file: item.file,
|
|
24533
|
+
meta: item.meta
|
|
24534
|
+
});
|
|
24535
|
+
}
|
|
24536
|
+
if (replacements.size === 0) {
|
|
24537
|
+
return { html, uploaded, errors };
|
|
24538
|
+
}
|
|
24539
|
+
let transformed = "";
|
|
24540
|
+
let cursor = 0;
|
|
24541
|
+
for (const match of imgMatches) {
|
|
24542
|
+
transformed += html.slice(cursor, match.start);
|
|
24543
|
+
const replacementKey = `${match.start}:${match.end}`;
|
|
24544
|
+
const replacementUrl = replacements.get(replacementKey);
|
|
24545
|
+
transformed += replacementUrl ? replaceSrcInTag(match, replacementUrl) : match.tag;
|
|
24546
|
+
cursor = match.end;
|
|
24547
|
+
}
|
|
24548
|
+
transformed += html.slice(cursor);
|
|
24549
|
+
return { html: transformed, uploaded, errors };
|
|
24550
|
+
}
|
|
24551
|
+
|
|
24358
24552
|
// ../../components/ui/UEditor/UEditor.tsx
|
|
24359
24553
|
var import_jsx_runtime86 = require("react/jsx-runtime");
|
|
24360
|
-
var UEditor = ({
|
|
24554
|
+
var UEditor = import_react51.default.forwardRef(({
|
|
24361
24555
|
content = "",
|
|
24362
24556
|
onChange,
|
|
24363
24557
|
onHtmlChange,
|
|
24364
24558
|
onJsonChange,
|
|
24365
24559
|
uploadImage,
|
|
24560
|
+
uploadImageForSave,
|
|
24366
24561
|
imageInsertMode = "base64",
|
|
24367
24562
|
placeholder,
|
|
24368
24563
|
className,
|
|
@@ -24376,9 +24571,10 @@ var UEditor = ({
|
|
|
24376
24571
|
minHeight = "200px",
|
|
24377
24572
|
maxHeight = "auto",
|
|
24378
24573
|
variant = "default"
|
|
24379
|
-
}) => {
|
|
24574
|
+
}, ref) => {
|
|
24380
24575
|
const t = (0, import_next_intl6.useTranslations)("UEditor");
|
|
24381
24576
|
const effectivePlaceholder = placeholder ?? t("placeholder");
|
|
24577
|
+
const inFlightPrepareRef = (0, import_react51.useRef)(null);
|
|
24382
24578
|
const extensions = (0, import_react51.useMemo)(
|
|
24383
24579
|
() => buildUEditorExtensions({ placeholder: effectivePlaceholder, maxCharacters, uploadImage, imageInsertMode, editable }),
|
|
24384
24580
|
[effectivePlaceholder, maxCharacters, uploadImage, imageInsertMode, editable]
|
|
@@ -24488,6 +24684,28 @@ var UEditor = ({
|
|
|
24488
24684
|
onJsonChange?.(editor2.getJSON());
|
|
24489
24685
|
}
|
|
24490
24686
|
});
|
|
24687
|
+
(0, import_react51.useImperativeHandle)(
|
|
24688
|
+
ref,
|
|
24689
|
+
() => ({
|
|
24690
|
+
prepareContentForSave: async ({ throwOnError = false } = {}) => {
|
|
24691
|
+
if (!inFlightPrepareRef.current) {
|
|
24692
|
+
const htmlSnapshot = editor?.getHTML() ?? content ?? "";
|
|
24693
|
+
inFlightPrepareRef.current = prepareUEditorContentForSave({
|
|
24694
|
+
html: htmlSnapshot,
|
|
24695
|
+
uploadImageForSave
|
|
24696
|
+
}).finally(() => {
|
|
24697
|
+
inFlightPrepareRef.current = null;
|
|
24698
|
+
});
|
|
24699
|
+
}
|
|
24700
|
+
const result = await inFlightPrepareRef.current;
|
|
24701
|
+
if (throwOnError && result.errors.length > 0) {
|
|
24702
|
+
throw new UEditorPrepareContentForSaveError(result);
|
|
24703
|
+
}
|
|
24704
|
+
return result;
|
|
24705
|
+
}
|
|
24706
|
+
}),
|
|
24707
|
+
[content, editor, uploadImageForSave]
|
|
24708
|
+
);
|
|
24491
24709
|
(0, import_react51.useEffect)(() => {
|
|
24492
24710
|
if (editor && content !== editor.getHTML()) {
|
|
24493
24711
|
if (editor.isEmpty && content) {
|
|
@@ -24535,7 +24753,8 @@ var UEditor = ({
|
|
|
24535
24753
|
]
|
|
24536
24754
|
}
|
|
24537
24755
|
);
|
|
24538
|
-
};
|
|
24756
|
+
});
|
|
24757
|
+
UEditor.displayName = "UEditor";
|
|
24539
24758
|
var UEditor_default = UEditor;
|
|
24540
24759
|
|
|
24541
24760
|
// src/index.ts
|
|
@@ -24681,6 +24900,7 @@ function getUnderverseMessages(locale = "en") {
|
|
|
24681
24900
|
Tooltip,
|
|
24682
24901
|
TranslationProvider,
|
|
24683
24902
|
UEditor,
|
|
24903
|
+
UEditorPrepareContentForSaveError,
|
|
24684
24904
|
UnderverseProvider,
|
|
24685
24905
|
VARIANT_STYLES_ALERT,
|
|
24686
24906
|
VARIANT_STYLES_BTN,
|
|
@@ -24691,6 +24911,7 @@ function getUnderverseMessages(locale = "en") {
|
|
|
24691
24911
|
getAnimationStyles,
|
|
24692
24912
|
getUnderverseMessages,
|
|
24693
24913
|
injectAnimationStyles,
|
|
24914
|
+
prepareUEditorContentForSave,
|
|
24694
24915
|
shadcnAnimationStyles,
|
|
24695
24916
|
underverseMessages,
|
|
24696
24917
|
useFormField,
|