feat: 实现 Mermaid 库加载器
- 添加 argon_has_mermaid_content() 函数检测页面是否包含 Mermaid 代码块 - 支持多种格式:div.mermaid、code.language-mermaid、pre[data-lang=mermaid]、code.mermaid - 添加 argon_get_mermaid_library_url() 函数根据配置返回 CDN 或本地路径 - 支持 jsdelivr、unpkg、自定义 CDN 和本地镜像 - 添加 argon_get_mermaid_fallback_urls() 函数提供备用 CDN 列表 - 添加 argon_enqueue_mermaid_scripts() 函数按需加载 Mermaid 库 - 检测文章内容和评论内容中的 Mermaid 代码块 - 实现异步加载(async 属性) - 通过 wp_localize_script 传递配置到前端 - 添加单元测试文件 test-mermaid-loader.php - Requirements: 1.1, 1.2, 1.3, 1.5, 8.2
This commit is contained in:
170
functions.php
170
functions.php
@@ -9173,3 +9173,173 @@ function argon_update_mermaid_settings($settings) {
|
||||
'errors' => []
|
||||
];
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// Mermaid 图表支持 - 库加载器
|
||||
// ==========================================================================
|
||||
|
||||
/**
|
||||
* 检测页面内容是否包含 Mermaid 代码块
|
||||
*
|
||||
* 支持多种格式:
|
||||
* - <div class="mermaid">
|
||||
* - <pre><code class="language-mermaid">
|
||||
* - <pre data-lang="mermaid">
|
||||
* - <code class="mermaid">
|
||||
*
|
||||
* @param string $content 页面内容
|
||||
* @return bool 是否包含 Mermaid 代码块
|
||||
*/
|
||||
function argon_has_mermaid_content($content) {
|
||||
if (empty($content)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检测多种 Mermaid 代码块格式
|
||||
$patterns = [
|
||||
'/<div[^>]*class=["\']([^"\']*\s)?mermaid(\s[^"\']*)?["\'][^>]*>/i', // <div class="mermaid">
|
||||
'/<code[^>]*class=["\']([^"\']*\s)?language-mermaid(\s[^"\']*)?["\'][^>]*>/i', // <code class="language-mermaid">
|
||||
'/<pre[^>]*data-lang=["\']mermaid["\'][^>]*>/i', // <pre data-lang="mermaid">
|
||||
'/<code[^>]*class=["\']([^"\']*\s)?mermaid(\s[^"\']*)?["\'][^>]*>/i' // <code class="mermaid">
|
||||
];
|
||||
|
||||
foreach ($patterns as $pattern) {
|
||||
if (preg_match($pattern, $content)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 Mermaid 库的 URL
|
||||
* 根据配置返回对应的 CDN 或本地路径
|
||||
*
|
||||
* @return string Mermaid 库 URL
|
||||
*/
|
||||
function argon_get_mermaid_library_url() {
|
||||
$cdn_source = argon_get_mermaid_option('cdn_source', 'jsdelivr');
|
||||
$use_local = argon_get_mermaid_option('use_local', false);
|
||||
|
||||
// 如果启用本地镜像,直接返回本地路径
|
||||
if ($use_local) {
|
||||
return get_template_directory_uri() . '/assets/vendor/mermaid/mermaid.min.js';
|
||||
}
|
||||
|
||||
// 根据 CDN 来源返回对应的 URL
|
||||
$cdn_urls = [
|
||||
'jsdelivr' => 'https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js',
|
||||
'unpkg' => 'https://unpkg.com/mermaid@10/dist/mermaid.min.js',
|
||||
'local' => get_template_directory_uri() . '/assets/vendor/mermaid/mermaid.min.js'
|
||||
];
|
||||
|
||||
// 如果是自定义 CDN,返回自定义 URL
|
||||
if ($cdn_source === 'custom') {
|
||||
$custom_url = argon_get_mermaid_option('custom_cdn_url', '');
|
||||
if (!empty($custom_url) && argon_validate_mermaid_cdn_url($custom_url)) {
|
||||
return $custom_url;
|
||||
}
|
||||
// 如果自定义 URL 无效,降级到 jsdelivr
|
||||
return $cdn_urls['jsdelivr'];
|
||||
}
|
||||
|
||||
// 返回对应的 CDN URL,如果不存在则返回 jsdelivr
|
||||
return isset($cdn_urls[$cdn_source]) ? $cdn_urls[$cdn_source] : $cdn_urls['jsdelivr'];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取备用 CDN URL 列表
|
||||
* 用于加载失败时的降级处理
|
||||
*
|
||||
* @return array 备用 CDN URL 数组
|
||||
*/
|
||||
function argon_get_mermaid_fallback_urls() {
|
||||
return [
|
||||
'https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js',
|
||||
'https://unpkg.com/mermaid@10/dist/mermaid.min.js',
|
||||
get_template_directory_uri() . '/assets/vendor/mermaid/mermaid.min.js'
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载 Mermaid JavaScript 库
|
||||
* 在 wp_enqueue_scripts 钩子中调用
|
||||
*/
|
||||
function argon_enqueue_mermaid_scripts() {
|
||||
// 检查是否启用 Mermaid 支持
|
||||
if (!argon_get_mermaid_option('enabled', false)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查当前页面是否包含 Mermaid 代码块
|
||||
global $post;
|
||||
$has_mermaid = false;
|
||||
|
||||
// 检查文章内容
|
||||
if (is_singular() && isset($post->post_content)) {
|
||||
$has_mermaid = argon_has_mermaid_content($post->post_content);
|
||||
}
|
||||
|
||||
// 检查评论内容(如果启用了评论)
|
||||
if (!$has_mermaid && is_singular() && comments_open()) {
|
||||
$comments = get_comments([
|
||||
'post_id' => $post->ID,
|
||||
'status' => 'approve'
|
||||
]);
|
||||
|
||||
foreach ($comments as $comment) {
|
||||
if (argon_has_mermaid_content($comment->comment_content)) {
|
||||
$has_mermaid = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果页面不包含 Mermaid 代码块,不加载库
|
||||
if (!$has_mermaid) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取 Mermaid 库 URL
|
||||
$mermaid_url = argon_get_mermaid_library_url();
|
||||
|
||||
// 注册并加载 Mermaid 库
|
||||
wp_enqueue_script(
|
||||
'mermaid',
|
||||
$mermaid_url,
|
||||
[], // 不依赖其他脚本
|
||||
'10.0.0', // Mermaid 版本
|
||||
true // 在页脚加载
|
||||
);
|
||||
|
||||
// 添加 async 属性实现异步加载
|
||||
add_filter('script_loader_tag', 'argon_add_mermaid_async_attribute', 10, 2);
|
||||
|
||||
// 传递配置到前端
|
||||
$mermaid_config = [
|
||||
'enabled' => true,
|
||||
'theme' => argon_get_mermaid_option('theme', 'auto'),
|
||||
'debugMode' => argon_get_mermaid_option('debug_mode', false),
|
||||
'fallbackUrls' => argon_get_mermaid_fallback_urls()
|
||||
];
|
||||
|
||||
wp_localize_script('mermaid', 'argonMermaidConfig', $mermaid_config);
|
||||
}
|
||||
add_action('wp_enqueue_scripts', 'argon_enqueue_mermaid_scripts');
|
||||
|
||||
/**
|
||||
* 为 Mermaid 脚本添加 async 属性
|
||||
*
|
||||
* @param string $tag 脚本标签
|
||||
* @param string $handle 脚本句柄
|
||||
* @return string 修改后的脚本标签
|
||||
*/
|
||||
function argon_add_mermaid_async_attribute($tag, $handle) {
|
||||
if ('mermaid' !== $handle) {
|
||||
return $tag;
|
||||
}
|
||||
|
||||
// 添加 async 属性
|
||||
return str_replace(' src', ' async src', $tag);
|
||||
}
|
||||
|
||||
136
tests/test-mermaid-loader.php
Normal file
136
tests/test-mermaid-loader.php
Normal file
@@ -0,0 +1,136 @@
|
||||
<?php
|
||||
/**
|
||||
* Mermaid 库加载器单元测试
|
||||
*
|
||||
* 测试 argon_has_mermaid_content() 和 argon_get_mermaid_library_url() 函数
|
||||
*/
|
||||
|
||||
// 加载 WordPress 测试环境
|
||||
require_once dirname(__FILE__) . '/../../../wp-load.php';
|
||||
require_once dirname(__FILE__) . '/../functions.php';
|
||||
|
||||
/**
|
||||
* 测试辅助函数
|
||||
*/
|
||||
function test_assert($condition, $message) {
|
||||
if ($condition) {
|
||||
echo "✓ {$message}\n";
|
||||
return true;
|
||||
} else {
|
||||
echo "✗ {$message}\n";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function test_assert_equals($expected, $actual, $message) {
|
||||
if ($expected === $actual) {
|
||||
echo "✓ {$message}\n";
|
||||
return true;
|
||||
} else {
|
||||
echo "✗ {$message}\n";
|
||||
echo " 期望值: " . var_export($expected, true) . "\n";
|
||||
echo " 实际值: " . var_export($actual, true) . "\n";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function test_assert_contains($needle, $haystack, $message) {
|
||||
if (strpos($haystack, $needle) !== false) {
|
||||
echo "✓ {$message}\n";
|
||||
return true;
|
||||
} else {
|
||||
echo "✗ {$message}\n";
|
||||
echo " 在字符串中未找到: {$needle}\n";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
echo "=== Mermaid 库加载器单元测试 ===\n\n";
|
||||
|
||||
// 测试 1: 检测 div class="mermaid" 格式
|
||||
$content1 = '<div class="mermaid">flowchart TD\nA-->B</div>';
|
||||
test_assert(argon_has_mermaid_content($content1), "测试 1: 检测 div class=\"mermaid\" 格式");
|
||||
|
||||
// 测试 2: 检测 code class="language-mermaid" 格式
|
||||
$content2 = '<pre><code class="language-mermaid">graph LR\nA-->B</code></pre>';
|
||||
test_assert(argon_has_mermaid_content($content2), "测试 2: 检测 code class=\"language-mermaid\" 格式");
|
||||
|
||||
// 测试 3: 检测 pre data-lang="mermaid" 格式
|
||||
$content3 = '<pre data-lang="mermaid">sequenceDiagram\nA->>B: Hello</pre>';
|
||||
test_assert(argon_has_mermaid_content($content3), "测试 3: 检测 pre data-lang=\"mermaid\" 格式");
|
||||
|
||||
// 测试 4: 检测 code class="mermaid" 格式
|
||||
$content4 = '<code class="mermaid">pie title Pets\n"Dogs" : 386</code>';
|
||||
test_assert(argon_has_mermaid_content($content4), "测试 4: 检测 code class=\"mermaid\" 格式");
|
||||
|
||||
// 测试 5: 不包含 Mermaid 代码块
|
||||
$content5 = '<p>This is a regular paragraph</p><code class="language-javascript">console.log("hello")</code>';
|
||||
test_assert(!argon_has_mermaid_content($content5), "测试 5: 不包含 Mermaid 代码块");
|
||||
|
||||
// 测试 6: 空内容
|
||||
test_assert(!argon_has_mermaid_content(''), "测试 6: 空内容返回 false");
|
||||
|
||||
// 测试 7: 检测多个 class 的情况
|
||||
$content7 = '<div class="code-block mermaid highlight">flowchart TD</div>';
|
||||
test_assert(argon_has_mermaid_content($content7), "测试 7: 检测多个 class 的情况");
|
||||
|
||||
// 测试 8: 大小写不敏感
|
||||
$content8 = '<div class="MERMAID">flowchart TD</div>';
|
||||
test_assert(argon_has_mermaid_content($content8), "测试 8: 大小写不敏感");
|
||||
|
||||
echo "\n=== 测试 argon_get_mermaid_library_url() ===\n\n";
|
||||
|
||||
// 测试 9: jsdelivr CDN
|
||||
update_option('argon_mermaid_cdn_source', 'jsdelivr');
|
||||
update_option('argon_mermaid_use_local', false);
|
||||
$url9 = argon_get_mermaid_library_url();
|
||||
test_assert_contains('cdn.jsdelivr.net', $url9, "测试 9: jsdelivr CDN URL");
|
||||
|
||||
// 测试 10: unpkg CDN
|
||||
update_option('argon_mermaid_cdn_source', 'unpkg');
|
||||
update_option('argon_mermaid_use_local', false);
|
||||
$url10 = argon_get_mermaid_library_url();
|
||||
test_assert_contains('unpkg.com', $url10, "测试 10: unpkg CDN URL");
|
||||
|
||||
// 测试 11: 本地镜像
|
||||
update_option('argon_mermaid_use_local', true);
|
||||
$url11 = argon_get_mermaid_library_url();
|
||||
test_assert_contains('/assets/vendor/mermaid/', $url11, "测试 11: 本地镜像 URL");
|
||||
|
||||
// 测试 12: 自定义 CDN(有效 URL)
|
||||
update_option('argon_mermaid_cdn_source', 'custom');
|
||||
update_option('argon_mermaid_cdn_custom_url', 'https://example.com/mermaid.min.js');
|
||||
update_option('argon_mermaid_use_local', false);
|
||||
$url12 = argon_get_mermaid_library_url();
|
||||
test_assert_equals('https://example.com/mermaid.min.js', $url12, "测试 12: 自定义 CDN URL");
|
||||
|
||||
// 测试 13: 自定义 CDN(无效 URL,降级到 jsdelivr)
|
||||
update_option('argon_mermaid_cdn_source', 'custom');
|
||||
update_option('argon_mermaid_cdn_custom_url', 'invalid-url');
|
||||
update_option('argon_mermaid_use_local', false);
|
||||
$url13 = argon_get_mermaid_library_url();
|
||||
test_assert_contains('cdn.jsdelivr.net', $url13, "测试 13: 无效自定义 URL 降级到 jsdelivr");
|
||||
|
||||
// 测试 14: 本地镜像优先级最高
|
||||
update_option('argon_mermaid_cdn_source', 'jsdelivr');
|
||||
update_option('argon_mermaid_use_local', true);
|
||||
$url14 = argon_get_mermaid_library_url();
|
||||
test_assert_contains('/assets/vendor/mermaid/', $url14, "测试 14: 本地镜像优先级最高");
|
||||
|
||||
// 测试 15: 未知 CDN 来源降级到 jsdelivr
|
||||
update_option('argon_mermaid_cdn_source', 'unknown-source');
|
||||
update_option('argon_mermaid_use_local', false);
|
||||
$url15 = argon_get_mermaid_library_url();
|
||||
test_assert_contains('cdn.jsdelivr.net', $url15, "测试 15: 未知 CDN 来源降级到 jsdelivr");
|
||||
|
||||
echo "\n=== 测试 argon_get_mermaid_fallback_urls() ===\n\n";
|
||||
|
||||
// 测试 16: 备用 URL 列表
|
||||
$fallback_urls = argon_get_mermaid_fallback_urls();
|
||||
test_assert(is_array($fallback_urls), "测试 16: 返回数组");
|
||||
test_assert(count($fallback_urls) === 3, "测试 17: 包含 3 个备用 URL");
|
||||
test_assert_contains('cdn.jsdelivr.net', $fallback_urls[0], "测试 18: 第一个备用 URL 是 jsdelivr");
|
||||
test_assert_contains('unpkg.com', $fallback_urls[1], "测试 19: 第二个备用 URL 是 unpkg");
|
||||
test_assert_contains('/assets/vendor/mermaid/', $fallback_urls[2], "测试 20: 第三个备用 URL 是本地");
|
||||
|
||||
echo "\n=== 所有测试完成 ===\n";
|
||||
Reference in New Issue
Block a user