Add markdown conversion function

This commit is contained in:
Nate Kelley 2025-08-05 12:21:14 -06:00
parent a26242c46d
commit 8e31d18066
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
6 changed files with 114 additions and 26 deletions

View File

@ -2,6 +2,7 @@ import type { User } from '@buster/database';
import { getUserOrganizationId, updateReport } from '@buster/database';
import type { UpdateReportRequest, UpdateReportResponse } from '@buster/server-shared/reports';
import { UpdateReportRequestSchema } from '@buster/server-shared/reports';
import { platejsToMarkdown } from '@buster/server-utils/report';
import { zValidator } from '@hono/zod-validator';
import { Hono } from 'hono';
import { HTTPException } from 'hono/http-exception';
@ -29,7 +30,9 @@ async function updateReportHandler(
throw new HTTPException(403, { message: 'User does not have permission to edit asset' });
}
const { name, content, update_version = false } = request;
const { name, content: contentPlateJs, update_version = false } = request;
const transformedContent = contentPlateJs ? await platejsToMarkdown(contentPlateJs) : undefined;
// Update the report in the database
await updateReport(
@ -37,7 +40,7 @@ async function updateReportHandler(
reportId,
userId: user.id,
name,
content,
content: transformedContent,
},
update_version
);

View File

@ -1,22 +1,27 @@
import {
type MdNodeParser,
convertChildrenDeserialize,
convertNodesSerialize,
parseAttributes,
serializeMd,
} from '@platejs/markdown';
export const calloutSerializer: MdNodeParser<'callout'> = {
serialize: (node, options) => {
// Extract the icon from the node (assuming it's stored as an attribute)
const icon = node.icon || '💡';
const content = convertNodesSerialize(node.children, options);
// Return the markdown representation
if (!options.editor) {
throw new Error('Editor is required');
}
const content = serializeMd(options.editor, {
...options,
value: node.children,
});
return {
attributes: {
icon,
},
children: content,
type: 'html',
value: `<callout icon="${icon}">${content}</callout>`,
};
},
deserialize: (node, deco, options) => {

View File

@ -1,2 +1,2 @@
export { markdownToPlatejs } from './markdown-to-platejs';
export { markdownToPlatejs, platejsToMarkdown } from './platejs-conversions';
export { MarkdownPlugin } from './MarkdownPlugin';

View File

@ -1,5 +1,6 @@
import type { ReportElements } from '@buster/database';
import { describe, expect, it } from 'vitest';
import { markdownToPlatejs } from './markdown-to-platejs';
import { markdownToPlatejs, platejsToMarkdown } from './platejs-conversions';
describe('markdownToPlatejs', () => {
it('should convert elaborate markdown to platejs', async () => {
@ -71,3 +72,76 @@ Here's an unordered list:
expect(platejs).toBeDefined();
});
});
describe('platejsToMarkdown', () => {
it('should convert platejs to markdown', async () => {
const markdown = `This is a simple paragraph.\n`;
const elements: ReportElements = [
{
type: 'p',
children: [
{
text: 'This is a simple paragraph.',
},
],
},
];
const markdown2 = await platejsToMarkdown(elements);
expect(markdown2).toBe(markdown);
});
it('should convert callout platejs element to markdown', async () => {
const elements: ReportElements = [
{
type: 'h1',
children: [
{
text: 'Main Title',
},
],
},
{
type: 'p',
children: [
{
text: 'This paragraph has ',
},
{
text: 'bold text',
bold: true,
},
{
text: ' in it.',
},
],
},
];
const markdown2 = await platejsToMarkdown(elements);
const markdown = `# Main Title\n\nThis paragraph has **bold text** in it.\n`;
expect(markdown2).toBe(markdown);
});
it('should convert callout platejs element to markdown', async () => {
const elements: ReportElements = [
{
type: 'callout',
icon: '⚠️',
variant: 'warning',
children: [
{
type: 'p' as const,
children: [
{
text: 'This is a simple paragraph.',
},
],
},
],
},
];
const markdownFromPlatejs = await platejsToMarkdown(elements);
const expectedMarkdown = `<callout icon="⚠️">This is a simple paragraph.\n</callout>\n`;
expect(markdownFromPlatejs).toBe(expectedMarkdown);
});
});

View File

@ -0,0 +1,20 @@
import { type ReportElements, ReportElementsSchema } from '@buster/database';
import type { Descendant } from 'platejs';
import { SERVER_EDITOR } from './server-editor';
export const markdownToPlatejs = async (markdown: string) => {
const descendants = SERVER_EDITOR.api.markdown.deserialize(markdown);
const safeParsedElements = ReportElementsSchema.safeParse(descendants);
return {
error: safeParsedElements.error,
elements: safeParsedElements.data,
descendants,
markdown,
};
};
export const platejsToMarkdown = async (elements: ReportElements): Promise<string> => {
return SERVER_EDITOR.api.markdown.serialize({ value: elements as Descendant[] });
};

View File

@ -1,4 +1,3 @@
import { type ReportElements, ReportElementsSchema } from '@buster/database';
import { AutoformatPlugin } from '@platejs/autoformat';
import {
BaseBasicBlocksPlugin,
@ -50,19 +49,6 @@ const serverNode = [
MarkdownPlugin,
];
const SERVER_EDITOR = createSlateEditor({
export const SERVER_EDITOR = createSlateEditor({
plugins: serverNode,
});
export const markdownToPlatejs = async (markdown: string) => {
const descendants = SERVER_EDITOR.api.markdown.deserialize(markdown);
const safeParsedElements = ReportElementsSchema.safeParse(descendants);
return {
error: safeParsedElements.error,
elements: safeParsedElements.data,
descendants,
markdown,
};
};