Skip to content
Merged

Dev #13

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,7 @@ data URLs into an atomic `🖼 image` chip — display-only; the underlying text
| A **use-case page** | Add a `UseCaseContent` file under `marketing/content/use-cases/` and register it in that folder's `index.ts` (Arabic variant → `USE_CASES_AR`). |
| A **config option** | Add to `PdfConfig` (types) + `DEFAULT_CONFIG` + a control in `ConfigPanel` + consume in `documentStyle.ts`/`document.css`/`pageStyles.ts`. |
| A **Markdown feature** | Add a remark/rehype plugin (or a custom one under `markdown/plugins/`) in `MarkdownRenderer.tsx`. |
| A **diagram flavor** | Branch on the fence language in `MarkdownRenderer.tsx`'s `pre` override (see the `mermaid` / `ascii` branches) → render a dedicated component under `markdown/components/`; styles in `document.css`; detection/measurement helpers live in `markdown/asciiDiagram.ts`; mirror the branch in `marketing/pages/TemplatePage.tsx` so template pages match. |
| An **export format** | Add to `io/exporters.ts` + a `MenuItem`/command. |
| A **command / dialog** | See the wiring recipe in [docs/P1_FEATURES.md](docs/P1_FEATURES.md) §0. |

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ Most "Markdown to PDF" tools render your document twice — once for the screen,
- Callouts / admonitions (`:::tip … :::`)
- Syntax-highlighted code (Prism, 4 themes, copy button)
- Math (KaTeX), Mermaid diagrams, HTML passthrough
- ASCII / box-drawing diagrams (` ```ascii `, auto-detected in plain fences) — crisp, auto-fitted figures with optional `title="…"` captions

#### 📄 PDF export (Paged.js)

Expand Down
273 changes: 273 additions & 0 deletions docs/ASCII_DIAGRAMS_PROMPT.md

Large diffs are not rendered by default.

24 changes: 23 additions & 1 deletion src/components/editor/slashCommands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ const S_AI: SlashSection = { titleKey: 'slash.section.ai', rank: 0 }
const S_TEXT: SlashSection = { titleKey: 'slash.section.text', rank: 1 }
const S_LISTS: SlashSection = { titleKey: 'slash.section.lists', rank: 2 }
const S_INSERT: SlashSection = { titleKey: 'slash.section.insert', rank: 3 }
const S_DIAGRAM: SlashSection = { titleKey: 'slash.section.diagram', title: 'Diagrams (Mermaid)', rank: 3.5 }
const S_DIAGRAM: SlashSection = { titleKey: 'slash.section.diagram', title: 'Diagrams', rank: 3.5 }
const S_CALLOUT: SlashSection = { titleKey: 'slash.section.callout', rank: 4 }
const S_TEMPLATE: SlashSection = { titleKey: 'slash.section.template', rank: 5 }
const S_HELP: SlashSection = { titleKey: 'slash.section.help', rank: 6 }
Expand Down Expand Up @@ -391,6 +391,28 @@ const ITEMS: SlashItem[] = [
insert:
'```mermaid\ntimeline\n title Product history\n 2021 : Founded\n 2023 : Public launch\n 2026 : 1M users\n```\n',
},
{
label: 'Diagram (ASCII)',
detail: 'Diagram',
info: 'A text-drawn box diagram, rendered as a crisp auto-fitted figure. Pasted ASCII art is detected automatically.',
labelKey: 'slash.asciiDiagram.label',
detailKey: 'slash.detail.diagram',
descKey: 'slash.asciiDiagram.desc',
section: S_DIAGRAM,
insert:
'```ascii title="Architecture"\n┌──────────┐ ┌──────────┐\n│ Client │ ───► │ Server │\n└──────────┘ └──────────┘\n```\n',
},
{
label: 'Folder tree (ASCII)',
detail: 'Diagram',
info: 'A project or folder layout drawn with tree characters.',
labelKey: 'slash.asciiTree.label',
detailKey: 'slash.detail.diagram',
descKey: 'slash.asciiTree.desc',
section: S_DIAGRAM,
insert:
'```ascii title="Project layout"\nsrc/\n├── components/\n│ ├── App.tsx\n│ └── Button.tsx\n└── index.ts\n```\n',
},

// ---- Highlight boxes (callouts) ----
{
Expand Down
12 changes: 12 additions & 0 deletions src/components/layout/FormattingHelpDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,18 @@ const ROWS: FormatRow[] = [
</span>
),
},
{
labelKey: 'formatHelp.row.asciiDiagram',
syntax: '```ascii\n┌───┐ ┌───┐\n│ A │──►│ B │\n└───┘ └───┘\n```',
sample: (
<pre
dir="ltr"
className="rounded-md bg-muted px-2 py-1 font-mono text-[10px] leading-[1.2] [font-variant-ligatures:none]"
>
{'┌───┐ ┌───┐\n│ A │──►│ B │\n└───┘ └───┘'}
</pre>
),
},
]

/** Beginner cheat-sheet showing "you type" (Markdown) → "you get" (a live preview). */
Expand Down
69 changes: 69 additions & 0 deletions src/data/templates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1583,6 +1583,75 @@ timeline
2023 : Public launch
2026 : 1M users
\`\`\`
`,
},
{
id: 'diagram-ascii-architecture',
name: 'ASCII Architecture',
description: 'A text-drawn infrastructure diagram that exports crisp and auto-fitted.',
nameKey: 'template.diagram-ascii-architecture.name',
descKey: 'template.diagram-ascii-architecture.desc',
category: 'diagram',
emoji: '🖥️',
content: `# Server Architecture

Everything below runs on a single server behind Cloudflare.

\`\`\`ascii title="Production topology"
Internet (your users & clients)
│
▼
┌──────────────────┐
│ Cloudflare │ DNS + proxy/CDN in front of the domain
└────────┬─────────┘
│
══════════════════▼══════════════════════════════════
ONE ECS SERVER (ARM64) — everything below is this box
══════════════════════════════════════════════════════
┌──────────────────┐
│ Nginx │ Terminates HTTPS, forwards to:
│ (ports 80/443) │
└────────┬─────────┘
│
┌───────────────┼───────────────────────┐
▼ ▼ ▼
:3000 sqcm-prod :3003 marketing :3002 hr-backend
│
┌────────┴─────────────────────────────┐
│ PM2 (process manager) │
│ ├─ marketing (Next.js web) │
│ └─ marketing-worker (BullMQ jobs) │
└────────┬──────────────┬──────────────┘
▼ ▼
MongoDB (Docker) Redis (queues)
\`\`\`

Untagged ASCII art in a plain fence is detected automatically; tag a fence
as \`text\` to force plain rendering instead.
`,
},
{
id: 'diagram-ascii-tree',
name: 'ASCII Folder Tree',
description: 'A project layout drawn with tree characters, with a caption.',
nameKey: 'template.diagram-ascii-tree.name',
descKey: 'template.diagram-ascii-tree.desc',
category: 'diagram',
emoji: '🌳',
content: `# Project Layout

The folders that matter, at a glance.

\`\`\`ascii title="Source layout"
src/
├── markdown/
│ ├── MarkdownRenderer.tsx
│ └── components/
│ ├── Mermaid.tsx
│ └── AsciiDiagram.tsx
└── styles/
└── document.css
\`\`\`
`,
},
{
Expand Down
28 changes: 26 additions & 2 deletions src/lib/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,11 @@ export const EN_STRINGS = {
'template.diagram-mindmap.desc': 'A branching map of ideas around a theme.',
'template.diagram-timeline.name': 'Timeline — Milestones',
'template.diagram-timeline.desc': 'A chronological list of milestones.',
'template.diagram-ascii-architecture.name': 'ASCII Architecture',
'template.diagram-ascii-architecture.desc':
'A text-drawn infrastructure diagram that exports crisp and auto-fitted.',
'template.diagram-ascii-tree.name': 'ASCII Folder Tree',
'template.diagram-ascii-tree.desc': 'A project layout drawn with tree characters, with a caption.',
'slash.section.ai': 'AI help',
'slash.section.text': 'Text',
'slash.section.lists': 'Lists',
Expand Down Expand Up @@ -542,7 +547,7 @@ export const EN_STRINGS = {
'slash.stepGuide.desc': 'A numbered how-to structure with a title.',
'slash.help.label': 'Formatting help',
'slash.help.desc': 'Open the cheatsheet of formatting shortcuts and tips.',
'slash.section.diagram': 'Diagrams (Mermaid)',
'slash.section.diagram': 'Diagrams',
'slash.detail.diagram': 'Diagram',
'slash.flowchart.label': 'Flowchart',
'slash.flowchart.desc': 'A process or decision flow with boxes and arrows (Mermaid).',
Expand All @@ -566,6 +571,11 @@ export const EN_STRINGS = {
'slash.journey.desc': 'An experience map scoring each step of a journey (Mermaid).',
'slash.timeline.label': 'Timeline',
'slash.timeline.desc': 'A chronological sequence of events (Mermaid).',
'slash.asciiDiagram.label': 'Diagram (ASCII)',
'slash.asciiDiagram.desc':
'A text-drawn box diagram, rendered as a crisp auto-fitted figure. Pasted ASCII art is detected automatically.',
'slash.asciiTree.label': 'Folder tree (ASCII)',
'slash.asciiTree.desc': 'A project or folder layout drawn with tree characters.',
'editor.tooltip.undo': 'Undo',
'editor.tooltip.redo': 'Redo',
'editor.tooltip.heading1': 'Heading 1',
Expand Down Expand Up @@ -716,6 +726,7 @@ export const EN_STRINGS = {
'print.close': 'Close preview',
'print.tryAgain': 'Try again',
'markdown.diagramError': 'Diagram error:',
'markdown.asciiDiagram': 'ASCII diagram',
'error.boundaryTitle': 'Something went wrong while rendering.',
'error.tryAgain': 'Try again',
'template.pitch-one-pager.name': 'Startup One-Pager',
Expand Down Expand Up @@ -800,6 +811,7 @@ export const EN_STRINGS = {
'formatHelp.row.table': 'Table',
'formatHelp.row.callout': 'Callout',
'formatHelp.row.math': 'Math',
'formatHelp.row.asciiDiagram': 'ASCII diagram',

// Preview cover/Contents toggle
'preview.frontMatter.toggle': 'Show cover & contents',
Expand Down Expand Up @@ -1266,6 +1278,11 @@ export const STRINGS: Record<UiLanguage, Dictionary> = {
'template.diagram-mindmap.desc': 'خريطة متفرعة للأفكار حول موضوع.',
'template.diagram-timeline.name': 'الخط الزمني — المعالم',
'template.diagram-timeline.desc': 'قائمة زمنية بالمعالم.',
'template.diagram-ascii-architecture.name': 'بنية ASCII',
'template.diagram-ascii-architecture.desc':
'مخطط بنية تحتية مرسوم نصيًا، يُصدَّر واضحًا ومضبوط المقاس تلقائيًا.',
'template.diagram-ascii-tree.name': 'شجرة مجلدات ASCII',
'template.diagram-ascii-tree.desc': 'هيكل مشروع مرسوم بمحارف الشجرة، مع تسمية توضيحية.',
'slash.section.ai': 'مساعدة الذكاء الاصطناعي',
'slash.section.text': 'نص',
'slash.section.lists': 'قوائم',
Expand Down Expand Up @@ -1333,7 +1350,7 @@ export const STRINGS: Record<UiLanguage, Dictionary> = {
'slash.stepGuide.desc': 'هيكل إرشادي مرقَّم مع عنوان.',
'slash.help.label': 'مساعدة التنسيق',
'slash.help.desc': 'افتح ورقة مرجعية باختصارات التنسيق ونصائحه.',
'slash.section.diagram': 'المخططات (Mermaid)',
'slash.section.diagram': 'المخططات',
'slash.detail.diagram': 'مخطط',
'slash.flowchart.label': 'مخطط انسيابي',
'slash.flowchart.desc': 'تدفق عملية أو قرار بمربعات وأسهم (Mermaid).',
Expand All @@ -1357,6 +1374,11 @@ export const STRINGS: Record<UiLanguage, Dictionary> = {
'slash.journey.desc': 'خريطة تجربة تُقيّم كل خطوة في الرحلة (Mermaid).',
'slash.timeline.label': 'الخط الزمني',
'slash.timeline.desc': 'تسلسل زمني للأحداث (Mermaid).',
'slash.asciiDiagram.label': 'مخطط (ASCII)',
'slash.asciiDiagram.desc':
'مخطط صناديق مرسوم نصيًا، يُعرض كشكل واضح مضبوط المقاس تلقائيًا. يُكتشف فن ASCII الملصوق تلقائيًا.',
'slash.asciiTree.label': 'شجرة مجلدات (ASCII)',
'slash.asciiTree.desc': 'هيكل مشروع أو مجلدات مرسوم بمحارف الشجرة.',
'editor.tooltip.undo': 'تراجع',
'editor.tooltip.redo': 'إعادة',
'editor.tooltip.heading1': 'عنوان 1',
Expand Down Expand Up @@ -1507,6 +1529,7 @@ export const STRINGS: Record<UiLanguage, Dictionary> = {
'print.close': 'إغلاق المعاينة',
'print.tryAgain': 'حاول مجددًا',
'markdown.diagramError': 'خطأ في المخطّط:',
'markdown.asciiDiagram': 'مخطط ASCII',
'error.boundaryTitle': 'حدث خطأ أثناء العرض.',
'error.tryAgain': 'حاول مجددًا',
'template.pitch-one-pager.name': 'صفحة تعريفية للشركة الناشئة',
Expand Down Expand Up @@ -1591,6 +1614,7 @@ export const STRINGS: Record<UiLanguage, Dictionary> = {
'formatHelp.row.table': 'جدول',
'formatHelp.row.callout': 'صندوق تنبيه',
'formatHelp.row.math': 'معادلة',
'formatHelp.row.asciiDiagram': 'مخطط ASCII',

// Preview cover/Contents toggle
'preview.frontMatter.toggle': 'إظهار الغلاف والمحتويات',
Expand Down
17 changes: 16 additions & 1 deletion src/markdown/MarkdownRenderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,13 @@ import rehypeKatex from 'rehype-katex'
import rehypeSlug from 'rehype-slug'
import rehypePrismPlus from 'rehype-prism-plus'
import { rehypeSourceLine } from './plugins/rehypeSourceLine'
import { rehypeFenceMeta } from './plugins/rehypeFenceMeta'
import { remarkCallouts } from './plugins/remarkCallouts'
import { remarkMarks } from './plugins/remarkMarks'
import { CodeBlock } from './components/CodeBlock'
import { Mermaid } from './components/Mermaid'
import { AsciiDiagram } from './components/AsciiDiagram'
import { ASCII_DIAGRAM_LANGUAGES, isAsciiDiagram, parseFenceTitle } from './asciiDiagram'
import type { ResolvedTheme } from '@/types'

interface MarkdownRendererProps {
Expand All @@ -26,7 +29,7 @@ interface HastNode {
type: string
tagName?: string
value?: string
properties?: { className?: string[] | string }
properties?: { className?: string[] | string; dataMeta?: unknown }
children?: HastNode[]
}

Expand Down Expand Up @@ -69,6 +72,7 @@ const rehypePlugins = [
// First, while mdast positions are intact, so scroll-sync anchors survive
// rehype-raw's reserialization of raw HTML.
rehypeSourceLine,
rehypeFenceMeta,
rehypeRaw,
rehypeKatex,
rehypeSlug,
Expand All @@ -92,6 +96,17 @@ function MarkdownRendererImpl({ content, resolvedTheme }: MarkdownRendererProps)
if (language === 'mermaid') {
return <Mermaid code={raw} resolvedTheme={resolvedTheme} />
}
// ```text/```txt/```plain force plain rendering; any other tagged
// language is never hijacked — the heuristic runs on untagged blocks only.
if (ASCII_DIAGRAM_LANGUAGES.has(language) || (!language && isAsciiDiagram(raw))) {
const meta = codeChild?.properties?.dataMeta
return (
<AsciiDiagram
code={raw}
title={parseFenceTitle(typeof meta === 'string' ? meta : undefined)}
/>
)
}
return (
<CodeBlock language={language} raw={raw}>
<pre className={`language-${language || 'text'}`}>{children}</pre>
Expand Down
Loading
Loading