fix(submit): repair placeholder, code block, hidden block and settings toggles
- placeholder: hide the body ::before hint once the editor body has focus, so clicking an empty document no longer leaves the hint sitting under the caret - code block: edit as a bare <pre> and convert to pre > code on save. TinyMCE treats pre > code as an inline boundary, so typing there leaked into the following paragraph; Enlighter also splits lines on newlines, so the <br> the editor produced rendered the whole block as one overflowing line - code block: align the iframe rendering with Enlighter (Menlo 13px/1.4, #f8f9fa, 8px radius, 12px 17px padding, nowrap) in both content CSS files - code block: move the caret into the block after insertion - hidden block: hang the block-delete overlay on documentElement instead of body. data-mce-bogus only skips serialization, so the extra body child made mceInsertContent wipe the document and drop the first hidden block - settings: give the toggle a real 44x24 hit area, align it to the top of the row, add aria-labels and show the disabled state - styles: drop the duplicated @use/@import of publish-drawer and block-tools - media insert/embed now serialize through serializeBlocks so code blocks are never stored in editor form
This commit is contained in:
1 parent
4f9347b6aa
commit
ec582ac9f9
11 files changed
+248
-56
No files matched your search
@@ -29,13 +29,15 @@ export function dividerMarkup(): string {
|
||||
}
|
||||
|
||||
/**
|
||||
* 代码块:与前台文章页一致,只落 pre > code。
|
||||
* 代码块。
|
||||
*
|
||||
* 前台由 EnlighterJS 按「全局功能 → 代码高亮」统一高亮,因此正文里不再写
|
||||
* 语言 / 主题属性,也不需要编辑器内的语言角标。
|
||||
* 编辑器里放一个可直接输入的裸 `<pre>`:TinyMCE 会把 `pre > code` 当成行内边界,
|
||||
* 光标落不进去、输入会把内容拆到段落里。落库前由 `serializeBlocks()` 换成前台
|
||||
* Enlighter 认得的 `pre > code`(换行写成真正的换行符),打开已有内容时
|
||||
* `hydrateBlocks()` 再还原成可编辑的 `<pre>`。
|
||||
*/
|
||||
export function codeBlockMarkup(): string {
|
||||
return '<pre><code></code></pre><p></p>';
|
||||
return '<pre><br></pre><p></p>';
|
||||
}
|
||||
|
||||
export type TableOptions = {
|
||||
|
||||
@@ -2,9 +2,14 @@
|
||||
* 编辑器内容 ↔ 保存格式的转换。
|
||||
*
|
||||
* 编辑器里把公式、注释渲染成可读的样子(方便写作时看到最终效果),
|
||||
* 但落库必须回到短代码,服务端才能稳定渲染。隐藏内容与代码块本身就是
|
||||
* 但落库必须回到短代码,服务端才能稳定渲染。隐藏内容本身就是
|
||||
* 短代码 / 前台结构,不需要转换。
|
||||
*
|
||||
* 代码块例外:前台由 EnlighterJS 按「全局功能 → 代码高亮」统一高亮,认的是
|
||||
* `pre > code`,并把源码里的换行当作分行;而 TinyMCE 把 `pre > code` 当成
|
||||
* 行内边界,光标落不进去、输入会被拆成段落。因此编辑器里用裸 `<pre>` 编辑,
|
||||
* 保存前套回 `pre > code` 并把 `<br>` 还原成换行符。
|
||||
*
|
||||
* @package Jio
|
||||
*/
|
||||
|
||||
@@ -17,17 +22,104 @@ const decodeEntities = (value: string): string => {
|
||||
return area.value;
|
||||
};
|
||||
|
||||
/**
|
||||
* 读出代码块的源码行。
|
||||
*
|
||||
* 两种形态都要认:编辑器里的裸 `<pre>`(换行是 `<br>`),以及前台落库的
|
||||
* `pre > code`(换行是文本里的 `\n`)。`<code>` 只是容器,直接走进去读,
|
||||
* 不重复计数。
|
||||
*/
|
||||
const readCodeLines = (pre: Element): string[] => {
|
||||
const lines: string[] = [];
|
||||
let current = '';
|
||||
|
||||
const flush = (): void => {
|
||||
lines.push(current);
|
||||
current = '';
|
||||
};
|
||||
|
||||
const walk = (node: Node): void => {
|
||||
node.childNodes.forEach((child) => {
|
||||
if (child.nodeType === 3) {
|
||||
String(child.nodeValue || '')
|
||||
.replace(/ /g, ' ')
|
||||
.split('\n')
|
||||
.forEach((part, index) => {
|
||||
if (index > 0) {
|
||||
flush();
|
||||
}
|
||||
current += part;
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (child.nodeType !== 1) {
|
||||
return;
|
||||
}
|
||||
if ((child as Element).nodeName === 'BR') {
|
||||
flush();
|
||||
return;
|
||||
}
|
||||
walk(child);
|
||||
});
|
||||
};
|
||||
|
||||
walk(pre);
|
||||
flush();
|
||||
|
||||
// 末尾的空行只是排版残留(裸 pre 收尾的 <br>、落库时补的换行符)。
|
||||
while (lines.length > 0 && lines[lines.length - 1].trim() === '') {
|
||||
lines.pop();
|
||||
}
|
||||
return lines;
|
||||
};
|
||||
|
||||
/**
|
||||
* 编辑器里的裸 `<pre>` → 前台的 `pre > code`。
|
||||
*
|
||||
* `<br>` 与文本里的换行都换成分行符:Enlighter 直接按文本切行,留着 `<br>`
|
||||
* 会把整块压成一行、长行溢出卡片。
|
||||
*/
|
||||
const serializeCodeBlock = (pre: Element): void => {
|
||||
const lines = readCodeLines(pre);
|
||||
const code = document.createElement('code');
|
||||
code.textContent = lines.length === 0 ? '' : `${lines.join('\n')}\n`;
|
||||
pre.replaceChildren(code);
|
||||
};
|
||||
|
||||
/**
|
||||
* 前台 `pre > code` → 编辑器里可直接输入的裸 `<pre>`。
|
||||
*/
|
||||
const hydrateCodeBlock = (pre: Element): void => {
|
||||
const lines = readCodeLines(pre);
|
||||
pre.replaceChildren();
|
||||
if (lines.length === 0) {
|
||||
pre.appendChild(document.createElement('br'));
|
||||
return;
|
||||
}
|
||||
lines.forEach((line, index) => {
|
||||
if (index > 0) {
|
||||
pre.appendChild(document.createElement('br'));
|
||||
}
|
||||
if (line !== '') {
|
||||
pre.appendChild(document.createTextNode(line));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 保存前:把编辑器里的可视化块换回短代码 / 纯标记。
|
||||
*/
|
||||
export function serializeBlocks(html: string): string {
|
||||
if (!html || html.indexOf('data-jio-') === -1) {
|
||||
if (!html) {
|
||||
return html;
|
||||
}
|
||||
|
||||
const container = document.createElement('div');
|
||||
container.innerHTML = html;
|
||||
|
||||
// 代码块:裸 pre → 前台的 pre > code(换行写成换行符)。
|
||||
container.querySelectorAll('pre').forEach((pre) => serializeCodeBlock(pre));
|
||||
|
||||
// 公式
|
||||
container.querySelectorAll<HTMLElement>('[data-jio-math-source]').forEach((node) => {
|
||||
const formula = node.getAttribute('data-jio-math-source') || '';
|
||||
@@ -51,14 +143,19 @@ export function serializeBlocks(html: string): string {
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开已有内容时:把短代码还原成编辑器里的可视化块。
|
||||
* 打开已有内容时:把短代码 / 前台的代码块结构还原成编辑器里的可视化块。
|
||||
*/
|
||||
export function hydrateBlocks(html: string): string {
|
||||
if (!html || html.indexOf('[jio_') === -1) {
|
||||
if (!html) {
|
||||
return html;
|
||||
}
|
||||
|
||||
return html
|
||||
const container = document.createElement('div');
|
||||
container.innerHTML = html;
|
||||
|
||||
container.querySelectorAll('pre').forEach((pre) => hydrateCodeBlock(pre));
|
||||
|
||||
return container.innerHTML
|
||||
.replace(
|
||||
/\[jio_math display="([01])"\]([\s\S]*?)\[\/jio_math\]/g,
|
||||
(_match, display: string, formula: string) =>
|
||||
|
||||
Reference in new issue
Block a user