WordPress 打包的 TinyMCE 4.9 不含官方 placeholder 插件,tinymce.init 里的 placeholder 配置被静默忽略,投稿页「投稿内容占位符」从未显示过。 新增主题插件 assets/js/front-submit-placeholder.js:正文为空时插入一个 data-mce-bogus 的占位段落,有内容即移除;序列化 / 解析都剔除该段落, 字数统计也跳过它,提示文字不会写进文章。 - 经典投稿表单与创作者工作台都通过 external_plugins 加载 (WordPress 会丢弃名字已在 plugins 列表里的外部插件) - 占位样式写进 iframe 的 content CSS:submit-editor.css 与 editor-content-style.ts Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
135 lines
4.1 KiB
JavaScript
135 lines
4.1 KiB
JavaScript
/**
|
||
* TinyMCE 正文占位符插件。
|
||
*
|
||
* WordPress 打包的 TinyMCE 4.9 不含官方 `placeholder` 插件,`tinymce.init` 里的
|
||
* `placeholder` 配置会被静默忽略——投稿页的「投稿内容占位符」因此从来没有显示过。
|
||
* 这里用官方插件同样的机制补上:正文为空时插入一个不可编辑的占位段落,
|
||
* 有内容时移除。
|
||
*
|
||
* 占位段落带 `data-mce-bogus="all"`,TinyMCE 不参与序列化、字数和选区计算;
|
||
* 视觉样式写在 content CSS(submit-editor.css / editor-content-style.ts)的
|
||
* `.jio-editor-placeholder` 上——编辑器正文在 iframe 里,页面样式管不到。
|
||
*
|
||
* @package Jio
|
||
*/
|
||
(function (window) {
|
||
'use strict';
|
||
|
||
if (!window.tinymce || !window.tinymce.PluginManager) {
|
||
return;
|
||
}
|
||
|
||
var ATTR = 'data-jio-placeholder';
|
||
var MEDIA = 'img,iframe,table,pre,hr,video,audio,figure,.jio-math--block';
|
||
|
||
window.tinymce.PluginManager.add('jio_placeholder', function (editor) {
|
||
var text = String(editor.getParam('placeholder', '') || '').trim();
|
||
|
||
if (!text) {
|
||
return;
|
||
}
|
||
|
||
function body() {
|
||
return editor.getBody();
|
||
}
|
||
|
||
function nodes() {
|
||
var root = body();
|
||
return root ? root.querySelectorAll('[' + ATTR + ']') : [];
|
||
}
|
||
|
||
/** 正文字数:跳过占位段落自身的文字。 */
|
||
function contentText() {
|
||
var root = body();
|
||
if (!root) {
|
||
return '';
|
||
}
|
||
var clone = root.cloneNode(true);
|
||
Array.prototype.forEach.call(clone.querySelectorAll('[' + ATTR + ']'), function (node) {
|
||
node.parentNode && node.parentNode.removeChild(node);
|
||
});
|
||
return String(clone.textContent || '');
|
||
}
|
||
|
||
function isEmpty() {
|
||
var root = body();
|
||
if (!root) {
|
||
return false;
|
||
}
|
||
// 图片 / 视频 / 表格这类块即使没有文字也算有内容。
|
||
if (root.querySelector(MEDIA)) {
|
||
return false;
|
||
}
|
||
return contentText().replace(/[\s ]+/g, '') === '';
|
||
}
|
||
|
||
function hide() {
|
||
Array.prototype.forEach.call(nodes(), function (node) {
|
||
node.parentNode && node.parentNode.removeChild(node);
|
||
});
|
||
}
|
||
|
||
function show() {
|
||
var root = body();
|
||
if (!root || root.querySelector('[' + ATTR + ']') || !isEmpty()) {
|
||
return;
|
||
}
|
||
var paragraph = editor.dom.create('p', {
|
||
'class': 'jio-editor-placeholder',
|
||
'contenteditable': 'false',
|
||
'data-mce-bogus': 'all',
|
||
'data-jio-placeholder': '1',
|
||
}, editor.dom.encode(text));
|
||
root.insertBefore(paragraph, root.firstChild);
|
||
}
|
||
|
||
function sync() {
|
||
if (isEmpty()) {
|
||
show();
|
||
} else {
|
||
hide();
|
||
}
|
||
}
|
||
|
||
// 占位段落不进入文章内容:序列化时剔除,解析(setContent)时同样剔除,
|
||
// 避免把提示文字当成正文保存。
|
||
editor.on('PreInit', function () {
|
||
var strip = function (list) {
|
||
list.forEach(function (node) {
|
||
if (node.attr(ATTR)) {
|
||
node.remove();
|
||
}
|
||
});
|
||
};
|
||
editor.serializer.addNodeFilter('p', strip);
|
||
editor.parser.addNodeFilter('p', strip);
|
||
});
|
||
|
||
editor.on('init', sync);
|
||
editor.on('SetContent input keyup change Undo Redo NodeChange', function () {
|
||
// SetContent 之后 DOM 才稳定,放到下一帧再判断。
|
||
window.setTimeout(sync, 0);
|
||
});
|
||
|
||
// 正文里只有占位段落时,光标没有可落点:补一个空段落并把光标放进去。
|
||
var ensureCaret = function () {
|
||
var root = body();
|
||
if (!root) {
|
||
return;
|
||
}
|
||
// 占位段落 pointer-events:none,点击会落到 body 本身;光标已经落在
|
||
// 段落里时不要再动它,避免每次点击都挪动光标。
|
||
var node = editor.selection.getNode();
|
||
if (node && node.nodeType === 1 && node !== root && node.nodeName !== 'BODY') {
|
||
return;
|
||
}
|
||
hide();
|
||
if (!root.firstChild) {
|
||
root.appendChild(editor.dom.create('p', {}, '<br data-mce-bogus="1">'));
|
||
}
|
||
editor.selection.setCursorLocation(root.firstChild, 0);
|
||
};
|
||
editor.on('click', ensureCaret);
|
||
});
|
||
})(window);
|