Files
wordpress-theme-aksrt/assets/js/front-submit-editor-basics.js
T
kate522andClaude Opus 5 5afa7ab9df fix(submit): show body placeholder on first load and restore caret on blank clicks
两个前台投稿编辑器的缺陷,根因都在占位符的实现方式:

1. 首次加载不显示占位符,输入一个再删掉才出现。
   旧实现往正文插了一个 data-mce-bogus 占位段落,而 TinyMCE 在
   setContent(studio 初始化时必调)与清理 bogus 元素时会把它移除。

2. 光标在块级结构里时,点击正文空白处无法落回正文。
   占位段落占着首行、且 pointer-events:none,点击落不到可编辑元素上;
   正文以表格 / 代码块这类结构块结尾时也没有可承载光标的段落。

改为 front-submit-editor-basics.js(替换 front-submit-placeholder.js):
- 占位符改成 body 属性 + content CSS 的 ::before,正文 DOM 不含占位节点;
- 点击空白处(事件目标是 body)且光标不在普通文本里时,补一个可编辑尾段
  并把光标放进去;
- 两份 content CSS 让 html/body 撑满编辑区,studio 的 syncEditorHeight
  以编辑器声明高度为下限,空正文时 iframe 不会缩成一行。

新增 tests/widget-system/submit-placeholder-behavior.test.cjs(jsdom 跑真实插件)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-11-09 20:38:02 +08:00

124 lines
4.3 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();
}
function isEmpty() {
var body = root();
if (!body) {
return false;
}
// 图片 / 视频 / 表格这类块即使没有文字也算有内容。
if (body.querySelector(MEDIA)) {
return false;
}
return String(body.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);