Add Markdown importer to convert Markdown back into ContentState#155
Open
thibaudcolas wants to merge 1 commit into
Open
Add Markdown importer to convert Markdown back into ContentState#155thibaudcolas wants to merge 1 commit into
thibaudcolas wants to merge 1 commit into
Conversation
|
🔍 OpenCodeReview found 1 issue(s) in this PR.
|
Comment on lines
+254
to
+258
| for quote_char in ('"', "'"): | ||
| if raw_url.endswith(quote_char) and " " + quote_char in raw_url: | ||
| space_pos = raw_url.rfind(" " + quote_char) | ||
| url = raw_url[:space_pos] | ||
| title = raw_url[space_pos + 2 : -1] |
There was a problem hiding this comment.
Link title parsing is broken for titles with spaces. The logic uses raw_url.rfind(' ' + quote_char) which finds the LAST space before the closing quote. For input like url "Title With Spaces", this extracts url as the URL and Title With as the title (the Spaces" part is cut off incorrectly). Additionally, the condition " " + quote_char in raw_url doesn't properly handle titles with multiple spaces.
Suggestion:
Suggested change
| for quote_char in ('"', "'"): | |
| if raw_url.endswith(quote_char) and " " + quote_char in raw_url: | |
| space_pos = raw_url.rfind(" " + quote_char) | |
| url = raw_url[:space_pos] | |
| title = raw_url[space_pos + 2 : -1] | |
| Implement a proper parser: find the first space from the right that separates the URL from the title. A more robust approach is to find the opening quote position, then extract everything between the first quote and the last matching quote character, treating the URL as everything before the first space+quote. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
The exporter already supports Draft.js
ContentState→ Markdown viaDOM.MARKDOWNandbuild_markdown_config(). This is a first implementation of the reverse direction:Markdown → ContentState, enabling round-trip workflows and server-side ingestion of Markdown content from external sources (file imports, API consumers, content migrations).Description
Adds a new public function and options type, both re-exported from the top-level
draftjs_exporterpackage:Scope of the parser
The importer is dependency-free — a custom character-by-character inline walker plus a regex-based block dispatcher. It supports the same subset of Markdown that the exporter produces, plus common CommonMark edge cases:
./)ordered list delimiters), fenced code blocks (```and~~~, with optional info strings), horizontal rules (---/***/___), images (), links ([text](url), with optional"title").wagtail://core.Page.89round-trip unchanged (the importer does no URL scheme validation — that's the integrating application's responsibility, matching the exporter).<u>,<sup>,<sub>,<mark>,<q>,<small>,<samp>,<ins>,<del>,<kbd>) are parsed back intoinlineStyleRanges.Options surface
MarkdownImporterOptionsmirrors the three inline style marker options from the exporter'sMarkdownOptions(bold,italic,strikethrough). Block-level variants (list markers, HR variants, fence variants, ordered-list delimiters) are not mirrored because the importer's regexes accept all forms polymorphically — any block-level keys passed through fromMarkdownOptionsare simply ignored. A fourth option,html_style_tags, extends the default HTML tag → style mapping and is the inverse of the exporter'sSTYLE_MAP.Architectural notes
markdown/importer.py), not integrated into the rendering pipeline. It doesn't reuse the exporter'sCommand/EntityState/StyleStateabstractions because those consume ranges; this produces them from a different source representation._merge_style_rangesis an intentional round-trip trade-off: it repairs the exporter's inline-style nesting quirks (e.g.**Bold ****_Italic_**producing two BOLD ranges) by merging adjacent or overlapping ranges of the same style.unstyledatlogger.debug(notwarning) — plain paragraphs are the expected fall-through path, not an anomaly.Documentation
New "Importer" section in
docs/markdown.mdcovering quick start, options, fallback behavior, round-trip guarantees (what's preserved vs. lost), and supported Markdown features. New "Importer pipeline" section indocs/contributing/architecture.mddocumenting the design choices. New experimental entry indocs/ROADMAP.md.Tests
tests/markdown/test_importer.py— 137 tests, including fixture-driven round-trip tests (export → import → export) and import correctness tests for every supported Markdown feature. Reuses thetests/test_exports.jsonfixture matrix with an explicitLOSSLESS_CASESallowlist (13 of 18 fixtures), acknowledging that Markdown is a lossy representation.tests/strategies.py+tests/test_properties.py— a newTestImporterCrashSafetyproperty test using Hypothesis that assertsmarkdown_to_content_state(md)returns well-formedContentStateon adversarial input (unterminated links, mismatched markers, malformed HTML tags, deep nesting). 11 pinned@examplecases for known-tricky inputs.pyproject.toml—disallow_untyped_calls = falseoverride fortests.*, needed because the metaclass-generated test methods (ImporterTestMeta,RoundTripTestMeta) are untyped from mypy 2.0's perspective. Commented inline.Test evidence
just lint— clean (ruff, mypy, ty).just test— 563 tests passing (was 562; added the newTestImporterCrashSafetyproperty test).just test-coverage tests/markdown/test_importer.py— 100% coverage onimporter.py(717 lines, 284 statements, 0 missing).Tested on macOS, Python 3.14.5.
just test-coverage).just lint).