@underverse-ui/underverse 1.0.23 → 1.0.25
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 +283 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +45 -2
- package/dist/index.d.ts +45 -2
- package/dist/index.js +282 -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,
|
|
@@ -173,9 +174,12 @@ __export(index_exports, {
|
|
|
173
174
|
Watermark: () => Watermark_default,
|
|
174
175
|
cn: () => cn,
|
|
175
176
|
cnLocal: () => cn2,
|
|
177
|
+
extractImageSrcsFromHtml: () => extractImageSrcsFromHtml,
|
|
176
178
|
getAnimationStyles: () => getAnimationStyles,
|
|
177
179
|
getUnderverseMessages: () => getUnderverseMessages,
|
|
178
180
|
injectAnimationStyles: () => injectAnimationStyles,
|
|
181
|
+
normalizeImageUrl: () => normalizeImageUrl,
|
|
182
|
+
prepareUEditorContentForSave: () => prepareUEditorContentForSave,
|
|
179
183
|
shadcnAnimationStyles: () => shadcnAnimationStyles2,
|
|
180
184
|
underverseMessages: () => underverseMessages,
|
|
181
185
|
useFormField: () => useFormField,
|
|
@@ -21461,7 +21465,7 @@ function useSmartLocale() {
|
|
|
21461
21465
|
}
|
|
21462
21466
|
|
|
21463
21467
|
// ../../components/ui/UEditor/UEditor.tsx
|
|
21464
|
-
var import_react51 = require("react");
|
|
21468
|
+
var import_react51 = __toESM(require("react"), 1);
|
|
21465
21469
|
var import_next_intl6 = require("next-intl");
|
|
21466
21470
|
var import_react52 = require("@tiptap/react");
|
|
21467
21471
|
|
|
@@ -24355,14 +24359,261 @@ var CharacterCountDisplay = ({ editor, maxCharacters }) => {
|
|
|
24355
24359
|
] });
|
|
24356
24360
|
};
|
|
24357
24361
|
|
|
24362
|
+
// ../../components/ui/UEditor/prepare-content-for-save.ts
|
|
24363
|
+
var MIME_EXTENSION_MAP = {
|
|
24364
|
+
"image/png": "png",
|
|
24365
|
+
"image/jpeg": "jpg",
|
|
24366
|
+
"image/webp": "webp",
|
|
24367
|
+
"image/gif": "gif",
|
|
24368
|
+
"image/svg+xml": "svg",
|
|
24369
|
+
"image/bmp": "bmp",
|
|
24370
|
+
"image/x-icon": "ico",
|
|
24371
|
+
"image/avif": "avif"
|
|
24372
|
+
};
|
|
24373
|
+
function isDataImageUrl(value) {
|
|
24374
|
+
return /^data:image\//i.test(value.trim());
|
|
24375
|
+
}
|
|
24376
|
+
function parseDataImageUrl(dataUrl) {
|
|
24377
|
+
const value = dataUrl.trim();
|
|
24378
|
+
if (!isDataImageUrl(value)) return null;
|
|
24379
|
+
const commaIndex = value.indexOf(",");
|
|
24380
|
+
if (commaIndex < 0) return null;
|
|
24381
|
+
const header = value.slice(5, commaIndex);
|
|
24382
|
+
const base64Data = value.slice(commaIndex + 1).trim();
|
|
24383
|
+
if (!/;base64/i.test(header)) return null;
|
|
24384
|
+
const mime = header.split(";")[0]?.trim().toLowerCase();
|
|
24385
|
+
if (!mime || !base64Data) return null;
|
|
24386
|
+
return { mime, base64Data };
|
|
24387
|
+
}
|
|
24388
|
+
function decodeBase64ToBytes(base64Data) {
|
|
24389
|
+
const normalized = base64Data.replace(/\s+/g, "");
|
|
24390
|
+
const binary = atob(normalized);
|
|
24391
|
+
const bytes = new Uint8Array(binary.length);
|
|
24392
|
+
for (let i = 0; i < binary.length; i += 1) {
|
|
24393
|
+
bytes[i] = binary.charCodeAt(i);
|
|
24394
|
+
}
|
|
24395
|
+
return bytes;
|
|
24396
|
+
}
|
|
24397
|
+
function inferFileExtension(mime) {
|
|
24398
|
+
return MIME_EXTENSION_MAP[mime] ?? "bin";
|
|
24399
|
+
}
|
|
24400
|
+
function createFileFromDataImageUrl(dataUrl, index) {
|
|
24401
|
+
const parsed = parseDataImageUrl(dataUrl);
|
|
24402
|
+
if (!parsed) {
|
|
24403
|
+
throw new Error("Invalid data image URL format.");
|
|
24404
|
+
}
|
|
24405
|
+
const bytes = decodeBase64ToBytes(parsed.base64Data);
|
|
24406
|
+
const extension = inferFileExtension(parsed.mime);
|
|
24407
|
+
const name = `ueditor-image-${index + 1}.${extension}`;
|
|
24408
|
+
return new File([bytes], name, { type: parsed.mime });
|
|
24409
|
+
}
|
|
24410
|
+
function normalizeUploadResult(result) {
|
|
24411
|
+
if (typeof result === "string") {
|
|
24412
|
+
const url2 = result.trim();
|
|
24413
|
+
if (!url2) throw new Error("Upload handler returned an empty URL.");
|
|
24414
|
+
return { url: url2 };
|
|
24415
|
+
}
|
|
24416
|
+
if (!result || typeof result !== "object") {
|
|
24417
|
+
throw new Error("Upload handler returned invalid result.");
|
|
24418
|
+
}
|
|
24419
|
+
const url = typeof result.url === "string" ? result.url.trim() : "";
|
|
24420
|
+
if (!url) throw new Error("Upload handler object result is missing `url`.");
|
|
24421
|
+
const { url: _ignoredUrl, ...rest } = result;
|
|
24422
|
+
const meta = Object.keys(rest).length > 0 ? rest : void 0;
|
|
24423
|
+
return { url, meta };
|
|
24424
|
+
}
|
|
24425
|
+
function getErrorReason(error) {
|
|
24426
|
+
if (error instanceof Error && error.message) return error.message;
|
|
24427
|
+
if (typeof error === "string" && error.trim()) return error;
|
|
24428
|
+
return "Unknown upload error.";
|
|
24429
|
+
}
|
|
24430
|
+
function decodeHtmlEntities(value) {
|
|
24431
|
+
return value.replace(/"/gi, '"').replace(/'/gi, "'").replace(/&/gi, "&").replace(/</gi, "<").replace(/>/gi, ">").replace(/ /gi, " ");
|
|
24432
|
+
}
|
|
24433
|
+
function normalizeImageUrl(url) {
|
|
24434
|
+
const input = decodeHtmlEntities(url.trim());
|
|
24435
|
+
if (!input) return "";
|
|
24436
|
+
if (isDataImageUrl(input)) return input;
|
|
24437
|
+
const isAbsolute = /^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(input);
|
|
24438
|
+
if (!isAbsolute) {
|
|
24439
|
+
return input.split("#")[0] ?? input;
|
|
24440
|
+
}
|
|
24441
|
+
try {
|
|
24442
|
+
const parsed = new URL(input);
|
|
24443
|
+
parsed.hash = "";
|
|
24444
|
+
if (parsed.protocol === "http:" || parsed.protocol === "https:") {
|
|
24445
|
+
parsed.hostname = parsed.hostname.toLowerCase();
|
|
24446
|
+
if (parsed.protocol === "http:" && parsed.port === "80" || parsed.protocol === "https:" && parsed.port === "443") {
|
|
24447
|
+
parsed.port = "";
|
|
24448
|
+
}
|
|
24449
|
+
if (parsed.pathname.length > 1 && parsed.pathname.endsWith("/")) {
|
|
24450
|
+
parsed.pathname = parsed.pathname.slice(0, -1);
|
|
24451
|
+
}
|
|
24452
|
+
}
|
|
24453
|
+
return parsed.toString();
|
|
24454
|
+
} catch {
|
|
24455
|
+
return input.split("#")[0] ?? input;
|
|
24456
|
+
}
|
|
24457
|
+
}
|
|
24458
|
+
function replaceSrcInTag(match, nextSrc) {
|
|
24459
|
+
if (!match.srcAttr) return match.tag;
|
|
24460
|
+
const { start, end, quote } = match.srcAttr;
|
|
24461
|
+
const escaped = quote === '"' ? nextSrc.replace(/"/g, """) : quote === "'" ? nextSrc.replace(/'/g, "'") : nextSrc;
|
|
24462
|
+
const srcAttr = quote ? `src=${quote}${escaped}${quote}` : `src=${escaped}`;
|
|
24463
|
+
return `${match.tag.slice(0, start)}${srcAttr}${match.tag.slice(end)}`;
|
|
24464
|
+
}
|
|
24465
|
+
function collectImgTagMatches(html) {
|
|
24466
|
+
const matches = [];
|
|
24467
|
+
const imgRegex = /<img\b[^>]*>/gi;
|
|
24468
|
+
const srcAttrRegex = /\bsrc\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+))/i;
|
|
24469
|
+
let tagMatch = imgRegex.exec(html);
|
|
24470
|
+
while (tagMatch) {
|
|
24471
|
+
const tag = tagMatch[0];
|
|
24472
|
+
const start = tagMatch.index;
|
|
24473
|
+
const end = start + tag.length;
|
|
24474
|
+
const srcMatch = srcAttrRegex.exec(tag);
|
|
24475
|
+
let srcAttr = null;
|
|
24476
|
+
if (srcMatch) {
|
|
24477
|
+
const value = srcMatch[1] ?? srcMatch[2] ?? srcMatch[3] ?? "";
|
|
24478
|
+
const quote = srcMatch[1] !== void 0 ? '"' : srcMatch[2] !== void 0 ? "'" : "";
|
|
24479
|
+
srcAttr = {
|
|
24480
|
+
start: srcMatch.index,
|
|
24481
|
+
end: srcMatch.index + srcMatch[0].length,
|
|
24482
|
+
value,
|
|
24483
|
+
quote
|
|
24484
|
+
};
|
|
24485
|
+
}
|
|
24486
|
+
matches.push({ start, end, tag, srcAttr });
|
|
24487
|
+
tagMatch = imgRegex.exec(html);
|
|
24488
|
+
}
|
|
24489
|
+
return matches;
|
|
24490
|
+
}
|
|
24491
|
+
function extractImageSrcsFromHtml(html) {
|
|
24492
|
+
if (!html || !html.includes("<img")) return [];
|
|
24493
|
+
return collectImgTagMatches(html).map((match) => decodeHtmlEntities(match.srcAttr?.value.trim() ?? "")).filter(Boolean);
|
|
24494
|
+
}
|
|
24495
|
+
function createResult({
|
|
24496
|
+
html,
|
|
24497
|
+
uploaded,
|
|
24498
|
+
inlineUploaded,
|
|
24499
|
+
errors
|
|
24500
|
+
}) {
|
|
24501
|
+
return {
|
|
24502
|
+
html,
|
|
24503
|
+
uploaded,
|
|
24504
|
+
inlineImageUrls: extractImageSrcsFromHtml(html),
|
|
24505
|
+
inlineUploaded,
|
|
24506
|
+
errors
|
|
24507
|
+
};
|
|
24508
|
+
}
|
|
24509
|
+
var UEditorPrepareContentForSaveError = class extends Error {
|
|
24510
|
+
constructor(result) {
|
|
24511
|
+
super(
|
|
24512
|
+
`Failed to upload ${result.errors.length} image(s): ${result.errors.map((item) => `#${item.index} ${item.reason}`).join("; ")}`
|
|
24513
|
+
);
|
|
24514
|
+
this.name = "UEditorPrepareContentForSaveError";
|
|
24515
|
+
this.result = result;
|
|
24516
|
+
}
|
|
24517
|
+
};
|
|
24518
|
+
async function prepareUEditorContentForSave({
|
|
24519
|
+
html,
|
|
24520
|
+
uploadImageForSave
|
|
24521
|
+
}) {
|
|
24522
|
+
if (!html || !html.includes("<img")) {
|
|
24523
|
+
return createResult({ html, uploaded: [], inlineUploaded: [], errors: [] });
|
|
24524
|
+
}
|
|
24525
|
+
const imgMatches = collectImgTagMatches(html);
|
|
24526
|
+
if (imgMatches.length === 0) {
|
|
24527
|
+
return createResult({ html, uploaded: [], inlineUploaded: [], errors: [] });
|
|
24528
|
+
}
|
|
24529
|
+
const base64Candidates = [];
|
|
24530
|
+
for (const match of imgMatches) {
|
|
24531
|
+
if (!match.srcAttr) continue;
|
|
24532
|
+
const src = match.srcAttr.value.trim();
|
|
24533
|
+
if (!isDataImageUrl(src)) continue;
|
|
24534
|
+
base64Candidates.push({
|
|
24535
|
+
id: `${match.start}:${match.end}`,
|
|
24536
|
+
match,
|
|
24537
|
+
index: base64Candidates.length,
|
|
24538
|
+
src
|
|
24539
|
+
});
|
|
24540
|
+
}
|
|
24541
|
+
if (base64Candidates.length === 0) {
|
|
24542
|
+
return createResult({ html, uploaded: [], inlineUploaded: [], errors: [] });
|
|
24543
|
+
}
|
|
24544
|
+
if (!uploadImageForSave) {
|
|
24545
|
+
return createResult({
|
|
24546
|
+
html,
|
|
24547
|
+
uploaded: [],
|
|
24548
|
+
inlineUploaded: [],
|
|
24549
|
+
errors: base64Candidates.map((item) => ({
|
|
24550
|
+
index: item.index,
|
|
24551
|
+
reason: "`uploadImageForSave` is required to transform base64 images before save."
|
|
24552
|
+
}))
|
|
24553
|
+
});
|
|
24554
|
+
}
|
|
24555
|
+
const uploaded = [];
|
|
24556
|
+
const inlineUploaded = [];
|
|
24557
|
+
const errors = [];
|
|
24558
|
+
const replacements = /* @__PURE__ */ new Map();
|
|
24559
|
+
const uploadResults = await Promise.all(
|
|
24560
|
+
base64Candidates.map(async (candidate) => {
|
|
24561
|
+
try {
|
|
24562
|
+
const file = createFileFromDataImageUrl(candidate.src, candidate.index);
|
|
24563
|
+
const uploadResult = await uploadImageForSave(file);
|
|
24564
|
+
const normalized = normalizeUploadResult(uploadResult);
|
|
24565
|
+
return { candidate, file, ...normalized };
|
|
24566
|
+
} catch (error) {
|
|
24567
|
+
return { candidate, error: getErrorReason(error) };
|
|
24568
|
+
}
|
|
24569
|
+
})
|
|
24570
|
+
);
|
|
24571
|
+
for (const item of uploadResults) {
|
|
24572
|
+
if ("error" in item) {
|
|
24573
|
+
errors.push({
|
|
24574
|
+
index: item.candidate.index,
|
|
24575
|
+
reason: item.error ?? "Unknown upload error."
|
|
24576
|
+
});
|
|
24577
|
+
continue;
|
|
24578
|
+
}
|
|
24579
|
+
replacements.set(item.candidate.id, item.url);
|
|
24580
|
+
uploaded.push({
|
|
24581
|
+
url: item.url,
|
|
24582
|
+
file: item.file,
|
|
24583
|
+
meta: item.meta
|
|
24584
|
+
});
|
|
24585
|
+
inlineUploaded.push({
|
|
24586
|
+
index: item.candidate.index,
|
|
24587
|
+
url: item.url,
|
|
24588
|
+
file: item.file,
|
|
24589
|
+
meta: item.meta
|
|
24590
|
+
});
|
|
24591
|
+
}
|
|
24592
|
+
if (replacements.size === 0) {
|
|
24593
|
+
return createResult({ html, uploaded, inlineUploaded, errors });
|
|
24594
|
+
}
|
|
24595
|
+
let transformed = "";
|
|
24596
|
+
let cursor = 0;
|
|
24597
|
+
for (const match of imgMatches) {
|
|
24598
|
+
transformed += html.slice(cursor, match.start);
|
|
24599
|
+
const replacementKey = `${match.start}:${match.end}`;
|
|
24600
|
+
const replacementUrl = replacements.get(replacementKey);
|
|
24601
|
+
transformed += replacementUrl ? replaceSrcInTag(match, replacementUrl) : match.tag;
|
|
24602
|
+
cursor = match.end;
|
|
24603
|
+
}
|
|
24604
|
+
transformed += html.slice(cursor);
|
|
24605
|
+
return createResult({ html: transformed, uploaded, inlineUploaded, errors });
|
|
24606
|
+
}
|
|
24607
|
+
|
|
24358
24608
|
// ../../components/ui/UEditor/UEditor.tsx
|
|
24359
24609
|
var import_jsx_runtime86 = require("react/jsx-runtime");
|
|
24360
|
-
var UEditor = ({
|
|
24610
|
+
var UEditor = import_react51.default.forwardRef(({
|
|
24361
24611
|
content = "",
|
|
24362
24612
|
onChange,
|
|
24363
24613
|
onHtmlChange,
|
|
24364
24614
|
onJsonChange,
|
|
24365
24615
|
uploadImage,
|
|
24616
|
+
uploadImageForSave,
|
|
24366
24617
|
imageInsertMode = "base64",
|
|
24367
24618
|
placeholder,
|
|
24368
24619
|
className,
|
|
@@ -24376,9 +24627,10 @@ var UEditor = ({
|
|
|
24376
24627
|
minHeight = "200px",
|
|
24377
24628
|
maxHeight = "auto",
|
|
24378
24629
|
variant = "default"
|
|
24379
|
-
}) => {
|
|
24630
|
+
}, ref) => {
|
|
24380
24631
|
const t = (0, import_next_intl6.useTranslations)("UEditor");
|
|
24381
24632
|
const effectivePlaceholder = placeholder ?? t("placeholder");
|
|
24633
|
+
const inFlightPrepareRef = (0, import_react51.useRef)(null);
|
|
24382
24634
|
const extensions = (0, import_react51.useMemo)(
|
|
24383
24635
|
() => buildUEditorExtensions({ placeholder: effectivePlaceholder, maxCharacters, uploadImage, imageInsertMode, editable }),
|
|
24384
24636
|
[effectivePlaceholder, maxCharacters, uploadImage, imageInsertMode, editable]
|
|
@@ -24488,6 +24740,28 @@ var UEditor = ({
|
|
|
24488
24740
|
onJsonChange?.(editor2.getJSON());
|
|
24489
24741
|
}
|
|
24490
24742
|
});
|
|
24743
|
+
(0, import_react51.useImperativeHandle)(
|
|
24744
|
+
ref,
|
|
24745
|
+
() => ({
|
|
24746
|
+
prepareContentForSave: async ({ throwOnError = false } = {}) => {
|
|
24747
|
+
if (!inFlightPrepareRef.current) {
|
|
24748
|
+
const htmlSnapshot = editor?.getHTML() ?? content ?? "";
|
|
24749
|
+
inFlightPrepareRef.current = prepareUEditorContentForSave({
|
|
24750
|
+
html: htmlSnapshot,
|
|
24751
|
+
uploadImageForSave
|
|
24752
|
+
}).finally(() => {
|
|
24753
|
+
inFlightPrepareRef.current = null;
|
|
24754
|
+
});
|
|
24755
|
+
}
|
|
24756
|
+
const result = await inFlightPrepareRef.current;
|
|
24757
|
+
if (throwOnError && result.errors.length > 0) {
|
|
24758
|
+
throw new UEditorPrepareContentForSaveError(result);
|
|
24759
|
+
}
|
|
24760
|
+
return result;
|
|
24761
|
+
}
|
|
24762
|
+
}),
|
|
24763
|
+
[content, editor, uploadImageForSave]
|
|
24764
|
+
);
|
|
24491
24765
|
(0, import_react51.useEffect)(() => {
|
|
24492
24766
|
if (editor && content !== editor.getHTML()) {
|
|
24493
24767
|
if (editor.isEmpty && content) {
|
|
@@ -24535,7 +24809,8 @@ var UEditor = ({
|
|
|
24535
24809
|
]
|
|
24536
24810
|
}
|
|
24537
24811
|
);
|
|
24538
|
-
};
|
|
24812
|
+
});
|
|
24813
|
+
UEditor.displayName = "UEditor";
|
|
24539
24814
|
var UEditor_default = UEditor;
|
|
24540
24815
|
|
|
24541
24816
|
// src/index.ts
|
|
@@ -24681,6 +24956,7 @@ function getUnderverseMessages(locale = "en") {
|
|
|
24681
24956
|
Tooltip,
|
|
24682
24957
|
TranslationProvider,
|
|
24683
24958
|
UEditor,
|
|
24959
|
+
UEditorPrepareContentForSaveError,
|
|
24684
24960
|
UnderverseProvider,
|
|
24685
24961
|
VARIANT_STYLES_ALERT,
|
|
24686
24962
|
VARIANT_STYLES_BTN,
|
|
@@ -24688,9 +24964,12 @@ function getUnderverseMessages(locale = "en") {
|
|
|
24688
24964
|
Watermark,
|
|
24689
24965
|
cn,
|
|
24690
24966
|
cnLocal,
|
|
24967
|
+
extractImageSrcsFromHtml,
|
|
24691
24968
|
getAnimationStyles,
|
|
24692
24969
|
getUnderverseMessages,
|
|
24693
24970
|
injectAnimationStyles,
|
|
24971
|
+
normalizeImageUrl,
|
|
24972
|
+
prepareUEditorContentForSave,
|
|
24694
24973
|
shadcnAnimationStyles,
|
|
24695
24974
|
underverseMessages,
|
|
24696
24975
|
useFormField,
|