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),
|
(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),
|
(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),
|
(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),
|
(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),
|
(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),
|
(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),
|
(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),
|
(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),
|
(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),
|
(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),
|
(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),
|
(2041700000000120312, 2041700000000100302, 'BlogCategoryEdit', NULL, NULL, NULL, 'Blog:Category:Edit', 'button', '{"title":"common.edit"}', 1, 0),
|
||||||
|
|||||||
@ -362,7 +362,7 @@ export class BlogArticleService {
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
nextArticle.contentMarkdown = current?.contentMarkdown || '';
|
nextArticle.contentMarkdown = current?.contentMarkdown || '';
|
||||||
nextArticle.contentHtml = await this.markdownService.renderToHtml(
|
nextArticle.contentHtml = await this.markdownService.sanitizeHtml(
|
||||||
body.content,
|
body.content,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -643,12 +643,16 @@ const routeTestCases: Record<string, RouteTestCase> = {
|
|||||||
const response = await request(server)
|
const response = await request(server)
|
||||||
.post('/blog/article/update')
|
.post('/blog/article/update')
|
||||||
.send({
|
.send({
|
||||||
|
content: '<pre class="wp-block-code hljs-codeblock"></pre>',
|
||||||
|
contentFormat: 'html',
|
||||||
id: blogArticle.id,
|
id: blogArticle.id,
|
||||||
title: '本地文章',
|
title: '本地文章',
|
||||||
})
|
})
|
||||||
.expect(200);
|
.expect(200);
|
||||||
|
|
||||||
expect(blogArticleServiceMock.update).toHaveBeenCalledWith({
|
expect(blogArticleServiceMock.update).toHaveBeenCalledWith({
|
||||||
|
content: '<pre class="wp-block-code hljs-codeblock"></pre>',
|
||||||
|
contentFormat: 'html',
|
||||||
id: blogArticle.id,
|
id: blogArticle.id,
|
||||||
title: '本地文章',
|
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 () => {
|
it('queries public list with publish status and maps response fields', async () => {
|
||||||
const builder = createQueryBuilderMock([
|
const builder = createQueryBuilderMock([
|
||||||
{
|
{
|
||||||
|
|||||||
@ -2,7 +2,20 @@ import { readFileSync } from 'node:fs';
|
|||||||
import { join } from 'node:path';
|
import { join } from 'node:path';
|
||||||
|
|
||||||
describe('blog-menu.sql', () => {
|
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,
|
component: string,
|
||||||
authCode: string,
|
authCode: string,
|
||||||
) {
|
) {
|
||||||
expect(sql).toContain(
|
expectAllSqlToContain(
|
||||||
`'${name}', '${path}', '${component}', NULL, '${authCode}', 'menu'`,
|
`'${name}', '${path}', '${component}', NULL, '${authCode}', 'menu'`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -30,18 +43,20 @@ describe('blog-menu.sql', () => {
|
|||||||
'BlogArticleEdit',
|
'BlogArticleEdit',
|
||||||
'BlogArticleDelete',
|
'BlogArticleDelete',
|
||||||
'BlogArticleImport',
|
'BlogArticleImport',
|
||||||
|
'BlogArticlePreview',
|
||||||
|
'BlogArticlePreviewButton',
|
||||||
'BlogCategory',
|
'BlogCategory',
|
||||||
'BlogTag',
|
'BlogTag',
|
||||||
'BlogTheme',
|
'BlogTheme',
|
||||||
'BlogThemeSave',
|
'BlogThemeSave',
|
||||||
'BlogThemeImport',
|
'BlogThemeImport',
|
||||||
].forEach((name) => {
|
].forEach((name) => {
|
||||||
expect(sql).toContain(`'${name}'`);
|
expectAllSqlToContain(`'${name}'`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('keeps database menu routes aligned with admin route components', () => {
|
it('keeps database menu routes aligned with admin route components', () => {
|
||||||
expect(sql).toContain(
|
expectAllSqlToContain(
|
||||||
"'Blog', '/blog', NULL, '/blog/article', NULL, 'catalog'",
|
"'Blog', '/blog', NULL, '/blog/article', NULL, 'catalog'",
|
||||||
);
|
);
|
||||||
expectMenuRoute(
|
expectMenuRoute(
|
||||||
@ -50,6 +65,14 @@ describe('blog-menu.sql', () => {
|
|||||||
'/blog/article/list',
|
'/blog/article/list',
|
||||||
'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(
|
expectMenuRoute(
|
||||||
'BlogCategory',
|
'BlogCategory',
|
||||||
'/blog/category',
|
'/blog/category',
|
||||||
@ -66,9 +89,10 @@ describe('blog-menu.sql', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('grants blog menus to admin roles without deleting existing role menus', () => {
|
it('grants blog menus to admin roles without deleting existing role menus', () => {
|
||||||
expect(sql).toContain('INSERT IGNORE INTO `admin_role_menu`');
|
const blogMenuSql = sqlFiles[0] || '';
|
||||||
expect(sql).toContain("menu.`name` LIKE 'Blog%'");
|
expect(blogMenuSql).toContain('INSERT IGNORE INTO `admin_role_menu`');
|
||||||
expect(sql).toContain("role.`role_code` IN ('super', 'admin')");
|
expect(blogMenuSql).toContain("menu.`name` LIKE 'Blog%'");
|
||||||
expect(sql).not.toMatch(/DELETE\s+FROM\s+`admin_role_menu`/i);
|
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