fix(submit): detect empty body without editor chrome and let body fill the frame
登录态实测(Edge + 真实 TinyMCE)复现了上一版没修好的两点:
1. 占位符仍不显示:editor-runtime 会把块级删除浮层插进 body,它带
data-mce-bogus 但含「✕ 选中此块」文字,body.textContent 因此永远非空,
data-jio-empty 一直没被打上。判断正文是否为空时先剔除 data-mce-bogus 节点。
2. 点空白落不到正文:body 高度只有 29px。`min-height:100%` 的父级 html
没有确定高度,百分比退化成 auto。改为 html{height:100%} +
body{min-height:100%},body 现在铺满 520px 的 iframe。
实测结果(登录态 https://localhost/submit/):
- 首次加载 data-jio-empty=1、::before 渲染「请输入正文」;
- 点击正文中部空白,光标落进 P 并可继续输入;
- 引用块内编辑后点空白,光标落到引用之后的段落;
- 清空内容后占位符恢复。
测试:新增「编辑器辅助节点不算正文」回归用例;900 → 901 全通过。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
5afa7ab9df
commit
305896796c
5 files changed
+52
-10
No files matched your search
@@ -39,14 +39,17 @@ body[data-jio-empty="1"]::before {
|
|||||||
/*
|
/*
|
||||||
* body 撑满编辑区:内容很短时点击正文下方的空白区域也应落光标。
|
* body 撑满编辑区:内容很短时点击正文下方的空白区域也应落光标。
|
||||||
* iframe 高度由 syncEditorHeight() 跟随内容,这里保证 body 至少铺满。
|
* iframe 高度由 syncEditorHeight() 跟随内容,这里保证 body 至少铺满。
|
||||||
|
*
|
||||||
|
* min-height:100% 依赖父级有确定高度,所以 html 必须 height:100%
|
||||||
|
* (只写 min-height 的话 html 的高度仍由内容决定,百分比会退化成 auto)。
|
||||||
*/
|
*/
|
||||||
html,
|
html {
|
||||||
body {
|
height: 100%;
|
||||||
min-height: 100%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
min-height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1, h2, h3, h4, h5, h6 { margin: 1.5em 0 0.65em; line-height: 1.4; }
|
h1, h2, h3, h4, h5, h6 { margin: 1.5em 0 0.65em; line-height: 1.4; }
|
||||||
|
|||||||
@@ -227,13 +227,16 @@ body[data-jio-empty="1"]::before {
|
|||||||
/*
|
/*
|
||||||
* body 撑满编辑区:内容很短时点击正文下方的空白区域也应落光标,
|
* body 撑满编辑区:内容很短时点击正文下方的空白区域也应落光标,
|
||||||
* 否则只有文字所在的那一行能点。
|
* 否则只有文字所在的那一行能点。
|
||||||
|
*
|
||||||
|
* min-height:100% 依赖父级有确定高度,所以 html 必须 height:100%(只写
|
||||||
|
* min-height 的话 html 的高度仍由内容决定,百分比会退化成 auto)。
|
||||||
*/
|
*/
|
||||||
html,
|
html {
|
||||||
body.mce-content-body {
|
height: 100%;
|
||||||
min-height: 100%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body.mce-content-body {
|
body.mce-content-body {
|
||||||
position: relative;
|
position: relative;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
min-height: 100%;
|
||||||
}
|
}
|
||||||
@@ -40,16 +40,32 @@
|
|||||||
return editor.getBody();
|
return editor.getBody();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 正文自身的副本:剔除 TinyMCE 的辅助节点。
|
||||||
|
*
|
||||||
|
* 编辑器会把块级删除浮层(#jio-block-actions)之类带文字的工具节点插进
|
||||||
|
* body,它们都带 data-mce-bogus。直接读 body.textContent 会把「选中此块」
|
||||||
|
* 当成正文,导致空正文永远判成非空、占位符不显示。
|
||||||
|
*/
|
||||||
|
function contentClone(body) {
|
||||||
|
var clone = body.cloneNode(true);
|
||||||
|
Array.prototype.forEach.call(clone.querySelectorAll('[data-mce-bogus]'), function (node) {
|
||||||
|
node.parentNode && node.parentNode.removeChild(node);
|
||||||
|
});
|
||||||
|
return clone;
|
||||||
|
}
|
||||||
|
|
||||||
function isEmpty() {
|
function isEmpty() {
|
||||||
var body = root();
|
var body = root();
|
||||||
if (!body) {
|
if (!body) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
var content = contentClone(body);
|
||||||
// 图片 / 视频 / 表格这类块即使没有文字也算有内容。
|
// 图片 / 视频 / 表格这类块即使没有文字也算有内容。
|
||||||
if (body.querySelector(MEDIA)) {
|
if (content.querySelector(MEDIA)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return String(body.textContent || '').replace(/[\s ]+/g, '') === '';
|
return String(content.textContent || '').replace(/[\s ]+/g, '') === '';
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 空正文标记交给 CSS 渲染提示文字。 */
|
/** 空正文标记交给 CSS 渲染提示文字。 */
|
||||||
|
|||||||
@@ -412,8 +412,11 @@ test('submit editor renders the configured body placeholder', () => {
|
|||||||
assert.match(styles, /body\[data-jio-empty="1"\]::before\s*\{/);
|
assert.match(styles, /body\[data-jio-empty="1"\]::before\s*\{/);
|
||||||
assert.match(contentStyle, /body\[data-jio-empty="1"\]::before\s*\{/);
|
assert.match(contentStyle, /body\[data-jio-empty="1"\]::before\s*\{/);
|
||||||
// 两份 content CSS 都要让 body 撑满,否则正文下方的空白不属于 iframe。
|
// 两份 content CSS 都要让 body 撑满,否则正文下方的空白不属于 iframe。
|
||||||
assert.match(styles, /html,\s*\n\s*body\.mce-content-body\s*\{[^}]*min-height:\s*100%/);
|
// min-height:100% 依赖父级确定高度,html 必须 height:100%。
|
||||||
assert.match(contentStyle, /html,\s*\n\s*body\s*\{[^}]*min-height:\s*100%/);
|
assert.match(styles, /html\s*\{[^}]*height:\s*100%/);
|
||||||
|
assert.match(styles, /body\.mce-content-body\s*\{[^}]*min-height:\s*100%/);
|
||||||
|
assert.match(contentStyle, /html\s*\{[^}]*height:\s*100%/);
|
||||||
|
assert.match(contentStyle, /body\s*\{[^}]*min-height:\s*100%/);
|
||||||
|
|
||||||
// 空正文时 iframe 不能缩到只剩一行:高度下限取编辑器声明的高度。
|
// 空正文时 iframe 不能缩到只剩一行:高度下限取编辑器声明的高度。
|
||||||
assert.match(runtime, /getParam\?\.\('height', 0\)/);
|
assert.match(runtime, /getParam\?\.\('height', 0\)/);
|
||||||
|
|||||||
@@ -139,6 +139,23 @@ test('placeholder never becomes a DOM node in the editable body', () => {
|
|||||||
assert.equal(doc.body.textContent.trim(), '');
|
assert.equal(doc.body.textContent.trim(), '');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('editor chrome injected into the body does not count as content', () => {
|
||||||
|
// 真实页面上 editor-runtime 会把块删除浮层插进 body,它带 data-mce-bogus
|
||||||
|
// 但含文字(「✕ 选中此块」)。若按 body.textContent 判断,空正文永远非空,
|
||||||
|
// 占位符就永远不显示 —— 这正是「首次加载不显示」的真实原因。
|
||||||
|
const { doc, editor } = createEditor(
|
||||||
|
'<p><br data-mce-bogus="1"></p>' +
|
||||||
|
'<div id="jio-block-actions" data-mce-bogus="all">' +
|
||||||
|
'<button type="button"><span>✕</span><span>选中此块</span></button></div>',
|
||||||
|
{ placeholder: '请输入正文' }
|
||||||
|
);
|
||||||
|
const plugin = loadPlugin()[PLUGIN];
|
||||||
|
plugin(editor);
|
||||||
|
editor.fire('init');
|
||||||
|
|
||||||
|
assert.equal(doc.body.getAttribute('data-jio-empty'), '1', 'bogus chrome must not count as content');
|
||||||
|
});
|
||||||
|
|
||||||
test('no placeholder configured leaves the body untouched', () => {
|
test('no placeholder configured leaves the body untouched', () => {
|
||||||
const { doc, editor } = createEditor('<p><br data-mce-bogus="1"></p>', {});
|
const { doc, editor } = createEditor('<p><br data-mce-bogus="1"></p>', {});
|
||||||
const plugin = loadPlugin()[PLUGIN];
|
const plugin = loadPlugin()[PLUGIN];
|
||||||
|
|||||||
Reference in new issue
Block a user