diff --git a/functions.php b/functions.php index d91057c..8679931 100644 --- a/functions.php +++ b/functions.php @@ -9173,3 +9173,173 @@ function argon_update_mermaid_settings($settings) { 'errors' => [] ]; } + +// ========================================================================== +// Mermaid 图表支持 - 库加载器 +// ========================================================================== + +/** + * 检测页面内容是否包含 Mermaid 代码块 + * + * 支持多种格式: + * -
+ * -

+ * - 
+ * - 
+ * 
+ * @param string $content 页面内容
+ * @return bool 是否包含 Mermaid 代码块
+ */
+function argon_has_mermaid_content($content) {
+	if (empty($content)) {
+		return false;
+	}
+	
+	// 检测多种 Mermaid 代码块格式
+	$patterns = [
+		'/]*class=["\']([^"\']*\s)?mermaid(\s[^"\']*)?["\'][^>]*>/i',  // 
+ '/]*class=["\']([^"\']*\s)?language-mermaid(\s[^"\']*)?["\'][^>]*>/i', // + '/]*data-lang=["\']mermaid["\'][^>]*>/i', //
+		'/]*class=["\']([^"\']*\s)?mermaid(\s[^"\']*)?["\'][^>]*>/i'  // 
+	];
+	
+	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);
+}
diff --git a/tests/test-mermaid-loader.php b/tests/test-mermaid-loader.php
new file mode 100644
index 0000000..d589528
--- /dev/null
+++ b/tests/test-mermaid-loader.php
@@ -0,0 +1,136 @@
+flowchart TD\nA-->B
'; +test_assert(argon_has_mermaid_content($content1), "测试 1: 检测 div class=\"mermaid\" 格式"); + +// 测试 2: 检测 code class="language-mermaid" 格式 +$content2 = '
graph LR\nA-->B
'; +test_assert(argon_has_mermaid_content($content2), "测试 2: 检测 code class=\"language-mermaid\" 格式"); + +// 测试 3: 检测 pre data-lang="mermaid" 格式 +$content3 = '
sequenceDiagram\nA->>B: Hello
'; +test_assert(argon_has_mermaid_content($content3), "测试 3: 检测 pre data-lang=\"mermaid\" 格式"); + +// 测试 4: 检测 code class="mermaid" 格式 +$content4 = 'pie title Pets\n"Dogs" : 386'; +test_assert(argon_has_mermaid_content($content4), "测试 4: 检测 code class=\"mermaid\" 格式"); + +// 测试 5: 不包含 Mermaid 代码块 +$content5 = '

This is a regular paragraph

console.log("hello")'; +test_assert(!argon_has_mermaid_content($content5), "测试 5: 不包含 Mermaid 代码块"); + +// 测试 6: 空内容 +test_assert(!argon_has_mermaid_content(''), "测试 6: 空内容返回 false"); + +// 测试 7: 检测多个 class 的情况 +$content7 = '
flowchart TD
'; +test_assert(argon_has_mermaid_content($content7), "测试 7: 检测多个 class 的情况"); + +// 测试 8: 大小写不敏感 +$content8 = '
flowchart TD
'; +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";