fix(submit): keep the editor iframe tall enough for restored drafts and media

The iframe height is synced explicitly, and two paths were missing it, so long
content ended up clipped inside the editor's own scroll area.

- restoring a local draft calls setContent with a whole article at once; the
  iframe stayed at 520px while the body grew to 1410px, so only the first few
  paragraphs appeared to survive the restore
- media inserts called insertContent directly without resyncing, and images
  load asynchronously: the height was measured while the image was still 0px
  tall, leaving the paragraphs below it cut off (measured: iframe 946 vs body
  1006 with a single image after long text)

Fixes
- resync the height right after the restore's setContent, and add SetContent to
  the events that already sync height and content
- resync after both media callbacks (picker and embed)
- add installHeightWatchers(): capture-phase load listener inside the iframe for
  img / iframe / video / audio, plus a ResizeObserver on the body, so the height
  follows assets that finish loading later

Tests cover the restore path and both media paths, including async loading.
This commit is contained in:
kate522 committed 2026-11-10 18:45:14 +08:00
1 parent a5386f72c6
commit 49f1212e33
3 files changed
+83 -3

No files matched your search

@@ -137,7 +137,7 @@ test('studio app uses TinyMCE while preserving autosave, recovery, and publishin
assert.match(appSource, /PublishDrawer/);
assert.match(appSource, /wpEditor\.initialize\(EDITOR_ID/);
assert.match(appSource, /editor\.getContent\(\)/);
assert.match(appSource, /editorRef\.current\?\.setContent/);
assert.match(appSource, /editor\?\.setContent\(content\)/);
assert.doesNotMatch(appSource, /BlockEditorProvider|registerCoreBlocks|BlockList|registerCoreBlocks/);
assert.doesNotMatch(creatorRest, /block_content\(/);
assert.match(creatorRest, /'post_content'\s*=>\s*\$payload\['content'\]/);
@@ -774,3 +774,38 @@ test('draft saves never demote a submitted post or consume the publish quota', (
// REST 侧要把 autosave 传给 persist()。
assert.match(read('src/Core/CreatorRest.php'), /'autosave'\s*=>\s*\$is_auto/);
});
// 恢复本地草稿会一次性把正文换成很长的一篇:iframe 必须跟着变高,
// 否则超出的段落留在 iframe 内部滚动区,看起来像「只剩第一段」。
test('restoring a draft resyncs the editor height', () => {
const app = read('assets/creator-studio/src/index.tsx');
const restoreAt = app.indexOf('const restore = useCallback');
const restoreBody = app.slice(restoreAt, app.indexOf('const rememberSelection'));
// setContent 之后必须重新同步高度。
assert.match(restoreBody, /editor\?\.setContent\(content\)/);
assert.match(restoreBody, /syncEditorHeight\(editor\)/);
// SetContent 事件本身也要参与高度同步与内容同步。
assert.match(app, /editor\.on\('input change Undo Redo AddUndo SetContent'/);
});
// 图片/视频插入后正文会变高,且资源是异步加载的:高度必须跟着重算,
// 否则图片下方的段落会被 iframe 的固定高度裁掉。
test('media inserts resync the editor height, including after async loading', () => {
const app = read('assets/creator-studio/src/index.tsx');
const runtime = read('assets/creator-studio/src/editor-runtime.ts');
// 两个媒体回调(选择器与嵌入)插入后都要同步高度。
const mediaAt = app.indexOf('const openMedia = useCallback');
const mediaBody = app.slice(mediaAt, app.indexOf('return (', mediaAt));
assert.equal((mediaBody.match(/syncEditorHeight\(target\)/g) || []).length, 2);
// 图片 / iframe / video 加载完成后补一次同步。
assert.match(runtime, /export function installHeightWatchers/);
assert.match(runtime, /addEventListener\(\s*'load'/);
assert.match(runtime, /tag === 'IMG' \|\| tag === 'IFRAME' \|\| tag === 'VIDEO' \|\| tag === 'AUDIO'/);
assert.match(runtime, /ResizeObserver/);
// 只在 init 路径挂一次,重复安装要短路。
assert.match(runtime, /__jioHeightWatched/);
assert.match(app, /installHeightWatchers\(editor\)/);
});