feat(creator-studio): bottom status bar, editor-rendered math/footnote, code language picker, block delete
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
6928127bfa
commit
4a119358ca
11 files changed
+754
-63
No files matched your search
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* 编辑器内容 ↔ 保存格式的转换。
|
||||
*
|
||||
* 编辑器里把公式、注释、代码语言标签渲染成可读的样子(方便写作时看到最终效果),
|
||||
* 但落库必须回到短代码/纯标记,服务端才能稳定渲染。这里在两个方向上都做一次转换。
|
||||
*
|
||||
* @package Jio
|
||||
*/
|
||||
|
||||
const escapeAttr = (value: string): string =>
|
||||
value.replace(/&/g, '&').replace(/"/g, '"').replace(/</g, '<').replace(/>/g, '>');
|
||||
|
||||
const decodeEntities = (value: string): string => {
|
||||
const area = document.createElement('textarea');
|
||||
area.innerHTML = value;
|
||||
return area.value;
|
||||
};
|
||||
|
||||
/**
|
||||
* 保存前:把编辑器里的可视化块换回短代码 / 纯标记。
|
||||
*/
|
||||
export function serializeBlocks(html: string): string {
|
||||
if (!html || (html.indexOf('data-jio-') === -1 && html.indexOf('jio-code-lang') === -1)) {
|
||||
return html;
|
||||
}
|
||||
|
||||
const container = document.createElement('div');
|
||||
container.innerHTML = html;
|
||||
|
||||
// 公式
|
||||
container.querySelectorAll<HTMLElement>('[data-jio-math-source]').forEach((node) => {
|
||||
const formula = node.getAttribute('data-jio-math-source') || '';
|
||||
const display = node.getAttribute('data-jio-math-display') !== '0';
|
||||
const shortcode = `[jio_math display="${display ? '1' : '0'}"]${formula}[/jio_math]`;
|
||||
const paragraph = node.closest('p');
|
||||
if (display && paragraph && paragraph.textContent?.trim() === node.textContent?.trim()) {
|
||||
paragraph.textContent = shortcode;
|
||||
} else {
|
||||
node.replaceWith(document.createTextNode(shortcode));
|
||||
}
|
||||
});
|
||||
|
||||
// 注释
|
||||
container.querySelectorAll<HTMLElement>('[data-jio-footnote]').forEach((node) => {
|
||||
const note = node.getAttribute('data-jio-footnote') || '';
|
||||
node.replaceWith(document.createTextNode(`[jio_footnote]${note}[/jio_footnote]`));
|
||||
});
|
||||
|
||||
// 代码块语言角标只是编辑期提示
|
||||
container.querySelectorAll('.jio-code-lang').forEach((node) => node.remove());
|
||||
|
||||
return container.innerHTML;
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开已有内容时:把短代码还原成编辑器里的可视化块。
|
||||
*/
|
||||
export function hydrateBlocks(html: string): string {
|
||||
if (!html || html.indexOf('[jio_') === -1) {
|
||||
return html;
|
||||
}
|
||||
|
||||
return html
|
||||
.replace(
|
||||
/\[jio_math display="([01])"\]([\s\S]*?)\[\/jio_math\]/g,
|
||||
(_match, display: string, formula: string) =>
|
||||
display === '0'
|
||||
? `<span class="jio-math" data-jio-math-source="${escapeAttr(formula)}" data-jio-math-display="0" contenteditable="false">${typeset(formula)}</span>`
|
||||
: `<p><span class="jio-math jio-math--block" data-jio-math-source="${escapeAttr(formula)}" data-jio-math-display="1" contenteditable="false">${typeset(formula)}</span></p>`
|
||||
)
|
||||
.replace(
|
||||
/\[jio_footnote\]([\s\S]*?)\[\/jio_footnote\]/g,
|
||||
(_match, note: string) =>
|
||||
`<sup class="jio-footnote-ref" data-jio-footnote="${escapeAttr(decodeEntities(note))}" contenteditable="false">${note}</sup>`
|
||||
);
|
||||
}
|
||||
|
||||
const typeset = (raw: string): string => {
|
||||
const escaped = raw.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
||||
return escaped
|
||||
.replace(/\^\{([^{}]*)\}|\^(\S)/gu, '<sup>$1$2</sup>')
|
||||
.replace(/_\{([^{}]*)\}|_(\S)/gu, '<sub>$1$2</sub>');
|
||||
};
|
||||
Reference in new issue
Block a user