Skip to content

Commit 53e2c47

Browse files
authored
fix: make copy slice metadata hidden (#3232)
1 parent 766f82e commit 53e2c47

2 files changed

Lines changed: 35 additions & 11 deletions

File tree

packages/super-editor/src/editors/v1/core/helpers/superdocClipboardSlice.js

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -250,24 +250,32 @@ export function bodySectPrShouldEmbed(bodySectPr) {
250250
return !!(cols?.count && cols.count > 1);
251251
}
252252

253-
/** Embeds PM slice, media, and optional body sectPr as hidden base64 payloads. */
253+
function hiddenClipboardPayload(attr, base64) {
254+
return `<div ${attr}="${base64}" style="display:none"></div>`;
255+
}
256+
257+
function readClipboardPayload(el, attr) {
258+
return el.getAttribute(attr)?.trim() || el.textContent?.trim() || '';
259+
}
260+
261+
/** Embeds PM slice, media, and optional body sectPr as hidden base64 payload attributes. */
254262
export function embedSliceInHtml(html, sliceJson, bodySectPrJson = '', mediaJson = '') {
255263
let out = html;
256264
if (bodySectPrJson) {
257265
const body64 = encodeUtf8Base64(bodySectPrJson);
258-
out = `<div ${SUPERDOC_BODY_SECT_PR_ATTR} style="display:none">${body64}</div>${out}`;
266+
out = `${hiddenClipboardPayload(SUPERDOC_BODY_SECT_PR_ATTR, body64)}${out}`;
259267
}
260268
if (mediaJson) {
261269
const media64 = encodeUtf8Base64(mediaJson);
262-
out = `<div ${SUPERDOC_MEDIA_ATTR} style="display:none">${media64}</div>${out}`;
270+
out = `${hiddenClipboardPayload(SUPERDOC_MEDIA_ATTR, media64)}${out}`;
263271
}
264272
if (!sliceJson) return out;
265273
const base64 = encodeUtf8Base64(sliceJson);
266-
return `<div ${SUPERDOC_SLICE_ATTR} style="display:none">${base64}</div>${out}`;
274+
return `${hiddenClipboardPayload(SUPERDOC_SLICE_ATTR, base64)}${out}`;
267275
}
268276

269277
/**
270-
* Reads slice JSON from HTML produced by {@link embedSliceInHtml} (hidden div + base64 text).
278+
* Reads slice JSON from HTML produced by {@link embedSliceInHtml}.
271279
*/
272280
export function extractSliceFromHtml(html) {
273281
if (!html || !html.includes(SUPERDOC_SLICE_ATTR)) return null;
@@ -278,10 +286,7 @@ export function extractSliceFromHtml(html) {
278286
const el = doc.querySelector(`[${SUPERDOC_SLICE_ATTR}]`);
279287
if (!el) return null;
280288

281-
let b64 = el.textContent?.trim() ?? '';
282-
if (!b64) {
283-
b64 = el.getAttribute(SUPERDOC_SLICE_ATTR)?.trim() ?? '';
284-
}
289+
const b64 = readClipboardPayload(el, SUPERDOC_SLICE_ATTR);
285290
if (!b64) return null;
286291

287292
const decoded = decodeUtf8Base64(b64);
@@ -314,7 +319,7 @@ export function extractMediaFromHtml(html) {
314319
const doc = new DOMParser().parseFromString(html, 'text/html');
315320
const el = doc.querySelector(`[${SUPERDOC_MEDIA_ATTR}]`);
316321
if (!el) return null;
317-
const b64 = el.textContent?.trim() ?? '';
322+
const b64 = readClipboardPayload(el, SUPERDOC_MEDIA_ATTR);
318323
if (!b64) return null;
319324
const decoded = decodeUtf8Base64(b64);
320325
return decoded || null;
@@ -331,7 +336,7 @@ export function extractBodySectPrFromHtml(html) {
331336
const doc = new DOMParser().parseFromString(html, 'text/html');
332337
const el = doc.querySelector(`[${SUPERDOC_BODY_SECT_PR_ATTR}]`);
333338
if (!el) return null;
334-
const b64 = el.textContent?.trim() ?? '';
339+
const b64 = readClipboardPayload(el, SUPERDOC_BODY_SECT_PR_ATTR);
335340
if (!b64) return null;
336341
return JSON.parse(decodeUtf8Base64(b64));
337342
} catch {

packages/super-editor/src/editors/v1/core/helpers/superdocClipboardSlice.test.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,25 @@ describe('HTML slice embed/extract round-trip', () => {
248248
expect(extracted).toBe(sampleSlice);
249249
});
250250

251+
it('stores embedded payloads in attributes instead of hidden text nodes', () => {
252+
const mediaJson = JSON.stringify({ 'word/media/image1.png': 'data:image/png;base64,AAA' });
253+
const embedded = embedSliceInHtml(sampleHtml, sampleSlice, '', mediaJson);
254+
255+
expect(embedded).toMatch(/<div data-superdoc-slice="[^"]+" style="display:none"><\/div>/);
256+
expect(embedded).toMatch(/<div data-sd-superdoc-media="[^"]+" style="display:none"><\/div>/);
257+
expect(embedded).not.toMatch(/<div[^>]*data-superdoc-slice[^>]*>[^<]+<\/div>/);
258+
expect(embedded).not.toMatch(/<div[^>]*data-sd-superdoc-media[^>]*>[^<]+<\/div>/);
259+
});
260+
261+
it('extractSliceFromHtml still supports older hidden text payloads', () => {
262+
const embedded = embedSliceInHtml(sampleHtml, sampleSlice);
263+
const payload = embedded.match(/data-superdoc-slice="([^"]+)"/)?.[1];
264+
expect(payload).toBeTruthy();
265+
266+
const oldFormatHtml = `<div data-superdoc-slice style="display:none">${payload}</div>${sampleHtml}`;
267+
expect(extractSliceFromHtml(oldFormatHtml)).toBe(sampleSlice);
268+
});
269+
251270
it('extractMediaFromHtml recovers embedded media JSON', () => {
252271
const mediaJson = JSON.stringify({ 'word/media/image1.png': 'data:image/png;base64,AAA' });
253272
const embedded = embedSliceInHtml(sampleHtml, sampleSlice, '', mediaJson);

0 commit comments

Comments
 (0)