convertMarkdownToHtml($document); } /** * 将 Markdown 转换为 HTML * * @throws \Exception */ public function convertMarkdownToHtml(Document $document): string { $markdownContent = $document->getMarkdownContent(); if (empty($markdownContent)) { throw new \Exception('Markdown 内容为空'); } app(DocumentConversionService::class)->ensureMarkdownMediaAssets($document); $markdownContent = $this->stripPreviewFrontMatter($markdownContent); $markdownContent = $this->rewriteMarkdownMediaPaths($document, $markdownContent); $renderService = app(MarkdownRenderService::class); return $renderService->render($markdownContent); } protected function stripPreviewFrontMatter(string $markdownContent): string { if (!preg_match('/\A---\R(?P.*?\R)---\R*/s', $markdownContent, $matches)) { return $markdownContent; } $frontMatter = $matches['frontmatter'] ?? ''; if (!preg_match('/^(author|source_file):/m', $frontMatter)) { return $markdownContent; } return (string) preg_replace('/\A---\R.*?\R---\R*/s', '', $markdownContent, 1); } protected function rewriteMarkdownMediaPaths(Document $document, string $markdownContent): string { $documentDir = dirname($document->markdown_path); return (string) preg_replace_callback( '/!\[(?[^\]]*)]\((?(?:\.\/)?media\/[^)]+)\)/', function (array $matches) use ($documentDir): string { $relativePath = trim($matches['path'] ?? ''); $relativePath = preg_replace('/^\.?\//', '', $relativePath) ?? $relativePath; $relativePath = ltrim(str_replace('\\', '/', $relativePath), '/'); $segments = array_filter( explode('/', $documentDir . '/' . $relativePath), fn (string $segment): bool => $segment !== '' ); $url = '/markdown-media/' . implode('/', array_map('rawurlencode', $segments)); return sprintf('![%s](%s)', $matches['alt'] ?? '', $url); }, $markdownContent ); } /** * 检查文档是否可以预览 */ public function canPreview(Document $document): bool { return $document->conversion_status === 'completed' && !empty($document->markdown_path); } }