@openpresentation/opf 0.12.2 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -23
- package/dist/{catalogs-BnI7JKcn.d.ts → catalogs-D-yyQWCQ.d.ts} +86 -208
- package/dist/catalogs.d.ts +2 -2
- package/dist/catalogs.js +1 -1
- package/dist/chart-data-B_3z9VTb.d.ts +188 -0
- package/dist/chunk-4LDGKLUC.js +192 -0
- package/dist/{chunk-V2537ENL.js → chunk-4VJDTF55.js} +2 -90
- package/dist/chunk-5ZU6JAPP.js +83 -0
- package/dist/{chunk-HOBSL7AJ.js → chunk-6OXY2CP3.js} +3604 -1644
- package/dist/{chunk-RUC7L5DJ.js → chunk-BFLVSVA7.js} +26 -26
- package/dist/chunk-CSQHPVGL.js +94 -0
- package/dist/{chunk-GJKJL4YY.js → chunk-D3TY4TT6.js} +34 -412
- package/dist/chunk-JDJBDYSR.js +4167 -0
- package/dist/{chunk-4LAM3N6S.js → chunk-LS2FMPI5.js} +1497 -1097
- package/dist/{chunk-SOSOBKQ2.js → chunk-Q2TCMN56.js} +6 -7
- package/dist/chunk-UPQQD7MA.js +1840 -0
- package/dist/{chunk-AUM5MXBR.js → chunk-XIZUK2XM.js} +36 -32
- package/dist/{chunk-E34DJ5LL.js → chunk-XOZ4KMSR.js} +2427 -10820
- package/dist/chunk-Z65B2B77.js +601 -0
- package/dist/{composition-DWbCiMwF.d.ts → composition-_WEwBe2a.d.ts} +744 -26
- package/dist/composition.d.ts +2 -1
- package/dist/composition.js +2 -2
- package/dist/convert.d.ts +9 -4
- package/dist/convert.js +57 -26
- package/dist/data.d.ts +5 -4
- package/dist/data.js +3 -1
- package/dist/diff.d.ts +5 -5
- package/dist/diff.js +6 -603
- package/dist/docs.js +70 -52
- package/dist/examples.d.ts +1 -1
- package/dist/examples.js +1466 -2963
- package/dist/finding-C_por275.d.ts +212 -0
- package/dist/format.d.ts +4 -4
- package/dist/format.js +2 -83
- package/dist/index.d.ts +318 -281
- package/dist/index.js +530 -943
- package/dist/markdown.d.ts +28 -25
- package/dist/markdown.js +8 -1940
- package/dist/pagination-BZpXLvFL.d.ts +119 -0
- package/dist/pagination.d.ts +5 -60
- package/dist/pagination.js +5 -5
- package/dist/patch.d.ts +12 -23
- package/dist/patch.js +5 -5
- package/dist/{presentation-DcF-MZcz.d.ts → presentation-C5eHkevA.d.ts} +516 -499
- package/dist/repo-readme.js +1 -1
- package/dist/{schemas-BSjlG8-R.d.ts → schemas-DDpqAgyS.d.ts} +49 -17
- package/dist/schemas.d.ts +1 -1
- package/dist/schemas.js +1 -1
- package/dist/spec/README.md +10 -5
- package/dist/spec/catalogs/audiences/index.json +2 -50
- package/dist/spec/catalogs/chart-types/100pct-stacked-area.json +21 -0
- package/dist/spec/catalogs/chart-types/100pct-stacked-bar.json +22 -0
- package/dist/spec/catalogs/chart-types/100pct-stacked-column.json +22 -0
- package/dist/spec/catalogs/chart-types/area.json +2 -60
- package/dist/spec/catalogs/chart-types/bar.json +2 -48
- package/dist/spec/catalogs/chart-types/box-and-whisker.json +2 -132
- package/dist/spec/catalogs/chart-types/column.json +2 -52
- package/dist/spec/catalogs/chart-types/combo.json +37 -0
- package/dist/spec/catalogs/chart-types/doughnut.json +2 -40
- package/dist/spec/catalogs/chart-types/filled-radar.json +2 -52
- package/dist/spec/catalogs/chart-types/funnel.json +2 -46
- package/dist/spec/catalogs/chart-types/histogram.json +2 -149
- package/dist/spec/catalogs/chart-types/index.json +50 -520
- package/dist/spec/catalogs/chart-types/line-with-markers.json +2 -60
- package/dist/spec/catalogs/chart-types/line.json +2 -60
- package/dist/spec/catalogs/chart-types/pareto.json +2 -50
- package/dist/spec/catalogs/chart-types/pie.json +2 -40
- package/dist/spec/catalogs/chart-types/radar-with-markers.json +2 -52
- package/dist/spec/catalogs/chart-types/radar.json +2 -52
- package/dist/spec/catalogs/chart-types/scatter.json +2 -73
- package/dist/spec/catalogs/chart-types/stacked-area.json +21 -0
- package/dist/spec/catalogs/chart-types/stacked-bar.json +22 -0
- package/dist/spec/catalogs/chart-types/stacked-column.json +22 -0
- package/dist/spec/catalogs/chart-types/stacked-line-with-markers.json +22 -0
- package/dist/spec/catalogs/chart-types/stacked-line.json +21 -0
- package/dist/spec/catalogs/chart-types/treemap.json +2 -48
- package/dist/spec/catalogs/chart-types/waterfall.json +2 -52
- package/dist/spec/catalogs/chart-types/world.json +2 -64
- package/dist/spec/catalogs/font-schemes/angsana-new.json +2 -2
- package/dist/spec/catalogs/font-schemes/aparajita.json +3 -3
- package/dist/spec/catalogs/font-schemes/aptos.json +1 -1
- package/dist/spec/catalogs/font-schemes/arabic-typesetting.json +2 -2
- package/dist/spec/catalogs/font-schemes/arial.json +1 -1
- package/dist/spec/catalogs/font-schemes/batang.json +2 -2
- package/dist/spec/catalogs/font-schemes/bookman.json +1 -1
- package/dist/spec/catalogs/font-schemes/calibri.json +1 -1
- package/dist/spec/catalogs/font-schemes/century-schoolbook.json +1 -1
- package/dist/spec/catalogs/font-schemes/consolas.json +2 -4
- package/dist/spec/catalogs/font-schemes/constantia.json +1 -1
- package/dist/spec/catalogs/font-schemes/courier-new.json +2 -4
- package/dist/spec/catalogs/font-schemes/daunpenh.json +2 -2
- package/dist/spec/catalogs/font-schemes/david.json +2 -2
- package/dist/spec/catalogs/font-schemes/dilleniaupc.json +2 -2
- package/dist/spec/catalogs/font-schemes/fangsong.json +2 -2
- package/dist/spec/catalogs/font-schemes/garamond.json +1 -1
- package/dist/spec/catalogs/font-schemes/gautami.json +2 -2
- package/dist/spec/catalogs/font-schemes/georgia.json +1 -1
- package/dist/spec/catalogs/font-schemes/gisha.json +3 -3
- package/dist/spec/catalogs/font-schemes/grandview.json +1 -1
- package/dist/spec/catalogs/font-schemes/gungsuh.json +2 -2
- package/dist/spec/catalogs/font-schemes/impact.json +1 -1
- package/dist/spec/catalogs/font-schemes/index.json +1 -1
- package/dist/spec/catalogs/font-schemes/kalinga.json +2 -2
- package/dist/spec/catalogs/font-schemes/kartika.json +2 -2
- package/dist/spec/catalogs/font-schemes/khmer-ui.json +2 -2
- package/dist/spec/catalogs/font-schemes/latha.json +2 -2
- package/dist/spec/catalogs/font-schemes/lucida-sans.json +1 -1
- package/dist/spec/catalogs/font-schemes/malgun-gothic.json +2 -2
- package/dist/spec/catalogs/font-schemes/mangal.json +4 -5
- package/dist/spec/catalogs/font-schemes/meiryo.json +2 -2
- package/dist/spec/catalogs/font-schemes/microsoft-jhenghei.json +2 -2
- package/dist/spec/catalogs/font-schemes/microsoft-yahei.json +5 -5
- package/dist/spec/catalogs/font-schemes/mingliu.json +2 -2
- package/dist/spec/catalogs/font-schemes/miriam.json +2 -2
- package/dist/spec/catalogs/font-schemes/montserrat.json +1 -1
- package/dist/spec/catalogs/font-schemes/ms-mincho.json +2 -2
- package/dist/spec/catalogs/font-schemes/nirmala-ui.json +2 -2
- package/dist/spec/catalogs/font-schemes/noto-naksh-arabic.json +2 -2
- package/dist/spec/catalogs/font-schemes/noto-nastaliq-urdu.json +3 -3
- package/dist/spec/catalogs/font-schemes/noto-sans-arabic.json +4 -4
- package/dist/spec/catalogs/font-schemes/noto-sans-armenian.json +2 -2
- package/dist/spec/catalogs/font-schemes/noto-sans-bengali.json +3 -3
- package/dist/spec/catalogs/font-schemes/noto-sans-devangari.json +3 -3
- package/dist/spec/catalogs/font-schemes/noto-sans-ethiopic.json +2 -2
- package/dist/spec/catalogs/font-schemes/noto-sans-georgian.json +2 -2
- package/dist/spec/catalogs/font-schemes/noto-sans-gujarati.json +2 -2
- package/dist/spec/catalogs/font-schemes/noto-sans-gurmukhi.json +2 -2
- package/dist/spec/catalogs/font-schemes/noto-sans-hebrew.json +2 -2
- package/dist/spec/catalogs/font-schemes/noto-sans-jp.json +2 -2
- package/dist/spec/catalogs/font-schemes/noto-sans-kannada.json +2 -2
- package/dist/spec/catalogs/font-schemes/noto-sans-khmer.json +2 -2
- package/dist/spec/catalogs/font-schemes/noto-sans-kr.json +2 -2
- package/dist/spec/catalogs/font-schemes/noto-sans-malayalam.json +2 -2
- package/dist/spec/catalogs/font-schemes/noto-sans-mongolian.json +2 -2
- package/dist/spec/catalogs/font-schemes/noto-sans-oriya.json +2 -2
- package/dist/spec/catalogs/font-schemes/noto-sans-sc.json +5 -5
- package/dist/spec/catalogs/font-schemes/noto-sans-tamil.json +2 -2
- package/dist/spec/catalogs/font-schemes/noto-sans-tc.json +2 -2
- package/dist/spec/catalogs/font-schemes/noto-sans-telugu.json +2 -2
- package/dist/spec/catalogs/font-schemes/noto-sans-thai.json +2 -2
- package/dist/spec/catalogs/font-schemes/noto-sans.json +5 -5
- package/dist/spec/catalogs/font-schemes/nyala.json +2 -2
- package/dist/spec/catalogs/font-schemes/open-sans.json +1 -1
- package/dist/spec/catalogs/font-schemes/pmingliu.json +2 -2
- package/dist/spec/catalogs/font-schemes/poppins.json +1 -1
- package/dist/spec/catalogs/font-schemes/pt-serif.json +1 -1
- package/dist/spec/catalogs/font-schemes/raavi.json +2 -2
- package/dist/spec/catalogs/font-schemes/raleway.json +1 -1
- package/dist/spec/catalogs/font-schemes/roboto.json +1 -1
- package/dist/spec/catalogs/font-schemes/rockwell.json +1 -1
- package/dist/spec/catalogs/font-schemes/sakkal-majalla.json +4 -4
- package/dist/spec/catalogs/font-schemes/seaford.json +1 -1
- package/dist/spec/catalogs/font-schemes/segoe-ui-light.json +1 -1
- package/dist/spec/catalogs/font-schemes/segoe-ui.json +1 -1
- package/dist/spec/catalogs/font-schemes/shonar-bangla.json +3 -3
- package/dist/spec/catalogs/font-schemes/shruti.json +2 -2
- package/dist/spec/catalogs/font-schemes/simsun.json +2 -2
- package/dist/spec/catalogs/font-schemes/skeena.json +1 -1
- package/dist/spec/catalogs/font-schemes/sylfaen.json +3 -3
- package/dist/spec/catalogs/font-schemes/tahoma.json +1 -1
- package/dist/spec/catalogs/font-schemes/tenorite.json +1 -1
- package/dist/spec/catalogs/font-schemes/times-new-roman.json +1 -1
- package/dist/spec/catalogs/font-schemes/traditional-arabic.json +4 -4
- package/dist/spec/catalogs/font-schemes/trebuchet-ms.json +1 -1
- package/dist/spec/catalogs/font-schemes/tunga.json +2 -2
- package/dist/spec/catalogs/font-schemes/verdana.json +1 -1
- package/dist/spec/catalogs/font-schemes/vrinda.json +2 -2
- package/dist/spec/catalogs/font-schemes/yu-gothic.json +2 -2
- package/dist/spec/catalogs/layouts/action-plan.json +9 -15
- package/dist/spec/catalogs/layouts/agenda.json +8 -15
- package/dist/spec/catalogs/layouts/appendix-index.json +7 -15
- package/dist/spec/catalogs/layouts/architecture-stack.json +9 -15
- package/dist/spec/catalogs/layouts/assumptions-dependencies.json +9 -15
- package/dist/spec/catalogs/layouts/before-after.json +9 -15
- package/dist/spec/catalogs/layouts/benchmark-target-table.json +9 -15
- package/dist/spec/catalogs/layouts/blank.json +11 -1
- package/dist/spec/catalogs/layouts/bullet-list.json +9 -15
- package/dist/spec/catalogs/layouts/business-model-canvas.json +9 -15
- package/dist/spec/catalogs/layouts/capability-map.json +14 -16
- package/dist/spec/catalogs/layouts/case-study.json +9 -15
- package/dist/spec/catalogs/layouts/chapter-recap.json +8 -15
- package/dist/spec/catalogs/layouts/chart-1x.json +15 -0
- package/dist/spec/catalogs/layouts/chart-2x.json +15 -0
- package/dist/spec/catalogs/layouts/chart-3x.json +15 -0
- package/dist/spec/catalogs/layouts/circular-lifecycle.json +9 -15
- package/dist/spec/catalogs/layouts/code-1x.json +10 -0
- package/dist/spec/catalogs/layouts/comparison.json +9 -15
- package/dist/spec/catalogs/layouts/competitive-landscape.json +14 -16
- package/dist/spec/catalogs/layouts/customer-journey.json +14 -16
- package/dist/spec/catalogs/layouts/customer-logos.json +9 -15
- package/dist/spec/catalogs/layouts/dashboard.json +9 -15
- package/dist/spec/catalogs/layouts/data-visualization.json +9 -15
- package/dist/spec/catalogs/layouts/decision-tree.json +14 -16
- package/dist/spec/catalogs/layouts/detailed-data-table.json +9 -15
- package/dist/spec/catalogs/layouts/ecosystem-map.json +14 -16
- package/dist/spec/catalogs/layouts/executive-decision.json +9 -15
- package/dist/spec/catalogs/layouts/executive-summary.json +9 -15
- package/dist/spec/catalogs/layouts/extract/canonical.json +8 -8
- package/dist/spec/catalogs/layouts/faq.json +9 -15
- package/dist/spec/catalogs/layouts/feature-comparison-matrix.json +9 -15
- package/dist/spec/catalogs/layouts/forecast-scenario.json +9 -15
- package/dist/spec/catalogs/layouts/funnel-pyramid.json +9 -15
- package/dist/spec/catalogs/layouts/gantt-project-plan.json +9 -15
- package/dist/spec/catalogs/layouts/geographic-map.json +13 -15
- package/dist/spec/catalogs/layouts/heatmap-cohort.json +9 -15
- package/dist/spec/catalogs/layouts/hub-and-spoke.json +9 -15
- package/dist/spec/catalogs/layouts/icon-grid.json +14 -16
- package/dist/spec/catalogs/layouts/image-1x.json +11 -1
- package/dist/spec/catalogs/layouts/image-2x.json +12 -2
- package/dist/spec/catalogs/layouts/image-3x.json +13 -3
- package/dist/spec/catalogs/layouts/image-bleed.json +12 -2
- package/dist/spec/catalogs/layouts/image-focus.json +14 -16
- package/dist/spec/catalogs/layouts/index.json +1 -1
- package/dist/spec/catalogs/layouts/key-takeaway.json +8 -15
- package/dist/spec/catalogs/layouts/kpi-scorecard.json +9 -15
- package/dist/spec/catalogs/layouts/list-1x.json +17 -1
- package/dist/spec/catalogs/layouts/list-2x.json +16 -0
- package/dist/spec/catalogs/layouts/list-3x.json +16 -0
- package/dist/spec/catalogs/layouts/list-4x.json +16 -0
- package/dist/spec/catalogs/layouts/list-5x.json +16 -0
- package/dist/spec/catalogs/layouts/list-6x.json +16 -0
- package/dist/spec/catalogs/layouts/logic-tree.json +9 -15
- package/dist/spec/catalogs/layouts/market-size.json +9 -15
- package/dist/spec/catalogs/layouts/matrix-quadrant.json +9 -15
- package/dist/spec/catalogs/layouts/media-1x.json +11 -1
- package/dist/spec/catalogs/layouts/network-dependency-map.json +13 -15
- package/dist/spec/catalogs/layouts/number-1x.json +16 -1
- package/dist/spec/catalogs/layouts/number-2x.json +15 -0
- package/dist/spec/catalogs/layouts/number-3x.json +15 -0
- package/dist/spec/catalogs/layouts/number-4x.json +15 -0
- package/dist/spec/catalogs/layouts/number-5x.json +15 -0
- package/dist/spec/catalogs/layouts/number-6x.json +15 -0
- package/dist/spec/catalogs/layouts/one-column-explainer.json +9 -15
- package/dist/spec/catalogs/layouts/org-chart.json +9 -15
- package/dist/spec/catalogs/layouts/persona-grid.json +14 -16
- package/dist/spec/catalogs/layouts/photo-grid.json +17 -19
- package/dist/spec/catalogs/layouts/pricing-table.json +9 -15
- package/dist/spec/catalogs/layouts/process-flow.json +9 -15
- package/dist/spec/catalogs/layouts/product-feature.json +14 -16
- package/dist/spec/catalogs/layouts/pros-cons.json +9 -15
- package/dist/spec/catalogs/layouts/qa-discussion.json +7 -15
- package/dist/spec/catalogs/layouts/quote-1x.json +11 -1
- package/dist/spec/catalogs/layouts/quote-highlight.json +9 -15
- package/dist/spec/catalogs/layouts/raci-matrix.json +9 -15
- package/dist/spec/catalogs/layouts/risk-heatmap.json +9 -15
- package/dist/spec/catalogs/layouts/roadmap.json +9 -15
- package/dist/spec/catalogs/layouts/screenshot-walkthrough.json +14 -16
- package/dist/spec/catalogs/layouts/section-divider.json +8 -15
- package/dist/spec/catalogs/layouts/single-message-statement.json +8 -15
- package/dist/spec/catalogs/layouts/stats-metrics.json +9 -15
- package/dist/spec/catalogs/layouts/status-rag.json +9 -15
- package/dist/spec/catalogs/layouts/swimlane-process.json +14 -16
- package/dist/spec/catalogs/layouts/swot-analysis.json +9 -15
- package/dist/spec/catalogs/layouts/table-1x.json +10 -0
- package/dist/spec/catalogs/layouts/team-bio.json +14 -16
- package/dist/spec/catalogs/layouts/testimonial-grid.json +9 -15
- package/dist/spec/catalogs/layouts/text-1x.json +11 -1
- package/dist/spec/catalogs/layouts/text-2x.json +10 -0
- package/dist/spec/catalogs/layouts/text-3x.json +10 -0
- package/dist/spec/catalogs/layouts/thank-you-cta.json +8 -15
- package/dist/spec/catalogs/layouts/three-column.json +9 -15
- package/dist/spec/catalogs/layouts/timeline-1x.json +11 -1
- package/dist/spec/catalogs/layouts/timeline.json +9 -15
- package/dist/spec/catalogs/layouts/title-slide.json +8 -15
- package/dist/spec/catalogs/layouts/title-subtitle.json +11 -1
- package/dist/spec/catalogs/layouts/title.json +11 -1
- package/dist/spec/catalogs/layouts/two-column.json +9 -15
- package/dist/spec/catalogs/layouts/venn-overlap.json +9 -15
- package/dist/spec/catalogs/layouts/waterfall-bridge.json +9 -15
- package/dist/spec/catalogs/layouts/wheel-radial.json +9 -15
- package/dist/spec/catalogs/manifest.json +11 -11
- package/dist/spec/catalogs/narratives/board-meeting.json +45 -45
- package/dist/spec/catalogs/narratives/business-narrative.json +19 -19
- package/dist/spec/catalogs/narratives/business-review.json +27 -27
- package/dist/spec/catalogs/narratives/capacity-planning.json +19 -19
- package/dist/spec/catalogs/narratives/challenge-resolution.json +17 -17
- package/dist/spec/catalogs/narratives/change-story.json +13 -13
- package/dist/spec/catalogs/narratives/classic-story.json +17 -17
- package/dist/spec/catalogs/narratives/company-intro.json +21 -21
- package/dist/spec/catalogs/narratives/conference-talk.json +4 -4
- package/dist/spec/catalogs/narratives/data-story.json +13 -14
- package/dist/spec/catalogs/narratives/early-startup-pitch.json +27 -27
- package/dist/spec/catalogs/narratives/educate.json +21 -21
- package/dist/spec/catalogs/narratives/employee-review.json +19 -19
- package/dist/spec/catalogs/narratives/failure-analysis.json +23 -23
- package/dist/spec/catalogs/narratives/focus.json +27 -27
- package/dist/spec/catalogs/narratives/golden-circle.json +4 -4
- package/dist/spec/catalogs/narratives/heros-journey.json +13 -13
- package/dist/spec/catalogs/narratives/index.json +145 -145
- package/dist/spec/catalogs/narratives/innovation.json +21 -21
- package/dist/spec/catalogs/narratives/justice.json +25 -25
- package/dist/spec/catalogs/narratives/marketing-strategy.json +25 -25
- package/dist/spec/catalogs/narratives/performance-improvement-plan.json +19 -19
- package/dist/spec/catalogs/narratives/performance-review.json +25 -25
- package/dist/spec/catalogs/narratives/persuade.json +17 -17
- package/dist/spec/catalogs/narratives/persuasive-sales.json +19 -19
- package/dist/spec/catalogs/narratives/pitch-deck.json +6 -6
- package/dist/spec/catalogs/narratives/problem-solution.json +5 -5
- package/dist/spec/catalogs/narratives/product-launch.json +23 -23
- package/dist/spec/catalogs/narratives/project-proposal.json +23 -23
- package/dist/spec/catalogs/narratives/pyramid-principle.json +9 -9
- package/dist/spec/catalogs/narratives/qbr.json +8 -8
- package/dist/spec/catalogs/narratives/rags-to-riches.json +17 -17
- package/dist/spec/catalogs/narratives/reveal.json +19 -19
- package/dist/spec/catalogs/narratives/scqa.json +4 -4
- package/dist/spec/catalogs/narratives/situation-complication-resolution.json +11 -11
- package/dist/spec/catalogs/narratives/sparkline.json +11 -11
- package/dist/spec/catalogs/narratives/star-method.json +11 -11
- package/dist/spec/catalogs/narratives/status-update.json +15 -15
- package/dist/spec/catalogs/narratives/strategic-advisory.json +17 -17
- package/dist/spec/catalogs/narratives/strategic-narrative.json +4 -4
- package/dist/spec/catalogs/narratives/survey-analysis.json +23 -23
- package/dist/spec/catalogs/narratives/survival-story.json +23 -23
- package/dist/spec/catalogs/narratives/transformation-arc.json +4 -4
- package/dist/spec/catalogs/narratives/trend-analysis.json +17 -17
- package/dist/spec/catalogs/narratives/underdog-victory.json +19 -19
- package/dist/spec/catalogs/narratives/venture-pitch.json +27 -27
- package/dist/spec/catalogs/narratives/vision-roadmap.json +13 -13
- package/dist/spec/catalogs/narratives/weekly-progress.json +23 -23
- package/dist/spec/catalogs/narratives/what-so-what-now-what.json +9 -9
- package/dist/spec/openapi.yaml +2 -5
- package/dist/spec/reference/engine-defaults.json +2 -2
- package/dist/spec/schemas/audience.schema.json +1 -1
- package/dist/spec/schemas/catalog-index.schema.json +2 -2
- package/dist/spec/schemas/chart-type.schema.json +19 -290
- package/dist/spec/schemas/color-scheme.schema.json +20 -12
- package/dist/spec/schemas/finding.schema.json +191 -0
- package/dist/spec/schemas/font-scheme.schema.json +19 -46
- package/dist/spec/schemas/layout.schema.json +100 -159
- package/dist/spec/schemas/narrative.schema.json +17 -25
- package/dist/spec/schemas/opf.schema.json +503 -339
- package/dist/spec/schemas/social-platform.schema.json +1 -26
- package/dist/spec/schemas/theme.schema.json +4 -1
- package/dist/spec-files.d.ts +1 -1
- package/dist/spec-files.js +1 -1
- package/dist/symbol-font-encodings.js +18448 -1
- package/dist/types.d.ts +4 -4
- package/dist/validation-types-zh03LeZy.d.ts +276 -0
- package/dist/validator.d.ts +60 -4
- package/dist/validator.js +4 -4
- package/dist/yaml.d.ts +87 -0
- package/dist/yaml.js +321 -0
- package/package.json +6 -10
- package/dist/audit.d.ts +0 -156
- package/dist/audit.js +0 -7
- package/dist/chunk-6LZK2IZB.js +0 -1690
- package/dist/chunk-FYLJVF7X.js +0 -18448
- package/dist/chunk-JKVKYVWL.js +0 -1332
- package/dist/chunk-MWPMJE6P.js +0 -622
- package/dist/lint.d.ts +0 -70
- package/dist/lint.js +0 -6
- package/dist/spec/catalogs/audiences/customers.json +0 -27
- package/dist/spec/catalogs/audiences/executives.json +0 -27
- package/dist/spec/catalogs/audiences/investors.json +0 -27
- package/dist/spec/catalogs/audiences/marketing-team.json +0 -27
- package/dist/spec/catalogs/audiences/regulators.json +0 -27
- package/dist/spec/catalogs/audiences/sales-team.json +0 -27
- package/dist/spec/catalogs/chart-types/100pct-bullet-bar-2x.json +0 -91
- package/dist/spec/catalogs/chart-types/100pct-bullet-bar-3x.json +0 -103
- package/dist/spec/catalogs/chart-types/100pct-bullet-bar.json +0 -79
- package/dist/spec/catalogs/chart-types/100pct-bullet-column-2x.json +0 -91
- package/dist/spec/catalogs/chart-types/100pct-bullet-column-3x.json +0 -103
- package/dist/spec/catalogs/chart-types/100pct-bullet-column.json +0 -79
- package/dist/spec/catalogs/chart-types/100pct-progress-bar.json +0 -105
- package/dist/spec/catalogs/chart-types/100pct-stacked-area-2x.json +0 -106
- package/dist/spec/catalogs/chart-types/100pct-stacked-area-3x.json +0 -123
- package/dist/spec/catalogs/chart-types/100pct-stacked-bar-2x.json +0 -89
- package/dist/spec/catalogs/chart-types/100pct-stacked-bar-3x.json +0 -100
- package/dist/spec/catalogs/chart-types/100pct-stacked-column-2x.json +0 -95
- package/dist/spec/catalogs/chart-types/100pct-stacked-column-3x.json +0 -108
- package/dist/spec/catalogs/chart-types/australia.json +0 -76
- package/dist/spec/catalogs/chart-types/box-and-whisker-2x.json +0 -136
- package/dist/spec/catalogs/chart-types/box-and-whisker-3x.json +0 -168
- package/dist/spec/catalogs/chart-types/bullet-bar-2x.json +0 -89
- package/dist/spec/catalogs/chart-types/bullet-bar-3x.json +0 -101
- package/dist/spec/catalogs/chart-types/bullet-bar.json +0 -77
- package/dist/spec/catalogs/chart-types/bullet-column-2x.json +0 -89
- package/dist/spec/catalogs/chart-types/bullet-column-3x.json +0 -101
- package/dist/spec/catalogs/chart-types/bullet-column.json +0 -77
- package/dist/spec/catalogs/chart-types/canada.json +0 -86
- package/dist/spec/catalogs/chart-types/clustered-bar-2x.json +0 -89
- package/dist/spec/catalogs/chart-types/clustered-column.json +0 -95
- package/dist/spec/catalogs/chart-types/dot-plot-2x.json +0 -99
- package/dist/spec/catalogs/chart-types/dot-plot-3x.json +0 -124
- package/dist/spec/catalogs/chart-types/dot-plot-4x.json +0 -149
- package/dist/spec/catalogs/chart-types/dot-plot-5x.json +0 -174
- package/dist/spec/catalogs/chart-types/dot-plot-6x.json +0 -199
- package/dist/spec/catalogs/chart-types/dot-plot.json +0 -74
- package/dist/spec/catalogs/chart-types/dumbbell.json +0 -120
- package/dist/spec/catalogs/chart-types/line-2x.json +0 -105
- package/dist/spec/catalogs/chart-types/line-3x.json +0 -127
- package/dist/spec/catalogs/chart-types/line-with-high-low-and-markers.json +0 -144
- package/dist/spec/catalogs/chart-types/line-with-high-low.json +0 -144
- package/dist/spec/catalogs/chart-types/line-with-markers-2x.json +0 -106
- package/dist/spec/catalogs/chart-types/line-with-markers-3x.json +0 -128
- package/dist/spec/catalogs/chart-types/sparkline-2x.json +0 -138
- package/dist/spec/catalogs/chart-types/sparkline-3x.json +0 -174
- package/dist/spec/catalogs/chart-types/sparkline-4x.json +0 -210
- package/dist/spec/catalogs/chart-types/sparkline-5x.json +0 -246
- package/dist/spec/catalogs/chart-types/sparkline-6x.json +0 -282
- package/dist/spec/catalogs/chart-types/sparkline.json +0 -102
- package/dist/spec/catalogs/chart-types/stacked-area-2x.json +0 -106
- package/dist/spec/catalogs/chart-types/stacked-area-3x.json +0 -123
- package/dist/spec/catalogs/chart-types/stacked-bar-2x.json +0 -89
- package/dist/spec/catalogs/chart-types/stacked-bar-3x.json +0 -100
- package/dist/spec/catalogs/chart-types/stacked-column-2x.json +0 -95
- package/dist/spec/catalogs/chart-types/stacked-column-3x.json +0 -108
- package/dist/spec/catalogs/chart-types/stacked-line-2x.json +0 -106
- package/dist/spec/catalogs/chart-types/stacked-line-3x.json +0 -123
- package/dist/spec/catalogs/chart-types/stacked-line-with-markers-2x.json +0 -107
- package/dist/spec/catalogs/chart-types/stacked-line-with-markers-3x.json +0 -124
- package/dist/spec/catalogs/chart-types/treemap-2x.json +0 -101
- package/dist/spec/catalogs/chart-types/treemap-3x.json +0 -122
- package/dist/spec/catalogs/chart-types/united-kingdom.json +0 -68
- package/dist/spec/catalogs/chart-types/united-states.json +0 -82
- package/dist/validator-DfG9yxba.d.ts +0 -172
|
@@ -0,0 +1,1840 @@
|
|
|
1
|
+
import { Ctx, splitLines, lineRange, readYamlMapping, OPFMarkdownError, writeYaml, readCsv, DECIMAL, csvField, scalarText } from './chunk-4LDGKLUC.js';
|
|
2
|
+
import { parseQuoteLines, levelsFromIndents, parseCodeFence, splitWhen, fenceCode } from './chunk-4VJDTF55.js';
|
|
3
|
+
import { same, isRecord, runText, isPlainRun, FORMATTING } from './chunk-CSQHPVGL.js';
|
|
4
|
+
import { validate } from './chunk-JDJBDYSR.js';
|
|
5
|
+
|
|
6
|
+
// src/markdown/canon.ts
|
|
7
|
+
var FORMAT_KEYS = FORMATTING;
|
|
8
|
+
function canonRun(run) {
|
|
9
|
+
if (typeof run === "string") return run;
|
|
10
|
+
if (!isRecord(run) || typeof run.text !== "string") return void 0;
|
|
11
|
+
const out = { text: run.text };
|
|
12
|
+
for (const [key, value] of Object.entries(run)) {
|
|
13
|
+
if (key === "text") continue;
|
|
14
|
+
if (!FORMAT_KEYS.includes(key)) return void 0;
|
|
15
|
+
if (value === false || value === void 0) continue;
|
|
16
|
+
out[key] = value;
|
|
17
|
+
}
|
|
18
|
+
return Object.keys(out).length === 1 ? out.text : out;
|
|
19
|
+
}
|
|
20
|
+
var sameFormat = (a, b) => {
|
|
21
|
+
const keys = /* @__PURE__ */ new Set([...Object.keys(a), ...Object.keys(b)]);
|
|
22
|
+
keys.delete("text");
|
|
23
|
+
for (const key of keys) if (a[key] !== b[key]) return false;
|
|
24
|
+
return true;
|
|
25
|
+
};
|
|
26
|
+
function canonText(value) {
|
|
27
|
+
if (typeof value === "string") return value;
|
|
28
|
+
if (!Array.isArray(value)) return void 0;
|
|
29
|
+
const merged = [];
|
|
30
|
+
for (const raw of value) {
|
|
31
|
+
const run = canonRun(raw);
|
|
32
|
+
if (run === void 0) return void 0;
|
|
33
|
+
if (runText(run) === "") continue;
|
|
34
|
+
const last = merged.at(-1);
|
|
35
|
+
if (last !== void 0) {
|
|
36
|
+
if (typeof last === "string" && typeof run === "string") {
|
|
37
|
+
merged[merged.length - 1] = last + run;
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (typeof last === "object" && typeof run === "object" && sameFormat(last, run)) {
|
|
41
|
+
merged[merged.length - 1] = { ...last, text: last.text + run.text };
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
merged.push(run);
|
|
46
|
+
}
|
|
47
|
+
if (merged.length === 0) return "";
|
|
48
|
+
if (merged.every(isPlainRun)) return merged.map(runText).join("");
|
|
49
|
+
return merged;
|
|
50
|
+
}
|
|
51
|
+
function canonItem(item) {
|
|
52
|
+
if (typeof item === "string" || Array.isArray(item)) return canonText(item);
|
|
53
|
+
if (!isRecord(item)) return void 0;
|
|
54
|
+
for (const key of Object.keys(item)) if (key !== "text" && key !== "level" && key !== "description") return void 0;
|
|
55
|
+
const text = canonText(item.text);
|
|
56
|
+
if (text === void 0) return void 0;
|
|
57
|
+
const level = item.level === void 0 ? 0 : item.level;
|
|
58
|
+
if (!Number.isInteger(level) || level < 0) return void 0;
|
|
59
|
+
const description = item.description === void 0 ? void 0 : canonText(item.description);
|
|
60
|
+
if (item.description !== void 0 && description === void 0) return void 0;
|
|
61
|
+
if (level === 0 && description === void 0) return text;
|
|
62
|
+
const out = { text };
|
|
63
|
+
if (description !== void 0) out.description = description;
|
|
64
|
+
if (level !== 0) out.level = level;
|
|
65
|
+
return out;
|
|
66
|
+
}
|
|
67
|
+
var levelOf = (item) => isRecord(item) && typeof item.level === "number" ? item.level : 0;
|
|
68
|
+
function canonItems(value) {
|
|
69
|
+
if (!Array.isArray(value) || value.length === 0) return void 0;
|
|
70
|
+
const items = [];
|
|
71
|
+
let previous = -1;
|
|
72
|
+
for (const raw of value) {
|
|
73
|
+
const item = canonItem(raw);
|
|
74
|
+
if (item === void 0) return void 0;
|
|
75
|
+
const level = levelOf(item);
|
|
76
|
+
if (level > previous + 1 || previous < 0 && level !== 0) return void 0;
|
|
77
|
+
previous = level;
|
|
78
|
+
items.push(item);
|
|
79
|
+
}
|
|
80
|
+
return items;
|
|
81
|
+
}
|
|
82
|
+
var only = (value, keys) => Object.keys(value).every((key) => keys.includes(key));
|
|
83
|
+
function canonQuote(value) {
|
|
84
|
+
if (typeof value === "string") return value;
|
|
85
|
+
if (!isRecord(value) || !only(value, ["text", "attribution", "source"])) return void 0;
|
|
86
|
+
const text = canonText(value.text);
|
|
87
|
+
if (text === void 0) return void 0;
|
|
88
|
+
for (const key of ["attribution", "source"]) if (value[key] !== void 0 && typeof value[key] !== "string") return void 0;
|
|
89
|
+
if (value.source !== void 0 && value.attribution === void 0) return void 0;
|
|
90
|
+
if (value.attribution === void 0) return typeof text === "string" ? text : { text };
|
|
91
|
+
const out = { text, attribution: value.attribution };
|
|
92
|
+
if (value.source !== void 0) out.source = value.source;
|
|
93
|
+
return out;
|
|
94
|
+
}
|
|
95
|
+
function canonCode(value) {
|
|
96
|
+
if (typeof value === "string") return value;
|
|
97
|
+
if (!isRecord(value) || !only(value, ["source", "language", "filename"]) || typeof value.source !== "string") return void 0;
|
|
98
|
+
for (const key of ["language", "filename"]) if (value[key] !== void 0 && (typeof value[key] !== "string" || value[key] === "")) return void 0;
|
|
99
|
+
if (value.language === void 0 && value.filename === void 0) return value.source;
|
|
100
|
+
const out = { source: value.source };
|
|
101
|
+
if (value.language !== void 0) out.language = value.language;
|
|
102
|
+
if (value.filename !== void 0) out.filename = value.filename;
|
|
103
|
+
return out;
|
|
104
|
+
}
|
|
105
|
+
function canonMetric(value) {
|
|
106
|
+
if (typeof value === "string" || typeof value === "number") return value;
|
|
107
|
+
if (!isRecord(value) || !only(value, ["value", "label", "description", "unit", "delta", "trend", "sentiment"])) return void 0;
|
|
108
|
+
if (typeof value.value !== "string" && typeof value.value !== "number") return void 0;
|
|
109
|
+
const out = { value: value.value };
|
|
110
|
+
for (const key of ["label", "description", "unit", "delta", "trend", "sentiment"]) {
|
|
111
|
+
const entry = value[key];
|
|
112
|
+
if (entry === void 0) continue;
|
|
113
|
+
if (typeof entry !== "string" && !(key === "delta" && typeof entry === "number")) return void 0;
|
|
114
|
+
out[key] = entry;
|
|
115
|
+
}
|
|
116
|
+
return Object.keys(out).length === 1 ? out.value : out;
|
|
117
|
+
}
|
|
118
|
+
function canonAsset(value) {
|
|
119
|
+
if (typeof value === "string") return value;
|
|
120
|
+
if (!isRecord(value) || !only(value, ["src", "alt", "title"]) || typeof value.src !== "string") return void 0;
|
|
121
|
+
for (const key of ["alt", "title"]) if (value[key] !== void 0 && typeof value[key] !== "string") return void 0;
|
|
122
|
+
const out = { src: value.src };
|
|
123
|
+
if (value.alt !== void 0) out.alt = value.alt;
|
|
124
|
+
if (value.title !== void 0) out.title = value.title;
|
|
125
|
+
return Object.keys(out).length === 1 ? out.src : out;
|
|
126
|
+
}
|
|
127
|
+
function canonCell(cell) {
|
|
128
|
+
if (cell === null) return cell;
|
|
129
|
+
if (typeof cell === "string" || Array.isArray(cell)) return canonText(cell);
|
|
130
|
+
return void 0;
|
|
131
|
+
}
|
|
132
|
+
function canonTable(value) {
|
|
133
|
+
if (!isRecord(value) || !only(value, ["columns", "rows"]) || !Array.isArray(value.rows)) return void 0;
|
|
134
|
+
const out = {};
|
|
135
|
+
if (value.columns !== void 0) {
|
|
136
|
+
if (!Array.isArray(value.columns) || value.columns.length === 0) return void 0;
|
|
137
|
+
const columns = value.columns.map((cell) => cell === null ? null : typeof cell === "string" || Array.isArray(cell) ? canonText(cell) : void 0);
|
|
138
|
+
if (columns.includes(void 0) || columns.every((cell) => cell === null || cell === "")) return void 0;
|
|
139
|
+
out.columns = columns;
|
|
140
|
+
}
|
|
141
|
+
const rows = [];
|
|
142
|
+
for (const row of value.rows) {
|
|
143
|
+
if (!Array.isArray(row)) return void 0;
|
|
144
|
+
const cells = row.map(canonCell);
|
|
145
|
+
if (cells.includes(void 0)) return void 0;
|
|
146
|
+
rows.push(cells);
|
|
147
|
+
}
|
|
148
|
+
out.rows = rows;
|
|
149
|
+
return out;
|
|
150
|
+
}
|
|
151
|
+
function canonChart(value) {
|
|
152
|
+
if (!isRecord(value) || !only(value, ["type", "alt", "data"]) || typeof value.type !== "string" || !isRecord(value.data)) return void 0;
|
|
153
|
+
if (value.alt !== void 0 && typeof value.alt !== "string") return void 0;
|
|
154
|
+
const alt = value.alt === void 0 ? {} : { alt: value.alt };
|
|
155
|
+
const data = value.data;
|
|
156
|
+
if (Array.isArray(data.columns) && Array.isArray(data.rows) && only(data, ["columns", "rows"])) {
|
|
157
|
+
if (!data.columns.every((column) => typeof column === "string") || data.columns.length === 0 || data.rows.length === 0) return void 0;
|
|
158
|
+
for (const row of data.rows) if (!Array.isArray(row) || !row.every((cell) => cell === null || ["string", "number", "boolean"].includes(typeof cell))) return void 0;
|
|
159
|
+
return { type: value.type, ...alt, data: { columns: data.columns, rows: data.rows } };
|
|
160
|
+
}
|
|
161
|
+
return void 0;
|
|
162
|
+
}
|
|
163
|
+
function canonEvent(event) {
|
|
164
|
+
if (!isRecord(event) || !only(event, ["when", "what", "description", "status"]) || typeof event.what !== "string") return void 0;
|
|
165
|
+
for (const key of ["when", "description"]) if (event[key] !== void 0 && typeof event[key] !== "string") return void 0;
|
|
166
|
+
if (event.status !== void 0 && !["done", "current", "planned"].includes(event.status)) return void 0;
|
|
167
|
+
const out = {};
|
|
168
|
+
if (event.when !== void 0) out.when = event.when;
|
|
169
|
+
out.what = event.what;
|
|
170
|
+
if (event.description !== void 0) out.description = event.description;
|
|
171
|
+
if (event.status !== void 0) out.status = event.status;
|
|
172
|
+
return out;
|
|
173
|
+
}
|
|
174
|
+
function canonTimeline(value) {
|
|
175
|
+
const events = Array.isArray(value) ? value : isRecord(value) && only(value, ["name", "description", "events"]) ? value.events : void 0;
|
|
176
|
+
if (!Array.isArray(events) || events.length === 0) return void 0;
|
|
177
|
+
const canon = events.map(canonEvent);
|
|
178
|
+
if (canon.includes(void 0)) return void 0;
|
|
179
|
+
if (Array.isArray(value)) return canon;
|
|
180
|
+
for (const key of ["name", "description"]) if (value[key] !== void 0 && typeof value[key] !== "string") return void 0;
|
|
181
|
+
const { name, description } = value;
|
|
182
|
+
if (name === void 0 && description === void 0) return canon;
|
|
183
|
+
const out = {};
|
|
184
|
+
if (name !== void 0) out.name = name;
|
|
185
|
+
if (description !== void 0) out.description = description;
|
|
186
|
+
out.events = canon;
|
|
187
|
+
return out;
|
|
188
|
+
}
|
|
189
|
+
function canonContent(key, value) {
|
|
190
|
+
switch (key) {
|
|
191
|
+
case "text":
|
|
192
|
+
return canonText(value);
|
|
193
|
+
case "items":
|
|
194
|
+
case "bullets":
|
|
195
|
+
return canonItems(value);
|
|
196
|
+
case "quote":
|
|
197
|
+
return canonQuote(value);
|
|
198
|
+
case "code":
|
|
199
|
+
return canonCode(value);
|
|
200
|
+
case "metric":
|
|
201
|
+
return canonMetric(value);
|
|
202
|
+
case "image":
|
|
203
|
+
case "video":
|
|
204
|
+
return canonAsset(value);
|
|
205
|
+
case "table":
|
|
206
|
+
return canonTable(value);
|
|
207
|
+
case "chart":
|
|
208
|
+
return canonChart(value);
|
|
209
|
+
case "timeline":
|
|
210
|
+
return canonTimeline(value);
|
|
211
|
+
default:
|
|
212
|
+
return void 0;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// src/markdown/inline.ts
|
|
217
|
+
var PUNCT = /[\p{P}\p{S}]/u;
|
|
218
|
+
var WS = /\s/u;
|
|
219
|
+
var ALNUM = /[\p{L}\p{N}]/u;
|
|
220
|
+
var ASCII_PUNCT = /^[!-/:-@[-`{-~]$/;
|
|
221
|
+
var MAX_DEPTH = 16;
|
|
222
|
+
var MAX_TARGET = 2e3;
|
|
223
|
+
var MAX_SPAN = 300;
|
|
224
|
+
var SPAN_FLAGS = {
|
|
225
|
+
bold: "bold",
|
|
226
|
+
italic: "italic",
|
|
227
|
+
underline: "underline",
|
|
228
|
+
strike: "strikethrough",
|
|
229
|
+
strikethrough: "strikethrough",
|
|
230
|
+
sup: "superscript",
|
|
231
|
+
superscript: "superscript",
|
|
232
|
+
sub: "subscript",
|
|
233
|
+
subscript: "subscript"
|
|
234
|
+
};
|
|
235
|
+
var SPAN_VALUES = { color: "color", size: "fontSize", fontsize: "fontSize", font: "fontFamily", fontfamily: "fontFamily", lang: "lang" };
|
|
236
|
+
function parseAttributes(source) {
|
|
237
|
+
const pairs = [];
|
|
238
|
+
let i = 0;
|
|
239
|
+
while (i < source.length) {
|
|
240
|
+
while (i < source.length && /[\s,]/.test(source[i])) i++;
|
|
241
|
+
if (i >= source.length) break;
|
|
242
|
+
const key = /^[A-Za-z][\w-]*/.exec(source.slice(i))?.[0];
|
|
243
|
+
if (!key) return { pairs, error: `Expected an attribute name at ${JSON.stringify(source.slice(i, i + 12))}.` };
|
|
244
|
+
i += key.length;
|
|
245
|
+
if (source[i] !== "=") {
|
|
246
|
+
pairs.push([key, true]);
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
i++;
|
|
250
|
+
if (source[i] === '"') {
|
|
251
|
+
let j = i + 1;
|
|
252
|
+
while (j < source.length && source[j] !== '"') j += source[j] === "\\" ? 2 : 1;
|
|
253
|
+
if (j >= source.length) return { pairs, error: `Unterminated quoted value for ${key}.` };
|
|
254
|
+
try {
|
|
255
|
+
pairs.push([key, JSON.parse(source.slice(i, j + 1))]);
|
|
256
|
+
} catch {
|
|
257
|
+
return { pairs, error: `Invalid quoted value for ${key}; use JSON string escapes.` };
|
|
258
|
+
}
|
|
259
|
+
i = j + 1;
|
|
260
|
+
} else {
|
|
261
|
+
const value = /^[^\s,"]+/.exec(source.slice(i))?.[0];
|
|
262
|
+
if (!value) return { pairs, error: `Missing value for ${key}.` };
|
|
263
|
+
pairs.push([key, value]);
|
|
264
|
+
i += value.length;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
return { pairs };
|
|
268
|
+
}
|
|
269
|
+
function spanStyle(source, issue) {
|
|
270
|
+
const style = {};
|
|
271
|
+
const { pairs, error } = parseAttributes(source);
|
|
272
|
+
if (error) issue("markdown/span-attributes", `${error} A span reads {bold italic color=#ff0000 size=24 font="Open Sans"}.`);
|
|
273
|
+
for (const [rawKey, value] of pairs) {
|
|
274
|
+
const key = rawKey.toLowerCase();
|
|
275
|
+
const flag = SPAN_FLAGS[key];
|
|
276
|
+
const field = SPAN_VALUES[key];
|
|
277
|
+
if (flag) {
|
|
278
|
+
if (value === true || value === "true") style[flag] = true;
|
|
279
|
+
else if (value !== "false") issue("markdown/span-attributes", `Span flag ${rawKey} takes no value (or =true/false).`);
|
|
280
|
+
} else if (field === "fontSize") {
|
|
281
|
+
const size = value === true ? Number.NaN : Number(value);
|
|
282
|
+
if (Number.isFinite(size) && size > 0) style.fontSize = size;
|
|
283
|
+
else issue("markdown/span-attributes", `Span size must be a positive number, not ${JSON.stringify(value)}.`);
|
|
284
|
+
} else if (field) {
|
|
285
|
+
if (typeof value === "string" && value) style[field] = value;
|
|
286
|
+
else issue("markdown/span-attributes", `Span ${rawKey} needs a value.`);
|
|
287
|
+
} else issue("markdown/span-attributes", `Unknown span attribute ${JSON.stringify(rawKey)}. Known: bold, italic, underline, strike, sup, sub, color, size, font, lang.`);
|
|
288
|
+
}
|
|
289
|
+
return style;
|
|
290
|
+
}
|
|
291
|
+
var TAGS = { u: "underline", sup: "superscript", sub: "subscript" };
|
|
292
|
+
function matchBracket(s, start) {
|
|
293
|
+
let depth = 0;
|
|
294
|
+
for (let i = start; i < s.length; i++) {
|
|
295
|
+
const c = s[i];
|
|
296
|
+
if (c === "\\") i++;
|
|
297
|
+
else if (c === "[") depth++;
|
|
298
|
+
else if (c === "]" && --depth === 0) return i;
|
|
299
|
+
}
|
|
300
|
+
return -1;
|
|
301
|
+
}
|
|
302
|
+
var ANGLE_AT = /<([^<>\n]*)>/y;
|
|
303
|
+
function readDestination(s, open) {
|
|
304
|
+
let i = open + 1;
|
|
305
|
+
while (s[i] === " ") i++;
|
|
306
|
+
let url = "";
|
|
307
|
+
if (s[i] === "<") {
|
|
308
|
+
ANGLE_AT.lastIndex = i;
|
|
309
|
+
const angle = ANGLE_AT.exec(s);
|
|
310
|
+
if (!angle) return void 0;
|
|
311
|
+
url = unescapeInline(angle[1]);
|
|
312
|
+
i += angle[0].length;
|
|
313
|
+
} else {
|
|
314
|
+
let depth = 0;
|
|
315
|
+
const from = i;
|
|
316
|
+
for (; i < s.length && i - from <= MAX_TARGET; i++) {
|
|
317
|
+
const c = s[i];
|
|
318
|
+
if (c === "\\" && ASCII_PUNCT.test(s[i + 1] ?? "")) {
|
|
319
|
+
i++;
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
if (/\s/.test(c)) break;
|
|
323
|
+
if (c === "(") depth++;
|
|
324
|
+
else if (c === ")") {
|
|
325
|
+
if (depth === 0) break;
|
|
326
|
+
depth--;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
if (i - from > MAX_TARGET) return void 0;
|
|
330
|
+
url = unescapeInline(s.slice(from, i));
|
|
331
|
+
}
|
|
332
|
+
while (s[i] === " ") i++;
|
|
333
|
+
let title;
|
|
334
|
+
if (s[i] === '"' || s[i] === "'") {
|
|
335
|
+
const quote = s[i];
|
|
336
|
+
let j = i + 1;
|
|
337
|
+
while (j < s.length && j - i <= MAX_TARGET && s[j] !== quote) j += s[j] === "\\" ? 2 : 1;
|
|
338
|
+
if (j >= s.length || j - i > MAX_TARGET) return void 0;
|
|
339
|
+
title = unescapeInline(s.slice(i + 1, j));
|
|
340
|
+
i = j + 1;
|
|
341
|
+
while (s[i] === " ") i++;
|
|
342
|
+
}
|
|
343
|
+
if (s[i] !== ")") return void 0;
|
|
344
|
+
return title === void 0 ? { url, end: i + 1 } : { url, end: i + 1, title };
|
|
345
|
+
}
|
|
346
|
+
function unescapeInline(text) {
|
|
347
|
+
return text.replace(/\\([!-/:-@[-`{-~])/g, "$1");
|
|
348
|
+
}
|
|
349
|
+
function flanking(prev, next, ch) {
|
|
350
|
+
const left = !WS.test(next) && (!PUNCT.test(next) || WS.test(prev) || PUNCT.test(prev));
|
|
351
|
+
const right = !WS.test(prev) && (!PUNCT.test(prev) || WS.test(next) || PUNCT.test(next));
|
|
352
|
+
if (ch === "_") return { canOpen: left && (!right || PUNCT.test(prev)), canClose: right && (!left || PUNCT.test(next)) };
|
|
353
|
+
return { canOpen: left, canClose: right };
|
|
354
|
+
}
|
|
355
|
+
function bracketPairs(s) {
|
|
356
|
+
const pairs = /* @__PURE__ */ new Map();
|
|
357
|
+
const open = [];
|
|
358
|
+
for (let i = 0; i < s.length; i++) {
|
|
359
|
+
const c = s[i];
|
|
360
|
+
if (c === "\\") i++;
|
|
361
|
+
else if (c === "[") open.push(i);
|
|
362
|
+
else if (c === "]" && open.length) pairs.set(open.pop(), i);
|
|
363
|
+
}
|
|
364
|
+
return pairs;
|
|
365
|
+
}
|
|
366
|
+
function findCodeClose(s, from, n, unclosed) {
|
|
367
|
+
if (unclosed.has(n)) return -1;
|
|
368
|
+
for (let i = from; i < s.length; ) {
|
|
369
|
+
if (s[i] !== "`") {
|
|
370
|
+
i++;
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
let j = i;
|
|
374
|
+
while (s[j] === "`") j++;
|
|
375
|
+
if (j - i === n) return i;
|
|
376
|
+
i = j;
|
|
377
|
+
}
|
|
378
|
+
unclosed.add(n);
|
|
379
|
+
return -1;
|
|
380
|
+
}
|
|
381
|
+
function codeSpanText(body) {
|
|
382
|
+
const text = body.replace(/\r\n|\r|\n/g, " ");
|
|
383
|
+
return text.length > 2 && text.startsWith(" ") && text.endsWith(" ") && /[^ ]/.test(text) ? text.slice(1, -1) : text;
|
|
384
|
+
}
|
|
385
|
+
var TAG_AT = /<(\/?)(u|sup|sub)>/iy;
|
|
386
|
+
var BR_AT = /<br\s*\/?>/iy;
|
|
387
|
+
var AUTOLINK_AT = /<([A-Za-z][A-Za-z0-9+.-]{1,31}:[^\s<>]*)>/y;
|
|
388
|
+
function tokenize(s, issue, depth, budget) {
|
|
389
|
+
const tokens = [];
|
|
390
|
+
const pairs = bracketPairs(s);
|
|
391
|
+
let text = "";
|
|
392
|
+
const flush = () => {
|
|
393
|
+
if (text) tokens.push({ t: "text", v: text });
|
|
394
|
+
text = "";
|
|
395
|
+
};
|
|
396
|
+
const before = (i) => i > 0 ? s[i - 1] : " ";
|
|
397
|
+
const unclosed = /* @__PURE__ */ new Set();
|
|
398
|
+
for (let i = 0; i < s.length; ) {
|
|
399
|
+
const c = s[i];
|
|
400
|
+
if (c === "\\" && i + 1 < s.length && ASCII_PUNCT.test(s[i + 1])) {
|
|
401
|
+
text += s[i + 1];
|
|
402
|
+
i += 2;
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
405
|
+
if (c === "`") {
|
|
406
|
+
let j = i;
|
|
407
|
+
while (s[j] === "`") j++;
|
|
408
|
+
const n = j - i;
|
|
409
|
+
const close = findCodeClose(s, j, n, unclosed);
|
|
410
|
+
if (close < 0) {
|
|
411
|
+
text += s.slice(i, j);
|
|
412
|
+
i = j;
|
|
413
|
+
continue;
|
|
414
|
+
}
|
|
415
|
+
flush();
|
|
416
|
+
tokens.push({ t: "node", children: [{ text: codeSpanText(s.slice(j, close)), style: { code: true } }], apply: {} });
|
|
417
|
+
i = close + n;
|
|
418
|
+
continue;
|
|
419
|
+
}
|
|
420
|
+
if (c === "[") {
|
|
421
|
+
const close = depth < MAX_DEPTH ? pairs.get(i) ?? -1 : -1;
|
|
422
|
+
if (close > 0) {
|
|
423
|
+
const next = s[close + 1];
|
|
424
|
+
if (next === "(" && budget.left > 0) {
|
|
425
|
+
const dest = readDestination(s, close + 1);
|
|
426
|
+
budget.left -= dest ? dest.end - close : Math.min(MAX_TARGET, s.length - close);
|
|
427
|
+
if (dest) {
|
|
428
|
+
flush();
|
|
429
|
+
tokens.push({ t: "node", children: parseSegs(s.slice(i + 1, close), issue, depth + 1, budget), apply: dest.url ? { link: dest.url } : {} });
|
|
430
|
+
i = dest.end;
|
|
431
|
+
continue;
|
|
432
|
+
}
|
|
433
|
+
} else if (next === "{") {
|
|
434
|
+
const end = s.slice(close + 2, close + 2 + MAX_SPAN).indexOf("}") + close + 2;
|
|
435
|
+
if (end > close + 1 && !s.slice(close + 2, end).includes("\n")) {
|
|
436
|
+
flush();
|
|
437
|
+
tokens.push({ t: "node", children: parseSegs(s.slice(i + 1, close), issue, depth + 1, budget), apply: spanStyle(s.slice(close + 2, end), issue) });
|
|
438
|
+
i = end + 1;
|
|
439
|
+
continue;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
text += c;
|
|
444
|
+
i++;
|
|
445
|
+
continue;
|
|
446
|
+
}
|
|
447
|
+
if (c === "<") {
|
|
448
|
+
TAG_AT.lastIndex = i;
|
|
449
|
+
const tag = TAG_AT.exec(s);
|
|
450
|
+
if (tag) {
|
|
451
|
+
flush();
|
|
452
|
+
tokens.push({ t: "tag", name: TAGS[tag[2].toLowerCase()], close: tag[1] === "/", matched: false });
|
|
453
|
+
i += tag[0].length;
|
|
454
|
+
continue;
|
|
455
|
+
}
|
|
456
|
+
BR_AT.lastIndex = i;
|
|
457
|
+
const br = BR_AT.exec(s);
|
|
458
|
+
if (br) {
|
|
459
|
+
text += "\n";
|
|
460
|
+
i += br[0].length;
|
|
461
|
+
continue;
|
|
462
|
+
}
|
|
463
|
+
AUTOLINK_AT.lastIndex = i;
|
|
464
|
+
const auto = AUTOLINK_AT.exec(s);
|
|
465
|
+
if (auto) {
|
|
466
|
+
flush();
|
|
467
|
+
tokens.push({ t: "node", children: [{ text: auto[1], style: {} }], apply: { link: auto[1] } });
|
|
468
|
+
i += auto[0].length;
|
|
469
|
+
continue;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
if (c === "*" || c === "_" || c === "~") {
|
|
473
|
+
let j = i;
|
|
474
|
+
while (s[j] === c) j++;
|
|
475
|
+
const n = j - i;
|
|
476
|
+
if (n <= 3 && (c !== "~" || n === 2)) {
|
|
477
|
+
flush();
|
|
478
|
+
const { canOpen, canClose } = flanking(before(i), s[j] ?? " ", c);
|
|
479
|
+
tokens.push({ t: "delim", ch: c, n, orig: n, canOpen, canClose, opens: [], closes: [] });
|
|
480
|
+
i = j;
|
|
481
|
+
continue;
|
|
482
|
+
}
|
|
483
|
+
text += s.slice(i, j);
|
|
484
|
+
i = j;
|
|
485
|
+
continue;
|
|
486
|
+
}
|
|
487
|
+
text += c;
|
|
488
|
+
i++;
|
|
489
|
+
}
|
|
490
|
+
flush();
|
|
491
|
+
return tokens;
|
|
492
|
+
}
|
|
493
|
+
function resolve(tokens) {
|
|
494
|
+
const stack = [];
|
|
495
|
+
for (let k = 0; k < tokens.length; k++) {
|
|
496
|
+
const token = tokens[k];
|
|
497
|
+
if (token.t === "tag") {
|
|
498
|
+
if (!token.close) stack.push(k);
|
|
499
|
+
else {
|
|
500
|
+
for (let s = stack.length - 1; s >= 0; s--) {
|
|
501
|
+
const other = tokens[stack[s]];
|
|
502
|
+
if (other?.t === "tag" && other.name === token.name && !other.matched) {
|
|
503
|
+
other.matched = token.matched = true;
|
|
504
|
+
stack.length = s;
|
|
505
|
+
break;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
continue;
|
|
510
|
+
}
|
|
511
|
+
if (token.t !== "delim") continue;
|
|
512
|
+
if (token.canClose) {
|
|
513
|
+
while (token.n > 0) {
|
|
514
|
+
let found = -1;
|
|
515
|
+
for (let s = stack.length - 1; s >= 0; s--) {
|
|
516
|
+
const other = tokens[stack[s]];
|
|
517
|
+
if (other?.t === "delim" && other.ch === token.ch && other.n > 0 && other.canOpen) {
|
|
518
|
+
if ((other.canClose || token.canOpen) && (other.orig + token.orig) % 3 === 0 && !(other.orig % 3 === 0 && token.orig % 3 === 0)) continue;
|
|
519
|
+
found = s;
|
|
520
|
+
break;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
if (found < 0) break;
|
|
524
|
+
const opener = tokens[stack[found]];
|
|
525
|
+
const use = token.ch === "~" ? 2 : Math.min(opener.n, token.n) >= 2 ? 2 : 1;
|
|
526
|
+
const style = token.ch === "~" ? "strikethrough" : use === 2 ? "bold" : "italic";
|
|
527
|
+
opener.n -= use;
|
|
528
|
+
token.n -= use;
|
|
529
|
+
opener.opens.push(style);
|
|
530
|
+
token.closes.push(style);
|
|
531
|
+
stack.length = opener.n > 0 ? found + 1 : found;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
if (token.n > 0 && token.canOpen) stack.push(k);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
function addStyle(base, add) {
|
|
538
|
+
return { ...add, ...base };
|
|
539
|
+
}
|
|
540
|
+
function parseSegs(s, issue, depth, budget) {
|
|
541
|
+
const tokens = tokenize(s, issue, depth, budget);
|
|
542
|
+
resolve(tokens);
|
|
543
|
+
const segs = [];
|
|
544
|
+
const counts = {};
|
|
545
|
+
const current = () => {
|
|
546
|
+
const style = {};
|
|
547
|
+
for (const key of ["bold", "italic", "strikethrough", "underline", "superscript", "subscript"]) if ((counts[key] ?? 0) > 0) style[key] = true;
|
|
548
|
+
return style;
|
|
549
|
+
};
|
|
550
|
+
const push = (text, style = current()) => {
|
|
551
|
+
if (text) segs.push({ text, style });
|
|
552
|
+
};
|
|
553
|
+
for (const token of tokens) {
|
|
554
|
+
if (token.t === "text") push(token.v);
|
|
555
|
+
else if (token.t === "node") for (const child of token.children) segs.push({ text: child.text, style: addStyle(child.style, addStyle(token.apply, current())) });
|
|
556
|
+
else if (token.t === "tag") {
|
|
557
|
+
if (!token.matched) push(`<${token.close ? "/" : ""}${token.name === "underline" ? "u" : token.name === "superscript" ? "sup" : "sub"}>`);
|
|
558
|
+
else counts[token.name] = (counts[token.name] ?? 0) + (token.close ? -1 : 1);
|
|
559
|
+
} else {
|
|
560
|
+
for (const style of token.closes) counts[style] = (counts[style] ?? 0) - 1;
|
|
561
|
+
push(token.ch.repeat(token.n));
|
|
562
|
+
for (const style of [...token.opens].reverse()) counts[style] = (counts[style] ?? 0) + 1;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
return segs;
|
|
566
|
+
}
|
|
567
|
+
var sameStyle = (a, b) => {
|
|
568
|
+
const keys = /* @__PURE__ */ new Set([...Object.keys(a), ...Object.keys(b)]);
|
|
569
|
+
for (const key of keys) if (a[key] !== b[key]) return false;
|
|
570
|
+
return true;
|
|
571
|
+
};
|
|
572
|
+
function parseInlineSegments(text, issue = () => {
|
|
573
|
+
}) {
|
|
574
|
+
const merged = [];
|
|
575
|
+
const budget = { left: 40 * text.length + 1e3 };
|
|
576
|
+
for (const seg of parseSegs(text, issue, 0, budget)) {
|
|
577
|
+
const last = merged.at(-1);
|
|
578
|
+
if (last && sameStyle(last.style, seg.style)) last.text += seg.text;
|
|
579
|
+
else merged.push({ text: seg.text, style: seg.style });
|
|
580
|
+
}
|
|
581
|
+
return merged;
|
|
582
|
+
}
|
|
583
|
+
var toRun = (seg) => {
|
|
584
|
+
const keys = Object.keys(seg.style);
|
|
585
|
+
if (!keys.length) return seg.text;
|
|
586
|
+
const run = { text: seg.text };
|
|
587
|
+
for (const key of FORMATTING) if (seg.style[key] !== void 0) run[key] = seg.style[key];
|
|
588
|
+
return run;
|
|
589
|
+
};
|
|
590
|
+
function parseInline(text, issue = () => {
|
|
591
|
+
}) {
|
|
592
|
+
const segs = parseInlineSegments(text, issue);
|
|
593
|
+
if (!segs.length) return "";
|
|
594
|
+
if (segs.every((seg) => !Object.keys(seg.style).length)) return segs.map((seg) => seg.text).join("");
|
|
595
|
+
return segs.map(toRun);
|
|
596
|
+
}
|
|
597
|
+
function parsePlainInline(text, issue = () => {
|
|
598
|
+
}) {
|
|
599
|
+
const segs = parseInlineSegments(text, issue);
|
|
600
|
+
if (segs.some((seg) => Object.keys(seg.style).some((key) => key !== "code"))) issue("markdown/formatting-dropped", "This field holds plain text, so bold, italic, links and other formatting in it were dropped.");
|
|
601
|
+
return segs.map((seg) => seg.style.code ? codeSpan(seg.text) : seg.text).join("");
|
|
602
|
+
}
|
|
603
|
+
var BLOCK_START = [
|
|
604
|
+
/^#{1,6}(?:\s|$)/,
|
|
605
|
+
// heading
|
|
606
|
+
/^>/,
|
|
607
|
+
// quote
|
|
608
|
+
/^[-+*](?:\s|$)/,
|
|
609
|
+
// bullet
|
|
610
|
+
/^\d{1,9}[.)](?:\s|$)/,
|
|
611
|
+
// numbered item
|
|
612
|
+
/^(?:`{3,}|~{3,})/,
|
|
613
|
+
// fence
|
|
614
|
+
/^-{3,}\s*$/,
|
|
615
|
+
// slide separator
|
|
616
|
+
/^\|/,
|
|
617
|
+
// table
|
|
618
|
+
/^!\[/,
|
|
619
|
+
// image
|
|
620
|
+
/^Notes?:/,
|
|
621
|
+
// speaker notes
|
|
622
|
+
/^<!--/,
|
|
623
|
+
// comment
|
|
624
|
+
/^:(?:\s|$)/
|
|
625
|
+
// list item description
|
|
626
|
+
];
|
|
627
|
+
function escapeLineStart(line) {
|
|
628
|
+
if (!BLOCK_START.some((pattern) => pattern.test(line))) return line;
|
|
629
|
+
const match = /^(\d{1,9})([.)])/.exec(line);
|
|
630
|
+
if (match) return `${match[1]}\\${line.slice(match[1].length)}`;
|
|
631
|
+
const notes = /^(Notes?)(:)/.exec(line);
|
|
632
|
+
if (notes) return `${notes[1]}\\${line.slice(notes[1].length)}`;
|
|
633
|
+
return `\\${line}`;
|
|
634
|
+
}
|
|
635
|
+
function escapeInline(text) {
|
|
636
|
+
let out = "";
|
|
637
|
+
for (let i = 0; i < text.length; i++) {
|
|
638
|
+
const c = text[i];
|
|
639
|
+
const next = text[i + 1];
|
|
640
|
+
if (c === "\\") out += next === void 0 || ASCII_PUNCT.test(next) ? "\\\\" : c;
|
|
641
|
+
else if (c === "*" || c === "[" || c === "]" || c === "`") out += `\\${c}`;
|
|
642
|
+
else if (c === "<") out += next !== void 0 && /[A-Za-z/!]/.test(next) ? "\\<" : c;
|
|
643
|
+
else if (c === "_") out += ALNUM.test(text[i - 1] ?? "") && ALNUM.test(text[i + 1] ?? "") ? c : "\\_";
|
|
644
|
+
else if (c === "~") out += text[i - 1] === "~" || text[i + 1] === "~" ? "\\~" : c;
|
|
645
|
+
else out += c;
|
|
646
|
+
}
|
|
647
|
+
return out;
|
|
648
|
+
}
|
|
649
|
+
function serializeRun(run, breakText) {
|
|
650
|
+
const text = runText(run);
|
|
651
|
+
if (typeof run === "string") return text.split("\n").map(escapeInline).join(breakText);
|
|
652
|
+
if (!isRecord(run) || typeof run.text !== "string") return void 0;
|
|
653
|
+
for (const key of Object.keys(run)) if (key !== "text" && !FORMATTING.includes(key)) return void 0;
|
|
654
|
+
const edge = (s) => s.split("\n").map(escapeInline).join(breakText);
|
|
655
|
+
let lead = "";
|
|
656
|
+
let trail = "";
|
|
657
|
+
let out;
|
|
658
|
+
if (run.code === true) {
|
|
659
|
+
if (!text) return "";
|
|
660
|
+
if (/[\r\n]/.test(text)) return void 0;
|
|
661
|
+
out = codeSpan(text);
|
|
662
|
+
} else {
|
|
663
|
+
lead = /^\s*/.exec(text)[0];
|
|
664
|
+
trail = /\s*$/.exec(text.slice(lead.length))[0];
|
|
665
|
+
const core = text.slice(lead.length, text.length - trail.length);
|
|
666
|
+
if (!core) return edge(text);
|
|
667
|
+
out = core.split("\n").map(escapeInline).join(breakText);
|
|
668
|
+
}
|
|
669
|
+
if (run.color !== void 0 || run.fontSize !== void 0 || run.fontFamily !== void 0 || run.lang !== void 0) {
|
|
670
|
+
const attrs = [];
|
|
671
|
+
if (run.color !== void 0) attrs.push(`color=${attrValue(String(run.color))}`);
|
|
672
|
+
if (run.fontSize !== void 0) attrs.push(`size=${String(run.fontSize)}`);
|
|
673
|
+
if (run.fontFamily !== void 0) attrs.push(`font=${attrValue(String(run.fontFamily))}`);
|
|
674
|
+
if (run.lang !== void 0) attrs.push(`lang=${attrValue(String(run.lang))}`);
|
|
675
|
+
out = `[${out}]{${attrs.join(" ")}}`;
|
|
676
|
+
}
|
|
677
|
+
if (run.subscript === true) out = `<sub>${out}</sub>`;
|
|
678
|
+
if (run.superscript === true) out = `<sup>${out}</sup>`;
|
|
679
|
+
if (run.underline === true) out = `<u>${out}</u>`;
|
|
680
|
+
if (run.strikethrough === true) out = `~~${out}~~`;
|
|
681
|
+
if (run.italic === true) out = `*${out}*`;
|
|
682
|
+
if (run.bold === true) out = `**${out}**`;
|
|
683
|
+
if (run.link !== void 0) {
|
|
684
|
+
const url = String(run.link);
|
|
685
|
+
if (!url || /[\n\r<>]/.test(url)) return void 0;
|
|
686
|
+
out = `[${out}](${/^[^\s()]+$/.test(url) ? url : `<${url}>`})`;
|
|
687
|
+
}
|
|
688
|
+
return edge(lead) + out + edge(trail);
|
|
689
|
+
}
|
|
690
|
+
function codeSpan(text) {
|
|
691
|
+
const longest = Math.max(0, ...[...text.matchAll(/`+/g)].map((match) => match[0].length));
|
|
692
|
+
const fence = "`".repeat(longest + 1);
|
|
693
|
+
const pad = /^`|`$/.test(text) || text.startsWith(" ") && text.endsWith(" ") && /[^ ]/.test(text) ? " " : "";
|
|
694
|
+
return `${fence}${pad}${text}${pad}${fence}`;
|
|
695
|
+
}
|
|
696
|
+
var attrValue = (value) => /^[^\s,"}]+$/.test(value) ? value : JSON.stringify(value);
|
|
697
|
+
function serializeRuns(value, mode = "paragraph") {
|
|
698
|
+
const runs = typeof value === "string" ? [value] : Array.isArray(value) ? value : void 0;
|
|
699
|
+
if (!runs) return void 0;
|
|
700
|
+
const parts = [];
|
|
701
|
+
for (const run of runs) {
|
|
702
|
+
const part = serializeRun(run, mode === "cell" ? "<br>" : "\\\n");
|
|
703
|
+
if (part === void 0) return void 0;
|
|
704
|
+
parts.push(part);
|
|
705
|
+
}
|
|
706
|
+
const lines = parts.join("").split("\n");
|
|
707
|
+
return mode === "cell" ? [lines.join("")] : lines.map(escapeLineStart);
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
// src/markdown/parse.ts
|
|
711
|
+
var SLIDE_OPTION_KEYS = ["id", "type", "layout", "section", "tag", "hidden", "beat"];
|
|
712
|
+
var BLOCK_OPTION_KEYS = ["id", "type", "as", "region"];
|
|
713
|
+
var BLANK = /^\s*$/;
|
|
714
|
+
var TIMELINE_STATUS_PREFIX = /^\[([x> ])\]\s+(?=\S)/;
|
|
715
|
+
var TIMELINE_STATUS_OF_MARK = { x: "done", ">": "current", " ": "planned" };
|
|
716
|
+
var FENCE_OPEN = /^( {0,3})(`{3,}|~{3,})(.*)$/;
|
|
717
|
+
var SEPARATOR = /^-{3,}[ \t]*$/;
|
|
718
|
+
var HEADING = /^ {0,3}(#{1,6})(?:[ \t]+(.*?))?[ \t]*$/;
|
|
719
|
+
var QUOTE = /^ {0,3}>/;
|
|
720
|
+
var LIST_ITEM = /^([ \t]*)([-+*]|\d{1,9}[.)])(?:[ \t]+(.*))?$/;
|
|
721
|
+
var TABLE_ROW = /^ {0,3}\|/;
|
|
722
|
+
var TABLE_DELIMITER = /^ {0,3}\|?[ \t]*:?-+:?[ \t]*(?:\|[ \t]*:?-+:?[ \t]*)*\|?[ \t]*$/;
|
|
723
|
+
var IMAGE = /^ {0,3}!\[/;
|
|
724
|
+
var COMMENT = /^ {0,3}<!--/;
|
|
725
|
+
var NOTES = /^(Notes?):[ \t]*(.*)$/;
|
|
726
|
+
var indentWidth = (space) => [...space].reduce((width, char) => width + (char === " " ? 4 : 1), 0);
|
|
727
|
+
function fenceClose(open) {
|
|
728
|
+
return new RegExp(`^ {0,3}${open[0] === "`" ? "`" : "~"}{${open.length},}[ \\t]*$`);
|
|
729
|
+
}
|
|
730
|
+
function fenceStart(text) {
|
|
731
|
+
const match = FENCE_OPEN.exec(text);
|
|
732
|
+
if (!match) return void 0;
|
|
733
|
+
if (match[2][0] === "`" && match[3].includes("`")) return void 0;
|
|
734
|
+
return { fence: match[2], info: match[3].trim() };
|
|
735
|
+
}
|
|
736
|
+
var tableStart = (lines, i) => TABLE_ROW.test(lines[i].text) && lines[i + 1] !== void 0 && TABLE_DELIMITER.test(lines[i + 1].text) && lines[i + 1].text.includes("-");
|
|
737
|
+
function listMarker(text) {
|
|
738
|
+
const match = LIST_ITEM.exec(text);
|
|
739
|
+
if (!match) return void 0;
|
|
740
|
+
const marker = match[2];
|
|
741
|
+
const ordered = /^\d/.test(marker);
|
|
742
|
+
return { indent: indentWidth(match[1]), marker, content: match[3] ?? "", ordered, family: ordered ? marker.slice(-1) : marker };
|
|
743
|
+
}
|
|
744
|
+
function interrupts(lines, i) {
|
|
745
|
+
const text = lines[i].text;
|
|
746
|
+
if (BLANK.test(text) || fenceStart(text) || HEADING.test(text) && !/^ {0,3}#{7}/.test(text) || QUOTE.test(text) || COMMENT.test(text) || NOTES.test(text) || SEPARATOR.test(text)) return true;
|
|
747
|
+
if (tableStart(lines, i)) return true;
|
|
748
|
+
if (IMAGE.test(text) && /^ {0,3}!\[(?:[^\]\\]|\\.)*\]\(.*\)[ \t]*$/.test(text)) return true;
|
|
749
|
+
const item = listMarker(text);
|
|
750
|
+
return !!item && item.indent < 4 && item.content.trim() !== "" && (!item.ordered || /^0*1[.)]$/.test(item.marker));
|
|
751
|
+
}
|
|
752
|
+
function splitSegments(lines, split) {
|
|
753
|
+
const segments = [[]];
|
|
754
|
+
let fence;
|
|
755
|
+
let comment = false;
|
|
756
|
+
let notes = false;
|
|
757
|
+
const hasContent = (segment) => segment.some((line) => !BLANK.test(line.text) && !/^\s*<!--.*-->\s*$/.test(line.text));
|
|
758
|
+
for (const line of lines) {
|
|
759
|
+
const text = line.text;
|
|
760
|
+
const current = segments.at(-1);
|
|
761
|
+
if (fence !== void 0) {
|
|
762
|
+
if (fenceClose(fence).test(text)) fence = void 0;
|
|
763
|
+
current.push(line);
|
|
764
|
+
continue;
|
|
765
|
+
}
|
|
766
|
+
if (comment) {
|
|
767
|
+
if (text.includes("-->")) comment = false;
|
|
768
|
+
current.push(line);
|
|
769
|
+
continue;
|
|
770
|
+
}
|
|
771
|
+
if (SEPARATOR.test(text)) {
|
|
772
|
+
segments.push([]);
|
|
773
|
+
notes = false;
|
|
774
|
+
continue;
|
|
775
|
+
}
|
|
776
|
+
if (split === "headings" && /^#(?:[ \t]|$)/.test(text) && hasContent(current)) {
|
|
777
|
+
segments.push([line]);
|
|
778
|
+
notes = false;
|
|
779
|
+
continue;
|
|
780
|
+
}
|
|
781
|
+
if (!notes) {
|
|
782
|
+
const open = fenceStart(text);
|
|
783
|
+
if (open) fence = open.fence;
|
|
784
|
+
else if (COMMENT.test(text) && !text.includes("-->", text.indexOf("<!--") + 4)) comment = true;
|
|
785
|
+
else if (NOTES.test(text)) notes = true;
|
|
786
|
+
}
|
|
787
|
+
current.push(line);
|
|
788
|
+
}
|
|
789
|
+
return segments;
|
|
790
|
+
}
|
|
791
|
+
function joinParagraph(lines) {
|
|
792
|
+
let out = "";
|
|
793
|
+
lines.forEach((raw, k) => {
|
|
794
|
+
let text = raw.trim();
|
|
795
|
+
let separator = " ";
|
|
796
|
+
if (k < lines.length - 1) {
|
|
797
|
+
const slashes = /\\+$/.exec(text)?.[0].length ?? 0;
|
|
798
|
+
if (slashes % 2 === 1) {
|
|
799
|
+
text = text.slice(0, -1);
|
|
800
|
+
separator = "\n";
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
out += text + (k < lines.length - 1 ? separator : "");
|
|
804
|
+
});
|
|
805
|
+
return out;
|
|
806
|
+
}
|
|
807
|
+
var bold = (value) => typeof value === "string" ? [{ text: value, bold: true }] : value.map((run) => typeof run === "string" ? { text: run, bold: true } : { ...run, bold: true });
|
|
808
|
+
function parseOptions(text, keys, kind, ctx, range) {
|
|
809
|
+
const { pairs, error } = parseAttributes(text);
|
|
810
|
+
const help = `A ${kind} options comment reads <!-- ${kind}: ${kind === "slide" ? 'id=cover layout=title section="Part 1" hidden' : "id=chart-1 as=bullets region=top:left"} -->. Keys: ${keys.join(", ")}.`;
|
|
811
|
+
if (error) {
|
|
812
|
+
ctx.error("options-syntax", error, help, range);
|
|
813
|
+
return void 0;
|
|
814
|
+
}
|
|
815
|
+
const out = {};
|
|
816
|
+
let ok = true;
|
|
817
|
+
for (const [key, value] of pairs) {
|
|
818
|
+
if (!keys.includes(key)) {
|
|
819
|
+
ctx.error("options-unknown-key", `Unknown ${kind} option ${JSON.stringify(key)}.`, `${help} Anything else goes in a fenced opf-${kind} block.`, range);
|
|
820
|
+
ok = false;
|
|
821
|
+
} else if (key === "hidden") {
|
|
822
|
+
if (value === true || value === "true") out.hidden = true;
|
|
823
|
+
else if (value === "false") out.hidden = false;
|
|
824
|
+
else {
|
|
825
|
+
ctx.error("options-value", `hidden takes true or false, not ${JSON.stringify(value)}.`, help, range);
|
|
826
|
+
ok = false;
|
|
827
|
+
}
|
|
828
|
+
} else if (value === true) {
|
|
829
|
+
ctx.error("options-value", `Option ${key} needs a value: ${key}=value.`, help, range);
|
|
830
|
+
ok = false;
|
|
831
|
+
} else if (key === "beat" && out.beat !== void 0) out.beat = [...Array.isArray(out.beat) ? out.beat : [out.beat], value];
|
|
832
|
+
else if (key in out) {
|
|
833
|
+
ctx.error("options-duplicate", `Option ${key} is given twice.`, help, range);
|
|
834
|
+
ok = false;
|
|
835
|
+
} else if (key === "as" && value !== "bullets" && value !== "video") {
|
|
836
|
+
ctx.error("options-value", `as takes bullets (a list stored as bullets) or video (an image line stored as video), not ${JSON.stringify(value)}.`, help, range);
|
|
837
|
+
ok = false;
|
|
838
|
+
} else out[key] = value;
|
|
839
|
+
}
|
|
840
|
+
return ok ? out : void 0;
|
|
841
|
+
}
|
|
842
|
+
var SlideParser = class {
|
|
843
|
+
constructor(ctx, lines, path) {
|
|
844
|
+
this.ctx = ctx;
|
|
845
|
+
this.lines = lines;
|
|
846
|
+
this.path = path;
|
|
847
|
+
}
|
|
848
|
+
ctx;
|
|
849
|
+
lines;
|
|
850
|
+
path;
|
|
851
|
+
slide = {};
|
|
852
|
+
blocks = [];
|
|
853
|
+
slideOptions;
|
|
854
|
+
slideOptionsRange;
|
|
855
|
+
pending;
|
|
856
|
+
title;
|
|
857
|
+
subtitle;
|
|
858
|
+
notes;
|
|
859
|
+
extras;
|
|
860
|
+
notesRange;
|
|
861
|
+
i = 0;
|
|
862
|
+
issueAt(range) {
|
|
863
|
+
return (rule, message) => rule === "markdown/formatting-dropped" ? this.ctx.warn("formatting-dropped", message, "Use a plain string here, or put the field in a fenced opf-slide block.", range) : this.ctx.error(rule.replace(/^markdown\//, ""), message, "Fix the inline syntax.", range);
|
|
864
|
+
}
|
|
865
|
+
inline(text, range) {
|
|
866
|
+
return parseInline(text, this.issueAt(range));
|
|
867
|
+
}
|
|
868
|
+
plain(text, range) {
|
|
869
|
+
return parsePlainInline(text, this.issueAt(range));
|
|
870
|
+
}
|
|
871
|
+
run() {
|
|
872
|
+
const { lines, ctx } = this;
|
|
873
|
+
while (this.i < lines.length) {
|
|
874
|
+
const start = this.i;
|
|
875
|
+
const line = lines[start];
|
|
876
|
+
if (BLANK.test(line.text)) {
|
|
877
|
+
this.i++;
|
|
878
|
+
continue;
|
|
879
|
+
}
|
|
880
|
+
if (COMMENT.test(line.text)) this.comment();
|
|
881
|
+
else if (NOTES.test(line.text)) this.parseNotes();
|
|
882
|
+
else if (fenceStart(line.text)) this.fence();
|
|
883
|
+
else if (HEADING.test(line.text) && !/^ {0,3}#{7}/.test(line.text)) this.heading();
|
|
884
|
+
else if (QUOTE.test(line.text)) this.quote();
|
|
885
|
+
else if (tableStart(lines, start)) this.table();
|
|
886
|
+
else if (this.imageLine()) ; else if (listMarker(line.text) && listMarker(line.text).indent < 4) this.list();
|
|
887
|
+
else this.paragraph();
|
|
888
|
+
if (this.i === start) this.i++;
|
|
889
|
+
}
|
|
890
|
+
if (this.pending) ctx.error("options-orphan", "A block options comment is not followed by a block.", "Put the <!-- block: ... --> comment directly before the block it describes.", this.pending.range, this.path);
|
|
891
|
+
return this.finish();
|
|
892
|
+
}
|
|
893
|
+
add(block) {
|
|
894
|
+
if (this.pending) {
|
|
895
|
+
block.options = this.pending.options;
|
|
896
|
+
this.pending = void 0;
|
|
897
|
+
}
|
|
898
|
+
this.blocks.push(block);
|
|
899
|
+
}
|
|
900
|
+
native(key, value, range) {
|
|
901
|
+
const block = { payload: { [key]: value }, range, key };
|
|
902
|
+
this.add(block);
|
|
903
|
+
if (block.options) {
|
|
904
|
+
const as = block.options.as;
|
|
905
|
+
const stored = as === "bullets" && key === "items" ? "bullets" : as === "video" && key === "image" ? "video" : key;
|
|
906
|
+
if (as !== void 0 && stored === key) this.ctx.error("options-value", `as=${as} does not apply to this block.`, "as=bullets goes before a list, as=video before an image line.", range, this.path);
|
|
907
|
+
block.payload = { [stored]: value };
|
|
908
|
+
block.key = stored;
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
// --- comments --------------------------------------------------------------------------------
|
|
912
|
+
comment() {
|
|
913
|
+
const { lines, ctx } = this;
|
|
914
|
+
const first = lines[this.i];
|
|
915
|
+
let end = this.i;
|
|
916
|
+
while (end < lines.length && !lines[end].text.includes("-->", end === this.i ? lines[end].text.indexOf("<!--") + 4 : 0)) end++;
|
|
917
|
+
if (end >= lines.length) {
|
|
918
|
+
end = lines.length - 1;
|
|
919
|
+
ctx.error("comment-unterminated", "An HTML comment is not closed.", "End the comment with -->.", lineRange(first, lines[end]), this.path);
|
|
920
|
+
}
|
|
921
|
+
const last = lines[end];
|
|
922
|
+
const range = lineRange(first, last);
|
|
923
|
+
const body = lines.slice(this.i, end + 1).map((line) => line.text).join(" ");
|
|
924
|
+
this.i = end + 1;
|
|
925
|
+
const match = /^\s*<!--\s*(slide|block)(?::|(?=\s*-->))([\s\S]*?)-->([\s\S]*)$/.exec(body);
|
|
926
|
+
if (!match) return;
|
|
927
|
+
const kind = match[1];
|
|
928
|
+
if (match[3].trim()) ctx.error("options-trailing", "Text follows a closing --> on the same line.", "Put content on its own line after the options comment.", range, this.path);
|
|
929
|
+
const options = parseOptions(match[2], kind === "slide" ? SLIDE_OPTION_KEYS : BLOCK_OPTION_KEYS, kind, ctx, range);
|
|
930
|
+
if (!options) return;
|
|
931
|
+
if (kind === "slide") {
|
|
932
|
+
if (this.slideOptions) ctx.error("options-duplicate", "A slide has more than one <!-- slide: ... --> comment.", "Merge the options into one comment.", range, this.path);
|
|
933
|
+
else {
|
|
934
|
+
this.slideOptions = options;
|
|
935
|
+
this.slideOptionsRange = range;
|
|
936
|
+
}
|
|
937
|
+
} else if (this.pending) ctx.error("options-duplicate", "Two block options comments in a row.", "Merge them into one comment.", range, this.path);
|
|
938
|
+
else this.pending = { options, range };
|
|
939
|
+
}
|
|
940
|
+
// --- notes -----------------------------------------------------------------------------------
|
|
941
|
+
parseNotes() {
|
|
942
|
+
const { lines } = this;
|
|
943
|
+
const first = lines[this.i];
|
|
944
|
+
const match = NOTES.exec(first.text);
|
|
945
|
+
const rows = [match[2], ...lines.slice(this.i + 1).map((line) => line.text)];
|
|
946
|
+
while (rows.length && BLANK.test(rows[0])) rows.shift();
|
|
947
|
+
while (rows.length && BLANK.test(rows.at(-1))) rows.pop();
|
|
948
|
+
const range = lineRange(first, lines.at(-1));
|
|
949
|
+
this.notesRange = range;
|
|
950
|
+
this.notes = rows.join("\n");
|
|
951
|
+
this.i = lines.length;
|
|
952
|
+
}
|
|
953
|
+
// --- headings --------------------------------------------------------------------------------
|
|
954
|
+
heading() {
|
|
955
|
+
const { lines, ctx } = this;
|
|
956
|
+
const line = lines[this.i++];
|
|
957
|
+
const match = HEADING.exec(line.text);
|
|
958
|
+
const level = match[1].length;
|
|
959
|
+
const text = (match[2] ?? "").replace(/(?:^|[ \t]+)#+[ \t]*$/, "").trim();
|
|
960
|
+
const range = lineRange(line);
|
|
961
|
+
if (!text) {
|
|
962
|
+
ctx.error("empty-heading", "A heading has no text.", "Write the title after the #, or remove the line.", range, this.path);
|
|
963
|
+
return;
|
|
964
|
+
}
|
|
965
|
+
if (level === 1) {
|
|
966
|
+
if (this.title !== void 0) ctx.error("duplicate-title", "A slide has more than one # title.", "Use --- to start a new slide, or `##` for the subtitle.", range, `${this.path}/title`);
|
|
967
|
+
else {
|
|
968
|
+
this.title = this.inline(text, range);
|
|
969
|
+
ctx.ranges.set(`${this.path}/title`, range);
|
|
970
|
+
}
|
|
971
|
+
} else if (level === 2) {
|
|
972
|
+
if (this.subtitle !== void 0) ctx.error("duplicate-subtitle", "A slide has more than one ## subtitle.", "A slide has one subtitle. Use a paragraph for further lines.", range, `${this.path}/subtitle`);
|
|
973
|
+
else {
|
|
974
|
+
this.subtitle = this.inline(text, range);
|
|
975
|
+
ctx.ranges.set(`${this.path}/subtitle`, range);
|
|
976
|
+
}
|
|
977
|
+
} else {
|
|
978
|
+
ctx.warn("heading-demoted", `A level-${level} heading has no OPF form, so it became a bold paragraph.`, "Only # (title) and ## (subtitle) are slide fields. Use a bold paragraph on purpose to silence this.", range, this.path);
|
|
979
|
+
this.native("text", bold(this.inline(text, range)), range);
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
// --- paragraph -------------------------------------------------------------------------------
|
|
983
|
+
paragraph() {
|
|
984
|
+
const { lines } = this;
|
|
985
|
+
const first = this.i;
|
|
986
|
+
const rows = [];
|
|
987
|
+
do
|
|
988
|
+
rows.push(lines[this.i++].text);
|
|
989
|
+
while (this.i < lines.length && !interrupts(lines, this.i));
|
|
990
|
+
const range = lineRange(lines[first], lines[this.i - 1]);
|
|
991
|
+
this.native("text", this.inline(joinParagraph(rows), range), range);
|
|
992
|
+
}
|
|
993
|
+
// --- quote -----------------------------------------------------------------------------------
|
|
994
|
+
quote() {
|
|
995
|
+
const { lines, ctx } = this;
|
|
996
|
+
const first = this.i;
|
|
997
|
+
const rows = [];
|
|
998
|
+
while (this.i < lines.length && QUOTE.test(lines[this.i].text)) rows.push(lines[this.i++].text.replace(/^ {0,3}> ?/, ""));
|
|
999
|
+
const range = lineRange(lines[first], lines[this.i - 1]);
|
|
1000
|
+
while (rows.length && BLANK.test(rows[0])) rows.shift();
|
|
1001
|
+
while (rows.length && BLANK.test(rows.at(-1))) rows.pop();
|
|
1002
|
+
if (!rows.length) {
|
|
1003
|
+
ctx.error("empty-quote", "A block quote has no text.", "Write the quote after >.", range, this.path);
|
|
1004
|
+
return;
|
|
1005
|
+
}
|
|
1006
|
+
const parsed = parseQuoteLines(rows);
|
|
1007
|
+
const paragraphs = [[]];
|
|
1008
|
+
for (const row of parsed.text) {
|
|
1009
|
+
if (BLANK.test(row)) paragraphs.push([]);
|
|
1010
|
+
else paragraphs.at(-1).push(row);
|
|
1011
|
+
}
|
|
1012
|
+
const text = this.inline(
|
|
1013
|
+
paragraphs.filter((paragraph) => paragraph.length).map(joinParagraph).join("\n\n"),
|
|
1014
|
+
range
|
|
1015
|
+
);
|
|
1016
|
+
const value = { text };
|
|
1017
|
+
if (parsed.attribution !== void 0) value.attribution = this.plain(parsed.attribution, range);
|
|
1018
|
+
if (parsed.source !== void 0) value.source = this.plain(parsed.source, range);
|
|
1019
|
+
this.native("quote", Object.keys(value).length === 1 && typeof value.text === "string" ? value.text : value, range);
|
|
1020
|
+
}
|
|
1021
|
+
// --- lists -----------------------------------------------------------------------------------
|
|
1022
|
+
list() {
|
|
1023
|
+
const { lines, ctx } = this;
|
|
1024
|
+
const first = this.i;
|
|
1025
|
+
const entries = [];
|
|
1026
|
+
const head = listMarker(lines[first].text);
|
|
1027
|
+
let topIndent = head.indent;
|
|
1028
|
+
let numbered = false;
|
|
1029
|
+
let i = first;
|
|
1030
|
+
while (i < lines.length) {
|
|
1031
|
+
const text = lines[i].text;
|
|
1032
|
+
if (BLANK.test(text)) {
|
|
1033
|
+
let j = i + 1;
|
|
1034
|
+
while (j < lines.length && BLANK.test(lines[j].text)) j++;
|
|
1035
|
+
const item2 = j < lines.length ? listMarker(lines[j].text) : void 0;
|
|
1036
|
+
if (!item2) break;
|
|
1037
|
+
if (item2.indent <= topIndent && item2.family !== head.family) break;
|
|
1038
|
+
i = j;
|
|
1039
|
+
continue;
|
|
1040
|
+
}
|
|
1041
|
+
const item = listMarker(text);
|
|
1042
|
+
if (item) {
|
|
1043
|
+
if (item.indent <= topIndent) {
|
|
1044
|
+
if (item.family !== head.family) break;
|
|
1045
|
+
topIndent = Math.min(topIndent, item.indent);
|
|
1046
|
+
}
|
|
1047
|
+
if (item.ordered) numbered = true;
|
|
1048
|
+
entries.push({ indent: item.indent, rows: [item.content.trim()] });
|
|
1049
|
+
i++;
|
|
1050
|
+
continue;
|
|
1051
|
+
}
|
|
1052
|
+
const current = entries.at(-1);
|
|
1053
|
+
if (!current || interrupts(lines, i)) break;
|
|
1054
|
+
const description = /^([ \t]+):[ \t]+(.*)$/.exec(text);
|
|
1055
|
+
if (description && indentWidth(description[1]) > current.indent && !current.description) current.description = [description[2].trim()];
|
|
1056
|
+
else if (current.description) current.description.push(text.trim());
|
|
1057
|
+
else current.rows.push(text);
|
|
1058
|
+
i++;
|
|
1059
|
+
}
|
|
1060
|
+
this.i = i;
|
|
1061
|
+
const last = Math.max(first, i - 1);
|
|
1062
|
+
let end = last;
|
|
1063
|
+
while (end > first && BLANK.test(lines[end].text)) end--;
|
|
1064
|
+
const range = lineRange(lines[first], lines[end]);
|
|
1065
|
+
if (numbered) ctx.warn("numbered-list", "Numbers in a list are not kept: the Markdown dialect does not read them as `numbering`, so each item became a bullet.", "Add `numbering` to the slide or block in an opf-slide or opf-block fence (docs/numbered-lists.md), write the numbers into the text, or use a bulleted list.", range, this.path);
|
|
1066
|
+
const levels = levelsFromIndents(entries.map((entry) => entry.indent));
|
|
1067
|
+
const items = entries.map((entry, k) => {
|
|
1068
|
+
const text = this.inline(joinParagraph(entry.rows), range);
|
|
1069
|
+
const description = entry.description ? this.inline(joinParagraph(entry.description), range) : void 0;
|
|
1070
|
+
const level = levels[k];
|
|
1071
|
+
if (!level && description === void 0) return text;
|
|
1072
|
+
const out = { text };
|
|
1073
|
+
if (description !== void 0) out.description = description;
|
|
1074
|
+
if (level) out.level = level;
|
|
1075
|
+
return out;
|
|
1076
|
+
});
|
|
1077
|
+
this.native("items", items, range);
|
|
1078
|
+
}
|
|
1079
|
+
// --- tables ----------------------------------------------------------------------------------
|
|
1080
|
+
table() {
|
|
1081
|
+
const { lines, ctx } = this;
|
|
1082
|
+
const first = this.i;
|
|
1083
|
+
this.i += 2;
|
|
1084
|
+
while (this.i < lines.length && TABLE_ROW.test(lines[this.i].text)) this.i++;
|
|
1085
|
+
const range = lineRange(lines[first], lines[this.i - 1]);
|
|
1086
|
+
const header = splitRow(lines[first].text);
|
|
1087
|
+
const rows = lines.slice(first + 2, this.i).map((line) => splitRow(line.text));
|
|
1088
|
+
const cell = (text) => this.inline(text, range);
|
|
1089
|
+
const out = {};
|
|
1090
|
+
if (header.some((text) => text !== "")) out.columns = header.map((text) => text === "" ? null : cell(text));
|
|
1091
|
+
out.rows = rows.map((row) => {
|
|
1092
|
+
if (header.length && row.length !== header.length) ctx.warn("table-ragged", `A table row has ${row.length} cells but the header has ${header.length}.`, "Give every row the same number of cells.", range, this.path);
|
|
1093
|
+
const cells = row.map((text) => text === "" ? null : cell(text));
|
|
1094
|
+
while (header.length && cells.length < header.length) cells.push(null);
|
|
1095
|
+
return cells;
|
|
1096
|
+
});
|
|
1097
|
+
this.native("table", out, range);
|
|
1098
|
+
}
|
|
1099
|
+
// --- images ----------------------------------------------------------------------------------
|
|
1100
|
+
imageLine() {
|
|
1101
|
+
const { lines, ctx } = this;
|
|
1102
|
+
const line = lines[this.i];
|
|
1103
|
+
const text = line.text.replace(/^ {0,3}/, "");
|
|
1104
|
+
if (!text.startsWith("![")) return false;
|
|
1105
|
+
const close = matchBracket(text, 1);
|
|
1106
|
+
if (close < 0 || text[close + 1] !== "(") return false;
|
|
1107
|
+
const dest = readDestination(text, close + 1);
|
|
1108
|
+
if (!dest || text.slice(dest.end).trim() !== "") return false;
|
|
1109
|
+
this.i++;
|
|
1110
|
+
const range = lineRange(line);
|
|
1111
|
+
if (!dest.url) {
|
|
1112
|
+
ctx.error("image-source", "An image has no source.", "Write the file or URL inside the parentheses: .", range, this.path);
|
|
1113
|
+
return true;
|
|
1114
|
+
}
|
|
1115
|
+
const alt = unescapeInline(text.slice(2, close));
|
|
1116
|
+
const value = { src: dest.url };
|
|
1117
|
+
if (alt) value.alt = alt;
|
|
1118
|
+
if (dest.title !== void 0) value.title = dest.title;
|
|
1119
|
+
this.native("image", Object.keys(value).length === 1 ? value.src : value, range);
|
|
1120
|
+
return true;
|
|
1121
|
+
}
|
|
1122
|
+
// --- fences ----------------------------------------------------------------------------------
|
|
1123
|
+
fence() {
|
|
1124
|
+
const { lines, ctx } = this;
|
|
1125
|
+
const first = this.i;
|
|
1126
|
+
const open = fenceStart(lines[first].text);
|
|
1127
|
+
const close = fenceClose(open.fence);
|
|
1128
|
+
let end = first + 1;
|
|
1129
|
+
while (end < lines.length && !close.test(lines[end].text)) end++;
|
|
1130
|
+
const terminated = end < lines.length;
|
|
1131
|
+
const lastLine = terminated ? lines[end] : lines.at(-1);
|
|
1132
|
+
const range = lineRange(lines[first], lastLine);
|
|
1133
|
+
if (!terminated) ctx.error("fence-unterminated", "A fenced block is not closed.", `Add a closing ${open.fence} line.`, range, this.path);
|
|
1134
|
+
this.i = terminated ? end + 1 : lines.length;
|
|
1135
|
+
const body = lines.slice(first + 1, terminated ? end : lines.length);
|
|
1136
|
+
const bodyText = body.map((line) => line.text).join("\n");
|
|
1137
|
+
const bodyStart = body[0]?.start ?? lines[first].end;
|
|
1138
|
+
const info = open.info;
|
|
1139
|
+
const word = /^[^\s{]*/.exec(info)[0];
|
|
1140
|
+
const rest = info.slice(word.length).trim();
|
|
1141
|
+
if (word === "chart") this.chart(rest, bodyText, range);
|
|
1142
|
+
else if (word === "metric") this.metric(body, range);
|
|
1143
|
+
else if (word === "timeline") this.timeline(rest, body, range);
|
|
1144
|
+
else if (word === "opf-slide") this.embeddedSlide(bodyText, bodyStart, range);
|
|
1145
|
+
else if (word === "opf-block") this.embeddedBlock(bodyText, bodyStart, range);
|
|
1146
|
+
else {
|
|
1147
|
+
const parsed = parseCodeFence([lines[first].text, ...body.map((line) => line.text), open.fence]);
|
|
1148
|
+
if (!parsed) {
|
|
1149
|
+
ctx.error("code-fence", "This fenced block could not be read as code.", "Close the fence with at least as many backticks as opened it.", range, this.path);
|
|
1150
|
+
return;
|
|
1151
|
+
}
|
|
1152
|
+
const hasMeta = parsed.language !== void 0 || parsed.filename !== void 0;
|
|
1153
|
+
const value = { source: parsed.source };
|
|
1154
|
+
if (parsed.language) value.language = parsed.language;
|
|
1155
|
+
if (parsed.filename) value.filename = parsed.filename;
|
|
1156
|
+
this.native("code", hasMeta ? value : parsed.source, range);
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
chart(rest, text, range) {
|
|
1160
|
+
const { ctx } = this;
|
|
1161
|
+
const type = /^[^\s]+/.exec(rest)?.[0];
|
|
1162
|
+
if (!type) {
|
|
1163
|
+
ctx.error("chart-type", "A chart block names its type after `chart`.", "Write ```chart column (or line, pie, bar, ...), then the data.", range, this.path);
|
|
1164
|
+
return;
|
|
1165
|
+
}
|
|
1166
|
+
const { pairs, error } = parseAttributes(rest.slice(type.length));
|
|
1167
|
+
const meta = {};
|
|
1168
|
+
if (error) ctx.error("chart-attributes", error, 'Write ```chart column alt="What the data shows" (alt is optional).', range, this.path);
|
|
1169
|
+
for (const [key, value] of pairs) {
|
|
1170
|
+
if (key === "alt" && typeof value === "string") meta.alt = value;
|
|
1171
|
+
else ctx.error("chart-attributes", `Unknown chart attribute ${JSON.stringify(key)}.`, "A chart fence takes alt, a text alternative in double quotes.", range, this.path);
|
|
1172
|
+
}
|
|
1173
|
+
const trimmed = text.trim();
|
|
1174
|
+
if (!trimmed) {
|
|
1175
|
+
ctx.error("chart-data", "A chart block has no data.", "Write CSV with a header row, or a JSON object with columns and rows or a data source.", range, this.path);
|
|
1176
|
+
return;
|
|
1177
|
+
}
|
|
1178
|
+
if (trimmed.startsWith("{")) {
|
|
1179
|
+
try {
|
|
1180
|
+
const data = JSON.parse(trimmed);
|
|
1181
|
+
if (!isRecord(data)) throw new Error("not an object");
|
|
1182
|
+
this.native("chart", { type, ...meta, data }, range);
|
|
1183
|
+
} catch (error2) {
|
|
1184
|
+
ctx.error("chart-data", `Chart JSON is invalid: ${error2.message}.`, 'Write a JSON object: {"columns": [...], "rows": [[...]]} or {"src": ...}.', range, this.path);
|
|
1185
|
+
}
|
|
1186
|
+
return;
|
|
1187
|
+
}
|
|
1188
|
+
const csv = readCsv(text);
|
|
1189
|
+
if (csv.error) {
|
|
1190
|
+
ctx.error("chart-data", `Chart CSV is invalid: ${csv.error}`, "Quote fields that contain commas, quotes or line breaks.", range, this.path);
|
|
1191
|
+
return;
|
|
1192
|
+
}
|
|
1193
|
+
const [head, ...body] = csv.rows.filter((row) => row.some((field) => field.value !== "" || field.quoted));
|
|
1194
|
+
if (!head || !body.length) {
|
|
1195
|
+
ctx.error("chart-data", "Chart CSV needs a header row and at least one data row.", "The first line names the columns.", range, this.path);
|
|
1196
|
+
return;
|
|
1197
|
+
}
|
|
1198
|
+
const typed = (field) => field.quoted ? field.value : field.value === "" ? null : DECIMAL.test(field.value) ? Number(field.value) : field.value === "true" ? true : field.value === "false" ? false : field.value;
|
|
1199
|
+
const rows = body.map((row) => row.map((field, index) => index === 0 ? field.value : typed(field)));
|
|
1200
|
+
if (rows.some((row) => row.length !== head.length)) ctx.warn("chart-ragged", `A chart row has a different number of values than the ${head.length} columns.`, "Give every row one value per column.", range, this.path);
|
|
1201
|
+
this.native("chart", { type, ...meta, data: { columns: head.map((field) => field.value), rows } }, range);
|
|
1202
|
+
}
|
|
1203
|
+
metric(body, range) {
|
|
1204
|
+
const { ctx } = this;
|
|
1205
|
+
const allowed = ["value", "label", "description", "unit", "delta", "trend", "sentiment"];
|
|
1206
|
+
const help = `A metric block is key: value lines. Keys: ${allowed.join(", ")}. A value is plain text, or a JSON string in double quotes.`;
|
|
1207
|
+
const out = {};
|
|
1208
|
+
for (const line of body) {
|
|
1209
|
+
if (BLANK.test(line.text)) continue;
|
|
1210
|
+
const match = /^([A-Za-z]+):[ \t]*(.*?)[ \t]*$/.exec(line.text);
|
|
1211
|
+
const where = lineRange(line);
|
|
1212
|
+
if (!match) {
|
|
1213
|
+
ctx.error("metric-block", "Expected a key: value line.", help, where, this.path);
|
|
1214
|
+
return;
|
|
1215
|
+
}
|
|
1216
|
+
const [, key, raw] = match;
|
|
1217
|
+
if (!allowed.includes(key) || key in out || raw === "") {
|
|
1218
|
+
ctx.error("metric-block", !allowed.includes(key) ? `Unknown metric key ${JSON.stringify(key)}.` : key in out ? `Metric key ${key} is given twice.` : `Metric key ${key} has no value.`, help, where, this.path);
|
|
1219
|
+
return;
|
|
1220
|
+
}
|
|
1221
|
+
if (raw.startsWith('"')) {
|
|
1222
|
+
try {
|
|
1223
|
+
const value = JSON.parse(raw);
|
|
1224
|
+
if (typeof value !== "string") throw new Error("not a string");
|
|
1225
|
+
out[key] = value;
|
|
1226
|
+
} catch {
|
|
1227
|
+
ctx.error("metric-block", `The value of ${key} starts with a quote but is not a JSON string.`, help, where, this.path);
|
|
1228
|
+
return;
|
|
1229
|
+
}
|
|
1230
|
+
} else out[key] = (key === "value" || key === "delta") && DECIMAL.test(raw) ? Number(raw) : raw;
|
|
1231
|
+
}
|
|
1232
|
+
if (out.value === void 0) {
|
|
1233
|
+
ctx.error("metric-block", "A metric needs a value.", help, range, this.path);
|
|
1234
|
+
return;
|
|
1235
|
+
}
|
|
1236
|
+
const ordered = {};
|
|
1237
|
+
for (const key of allowed) if (out[key] !== void 0) ordered[key] = out[key];
|
|
1238
|
+
this.native("metric", Object.keys(ordered).length === 1 ? ordered.value : ordered, range);
|
|
1239
|
+
}
|
|
1240
|
+
timeline(rest, body, range) {
|
|
1241
|
+
const { ctx } = this;
|
|
1242
|
+
const { pairs, error } = parseAttributes(rest);
|
|
1243
|
+
const meta = {};
|
|
1244
|
+
if (error) ctx.error("timeline-attributes", error, 'Write ```timeline name="Roadmap" description="..." (both optional).', range, this.path);
|
|
1245
|
+
for (const [key, value] of pairs) {
|
|
1246
|
+
if ((key === "name" || key === "description") && typeof value === "string") meta[key] = value;
|
|
1247
|
+
else ctx.error("timeline-attributes", `Unknown timeline attribute ${JSON.stringify(key)}.`, "A timeline fence takes name and description.", range, this.path);
|
|
1248
|
+
}
|
|
1249
|
+
const events = [], statuses = [];
|
|
1250
|
+
for (const line of body) {
|
|
1251
|
+
if (BLANK.test(line.text)) continue;
|
|
1252
|
+
if (/^[ \t]+\S/.test(line.text)) {
|
|
1253
|
+
const event = events.at(-1);
|
|
1254
|
+
if (!event) {
|
|
1255
|
+
ctx.error("timeline-description", "An indented line must follow an event.", "Put the event first, then its description indented below it.", lineRange(line), this.path);
|
|
1256
|
+
continue;
|
|
1257
|
+
}
|
|
1258
|
+
event.description = event.description === void 0 ? line.text.trim() : `${event.description} ${line.text.trim()}`;
|
|
1259
|
+
continue;
|
|
1260
|
+
}
|
|
1261
|
+
let text = line.text.trim();
|
|
1262
|
+
const marked = TIMELINE_STATUS_PREFIX.exec(text);
|
|
1263
|
+
const status = marked ? TIMELINE_STATUS_OF_MARK[marked[1]] : void 0;
|
|
1264
|
+
if (marked) text = text.slice(marked[0].length).trim();
|
|
1265
|
+
const dash = text.indexOf(" \u2014 ");
|
|
1266
|
+
const split = dash > 0 && text.slice(dash + 3).trim() ? { when: text.slice(0, dash).trim(), what: text.slice(dash + 3).trim() } : splitWhen(text);
|
|
1267
|
+
events.push(split ? { when: split.when, what: split.what } : { what: text });
|
|
1268
|
+
statuses.push(status);
|
|
1269
|
+
}
|
|
1270
|
+
events.forEach((event, index) => {
|
|
1271
|
+
if (statuses[index]) event.status = statuses[index];
|
|
1272
|
+
});
|
|
1273
|
+
if (!events.length) {
|
|
1274
|
+
ctx.error("timeline-events", "A timeline has no events.", "Write one event per line: `2024 Q1 \u2014 Pilot`.", range, this.path);
|
|
1275
|
+
return;
|
|
1276
|
+
}
|
|
1277
|
+
this.native("timeline", Object.keys(meta).length ? { ...meta, events } : events, range);
|
|
1278
|
+
}
|
|
1279
|
+
embeddedBlock(text, base, range) {
|
|
1280
|
+
const { ctx } = this;
|
|
1281
|
+
const parsed = readYamlMapping(text, base, ctx, "opf-block", range);
|
|
1282
|
+
if (!parsed.ok) return;
|
|
1283
|
+
if (this.pending) {
|
|
1284
|
+
ctx.error("options-embedded", "A block options comment cannot precede an opf-block fence.", "Put id and type inside the opf-block YAML.", this.pending.range, this.path);
|
|
1285
|
+
this.pending = void 0;
|
|
1286
|
+
}
|
|
1287
|
+
const keys = Object.keys(parsed.value);
|
|
1288
|
+
this.blocks.push({ payload: parsed.value, range, key: keys.length === 1 ? keys[0] : void 0 });
|
|
1289
|
+
}
|
|
1290
|
+
embeddedSlide(text, base, range) {
|
|
1291
|
+
const { ctx } = this;
|
|
1292
|
+
const parsed = readYamlMapping(text, base, ctx, "opf-slide", range);
|
|
1293
|
+
if (!parsed.ok) return;
|
|
1294
|
+
if (this.extras) ctx.error("options-duplicate", "A slide has more than one opf-slide block.", "Merge them into one.", range, this.path);
|
|
1295
|
+
else {
|
|
1296
|
+
this.extras = parsed.value;
|
|
1297
|
+
for (const [key, found] of parsed.keys) ctx.ranges.set(`${this.path}/${key.replaceAll("~", "~0").replaceAll("/", "~1")}`, found);
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
// --- assembly --------------------------------------------------------------------------------
|
|
1301
|
+
finish() {
|
|
1302
|
+
const { slide, ctx, path } = this;
|
|
1303
|
+
const set = (key, value, range) => {
|
|
1304
|
+
if (value === void 0) return;
|
|
1305
|
+
if (Object.hasOwn(slide, key)) ctx.error("slide-property-conflict", `The slide property ${JSON.stringify(key)} is set twice.`, "Set it either in Markdown or in the opf-slide block, not both.", this.slideOptionsRange ?? range ?? { start: 0, end: 0 }, `${path}/${key}`);
|
|
1306
|
+
else Object.defineProperty(slide, key, { value, enumerable: true, writable: true, configurable: true });
|
|
1307
|
+
};
|
|
1308
|
+
const options = this.slideOptions ?? {};
|
|
1309
|
+
if (this.slideOptionsRange) for (const key of Object.keys(options)) ctx.ranges.set(`${path}/${key}`, this.slideOptionsRange);
|
|
1310
|
+
set("id", options.id);
|
|
1311
|
+
set("type", options.type);
|
|
1312
|
+
set("beat", options.beat);
|
|
1313
|
+
set("layout", options.layout);
|
|
1314
|
+
set("section", options.section);
|
|
1315
|
+
set("tag", options.tag);
|
|
1316
|
+
set("title", this.title);
|
|
1317
|
+
set("subtitle", this.subtitle);
|
|
1318
|
+
const content = this.blocks.filter((block) => block.options?.region === void 0);
|
|
1319
|
+
const regions = this.blocks.filter((block) => block.options?.region !== void 0);
|
|
1320
|
+
const [only2] = content;
|
|
1321
|
+
if (content.length === 1 && only2?.key !== void 0 && only2.options?.id === void 0 && only2.options?.type === void 0 && isContentKey(only2.key) && Object.keys(only2.payload).length === 1) {
|
|
1322
|
+
slide[only2.key] = only2.payload[only2.key];
|
|
1323
|
+
ctx.ranges.set(`${path}/${only2.key}`, only2.range);
|
|
1324
|
+
} else if (content.length) {
|
|
1325
|
+
slide.blocks = content.map((block, index) => {
|
|
1326
|
+
ctx.ranges.set(`${path}/blocks/${index}`, block.range);
|
|
1327
|
+
const out = {};
|
|
1328
|
+
if (block.options?.id !== void 0) out.id = block.options.id;
|
|
1329
|
+
if (block.options?.type !== void 0) out.type = block.options.type;
|
|
1330
|
+
return Object.assign(out, block.payload);
|
|
1331
|
+
});
|
|
1332
|
+
}
|
|
1333
|
+
for (const block of regions) {
|
|
1334
|
+
const region = block.options.region;
|
|
1335
|
+
const payload = {};
|
|
1336
|
+
if (block.options?.id !== void 0) payload.id = block.options.id;
|
|
1337
|
+
if (block.options?.type !== void 0) payload.type = block.options.type;
|
|
1338
|
+
Object.assign(payload, block.payload);
|
|
1339
|
+
ctx.ranges.set(`${path}/${region}`, block.range);
|
|
1340
|
+
set(region, payload, block.range);
|
|
1341
|
+
}
|
|
1342
|
+
set("notes", this.notes || void 0, this.notesRange);
|
|
1343
|
+
if (this.notesRange && this.notes) ctx.ranges.set(`${path}/notes`, this.notesRange);
|
|
1344
|
+
set("hidden", options.hidden);
|
|
1345
|
+
for (const [key, value] of Object.entries(this.extras ?? {})) set(key, value);
|
|
1346
|
+
return slide;
|
|
1347
|
+
}
|
|
1348
|
+
};
|
|
1349
|
+
var CONTENT = /* @__PURE__ */ new Set(["text", "items", "bullets", "image", "video", "chart", "table", "code", "metric", "quote", "timeline"]);
|
|
1350
|
+
var isContentKey = (key) => CONTENT.has(key);
|
|
1351
|
+
function splitRow(text) {
|
|
1352
|
+
let s = text.trim();
|
|
1353
|
+
if (s.startsWith("|")) s = s.slice(1);
|
|
1354
|
+
const cells = [];
|
|
1355
|
+
let cell = "";
|
|
1356
|
+
for (let i = 0; i < s.length; i++) {
|
|
1357
|
+
const c = s[i];
|
|
1358
|
+
if (c === "\\" && i + 1 < s.length) {
|
|
1359
|
+
cell += c + s[i + 1];
|
|
1360
|
+
i++;
|
|
1361
|
+
} else if (c === "|") {
|
|
1362
|
+
cells.push(cell.trim());
|
|
1363
|
+
cell = "";
|
|
1364
|
+
} else cell += c;
|
|
1365
|
+
}
|
|
1366
|
+
if (cell.trim() !== "" || !/\|\s*$/.test(text)) cells.push(cell.trim());
|
|
1367
|
+
return cells;
|
|
1368
|
+
}
|
|
1369
|
+
function parseSlide(ctx, lines, index) {
|
|
1370
|
+
const path = `/slides/${index}`;
|
|
1371
|
+
const first = lines.find((line) => !BLANK.test(line.text));
|
|
1372
|
+
const last = [...lines].reverse().find((line) => !BLANK.test(line.text));
|
|
1373
|
+
if (first && last) ctx.ranges.set(path, lineRange(first, last));
|
|
1374
|
+
return new SlideParser(ctx, lines, path).run();
|
|
1375
|
+
}
|
|
1376
|
+
function emptySegment(lines) {
|
|
1377
|
+
return lines.every((line) => BLANK.test(line.text));
|
|
1378
|
+
}
|
|
1379
|
+
function parseSlideText(text) {
|
|
1380
|
+
const ctx = new Ctx(text);
|
|
1381
|
+
const segments = splitSegments(splitLines(text), "rules").filter((segment) => !emptySegment(segment));
|
|
1382
|
+
if (segments.length !== 1) return { slide: void 0, clean: false };
|
|
1383
|
+
const slide = parseSlide(ctx, segments[0], 0);
|
|
1384
|
+
return { slide, clean: ctx.findings.length === 0 };
|
|
1385
|
+
}
|
|
1386
|
+
function frontMatter(lines) {
|
|
1387
|
+
if (!lines.length || lines[0].text.trimEnd() !== "---") return void 0;
|
|
1388
|
+
for (let i = 1; i < lines.length; i++) {
|
|
1389
|
+
const text = lines[i].text.trimEnd();
|
|
1390
|
+
if (text === "---" || text === "...") return { yaml: { start: lines[0].end, end: lines[i].start }, end: i + 1 };
|
|
1391
|
+
}
|
|
1392
|
+
return "unterminated";
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
// src/markdown/emit.ts
|
|
1396
|
+
var CONTENT2 = ["text", "items", "bullets", "image", "video", "chart", "table", "code", "metric", "quote", "timeline"];
|
|
1397
|
+
var isContent = (key) => CONTENT2.includes(key);
|
|
1398
|
+
var ROWS = "(?:top|middle|bottom|top\\+middle|middle\\+bottom|top\\+middle\\+bottom)";
|
|
1399
|
+
var COLUMNS = "(?:left|center|right|left\\+center|center\\+right|left\\+center\\+right)";
|
|
1400
|
+
var REGION = new RegExp(`^(?:${ROWS}(?::${COLUMNS})?|${COLUMNS})$`);
|
|
1401
|
+
var NATIVE_SLIDE = /* @__PURE__ */ new Set(["id", "type", "layout", "section", "tag", "hidden", "beat", "title", "subtitle", "notes", "blocks", ...CONTENT2]);
|
|
1402
|
+
function fenceFor(body, char = "`") {
|
|
1403
|
+
let longest = 2;
|
|
1404
|
+
for (const line of body) for (const run of line.matchAll(char === "`" ? /`+/g : /~+/g)) longest = Math.max(longest, run[0].length);
|
|
1405
|
+
return char.repeat(longest + 1);
|
|
1406
|
+
}
|
|
1407
|
+
var fenced = (info, body) => {
|
|
1408
|
+
const fence = fenceFor(body);
|
|
1409
|
+
return [`${fence}${info}`, ...body, fence];
|
|
1410
|
+
};
|
|
1411
|
+
var optionValue = (value) => {
|
|
1412
|
+
if (value.includes("--") || /[\r\n]/.test(value)) return void 0;
|
|
1413
|
+
return /^[A-Za-z0-9_][A-Za-z0-9_.:+/@#-]*$/.test(value) && value !== "true" && value !== "false" ? value : JSON.stringify(value);
|
|
1414
|
+
};
|
|
1415
|
+
var oneLine = (value) => typeof value === "string" && value !== "" && !/[\r\n]/.test(value) && value === value.trim();
|
|
1416
|
+
function listLines(items, marker) {
|
|
1417
|
+
const lines = [];
|
|
1418
|
+
for (const raw of items) {
|
|
1419
|
+
const item = raw;
|
|
1420
|
+
const text = isRecord(item) && !Array.isArray(item) ? item.text : item;
|
|
1421
|
+
const level = isRecord(item) && !Array.isArray(item) && typeof item.level === "number" ? item.level : 0;
|
|
1422
|
+
const rows = serializeRuns(text);
|
|
1423
|
+
if (!rows || rows.length !== 1) return void 0;
|
|
1424
|
+
const indent = " ".repeat(level);
|
|
1425
|
+
lines.push(rows[0] === "" ? `${indent}${marker}` : `${indent}${marker} ${rows[0]}`);
|
|
1426
|
+
if (isRecord(item) && !Array.isArray(item) && item.description !== void 0) {
|
|
1427
|
+
const description = serializeRuns(item.description);
|
|
1428
|
+
if (!description || description.length !== 1 || description[0] === "") return void 0;
|
|
1429
|
+
lines.push(`${indent} : ${description[0]}`);
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
return lines;
|
|
1433
|
+
}
|
|
1434
|
+
function destination(url) {
|
|
1435
|
+
if (!url || /[\r\n<>]/.test(url)) return void 0;
|
|
1436
|
+
return /^[^\s()]+$/.test(url) ? url : `<${url}>`;
|
|
1437
|
+
}
|
|
1438
|
+
function imageLines(value) {
|
|
1439
|
+
const asset = typeof value === "string" ? { src: value } : value;
|
|
1440
|
+
const dest = destination(String(asset.src));
|
|
1441
|
+
if (dest === void 0) return void 0;
|
|
1442
|
+
const alt = asset.alt === void 0 ? "" : String(asset.alt);
|
|
1443
|
+
if (/[\r\n]/.test(alt)) return void 0;
|
|
1444
|
+
let title = "";
|
|
1445
|
+
if (asset.title !== void 0) {
|
|
1446
|
+
if (/[\r\n]/.test(String(asset.title))) return void 0;
|
|
1447
|
+
title = ` "${String(asset.title).replace(/[\\"]/g, "\\$&")}"`;
|
|
1448
|
+
}
|
|
1449
|
+
return [``];
|
|
1450
|
+
}
|
|
1451
|
+
function tableLines(table) {
|
|
1452
|
+
const rows = table.rows;
|
|
1453
|
+
const columns = table.columns;
|
|
1454
|
+
const width = columns ? columns.length : Math.max(0, ...rows.map((row) => row.length));
|
|
1455
|
+
if (!width) return void 0;
|
|
1456
|
+
const cell = (value) => {
|
|
1457
|
+
if (value === null) return "";
|
|
1458
|
+
if (typeof value === "number") return String(value);
|
|
1459
|
+
const rows2 = serializeRuns(value, "cell");
|
|
1460
|
+
return rows2?.[0]?.replaceAll("|", "\\|");
|
|
1461
|
+
};
|
|
1462
|
+
const render = (cells) => {
|
|
1463
|
+
const out = cells.map(cell);
|
|
1464
|
+
return out.includes(void 0) ? void 0 : `| ${out.join(" | ")} |`;
|
|
1465
|
+
};
|
|
1466
|
+
const header = render(columns ?? Array.from({ length: width }, () => null));
|
|
1467
|
+
const body = rows.map(render);
|
|
1468
|
+
if (header === void 0 || body.includes(void 0)) return void 0;
|
|
1469
|
+
return [header, `| ${Array.from({ length: width }, () => "---").join(" | ")} |`, ...body];
|
|
1470
|
+
}
|
|
1471
|
+
function chartJson(data) {
|
|
1472
|
+
const entries = Object.entries(data).map(
|
|
1473
|
+
([key, value]) => key === "rows" && Array.isArray(value) ? [' "rows": [', ...value.map((row, index) => ` ${JSON.stringify(row)}${index < value.length - 1 ? "," : ""}`), " ]"] : [` ${JSON.stringify(key)}: ${JSON.stringify(value)}`]
|
|
1474
|
+
);
|
|
1475
|
+
return ["{", ...entries.flatMap((lines, index) => lines.map((line, at) => at === lines.length - 1 && index < entries.length - 1 ? `${line},` : line)), "}"];
|
|
1476
|
+
}
|
|
1477
|
+
function chartCandidates(chart) {
|
|
1478
|
+
if (!/^\S+$/.test(chart.type)) return [];
|
|
1479
|
+
const data = chart.data;
|
|
1480
|
+
if (typeof chart.alt === "string" && chart.alt.includes("`")) return [];
|
|
1481
|
+
const info = `chart ${chart.type}${typeof chart.alt === "string" ? ` alt=${JSON.stringify(chart.alt)}` : ""}`;
|
|
1482
|
+
const json = fenced(info, chartJson(data));
|
|
1483
|
+
if (Array.isArray(data.columns) && Array.isArray(data.rows)) {
|
|
1484
|
+
const body = [data.columns.map((column) => csvField(column, true)).join(","), ...data.rows.map((row) => row.map((value, index) => csvField(value, index === 0)).join(","))];
|
|
1485
|
+
return [fenced(info, body), json];
|
|
1486
|
+
}
|
|
1487
|
+
return [json];
|
|
1488
|
+
}
|
|
1489
|
+
var TIMELINE_STATUS_MARKS = { done: "[x] ", current: "[>] ", planned: "[ ] " };
|
|
1490
|
+
function timelineLines(timeline) {
|
|
1491
|
+
const events = Array.isArray(timeline) ? timeline : timeline.events;
|
|
1492
|
+
const meta = Array.isArray(timeline) ? {} : timeline;
|
|
1493
|
+
const info = ["timeline", ...["name", "description"].filter((key) => meta[key] !== void 0).map((key) => `${key}=${JSON.stringify(meta[key])}`)].join(" ");
|
|
1494
|
+
const body = [];
|
|
1495
|
+
for (const event of events) {
|
|
1496
|
+
for (const key of ["when", "what", "description"]) if (typeof event[key] === "string" && (/[\r\n]/.test(event[key]) || event[key] !== event[key].trim() || event[key] === "")) return void 0;
|
|
1497
|
+
const line = event.when === void 0 ? event.what : `${event.when} \u2014 ${event.what}`;
|
|
1498
|
+
if (event.status === void 0 && /^\[[x> ]\]\s+\S/.test(line)) return void 0;
|
|
1499
|
+
body.push(event.status === void 0 ? line : `${TIMELINE_STATUS_MARKS[event.status]}${line}`);
|
|
1500
|
+
if (event.description !== void 0) body.push(` ${event.description}`);
|
|
1501
|
+
}
|
|
1502
|
+
return fenced(info, body);
|
|
1503
|
+
}
|
|
1504
|
+
function metricLines(metric) {
|
|
1505
|
+
const value = typeof metric === "object" ? metric : { value: metric };
|
|
1506
|
+
const body = [];
|
|
1507
|
+
for (const key of ["value", "label", "description", "unit", "delta", "trend", "sentiment"]) if (value[key] !== void 0) body.push(`${key}: ${scalarText(value[key])}`);
|
|
1508
|
+
return fenced("metric", body);
|
|
1509
|
+
}
|
|
1510
|
+
function splitParagraphs(runs) {
|
|
1511
|
+
const out = [[]];
|
|
1512
|
+
for (const run of runs) {
|
|
1513
|
+
runText(run).split("\n\n").forEach((part, index) => {
|
|
1514
|
+
if (index > 0) out.push([]);
|
|
1515
|
+
if (part !== "") out.at(-1).push(typeof run === "string" ? part : { ...run, text: part });
|
|
1516
|
+
});
|
|
1517
|
+
}
|
|
1518
|
+
return out;
|
|
1519
|
+
}
|
|
1520
|
+
function quoteLines(quote) {
|
|
1521
|
+
const value = typeof quote === "string" ? { text: quote } : quote;
|
|
1522
|
+
const lines = [];
|
|
1523
|
+
const paragraphs = Array.isArray(value.text) ? splitParagraphs(value.text) : String(value.text).split("\n\n");
|
|
1524
|
+
for (const [index, paragraph] of paragraphs.entries()) {
|
|
1525
|
+
const rows = serializeRuns(paragraph);
|
|
1526
|
+
if (!rows) return void 0;
|
|
1527
|
+
if (index > 0) lines.push(">");
|
|
1528
|
+
for (const row of rows) lines.push(row === "" ? ">" : `> ${row}`);
|
|
1529
|
+
}
|
|
1530
|
+
for (const key of ["attribution", "source"]) {
|
|
1531
|
+
if (value[key] === void 0) continue;
|
|
1532
|
+
if (!oneLine(value[key])) return void 0;
|
|
1533
|
+
lines.push(`> \u2014 ${escapeInline(value[key])}`);
|
|
1534
|
+
}
|
|
1535
|
+
return lines;
|
|
1536
|
+
}
|
|
1537
|
+
function nativeCandidates(key, canon, marker) {
|
|
1538
|
+
if (key === "chart") return chartCandidates(canon);
|
|
1539
|
+
const lines = nativeLines(key, canon, marker);
|
|
1540
|
+
return lines?.length ? [lines] : [];
|
|
1541
|
+
}
|
|
1542
|
+
function nativeLines(key, canon, marker) {
|
|
1543
|
+
switch (key) {
|
|
1544
|
+
case "text": {
|
|
1545
|
+
if (canon === "") return void 0;
|
|
1546
|
+
return serializeRuns(canon);
|
|
1547
|
+
}
|
|
1548
|
+
case "items":
|
|
1549
|
+
case "bullets":
|
|
1550
|
+
return listLines(canon, marker);
|
|
1551
|
+
case "quote":
|
|
1552
|
+
return quoteLines(canon);
|
|
1553
|
+
case "code": {
|
|
1554
|
+
const code = typeof canon === "string" ? { source: canon } : canon;
|
|
1555
|
+
const language = code.language;
|
|
1556
|
+
if (language !== void 0 && (!/^[A-Za-z0-9_+#.-]+$/.test(language) || ["chart", "metric", "timeline", "opf-slide", "opf-block"].includes(language))) return void 0;
|
|
1557
|
+
return fenceCode(code.source, language, code.filename);
|
|
1558
|
+
}
|
|
1559
|
+
case "metric":
|
|
1560
|
+
return metricLines(canon);
|
|
1561
|
+
case "image":
|
|
1562
|
+
case "video":
|
|
1563
|
+
return imageLines(canon);
|
|
1564
|
+
case "table":
|
|
1565
|
+
return tableLines(canon);
|
|
1566
|
+
case "timeline":
|
|
1567
|
+
return timelineLines(canon);
|
|
1568
|
+
default:
|
|
1569
|
+
return void 0;
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
function blockOptionsLine(spec, as) {
|
|
1573
|
+
const parts = [];
|
|
1574
|
+
for (const [key, value] of [["id", spec.id], ["type", spec.type], ["as", as], ["region", spec.region]]) {
|
|
1575
|
+
if (value === void 0) continue;
|
|
1576
|
+
const text = typeof value === "string" ? optionValue(value) : void 0;
|
|
1577
|
+
if (text === void 0) return void 0;
|
|
1578
|
+
parts.push(`${key}=${text}`);
|
|
1579
|
+
}
|
|
1580
|
+
return parts.length ? `<!-- block: ${parts.join(" ")} -->` : "";
|
|
1581
|
+
}
|
|
1582
|
+
function nativeBlock(spec, marker) {
|
|
1583
|
+
const canon = canonContent(spec.key, spec.value);
|
|
1584
|
+
if (canon === void 0) return void 0;
|
|
1585
|
+
const as = spec.key === "bullets" ? "bullets" : spec.key === "video" ? "video" : void 0;
|
|
1586
|
+
const logical = spec.key === "bullets" ? "items" : spec.key === "video" ? "image" : spec.key;
|
|
1587
|
+
const options = blockOptionsLine(spec, as);
|
|
1588
|
+
if (options === void 0) return void 0;
|
|
1589
|
+
for (const lines of nativeCandidates(logical, canon, marker)) {
|
|
1590
|
+
const all = options ? [options, ...lines] : lines;
|
|
1591
|
+
const { slide, clean } = parseSlideText(all.join("\n"));
|
|
1592
|
+
if (!slide || !clean) continue;
|
|
1593
|
+
const target = spec.region !== void 0 ? slide[spec.region] : spec.id !== void 0 || spec.type !== void 0 ? slide.blocks?.[0] : slide;
|
|
1594
|
+
if (target && same(target[spec.key], canon)) return all;
|
|
1595
|
+
}
|
|
1596
|
+
return void 0;
|
|
1597
|
+
}
|
|
1598
|
+
function embed(value) {
|
|
1599
|
+
const text = writeYaml(value);
|
|
1600
|
+
return text === void 0 ? void 0 : fenced("opf-block", text.trimEnd().split("\n"));
|
|
1601
|
+
}
|
|
1602
|
+
function emitSlide(slide, index, mode) {
|
|
1603
|
+
const path = `/slides/${index}`;
|
|
1604
|
+
const embedded = [];
|
|
1605
|
+
const loss = [];
|
|
1606
|
+
const extras = {};
|
|
1607
|
+
const sections = [];
|
|
1608
|
+
const exp = {};
|
|
1609
|
+
const expBlocks = [];
|
|
1610
|
+
const handle = (where, reason, keep) => {
|
|
1611
|
+
if (mode === "embed") {
|
|
1612
|
+
embedded.push({ path: where, reason });
|
|
1613
|
+
keep();
|
|
1614
|
+
} else loss.push({ path: where, message: reason });
|
|
1615
|
+
};
|
|
1616
|
+
const options = [];
|
|
1617
|
+
const optionFields = [["id", slide.id], ["type", slide.type], ["beat", slide.beat], ["layout", slide.layout], ["section", slide.section], ["tag", slide.tag]];
|
|
1618
|
+
const failed = [];
|
|
1619
|
+
for (const [key, value] of optionFields) {
|
|
1620
|
+
if (value === void 0) continue;
|
|
1621
|
+
if (key === "beat" && Array.isArray(value) && value.length >= 2 && value.every((entry) => typeof entry === "string")) {
|
|
1622
|
+
const texts = value.map(optionValue);
|
|
1623
|
+
if (texts.every((entry) => entry !== void 0)) options.push(...texts.map((entry) => ["beat", entry]));
|
|
1624
|
+
else failed.push(key);
|
|
1625
|
+
continue;
|
|
1626
|
+
}
|
|
1627
|
+
const text = typeof value === "string" ? optionValue(value) : void 0;
|
|
1628
|
+
if (text === void 0) failed.push(key);
|
|
1629
|
+
else options.push([key, text]);
|
|
1630
|
+
}
|
|
1631
|
+
if (slide.hidden === true) options.push(["hidden", "true"]);
|
|
1632
|
+
else if (slide.hidden === false) options.push(["hidden", "false"]);
|
|
1633
|
+
else if (slide.hidden !== void 0) failed.push("hidden");
|
|
1634
|
+
const optionsText = options.map(([key, text]) => key === "hidden" && text === "true" ? "hidden" : `${key}=${text}`).join(" ");
|
|
1635
|
+
const optionsLine = options.length ? `<!-- slide: ${optionsText} -->` : "";
|
|
1636
|
+
if (optionsLine) {
|
|
1637
|
+
const { slide: back, clean } = parseSlideText(optionsLine);
|
|
1638
|
+
const expected = {};
|
|
1639
|
+
for (const [key] of options) expected[key] = slide[key];
|
|
1640
|
+
if (!back || !clean || !same(back, expected)) {
|
|
1641
|
+
for (const [key] of options) failed.push(key);
|
|
1642
|
+
options.length = 0;
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1645
|
+
for (const key of new Set(failed)) handle(`${path}/${key}`, `slide option ${key} has no <!-- slide: --> form`, () => extras[key] = slide[key]);
|
|
1646
|
+
const kept = options.length ? `<!-- slide: ${options.map(([key, text]) => key === "hidden" && text === "true" ? "hidden" : `${key}=${text}`).join(" ")} -->` : "";
|
|
1647
|
+
if (kept) {
|
|
1648
|
+
sections.push({ lines: [kept] });
|
|
1649
|
+
for (const [key] of options) exp[key] = slide[key];
|
|
1650
|
+
}
|
|
1651
|
+
for (const [key, mark] of [["title", "#"], ["subtitle", "##"]]) {
|
|
1652
|
+
const value = slide[key];
|
|
1653
|
+
if (value === void 0) continue;
|
|
1654
|
+
const canon = canonText(value);
|
|
1655
|
+
const row = canon === void 0 ? void 0 : typeof canon === "string" ? oneLine(canon) ? escapeInline(canon) : void 0 : serializeRuns(canon)?.length === 1 ? serializeRuns(canon)[0] : void 0;
|
|
1656
|
+
if (canon !== void 0 && row) {
|
|
1657
|
+
const line = `${mark} ${row.replace(/(^|\s)(#+)$/, "$1\\$2")}`;
|
|
1658
|
+
const { slide: back, clean } = parseSlideText(line);
|
|
1659
|
+
if (back && clean && same(back[key], canon)) {
|
|
1660
|
+
sections.push({ lines: [line] });
|
|
1661
|
+
exp[key] = canon;
|
|
1662
|
+
continue;
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
handle(`${path}/${key}`, `the ${key} has no single-line Markdown form`, () => extras[key] = value);
|
|
1666
|
+
}
|
|
1667
|
+
const specs = [];
|
|
1668
|
+
const handledRegions = /* @__PURE__ */ new Set();
|
|
1669
|
+
if (Array.isArray(slide.blocks)) {
|
|
1670
|
+
slide.blocks.forEach((block, i) => {
|
|
1671
|
+
const where = `${path}/blocks/${i}`;
|
|
1672
|
+
if (!isRecord(block)) return;
|
|
1673
|
+
const keys = Object.keys(block).filter((key) => key !== "id" && key !== "type");
|
|
1674
|
+
const only3 = keys.length === 1 && isContent(keys[0]) ? keys[0] : void 0;
|
|
1675
|
+
specs.push({ spec: { key: only3 ?? "", value: only3 ? block[only3] : void 0, id: block.id, type: block.type }, where, payload: block });
|
|
1676
|
+
});
|
|
1677
|
+
for (const key of CONTENT2) if (slide[key] !== void 0) extras[key] = slide[key];
|
|
1678
|
+
} else {
|
|
1679
|
+
for (const key of CONTENT2) if (slide[key] !== void 0) specs.push({ spec: { key, value: slide[key] }, where: `${path}/${key}` });
|
|
1680
|
+
}
|
|
1681
|
+
for (const [key, value] of Object.entries(slide)) {
|
|
1682
|
+
if (!REGION.test(key) || !isRecord(value)) continue;
|
|
1683
|
+
const rest = Object.keys(value).filter((entry) => entry !== "id" && entry !== "type");
|
|
1684
|
+
const only3 = rest.length === 1 && isContent(rest[0]) ? rest[0] : void 0;
|
|
1685
|
+
if (only3 === void 0) continue;
|
|
1686
|
+
handledRegions.add(key);
|
|
1687
|
+
specs.push({ spec: { key: only3, value: value[only3], id: value.id, type: value.type, region: key }, where: `${path}/${key}`, payload: value });
|
|
1688
|
+
}
|
|
1689
|
+
let marker = "-";
|
|
1690
|
+
for (const { spec, where, payload } of specs) {
|
|
1691
|
+
const lines2 = spec.key ? nativeBlock(spec, marker) : void 0;
|
|
1692
|
+
if (lines2) {
|
|
1693
|
+
sections.push({ lines: lines2 });
|
|
1694
|
+
const written = { ...spec.id !== void 0 ? { id: spec.id } : {}, ...spec.type !== void 0 ? { type: spec.type } : {}, [spec.key]: canonContent(spec.key, spec.value) };
|
|
1695
|
+
if (spec.region !== void 0) exp[spec.region] = written;
|
|
1696
|
+
else expBlocks.push(written);
|
|
1697
|
+
const list = spec.key === "items" || spec.key === "bullets";
|
|
1698
|
+
marker = list ? marker === "-" ? "*" : "-" : "-";
|
|
1699
|
+
continue;
|
|
1700
|
+
}
|
|
1701
|
+
marker = "-";
|
|
1702
|
+
handle(where, spec.key ? `${spec.key} content has a part with no Markdown form` : "a block with several fields or a nested group has no Markdown form", () => {
|
|
1703
|
+
if (spec.region !== void 0) {
|
|
1704
|
+
extras[spec.region] = slide[spec.region];
|
|
1705
|
+
return;
|
|
1706
|
+
}
|
|
1707
|
+
const payloadValue = payload ?? { [spec.key]: spec.value };
|
|
1708
|
+
const lines3 = embed(payloadValue);
|
|
1709
|
+
if (!lines3) throw new OPFMarkdownError("not-representable", `The block at ${where} holds a value that YAML cannot carry exactly.`, { path: where });
|
|
1710
|
+
sections.push({ lines: lines3 });
|
|
1711
|
+
if (spec.region !== void 0) exp[spec.region] = structuredClone(payloadValue);
|
|
1712
|
+
else expBlocks.push(structuredClone(payloadValue));
|
|
1713
|
+
});
|
|
1714
|
+
}
|
|
1715
|
+
let notesLines;
|
|
1716
|
+
if (slide.notes !== void 0) {
|
|
1717
|
+
const notes = slide.notes;
|
|
1718
|
+
if (typeof notes === "string" && notes !== "" && !/\r/.test(notes)) {
|
|
1719
|
+
const rows = notes.split("\n");
|
|
1720
|
+
const candidate = [`Note: ${rows[0]}`, ...rows.slice(1)];
|
|
1721
|
+
const { slide: back, clean } = parseSlideText(candidate.join("\n"));
|
|
1722
|
+
if (back && clean && back.notes === notes) {
|
|
1723
|
+
notesLines = candidate;
|
|
1724
|
+
exp.notes = notes;
|
|
1725
|
+
}
|
|
1726
|
+
}
|
|
1727
|
+
if (!notesLines) handle(`${path}/notes`, "the speaker notes have no Note: form (blank edges, a --- line or a carriage return)", () => extras.notes = notes);
|
|
1728
|
+
}
|
|
1729
|
+
for (const [key, value] of Object.entries(slide)) if (!NATIVE_SLIDE.has(key) && !handledRegions.has(key)) handle(`${path}/${key}`, `${JSON.stringify(key)} has no Markdown form`, () => extras[key] = value);
|
|
1730
|
+
if (Object.keys(extras).length) {
|
|
1731
|
+
const text = writeYaml(extras);
|
|
1732
|
+
if (text === void 0) throw new OPFMarkdownError("not-representable", `The slide at ${path} holds a value that YAML cannot carry exactly.`, { path });
|
|
1733
|
+
sections.push({ lines: fenced("opf-slide", text.trimEnd().split("\n")) });
|
|
1734
|
+
Object.assign(exp, structuredClone(extras));
|
|
1735
|
+
}
|
|
1736
|
+
const [only2] = expBlocks;
|
|
1737
|
+
const onlyKey = only2 && Object.keys(only2).length === 1 ? Object.keys(only2)[0] : void 0;
|
|
1738
|
+
if (expBlocks.length === 1 && onlyKey !== void 0 && isContent(onlyKey)) exp[onlyKey] = only2[onlyKey];
|
|
1739
|
+
else if (expBlocks.length) exp.blocks = expBlocks;
|
|
1740
|
+
if (notesLines) sections.push({ lines: notesLines });
|
|
1741
|
+
if (!sections.length) sections.push({ lines: ["<!-- slide -->"] });
|
|
1742
|
+
const lines = sections.flatMap((section, i) => i === 0 || i === 1 && kept ? section.lines : ["", ...section.lines]);
|
|
1743
|
+
if (mode === "embed") {
|
|
1744
|
+
const { slide: back, clean } = parseSlideText(lines.join("\n"));
|
|
1745
|
+
if (!back || !clean || !same(back, exp)) throw new OPFMarkdownError("not-representable", `The slide at ${path} does not read back from its Markdown.`, { path, expected: exp, actual: back });
|
|
1746
|
+
}
|
|
1747
|
+
return { lines, embedded, loss };
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1750
|
+
// src/markdown.ts
|
|
1751
|
+
function fromMarkdown(markdown, options = {}) {
|
|
1752
|
+
if (typeof markdown !== "string") throw new TypeError("fromMarkdown expects a string.");
|
|
1753
|
+
const source = markdown;
|
|
1754
|
+
const ctx = new Ctx(source);
|
|
1755
|
+
let lines = splitLines(source);
|
|
1756
|
+
if (lines[0]?.text.startsWith("\uFEFF")) lines = [{ ...lines[0], text: lines[0].text.slice(1), start: lines[0].start + 1 }, ...lines.slice(1)];
|
|
1757
|
+
const front = {};
|
|
1758
|
+
let body = lines;
|
|
1759
|
+
const matter = frontMatter(lines);
|
|
1760
|
+
if (matter === "unterminated") {
|
|
1761
|
+
ctx.error("front-matter-unterminated", "The front matter starting on line 1 is not closed.", "Close it with a line containing only ---, or remove the opening --- (a deck without front matter starts with its first slide).", lineRange(lines[0]));
|
|
1762
|
+
body = lines.slice(1);
|
|
1763
|
+
} else if (matter) {
|
|
1764
|
+
const range = { start: matter.yaml.start, end: matter.yaml.end };
|
|
1765
|
+
const parsed = readYamlMapping(source.slice(range.start, range.end), range.start, ctx, "front-matter", range);
|
|
1766
|
+
ctx.ranges.set("", range);
|
|
1767
|
+
if (parsed.ok && !Object.keys(parsed.value).length && source.slice(range.start, range.end).trim()) ctx.warn("front-matter-comments", "The text between the first two --- lines is only YAML comments, so it was not read as slide content.", "A deck that starts with a slide must not start with a --- line: only front matter may open the file. Remove the first --- or add the deck properties.", range);
|
|
1768
|
+
for (const [key, value] of Object.entries(parsed.value)) {
|
|
1769
|
+
if (key === "slides") ctx.error("front-matter-slides", "The front matter cannot hold `slides`: slides are the Markdown that follows it.", "Remove the slides key and write the slides below the front matter, separated by ---.", parsed.keys.get("slides") ?? range, "/slides");
|
|
1770
|
+
else Object.defineProperty(front, key, { value, enumerable: true, writable: true, configurable: true });
|
|
1771
|
+
}
|
|
1772
|
+
for (const [key, found] of parsed.keys) ctx.ranges.set(`/${key.replaceAll("~", "~0").replaceAll("/", "~1")}`, found);
|
|
1773
|
+
body = lines.slice(matter.end);
|
|
1774
|
+
}
|
|
1775
|
+
const document = { ...options.defaults ?? {}, ...front };
|
|
1776
|
+
const segments = splitSegments(body, options.split ?? "rules");
|
|
1777
|
+
const slides = [];
|
|
1778
|
+
segments.forEach((segment, index) => {
|
|
1779
|
+
if (emptySegment(segment)) {
|
|
1780
|
+
const edge = index === 0 || index === segments.length - 1;
|
|
1781
|
+
if (!edge) {
|
|
1782
|
+
const around = segment[0] ?? segments[index + 1]?.[0] ?? segments[index - 1]?.at(-1);
|
|
1783
|
+
if (around) ctx.warn("empty-slide", "Two --- lines in a row give an empty slide, which was skipped.", "Remove the extra ---, or write <!-- slide --> to keep an empty slide.", lineRange(around));
|
|
1784
|
+
}
|
|
1785
|
+
return;
|
|
1786
|
+
}
|
|
1787
|
+
slides.push(parseSlide(ctx, segment, slides.length));
|
|
1788
|
+
});
|
|
1789
|
+
document.slides = slides;
|
|
1790
|
+
if (!slides.length) ctx.error("no-slides", "The Markdown has no slides.", "Write at least one slide: a # title or any content, with --- between slides.", { start: 0, end: Math.min(1, source.length) }, "/slides");
|
|
1791
|
+
else if (options.validate !== false) {
|
|
1792
|
+
const checked = validate(document, options.validate === true || options.validate === void 0 ? { only: ["format", "references"] } : options.validate);
|
|
1793
|
+
for (const found of checked.findings) {
|
|
1794
|
+
const range = ctx.rangeOf(found.path) ?? ctx.rangeOf("") ?? { start: 0, end: 0 };
|
|
1795
|
+
ctx.findings.push({ ...found, location: ctx.location(range) });
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
const findings = [...ctx.findings].sort((a, b) => a.location.offset - b.location.offset);
|
|
1799
|
+
const counts = { error: 0, warning: 0, info: 0 };
|
|
1800
|
+
for (const entry of findings) counts[entry.severity]++;
|
|
1801
|
+
return { presentation: document, valid: counts.error === 0, findings, counts };
|
|
1802
|
+
}
|
|
1803
|
+
function toMarkdown(document, options = {}) {
|
|
1804
|
+
const checked = validate(document, { only: ["format"] });
|
|
1805
|
+
if (!checked.valid) {
|
|
1806
|
+
const errors = checked.findings.filter((entry) => entry.severity === "error");
|
|
1807
|
+
const first = errors[0];
|
|
1808
|
+
throw new OPFMarkdownError("invalid-document", `The document is not valid OPF: ${first?.message ?? "unknown error"}${first?.path ? ` (${first.path})` : ""}.`, { issues: errors });
|
|
1809
|
+
}
|
|
1810
|
+
const mode = options.unsupported === "drop" ? "drop" : "embed";
|
|
1811
|
+
const { slides, ...rest } = document;
|
|
1812
|
+
const parts = [];
|
|
1813
|
+
if (Object.keys(rest).length) {
|
|
1814
|
+
const yaml = writeYaml(rest);
|
|
1815
|
+
if (yaml === void 0) throw new OPFMarkdownError("not-representable", "The deck properties hold a value that YAML cannot carry exactly.");
|
|
1816
|
+
if (!same(readBack(yaml), rest)) throw new OPFMarkdownError("not-representable", "The deck properties do not read back from their front matter.");
|
|
1817
|
+
parts.push(`---
|
|
1818
|
+
${yaml}---
|
|
1819
|
+
`);
|
|
1820
|
+
}
|
|
1821
|
+
const embedded = [];
|
|
1822
|
+
const loss = [];
|
|
1823
|
+
const bodies = [];
|
|
1824
|
+
slides.forEach((slide, index) => {
|
|
1825
|
+
const out = emitSlide(slide, index, mode);
|
|
1826
|
+
embedded.push(...out.embedded);
|
|
1827
|
+
loss.push(...out.loss.map((part) => `${part.path}: ${part.message}`));
|
|
1828
|
+
bodies.push(out.lines.join("\n"));
|
|
1829
|
+
});
|
|
1830
|
+
const markdown = `${parts.length ? `${parts[0]}
|
|
1831
|
+
` : ""}${bodies.join("\n\n---\n\n")}
|
|
1832
|
+
`;
|
|
1833
|
+
return { markdown, report: { lossless: loss.length === 0, native: loss.length === 0 && embedded.length === 0, embedded, loss } };
|
|
1834
|
+
}
|
|
1835
|
+
function readBack(yaml) {
|
|
1836
|
+
const ctx = new Ctx(yaml);
|
|
1837
|
+
return readYamlMapping(yaml, 0, ctx, "front-matter", { start: 0, end: yaml.length }).value;
|
|
1838
|
+
}
|
|
1839
|
+
|
|
1840
|
+
export { fromMarkdown, toMarkdown };
|