mirror of https://github.com/buster-so/buster.git
preprocess markdown for mdx
This commit is contained in:
parent
b7ff601f89
commit
8b4ad281c1
|
@ -18,3 +18,16 @@ export const unescapeHtmlAttribute = (str: string): string => {
|
|||
.replace(/>/g, '>')
|
||||
.replace(/&/g, '&'); // Must be last to avoid double-decoding
|
||||
};
|
||||
|
||||
// Pre-process markdown to escape < symbols that are not part of HTML tags or components
|
||||
// This prevents them from being interpreted as unclosed tags by the MDX parser
|
||||
export const preprocessMarkdownForMdx = (markdown: string): string => {
|
||||
// Pattern explanation:
|
||||
// - Matches < that is NOT followed by:
|
||||
// - A letter (start of HTML tag like <div)
|
||||
// - A slash (closing tag like </div)
|
||||
// - An uppercase letter (React component like <Metric)
|
||||
// - metric (our custom metric tags)
|
||||
// - But IS part of text content (has word boundary or number after it)
|
||||
return markdown.replace(/<(?![a-zA-Z/]|metric\s)/g, '\\<');
|
||||
};
|
||||
|
|
|
@ -1849,4 +1849,18 @@ More streamed content.`;
|
|||
expect(currentContent).not.toContain('\\<metric');
|
||||
expect(currentContent).toContain('<metric metricId="streamed-metric"');
|
||||
});
|
||||
|
||||
it('should handle problematic content', async () => {
|
||||
const markdown =
|
||||
'This analysis reveals two dramatically different customer universes within our business. Of our 19,119 total customers, **31 elite customers (0.16%) generate >500k CLV** while **19,088 customers (99.84%) have <500k CLV**. The >500k CLV segment represents serious cycling enthusiasts who make large in-store purchases averaging $66,232 per order, while the <500k CLV segment consists of casual recreational cyclists making smaller online purchases averaging $2,863 per order. These segments exhibit completely different behavioral profiles, geographic concentrations, and product preferences, suggesting they require entirely different marketing and service strategies.\n\n## Customer Segment Overview\n\n<metric metricId="be286e99-77f9-4b6e-959c-c2691d2d549e"/>\n\nThe data reveals a stark divide in our customer base. The **>500k CLV segment averages $666,590 per customer** compared to just **$4,672 for the <500k CLV segment** - a 143x difference. Despite representing only 0.16% of customers, the elite >500k CLV segment contributes **$20.7 million in total lifetime value**.\n\n## Customer Behavior Profiles Show Completely Different Cycling Enthusiasts\n\n<metric metricId="ed9c70f3-619b-40ee-a99d-c6dfa2945bb8"/>\n\nThe behavioral analysis reveals two entirely different customer types. **97% of >500k CLV customers are daily cyclists with advanced technical knowledge**, representing serious cycling enthusiasts. In stark contrast, the <500k CLV segment is dominated by **occasional cyclists with basic technical knowledge** (6,697 customers) and **monthly cyclists with intermediate knowledge** (5,493 customers). This suggests the high-value customers are passionate cyclists who view cycling as a serious pursuit, while the majority are casual recreational users.\n\n## Geographic Concentration Reveals Strategic Opportunities\n\n<metric metricId="0219343f-95c6-4a04-bbd9-7e052867abd9"/>\n\nThe >500k CLV customers show significant geographic concentration, with **Southwest (9 customers) and Northwest (7 customers) territories accounting for 52% of elite customers**. This contrasts sharply with the <500k CLV segment\'s broader global distribution, including strong presence in Australia (3,625 customers). The concentration of high-value customers in specific US regions suggests targeted relationship management and premium service opportunities in these key markets.\n\n## Order Behavior Reveals Dramatically Different Purchase Patterns\n\n<metric metricId="4e276567-45ae-4618-9176-9173c9535464"/>\n\nThe purchase behavior differences are striking. **>500k CLV customers average $66,232 per order and place 10.1 orders per customer**, indicating they make substantial, repeat purchases. Meanwhile, **<500k CLV customers average just $2,863 per order with only 1.6 orders per customer**, suggesting primarily one-time or infrequent purchases. This 23x difference in order value demonstrates that elite customers are making major cycling investments rather than casual purchases.\n\n## Product Category Preferences Show Elite Focus on Premium Equipment\n\n<metric metricId="83756125-45fe-4a38-a4c0-9badc0abc458"/>\n\nBoth segments prioritize bikes, but with different spending patterns. **>500k CLV customers spend 83% of their budget on bikes ($17.1M) and 15% on components ($3.1M)**, indicating serious cyclists investing in high-end equipment and performance upgrades. The <500k CLV segment also focuses on bikes ($77.6M) but with much lower per-customer spending. The elite segment\'s heavy component spending suggests they\'re upgrading and customizing their bikes extensively.\n\n## Sales Channel Preferences Highlight Service Expectations\n\n<metric metricId="654aa8cb-d2e0-4b1d-812a-1ab94182c656"/>\n\n**100% of >500k CLV customers purchase exclusively in-store**, demonstrating their preference for personal service, expert consultation, and hands-on product evaluation. This contrasts dramatically with <500k CLV customers, who make **89% of their purchases online** (27,659 orders) for convenience and price comparison. The elite segment\'s in-store preference suggests they value relationship-based selling and technical expertise, making them ideal candidates for premium service programs and dedicated account management.\n\n## Product Preferences Reveal Different Quality Tiers\n\n<metric metricId="dd0e7ca4-e3f7-4842-8459-7ad588e9b81a"/>\n\nThe product preferences show interesting patterns. **>500k CLV customers favor Road-250 models** (particularly Road-250 Black, 44 with $780K revenue), indicating preference for road cycling and premium models. Meanwhile, **<500k CLV customers predominantly choose Mountain-200 series bikes**, with Mountain-200 Black, 38 generating $3.7M in total revenue across 1,162 orders. The elite segment\'s focus on Road-250 models suggests they\'re serious road cyclists, while the broader market prefers versatile mountain bikes.';
|
||||
|
||||
const platejs = await markdownToPlatejs(editor, markdown);
|
||||
// Test passes if no error is thrown - the markdown should be fully parsed
|
||||
expect(platejs).toBeDefined();
|
||||
expect(platejs.length).toBeGreaterThan(10); // Should have many elements, not just the first paragraph
|
||||
expect(platejs[1].type).toBe('h2');
|
||||
expect(platejs[2].type).toBe('metric');
|
||||
expect(platejs[2].metricId).toBe('be286e99-77f9-4b6e-959c-c2691d2d549e');
|
||||
expect(platejs[3].type).toBe('p');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import type { Descendant, TElement, Value } from 'platejs';
|
||||
import type { IReportEditor } from '../../ReportEditor';
|
||||
import { preprocessMarkdownForMdx } from './escape-handlers';
|
||||
import { postProcessToggleDeserialization, postProcessToggleMarkdown } from './toggle-serializer';
|
||||
|
||||
export const markdownToPlatejs = async (
|
||||
|
@ -7,16 +8,14 @@ export const markdownToPlatejs = async (
|
|||
markdown: string
|
||||
): Promise<Value> => {
|
||||
try {
|
||||
const descendants: Value = editor.api.markdown.deserialize(markdown);
|
||||
// Pre-process markdown to escape < symbols that aren't part of HTML tags
|
||||
const processedMarkdown = preprocessMarkdownForMdx(markdown);
|
||||
const descendants: Value = editor.api.markdown.deserialize(processedMarkdown);
|
||||
const descendantsWithIds: Value = descendants.map((element, index) => ({
|
||||
...element,
|
||||
id: `id-${index}`,
|
||||
}));
|
||||
|
||||
// Apply post-processing to handle details elements
|
||||
const processedElements = postProcessToggleDeserialization(descendantsWithIds as TElement[]);
|
||||
|
||||
return processedElements as Value;
|
||||
return postProcessToggleDeserialization(descendantsWithIds);
|
||||
} catch (error) {
|
||||
console.error('Error converting markdown to PlateJS:', error);
|
||||
return [];
|
||||
|
|
Loading…
Reference in New Issue