fix(submit): let the editor iframe grow with the body instead of scrolling internally

正文超过 iframe 高度时会在编辑器内部出现第二根滚动条,正文应该随页面一起变长。
登录态实测确认了滚动来源与两个副作用:

1. 经典投稿表单完全没做高度同步(iframe 固定 504px,正文 1824px → 内滚动)。
   在共用基础插件里补上 syncHeight():测量正文高度并同步 iframe,
   下限取编辑器声明高度;图片加载后重新同步。工作台自带 syncEditorHeight()
   负责块浮层定位,用 __jioStudioOwnsEditorHeight 让插件跳过,避免双写。

2. 直接量 scrollHeight 会把 body 的 min-height 撑满算进内容高度,形成
   「撑高 → 再撑高」的反馈循环(实测涨到 22616px)。测量时临时把
   min-height 归零、同帧恢复。

3. body 的 min-height 基准从 100% 改成 100vh:iframe 随正文变高,
   百分比会跟着循环;同时把 WordPress content CSS 的 body 外边距改内边距,
   外边距不计入高度,会让 html 比 iframe 高一截、长正文时又冒出内滚动。

登录态实测(studio 与经典两条路径):
- iframe 2152px / 1874px,与正文一致,innerHasScrollbar=false;
- 空正文占位符正常、点击空白落光标正常;
- 只有页面级滚动。

测试:新增 iframe 随正文增长、测量忽略 min-height 两个回归用例;903 全通过。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
kate522andClaude Opus 5 committed 2026-11-09 21:37:14 +08:00
1 parent 305896796c
commit 4f9347b6aa
7 files changed
+164 -38

No files matched your search

@@ -156,6 +156,68 @@ test('editor chrome injected into the body does not count as content', () => {
assert.equal(doc.body.getAttribute('data-jio-empty'), '1', 'bogus chrome must not count as content');
});
test('editor iframe grows with the content instead of scrolling internally', () => {
const { doc, editor } = createEditor('<p>短正文</p>', { height: 520, placeholder: '请输入正文' });
// 极简 iframe 替身:插件只读写 style.height / overflow / scrolling。
const iframe = doc.createElement('iframe');
const container = doc.createElement('div');
container.appendChild(iframe);
editor.getContentAreaContainer = () => container;
const plugin = loadPlugin()[PLUGIN];
plugin(editor);
editor.fire('init');
const initial = iframe.style.height;
assert.equal(initial, '520px', 'short content keeps the declared writing area');
// 正文变长:scrollHeight 变大,iframe 必须跟着长。
Object.defineProperty(doc.body, 'scrollHeight', { configurable: true, get: () => 2400 });
Object.defineProperty(doc.body, 'offsetHeight', { configurable: true, get: () => 2400 });
editor.fire('input');
assert.equal(iframe.style.height, '2400px', 'iframe height must follow the content');
assert.equal(iframe.style.overflow, 'hidden');
assert.equal(iframe.getAttribute('scrolling'), 'no');
});
test('height measurement ignores the body min-height fill', () => {
// body 的 min-height 让空正文铺满视口;如果测量时也带上它,
// 就会得到「撑高 → 再撑高」的反馈循环(实测会涨到两万多 px)。
const { doc, editor } = createEditor('<p>正文</p>', { height: 520 });
const iframe = doc.createElement('iframe');
const container = doc.createElement('div');
container.appendChild(iframe);
editor.getContentAreaContainer = () => container;
// 模拟「body 被 min-height 撑到 5000」但真实内容只有 1800。
let minHeightApplied = false;
Object.defineProperty(doc.body, 'scrollHeight', {
configurable: true,
get: () => (minHeightApplied ? 5000 : 1800),
});
Object.defineProperty(doc.body, 'offsetHeight', {
configurable: true,
get: () => (minHeightApplied ? 5000 : 1800),
});
// 插件把 min-height 归零时视为摘掉撑满。
Object.defineProperty(doc.body.style, 'minHeight', {
configurable: true,
get: () => (minHeightApplied ? '100vh' : ''),
set: (value) => {
minHeightApplied = value !== '0';
},
});
minHeightApplied = true;
const plugin = loadPlugin()[PLUGIN];
plugin(editor);
editor.fire('init');
assert.equal(iframe.style.height, '1800px', 'measure the content, not the viewport fill');
});
test('no placeholder configured leaves the body untouched', () => {
const { doc, editor } = createEditor('<p><br data-mce-bogus="1"></p>', {});
const plugin = loadPlugin()[PLUGIN];