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:
1 parent
7885f42cff
commit
6928127bfa
11 files changed
+235
-202
No files matched your search
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in new issue
Block a user