super nested list items

This commit is contained in:
Nate Kelley 2025-08-07 17:01:45 -06:00
parent 69eb14c836
commit 75950ae108
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
6 changed files with 465 additions and 25 deletions

View File

@ -9,15 +9,12 @@ export async function getReportHandler(
reportId: string,
user: { id: string }
): Promise<GetReportIndividualResponse> {
try {
const report = await getReport({ reportId, userId: user.id });
const platejsResult = await markdownToPlatejs(report.content);
if (platejsResult.error) {
throw new HTTPException(500, {
message: `Error converting markdown to PlateJS: ${platejsResult.error.message}`,
});
throw platejsResult.error;
}
const content = platejsResult.elements ?? [];
@ -28,12 +25,6 @@ export async function getReportHandler(
};
return response;
} catch (error) {
console.error('Error getting report:', error);
throw new HTTPException(500, {
message: `Error getting report: ${error instanceof Error ? error.message : 'Unknown error'}`,
});
}
}
const app = new Hono()

View File

@ -17,7 +17,8 @@
"@buster/data-source#dev",
"@buster-app/trigger#dev",
"@buster-app/electric-server#dev",
"@buster-app/api-legacy#dev"
"@buster-app/api-legacy#dev",
"@buster/server-utils#dev"
]
}
}

View File

@ -322,8 +322,8 @@ const ListTypeEnum = z.enum(['ul', 'ol']);
// Nested list item for complex lists
const NestedListElementSchema = z.object({
type: z.enum(['li', 'lic', 'lii']),
children: z.array(z.union([TextSchema, ParagraphElementSchema])).default([]),
type: z.enum(['li', 'lic', 'lii', 'ul', 'ol']),
children: z.array(z.any()).default([]),
});
// List container (unordered or ordered)

View File

@ -71,6 +71,37 @@ Here's an unordered list:
const platejs = await markdownToPlatejs(markdown);
expect(platejs).toBeDefined();
});
it('real world markdown', async () => {
const markdown = `Our most popular mountain bike over the last 12 months is Mountain-200 Black, 38 with 825 units sold.
## Key Findings
- The top-selling mountain bike model is **Mountain-200 Black, 38**.
- It sold **825 units** in the last 12 months.
## Metric
<metric metricId="d3bf75d2-28f7-408e-93ec-ae06b509ad27" />
## Context
- I focused specifically on complete bicycle products in the **Mountain Bikes** subcategory within the broader **Bikes** category to avoid counting components or frames.
- I measured popularity by **units sold**, which reflects the number of bikes customers purchased.
- Timeframe defaults to the **last 12 months** to show a current view.
## Methodology
- Data sources: Sales order lines and headers, and product catalog tables in the operational analytics database.
- Filters:
- Product Category = "Bikes"
- Product Subcategory = "Mountain Bikes"
- Order Date between CURRENT_DATE - 12 months and CURRENT_DATE
- Calculation:
- For each mountain bike product, sum of sales order quantities from sales order details.
- Select the product with the highest total units sold.
- Notes on definitions:
- "Most popular" defined as highest **units sold**; alternative definitions could use revenue or number of distinct orders, but units sold most directly represents product popularity by volume.
- Product names are used as the display label to identify the specific model.
- Alternatives considered:
- Using revenue-based popularity could favor higher-priced bikes; I chose units to avoid price bias.
- Using the riding discipline filter (e.g., Mountain) was considered, but I used the explicit Mountain Bikes subcategory to exclude components.
`;
const platejs = await markdownToPlatejs(markdown);
expect(platejs).toBeDefined();
});
});
describe('platejsToMarkdown', () => {
@ -144,4 +175,394 @@ describe('platejsToMarkdown', () => {
const expectedMarkdown = `<callout icon="⚠️">This is a simple paragraph.\n</callout>\n`;
expect(markdownFromPlatejs).toBe(expectedMarkdown);
});
it('should convert callout platejs element to markdown', async () => {
const elements: ReportElements = [
{
children: [
{
text: 'Our most popular mountain bike over the last 12 months is Mountain-200 Black, 38 with 825 units sold.',
},
],
type: 'p',
},
{
children: [
{
text: 'Key Findings',
},
],
type: 'h2',
},
{
children: [
{
children: [
{
children: [
{
text: 'The top-selling mountain bike model is ',
},
{
bold: true,
text: 'Mountain-200 Black, 38',
},
{
text: '.',
},
],
type: 'lic',
},
],
type: 'li',
},
{
children: [
{
children: [
{
text: 'It sold ',
},
{
bold: true,
text: '825 units',
},
{
text: ' in the last 12 months.',
},
],
type: 'lic',
},
],
type: 'li',
},
],
type: 'ul',
},
{
children: [
{
text: 'Metric',
},
],
type: 'h2',
},
{
children: [
{
text: '<metric>\n',
},
{
text: '',
},
{
text: '\n</metric>',
},
],
type: 'p',
},
{
children: [
{
text: 'Context',
},
],
type: 'h2',
},
{
children: [
{
children: [
{
children: [
{
text: 'I focused specifically on complete bicycle products in the ',
},
{
bold: true,
text: 'Mountain Bikes',
},
{
text: ' subcategory within the broader ',
},
{
bold: true,
text: 'Bikes',
},
{
text: ' category to avoid counting components or frames.',
},
],
type: 'lic',
},
],
type: 'li',
},
{
children: [
{
children: [
{
text: 'I measured popularity by ',
},
{
bold: true,
text: 'units sold',
},
{
text: ', which reflects the number of bikes customers purchased.',
},
],
type: 'lic',
},
],
type: 'li',
},
{
children: [
{
children: [
{
text: 'Timeframe defaults to the ',
},
{
bold: true,
text: 'last 12 months',
},
{
text: ' to show a current view.',
},
],
type: 'lic',
},
],
type: 'li',
},
],
type: 'ul',
},
{
children: [
{
text: 'Methodology',
},
],
type: 'h2',
},
{
children: [
{
children: [
{
children: [
{
text: 'Data sources: Sales order lines and headers, and product catalog tables in the operational analytics database.',
},
],
type: 'lic',
},
],
type: 'li',
},
{
children: [
{
children: [
{
text: 'Filters:',
},
],
type: 'lic',
},
{
children: [
{
children: [
{
children: [
{
text: 'Product Category = "Bikes"',
},
],
type: 'lic',
},
],
type: 'li',
},
{
children: [
{
children: [
{
text: 'Product Subcategory = "Mountain Bikes"',
},
],
type: 'lic',
},
],
type: 'li',
},
{
children: [
{
children: [
{
text: 'Order Date between CURRENT_DATE - 12 months and CURRENT_DATE',
},
],
type: 'lic',
},
],
type: 'li',
},
],
type: 'ul',
},
],
type: 'li',
},
{
children: [
{
children: [
{
text: 'Calculation:',
},
],
type: 'lic',
},
{
children: [
{
children: [
{
children: [
{
text: 'For each mountain bike product, sum of sales order quantities from sales order details.',
},
],
type: 'lic',
},
],
type: 'li',
},
{
children: [
{
children: [
{
text: 'Select the product with the highest total units sold.',
},
],
type: 'lic',
},
],
type: 'li',
},
],
type: 'ul',
},
],
type: 'li',
},
{
children: [
{
children: [
{
text: 'Notes on definitions:',
},
],
type: 'lic',
},
{
children: [
{
children: [
{
children: [
{
text: '"Most popular" defined as highest ',
},
{
bold: true,
text: 'units sold',
},
{
text: '; alternative definitions could use revenue or number of distinct orders, but units sold most directly represents product popularity by volume.',
},
],
type: 'lic',
},
],
type: 'li',
},
{
children: [
{
children: [
{
text: 'Product names are used as the display label to identify the specific model.',
},
],
type: 'lic',
},
],
type: 'li',
},
],
type: 'ul',
},
],
type: 'li',
},
{
children: [
{
children: [
{
text: 'Alternatives considered:',
},
],
type: 'lic',
},
{
children: [
{
children: [
{
children: [
{
text: 'Using revenue-based popularity could favor higher-priced bikes; I chose units to avoid price bias.',
},
],
type: 'lic',
},
],
type: 'li',
},
{
children: [
{
children: [
{
text: 'Using the riding discipline filter (e.g., Mountain) was considered, but I used the explicit Mountain Bikes subcategory to exclude components.',
},
],
type: 'lic',
},
],
type: 'li',
},
],
type: 'ul',
},
],
type: 'li',
},
],
type: 'ul',
},
];
});
});

View File

@ -5,9 +5,14 @@ import { SERVER_EDITOR } from './server-editor';
export const markdownToPlatejs = async (markdown: string) => {
try {
const descendants = SERVER_EDITOR.api.markdown.deserialize(markdown);
console.log('descendants', descendants);
console.log('descendants.json', JSON.stringify(descendants, null, 2));
const safeParsedElements = ReportElementsSchema.safeParse(descendants);
console.log('safeParsedElements', safeParsedElements);
return {
error: safeParsedElements.error,
elements: safeParsedElements.data,

View File

@ -0,0 +1,22 @@
{
"$schema": "https://turbo.build/schema.json",
"extends": ["//"],
"tasks": {
"build": {
"inputs": [
"src/**/*",
"!src/**/*.test.{ts,tsx,js,jsx}",
"!src/**/*.spec.{ts,tsx,js,jsx}",
"package.json",
"tsconfig.json"
],
"outputs": ["dist/**/*"]
},
"dev": {
"cache": false,
"persistent": true,
"dependsOn": ["^build"],
"with": []
}
}
}