/** * 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', {}, '
')); } editor.selection.setCursorLocation(root.firstChild, 0); }; editor.on('click', ensureCaret); }); })(window);