fix: 保留博客HTML并补齐预览菜单
This commit is contained in:
parent
616346ccbc
commit
8c3a1d793c
@ -11,6 +11,8 @@ VALUES
|
||||
(2041700000000120302, 2041700000000100301, 'BlogArticleEdit', NULL, NULL, NULL, 'Blog:Article:Edit', 'button', '{"title":"common.edit"}', 1, 0),
|
||||
(2041700000000120303, 2041700000000100301, 'BlogArticleDelete', NULL, NULL, NULL, 'Blog:Article:Delete', 'button', '{"title":"common.delete"}', 1, 0),
|
||||
(2041700000000120304, 2041700000000100301, 'BlogArticleImport', NULL, NULL, NULL, 'Blog:Article:Import', 'button', '{"title":"导入 WordPress"}', 1, 1),
|
||||
(2041700000000120305, 2041700000000100301, 'BlogArticlePreviewButton', NULL, NULL, NULL, 'Blog:Article:Preview', 'button', '{"title":"预览"}', 1, 2),
|
||||
(2041700000000100305, 2041700000000100300, 'BlogArticlePreview', '/blog/article/:articleId/preview', '/blog/article/preview/index', NULL, 'Blog:Article:Preview', 'menu', '{"activePath":"/blog/article","hideInMenu":true,"title":"文章预览"}', 1, 4),
|
||||
(2041700000000100302, 2041700000000100300, 'BlogCategory', '/blog/category', '/blog/category/list', NULL, 'Blog:Category:List', 'menu', '{"icon":"lucide:folder-tree","title":"分类管理"}', 1, 1),
|
||||
(2041700000000120311, 2041700000000100302, 'BlogCategoryCreate', NULL, NULL, NULL, 'Blog:Category:Create', 'button', '{"title":"common.create"}', 1, 0),
|
||||
(2041700000000120312, 2041700000000100302, 'BlogCategoryEdit', NULL, NULL, NULL, 'Blog:Category:Edit', 'button', '{"title":"common.edit"}', 1, 0),
|
||||
|
||||
@ -245,6 +245,8 @@ VALUES
|
||||
(2041700000000120302, 2041700000000100301, 'BlogArticleEdit', NULL, NULL, NULL, 'Blog:Article:Edit', 'button', '{"title":"common.edit"}', 1, 0),
|
||||
(2041700000000120303, 2041700000000100301, 'BlogArticleDelete', NULL, NULL, NULL, 'Blog:Article:Delete', 'button', '{"title":"common.delete"}', 1, 0),
|
||||
(2041700000000120304, 2041700000000100301, 'BlogArticleImport', NULL, NULL, NULL, 'Blog:Article:Import', 'button', '{"title":"导入 WordPress"}', 1, 1),
|
||||
(2041700000000120305, 2041700000000100301, 'BlogArticlePreviewButton', NULL, NULL, NULL, 'Blog:Article:Preview', 'button', '{"title":"预览"}', 1, 2),
|
||||
(2041700000000100305, 2041700000000100300, 'BlogArticlePreview', '/blog/article/:articleId/preview', '/blog/article/preview/index', NULL, 'Blog:Article:Preview', 'menu', '{"activePath":"/blog/article","hideInMenu":true,"title":"文章预览"}', 1, 4),
|
||||
(2041700000000100302, 2041700000000100300, 'BlogCategory', '/blog/category', '/blog/category/list', NULL, 'Blog:Category:List', 'menu', '{"icon":"lucide:folder-tree","title":"分类管理"}', 1, 1),
|
||||
(2041700000000120311, 2041700000000100302, 'BlogCategoryCreate', NULL, NULL, NULL, 'Blog:Category:Create', 'button', '{"title":"common.create"}', 1, 0),
|
||||
(2041700000000120312, 2041700000000100302, 'BlogCategoryEdit', NULL, NULL, NULL, 'Blog:Category:Edit', 'button', '{"title":"common.edit"}', 1, 0),
|
||||
|
||||
@ -362,7 +362,7 @@ export class BlogArticleService {
|
||||
);
|
||||
} else {
|
||||
nextArticle.contentMarkdown = current?.contentMarkdown || '';
|
||||
nextArticle.contentHtml = await this.markdownService.renderToHtml(
|
||||
nextArticle.contentHtml = await this.markdownService.sanitizeHtml(
|
||||
body.content,
|
||||
);
|
||||
}
|
||||
|
||||
@ -643,12 +643,16 @@ const routeTestCases: Record<string, RouteTestCase> = {
|
||||
const response = await request(server)
|
||||
.post('/blog/article/update')
|
||||
.send({
|
||||
content: '<pre class="wp-block-code hljs-codeblock"></pre>',
|
||||
contentFormat: 'html',
|
||||
id: blogArticle.id,
|
||||
title: '本地文章',
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
expect(blogArticleServiceMock.update).toHaveBeenCalledWith({
|
||||
content: '<pre class="wp-block-code hljs-codeblock"></pre>',
|
||||
contentFormat: 'html',
|
||||
id: blogArticle.id,
|
||||
title: '本地文章',
|
||||
});
|
||||
|
||||
@ -119,6 +119,38 @@ describe('BlogArticleService', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('sanitizes html content without markdown rerendering when updating imported articles', async () => {
|
||||
const rawArgonCodeblock =
|
||||
'<pre class="wp-block-code hljs-codeblock" id="demo"><code class="hljs sql"><table class="hljs-ln"><tbody><tr><td class="hljs-ln-line hljs-ln-code">select 1;</td></tr></tbody></table></code><div class="hljs-control"><div class="hljs-control-btn hljs-control-copy"></div></div></pre><script>alert(1)</script>';
|
||||
const currentArticle = {
|
||||
contentHtml: '<p>旧正文</p>',
|
||||
contentMarkdown: '旧正文',
|
||||
id: '50',
|
||||
isDeleted: false,
|
||||
slug: 'wordpress-post',
|
||||
status: 'publish',
|
||||
title: 'WordPress 文章',
|
||||
};
|
||||
repository.findOne.mockResolvedValue(currentArticle);
|
||||
|
||||
await service.update({
|
||||
content: rawArgonCodeblock,
|
||||
contentFormat: 'html',
|
||||
id: '50',
|
||||
title: 'WordPress 文章',
|
||||
});
|
||||
|
||||
expect(markdownService.renderToHtml).not.toHaveBeenCalled();
|
||||
expect(markdownService.sanitizeHtml).toHaveBeenCalledWith(rawArgonCodeblock);
|
||||
expect(repository.save).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
contentHtml:
|
||||
'<pre class="wp-block-code hljs-codeblock" id="demo"><code class="hljs sql"><table class="hljs-ln"><tbody><tr><td class="hljs-ln-line hljs-ln-code">select 1;</td></tr></tbody></table></code><div class="hljs-control"><div class="hljs-control-btn hljs-control-copy"></div></div></pre>',
|
||||
contentMarkdown: '旧正文',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('queries public list with publish status and maps response fields', async () => {
|
||||
const builder = createQueryBuilderMock([
|
||||
{
|
||||
|
||||
@ -2,7 +2,20 @@ import { readFileSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
|
||||
describe('blog-menu.sql', () => {
|
||||
const sql = readFileSync(join(process.cwd(), 'sql/blog-menu.sql'), 'utf8');
|
||||
const sqlFiles = [
|
||||
readFileSync(join(process.cwd(), 'sql/blog-menu.sql'), 'utf8'),
|
||||
readFileSync(join(process.cwd(), 'sql/vben-admin-init.sql'), 'utf8'),
|
||||
];
|
||||
|
||||
/**
|
||||
* 断言博客菜单种子和全量初始化 SQL 同步包含同一段菜单配置。
|
||||
* @param snippet - SQL 片段;同时约束增量菜单脚本和全量初始化脚本。
|
||||
*/
|
||||
function expectAllSqlToContain(snippet: string) {
|
||||
sqlFiles.forEach((sql) => {
|
||||
expect(sql).toContain(snippet);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行 博客内容流程。
|
||||
@ -17,7 +30,7 @@ describe('blog-menu.sql', () => {
|
||||
component: string,
|
||||
authCode: string,
|
||||
) {
|
||||
expect(sql).toContain(
|
||||
expectAllSqlToContain(
|
||||
`'${name}', '${path}', '${component}', NULL, '${authCode}', 'menu'`,
|
||||
);
|
||||
}
|
||||
@ -30,18 +43,20 @@ describe('blog-menu.sql', () => {
|
||||
'BlogArticleEdit',
|
||||
'BlogArticleDelete',
|
||||
'BlogArticleImport',
|
||||
'BlogArticlePreview',
|
||||
'BlogArticlePreviewButton',
|
||||
'BlogCategory',
|
||||
'BlogTag',
|
||||
'BlogTheme',
|
||||
'BlogThemeSave',
|
||||
'BlogThemeImport',
|
||||
].forEach((name) => {
|
||||
expect(sql).toContain(`'${name}'`);
|
||||
expectAllSqlToContain(`'${name}'`);
|
||||
});
|
||||
});
|
||||
|
||||
it('keeps database menu routes aligned with admin route components', () => {
|
||||
expect(sql).toContain(
|
||||
expectAllSqlToContain(
|
||||
"'Blog', '/blog', NULL, '/blog/article', NULL, 'catalog'",
|
||||
);
|
||||
expectMenuRoute(
|
||||
@ -50,6 +65,14 @@ describe('blog-menu.sql', () => {
|
||||
'/blog/article/list',
|
||||
'Blog:Article:List',
|
||||
);
|
||||
expectAllSqlToContain(
|
||||
"'BlogArticlePreview', '/blog/article/:articleId/preview', '/blog/article/preview/index', NULL, 'Blog:Article:Preview', 'menu'",
|
||||
);
|
||||
expectAllSqlToContain(
|
||||
`'BlogArticlePreviewButton', NULL, NULL, NULL, 'Blog:Article:Preview', 'button'`,
|
||||
);
|
||||
expectAllSqlToContain('"hideInMenu":true');
|
||||
expectAllSqlToContain('"activePath":"/blog/article"');
|
||||
expectMenuRoute(
|
||||
'BlogCategory',
|
||||
'/blog/category',
|
||||
@ -66,9 +89,10 @@ describe('blog-menu.sql', () => {
|
||||
});
|
||||
|
||||
it('grants blog menus to admin roles without deleting existing role menus', () => {
|
||||
expect(sql).toContain('INSERT IGNORE INTO `admin_role_menu`');
|
||||
expect(sql).toContain("menu.`name` LIKE 'Blog%'");
|
||||
expect(sql).toContain("role.`role_code` IN ('super', 'admin')");
|
||||
expect(sql).not.toMatch(/DELETE\s+FROM\s+`admin_role_menu`/i);
|
||||
const blogMenuSql = sqlFiles[0] || '';
|
||||
expect(blogMenuSql).toContain('INSERT IGNORE INTO `admin_role_menu`');
|
||||
expect(blogMenuSql).toContain("menu.`name` LIKE 'Blog%'");
|
||||
expect(blogMenuSql).toContain("role.`role_code` IN ('super', 'admin')");
|
||||
expect(blogMenuSql).not.toMatch(/DELETE\s+FROM\s+`admin_role_menu`/i);
|
||||
});
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user