Files
wordpress-theme-aksrt/assets/js/front-submit-editor-basics.js
T
kate522andClaude Opus 5 305896796c 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>
2026-11-09 21:16:45 +08:00

140 lines
4.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 投稿编辑器的正文基础行为(经典投稿表单与创作者工作台共用)。
*
* 1) 正文占位符
* WordPress 打包的 TinyMCE 4.9 不含官方 `placeholder` 插件,`tinymce.init`
* 里的 `placeholder` 配置会被静默忽略。这里改为在 body 上打属性、由 content
* CSS 的 ::before 渲染:
* - data-jio-placeholder="<提示文字>":始终存在,供 CSS 取文案;
* - data-jio-empty="1":仅正文为空时存在,控制是否显示。
* 刻意不插占位节点:插入的段落会被 TinyMCE 在 setContent / 清理 bogus 元素时
* 移除(表现为「首次加载不显示,输入再删掉才出现」),也会占住首行。
*
* 2) 空白区域落光标
* 光标停在引用 / 隐藏内容这类嵌套可编辑块里时,点击正文下方的空白处不一定
* 能把光标移回正文;正文以表格、代码块这类结构块结尾时更是无处可落。这里
* 在点击空白处时补一个可编辑尾段并把光标放进去。
*
* 视觉样式写在 content CSS(submit-editor.css / editor-content-style.ts)——
* 编辑器正文在 iframe 里,页面样式管不到。
*
* @package Jio
*/
(function (window) {
'use strict';
if (!window.tinymce || !window.tinymce.PluginManager) {
return;
}
var TEXT_ATTR = 'data-jio-placeholder';
var EMPTY_ATTR = 'data-jio-empty';
var MEDIA = 'img,iframe,table,pre,hr,video,audio,figure,.jio-math--block';
/** 不能直接承载光标的块:结尾是它们时必须补一个段落。 */
var STRUCTURAL = 'table,pre,hr,figure,.quote_q,.tinymce-hide,.jio-math--block,.jio-file-link';
window.tinymce.PluginManager.add('jio_editor_basics', function (editor) {
var placeholder = String(editor.getParam('placeholder', '') || '').trim();
function root() {
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() {
var body = root();
if (!body) {
return false;
}
var content = contentClone(body);
// 图片 / 视频 / 表格这类块即使没有文字也算有内容。
if (content.querySelector(MEDIA)) {
return false;
}
return String(content.textContent || '').replace(/[\s ]+/g, '') === '';
}
/** 空正文标记交给 CSS 渲染提示文字。 */
function syncPlaceholder() {
var body = root();
if (!body || !placeholder) {
return;
}
body.setAttribute(TEXT_ATTR, placeholder);
if (isEmpty()) {
body.setAttribute(EMPTY_ATTR, '1');
} else {
body.removeAttribute(EMPTY_ATTR);
}
}
function isStructural(node) {
return !!node && node.nodeType === 1 && !!node.matches && node.matches(STRUCTURAL);
}
/**
* 光标可落的位置:正文最后一个块。
*
* 结尾是结构块(表格 / 代码块 / 引用…)时补一个空段落,否则光标无处可放。
*/
function caretHome() {
var body = root();
if (!body) {
return null;
}
var last = body.lastElementChild;
if (isStructural(last)) {
last = editor.dom.create('p', {}, '<br data-mce-bogus="1">');
body.appendChild(last);
}
return last;
}
/**
* 点击正文空白处(事件目标就是 body)时把光标移回正文末尾。
*
* 光标还在普通段落里时不插手:浏览器已经把它放在了正确位置。
*/
function restoreCaret(event) {
var body = root();
if (!body || event.target !== body) {
return;
}
var node = editor.selection.getNode();
var insideText = node && node !== body && node.nodeType === 1
&& !isStructural(node) && !editor.dom.getParent(node, STRUCTURAL, body);
if (insideText) {
return;
}
var home = caretHome();
if (!home) {
return;
}
editor.selection.setCursorLocation(home, home.childNodes.length);
}
// PreInit 时 body 可能已就绪:先打上属性,避免首帧闪一下空白再出现提示。
editor.on('PreInit init', syncPlaceholder);
editor.on('SetContent input keyup change Undo Redo NodeChange', function () {
// SetContent 之后 DOM 才稳定,放到下一帧再判断。
window.setTimeout(syncPlaceholder, 0);
});
editor.on('click', restoreCaret);
});
})(window);