fix(creator-studio): render math/footnote shortcodes, inline code blocks, block deletion

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
kate522andClaude Opus 5 committed 2026-11-06 23:52:39 +08:00
1 parent 7885f42cff
commit 6928127bfa
11 files changed
+235 -202

No files matched your search

+61 -81
View File
@@ -1,11 +1,12 @@
<?php
/**
* 正文扩展块的渲染:目录、脚注、公式。
* 正文扩展块:目录、脚注、公式。
*
* 编辑器插入的都是 HTML/文本占位,读者端由这里转换成真正的渲染结果:
* - 目录:占位替换成前台 section-navs 识别的 .posts-nav-box 卡片;
* - 脚注:[^1] 引用与 [^1]: 定义转换成脚注列表并重新编号;
* - 公式:.jio-math 内的 ^ / _ 转成上标下标,给出可读的数学排版。
* 三块都以短代码落库,WordPress 保证在 `the_content` 里稳定替换,
* 不必依赖编辑器保存后仍然保留的 class / data 属性:
* - [jio_toc] → 文章目录卡片(前台 section-navs 填充)
* - [jio_footnote]...[/jio_footnote] → 编号脚注 + 文末注释列表
* - [jio_math]...[/jio_math] → 公式排版(上标 / 下标)
*
* @package Jio
*/
@@ -16,9 +17,12 @@ defined('ABSPATH') || exit;
class ContentBlocks {
/** 编辑器插入的目录占位 class。 */
/** 目录占位短代码(历史 HTML 占位也用这个 class 识别)。 */
public const TOC_CLASS = 'jio-toc-placeholder';
/** 本次渲染已收集的脚注。 */
private $footnotes = [];
private static $instance = null;
public static function get_instance(): self {
@@ -29,91 +33,80 @@ class ContentBlocks {
}
public function init(): void {
add_filter('the_content', [$this, 'render_blocks'], 9);
// 必须晚于 do_shortcode(优先级 11),才能拿到本次渲染收集到的脚注。
add_filter('the_content', [$this, 'append_footnotes'], 15);
}
/**
* 正文里是否插入了目录占位(用于决定是否加载目录脚本)。
* 正文里是否含目录短代码或旧占位。
*/
public static function has_toc_placeholder(string $content): bool {
return strpos($content, self::TOC_CLASS) !== false;
}
public function render_blocks(string $content): string {
if ($content === '' || is_admin()) {
return $content;
}
$content = $this->render_toc($content);
$content = $this->render_footnotes($content);
return $this->render_math($content);
return strpos($content, '[jio_toc') !== false || strpos($content, self::TOC_CLASS) !== false;
}
/**
* 目录占位 → 文章目录卡片。
* [jio_toc]
*/
private function render_toc(string $content): string {
if (!self::has_toc_placeholder($content)) {
return $content;
}
$box = '<div class="widget-container"><div class="posts-nav-box" data-title="'
public function shortcode_toc(array $atts = [], string $content = ''): string {
return '<div class="widget-container"><div class="posts-nav-box" data-title="'
. esc_attr__('本文目录', 'jio')
. '"></div></div>';
$pattern = '/<div[^>]*class="[^"]*' . preg_quote(self::TOC_CLASS, '/') . '[^"]*"[^>]*>\s*<\/div>\s*(?:<p>\s*<\/p>)?/i';
$replaced = preg_replace($pattern, $box, $content, 1);
return is_string($replaced) ? $replaced : $content;
}
/**
* Markdown 脚注:[^1] 引用与 [^1]: 定义。
* [jio_math display="1"]公式[/jio_math]
*/
private function render_footnotes(string $content): string {
if (strpos($content, '[^') === false) {
return $content;
}
if (!preg_match_all('/\[\^([^\]]+)\]:[ \t]*([^\n<]+)/u', $content, $defs, PREG_SET_ORDER)) {
return $content;
public function shortcode_math(array $atts = [], string $content = ''): string {
$atts = shortcode_atts(['display' => '1'], $atts, 'jio_math');
$raw = trim(wp_strip_all_tags((string) $content));
if ($raw === '') {
return '';
}
$body = $this->typeset_math($raw);
$display = (string) $atts['display'] !== '0';
return $display
? '<p><span class="jio-math jio-math--block">' . $body . '</span></p>'
: '<span class="jio-math">' . $body . '</span>';
}
$notes = [];
foreach ($defs as $def) {
$notes[(string) $def[1]] = trim((string) $def[2]);
$content = str_replace($def[0], '', $content);
}
if ($notes === []) {
return $content;
/**
* [jio_footnote]补充说明[/jio_footnote]
*
* 引用位置插入上标序号,注释正文收集到文末列表。
*/
public function shortcode_footnote(array $atts = [], string $content = ''): string {
$text = trim(wp_strip_all_tags((string) $content));
if ($text === '') {
return '';
}
$this->footnotes[] = $text;
$index = count($this->footnotes);
return '<sup class="jio-footnote-ref"><a href="#jio-fn-' . $index . '" id="jio-fnref-' . $index . '">'
. $index
. '</a></sup>';
}
// 按首次引用顺序编号,而不是按定义顺序。
$order = [];
$result = preg_replace_callback('/\[\^([^\]]+)\]/u', static function (array $match) use (&$order): string {
$key = (string) $match[1];
if (!isset($order[$key])) {
$order[$key] = count($order) + 1;
}
$index = $order[$key];
return '<sup class="jio-footnote-ref"><a href="#jio-fn-' . $index . '" id="jio-fnref-' . $index . '">'
. $index
. '</a></sup>';
}, $content);
if (!is_string($result)) {
/**
* 在正文末尾补上注释列表。
*/
public function append_footnotes(string $content): string {
// 同一段正文可能被渲染多次(列表摘要、相关文章等),注释用完后即清空,
// 避免把上一篇文章的脚注带到下一篇。
$footnotes = $this->footnotes;
$this->footnotes = [];
if ($footnotes === [] || strpos($content, 'jio-footnotes') !== false) {
return $content;
}
$content = $result;
$items = '';
foreach ($order as $key => $index) {
if (!isset($notes[$key])) {
continue;
}
foreach ($footnotes as $offset => $text) {
$index = $offset + 1;
$items .= '<li id="jio-fn-' . $index . '">'
. esc_html($notes[$key])
. esc_html($text)
. ' <a class="jio-footnote-back" href="#jio-fnref-' . $index . '" aria-label="'
. esc_attr__('返回正文', 'jio')
. '">↩</a></li>';
}
if ($items === '') {
return $content;
}
return $content
. '<section class="jio-footnotes"><h4 class="jio-footnotes__title">'
@@ -122,24 +115,11 @@ class ContentBlocks {
}
/**
* 公式:把 .jio-math 片段里的 ^ 与 _ 转成真正可见的上标下标。
*
* 主题没有内置 TeX 引擎,这里只做确定性的公式排版,不猜测其它语法。
* 公式排版:把 ^ 与 _ 转成上标下标,其余字符按文本转义。
*/
private function render_math(string $content): string {
if (strpos($content, 'jio-math') === false) {
return $content;
}
$result = preg_replace_callback(
'#<span([^>]*\bclass="[^"]*jio-math[^"]*"[^>]*)>(.*?)</span>#is',
static function (array $match): string {
$inner = esc_html(html_entity_decode($match[2], ENT_QUOTES, 'UTF-8'));
$inner = (string) preg_replace('/\^\{([^{}]*)\}|\^(\S)/u', '<sup>$1$2</sup>', $inner);
$inner = (string) preg_replace('/_\{([^{}]*)\}|_(\S)/u', '<sub>$1$2</sub>', $inner);
return '<span' . $match[1] . '>' . $inner . '</span>';
},
$content
);
return is_string($result) ? $result : $content;
private function typeset_math(string $raw): string {
$escaped = esc_html($raw);
$escaped = (string) preg_replace('/\^\{([^{}]*)\}|\^(\S)/u', '<sup>$1$2</sup>', $escaped);
return (string) preg_replace('/_\{([^{}]*)\}|_(\S)/u', '<sub>$1$2</sub>', $escaped);
}
}
+34
View File
@@ -37,6 +37,40 @@ class Shortcodes {
add_shortcode('jio_profile', [$this, 'render_profile']);
// 文章列表(统一渲染器)
add_shortcode('jio_posts', [$this, 'render_posts']);
// 正文扩展块 → \Jio\Content\ContentBlocks
add_shortcode('jio_math', [$this, 'render_math']);
add_shortcode('jio_footnote', [$this, 'render_footnote']);
add_shortcode('jio_toc', [$this, 'render_toc']);
}
/**
* [jio_math display="1"]a^2 + b^2 = c^2[/jio_math]
*/
public function render_math(array $atts = [], string $content = ''): string {
if (!class_exists('\Jio\Content\ContentBlocks')) {
return esc_html($content);
}
return \Jio\Content\ContentBlocks::get_instance()->shortcode_math($atts, $content);
}
/**
* [jio_footnote]补充说明[/jio_footnote]
*/
public function render_footnote(array $atts = [], string $content = ''): string {
if (!class_exists('\Jio\Content\ContentBlocks')) {
return esc_html($content);
}
return \Jio\Content\ContentBlocks::get_instance()->shortcode_footnote($atts, $content);
}
/**
* [jio_toc]
*/
public function render_toc(array $atts = [], string $content = ''): string {
if (!class_exists('\Jio\Content\ContentBlocks')) {
return '';
}
return \Jio\Content\ContentBlocks::get_instance()->shortcode_toc($atts, $content);
}
/**