Skip to content

Feature/markdown export import#717

Merged
Crustack merged 3 commits intomainfrom
feature/markdown-export-import
Nov 9, 2025
Merged

Feature/markdown export import#717
Crustack merged 3 commits intomainfrom
feature/markdown-export-import

Conversation

@Crustack
Copy link
Owner

@Crustack Crustack commented Nov 9, 2025

Closes #713

  • Adds Import from Markdown (integrated to import from text plain), if the file is of mimetype markdown or .md extension it is parsed. Markdown formatting options that are not available in NotallyX are ignored and the raw markdown is imported to the notes contents
  • Removed the popup dialog when exporting a single note to "View File" or "Save to device". When Exporting a single note the user now directly chooses where to save it

Summary by CodeRabbit

  • New Features

    • Markdown import with formatting preservation (bold, italic, code, links, strikethrough)
    • Markdown export for individual notes and checklists; single-note and folder export flows with improved file-type support
    • Inline UI helpers for showing snackbar feedback during exports
  • Localization

    • File-type labels updated across locales to include "Markdown/Plain Text"
  • Tests

    • Added test suites for Markdown import/export and span handling
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 9, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

Adds bidirectional Markdown import/export: CommonMark dependencies, Markdown parsing/rendering utilities, integration into import/export workflows, refactors export flows to file-based APIs with snackbar feedback, updates UI strings for Markdown support, and adds unit tests and resources.

Changes

Cohort / File(s) Summary
Project config & docs
app/build.gradle.kts, README.md
Add CommonMark + strikethrough dependency; minor README formatting
Markdown core
app/src/main/java/.../data/imports/markdown/MarkdownUtils.kt
New parse and render utilities: parseBodyAndSpansFromMarkdown() and createMarkdownFromBodyAndSpans() producing/consuming plain body + span metadata
Import adjustments
app/src/main/java/.../data/imports/txt/PlainTextImporter.kt
Detect Markdown files, parse to body+spans when appropriate, fallback behavior and logging; header handling for MD
Model export helpers
app/src/main/java/.../data/model/ModelExtensions.kt
Add BaseNote.toMarkdown() and helpers to render lists and apply spans into Markdown
Export refactor & file APIs
app/src/main/java/.../utils/backup/ExportExtensions.kt
New file-based export flows: exportNote, exportPlainTextFile*, exportPdfFile*; MD wired into plaintext exports; launcher-based single-note export
ViewModel export API
app/src/main/java/.../presentation/viewmodel/BaseNoteModel.kt
Remove selectedExportFile; add exportNoteToFile(...), exportNotesToFolder(..., snackbarView), exportSelectedNoteToFile(...); thread snackbarView for UI feedback; add ExportMimeType.MD
Activity & UI
app/src/main/java/.../presentation/activity/main/MainActivity.kt, app/src/main/java/.../presentation/activity/note/EditActivity.kt, app/src/main/java/.../presentation/UiExtensions.kt
Update callers to new export signatures and launcher usage; add View.showSnackbar(...) overloads
Context intent behavior
app/src/main/java/.../utils/AndroidExtensions.kt
Context.viewFile() adds FLAG_ACTIVITY_NEW_TASK for intents/chooser
I18n strings
app/src/main/res/values*/strings.xml (default, cs,de,es,fr,it,pl,ro,ru,zh-rCN,zh-rTW)
Update plain_text_files (and help text) to include "Markdown/Plain Text Files" across locales
Tests & resources
app/src/test/java/.../data/imports/markdown/MarkdownUtilsTest.kt, app/src/test/java/.../data/model/MarkdownExportTest.kt, app/src/test/resources/markdown/all_spans_and_image.md
Add tests for parsing and exporting Markdown and test resource with various spans

Sequence Diagram(s)

sequenceDiagram participant User participant EditActivity participant BaseNoteModel participant ExportExtensions participant FileSystem participant UI User->>EditActivity: request export to file EditActivity->>BaseNoteModel: exportNoteToFile(fileUri, note, rootView) BaseNoteModel->>ExportExtensions: exportNote(note, mimeType, launcher) alt mime = MD ExportExtensions->>MarkdownUtils: createMarkdownFromBodyAndSpans(body, spans) else mime = PDF ExportExtensions->>ExportExtensions: exportPdfFile(...) else plaintext ExportExtensions->>ExportExtensions: exportPlainTextFile(...) end ExportExtensions->>FileSystem: write document ExportExtensions->>UI: showFileSnackbar(success, fileUri, mime) UI->>User: snackbar with action 
Loading
sequenceDiagram participant User participant PlainTextImporter participant MarkdownUtils participant Model User->>PlainTextImporter: import file alt file is Markdown PlainTextImporter->>MarkdownUtils: parseBodyAndSpansFromMarkdown(content) MarkdownUtils-->>PlainTextImporter: (body, spans) else list/plain text PlainTextImporter->>PlainTextImporter: parse as list/plain text end PlainTextImporter->>Model: create BaseNote(body/spans or list) Model-->>User: note created 
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Files/areas to focus review on:
    • Markdown parsing/rendering: MarkdownUtils.kt (span boundary math, nesting/prioritization)
    • Export flow changes: ExportExtensions.kt (file creation, temp write, progress, MD/PLAINTEXT branching)
    • ViewModel/Activity integration: BaseNoteModel.kt, MainActivity.kt, EditActivity.kt (new signatures, snackbar threading)
    • Tests: ensure tests reflect intended Markdown nesting/priority behavior and resource parsing
    • I18n: confirm consistent string updates across locales

Possibly related PRs

Poem

🐰 I nibbled text and found a span,

bold and italic in my hand,
Links and code, a markdown trail,
Exported neat without a fail,
A hoppity snack—snackbar!—tells the tale. ✨

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Feature/markdown export import' clearly describes the main change—adding Markdown export and import functionality—and accurately reflects the changeset.
Linked Issues check ✅ Passed The PR fully addresses issue #713 by implementing Markdown import parsing into notes and Markdown export rendering from notes, supporting bold, italic, code, strikethrough, and link formatting.
Out of Scope Changes check ✅ Passed All changes directly support Markdown import/export objectives. Minor changes include UI helper extensions (snackbar display), export flow refactoring to pass UI views, and string resource updates for localization—all reasonably scoped to enable the feature.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 83ecab4 and 4c8e78b.

📒 Files selected for processing (7)
  • app/src/main/java/com/philkes/notallyx/data/imports/txt/PlainTextImporter.kt (4 hunks)
  • app/src/main/java/com/philkes/notallyx/presentation/UiExtensions.kt (2 hunks)
  • app/src/main/java/com/philkes/notallyx/presentation/activity/main/MainActivity.kt (2 hunks)
  • app/src/main/java/com/philkes/notallyx/presentation/activity/note/EditActivity.kt (3 hunks)
  • app/src/main/java/com/philkes/notallyx/presentation/viewmodel/BaseNoteModel.kt (10 hunks)
  • app/src/main/java/com/philkes/notallyx/utils/AndroidExtensions.kt (1 hunks)
  • app/src/main/java/com/philkes/notallyx/utils/backup/ExportExtensions.kt (8 hunks)

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8504ae0 and 83ecab4.

⛔ Files ignored due to path filters (1)
  • app/translations.xlsx is excluded by !**/*.xlsx
📒 Files selected for processing (25)
  • README.md (1 hunks)
  • app/build.gradle.kts (1 hunks)
  • app/src/main/java/com/philkes/notallyx/data/imports/markdown/MarkdownUtils.kt (1 hunks)
  • app/src/main/java/com/philkes/notallyx/data/imports/txt/PlainTextImporter.kt (4 hunks)
  • app/src/main/java/com/philkes/notallyx/data/model/ModelExtensions.kt (2 hunks)
  • app/src/main/java/com/philkes/notallyx/presentation/UiExtensions.kt (2 hunks)
  • app/src/main/java/com/philkes/notallyx/presentation/activity/main/MainActivity.kt (2 hunks)
  • app/src/main/java/com/philkes/notallyx/presentation/activity/note/EditActivity.kt (3 hunks)
  • app/src/main/java/com/philkes/notallyx/presentation/viewmodel/BaseNoteModel.kt (11 hunks)
  • app/src/main/java/com/philkes/notallyx/utils/AndroidExtensions.kt (1 hunks)
  • app/src/main/java/com/philkes/notallyx/utils/backup/ExportExtensions.kt (8 hunks)
  • app/src/main/res/values-cs/strings.xml (1 hunks)
  • app/src/main/res/values-de/strings.xml (1 hunks)
  • app/src/main/res/values-es/strings.xml (1 hunks)
  • app/src/main/res/values-fr/strings.xml (1 hunks)
  • app/src/main/res/values-it/strings.xml (1 hunks)
  • app/src/main/res/values-pl/strings.xml (1 hunks)
  • app/src/main/res/values-ro/strings.xml (1 hunks)
  • app/src/main/res/values-ru/strings.xml (1 hunks)
  • app/src/main/res/values-zh-rCN/strings.xml (1 hunks)
  • app/src/main/res/values-zh-rTW/strings.xml (1 hunks)
  • app/src/main/res/values/strings.xml (1 hunks)
  • app/src/test/java/com/philkes/notallyx/data/imports/markdown/MarkdownUtilsTest.kt (1 hunks)
  • app/src/test/java/com/philkes/notallyx/data/model/MarkdownExportTest.kt (1 hunks)
  • app/src/test/resources/markdown/all_spans_and_image.md (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (6)
app/src/main/java/com/philkes/notallyx/data/imports/txt/PlainTextImporter.kt (1)
app/src/main/java/com/philkes/notallyx/data/imports/markdown/MarkdownUtils.kt (1)
  • parseBodyAndSpansFromMarkdown (16-131)
app/src/test/java/com/philkes/notallyx/data/imports/markdown/MarkdownUtilsTest.kt (2)
app/src/test/java/com/philkes/notallyx/data/model/MarkdownExportTest.kt (2)
  • note (6-125)
  • note (8-29)
app/src/main/java/com/philkes/notallyx/data/imports/markdown/MarkdownUtils.kt (1)
  • parseBodyAndSpansFromMarkdown (16-131)
app/src/main/java/com/philkes/notallyx/presentation/activity/note/EditActivity.kt (1)
app/src/main/java/com/philkes/notallyx/utils/backup/ExportExtensions.kt (1)
  • exportNote (828-846)
app/src/main/java/com/philkes/notallyx/data/model/ModelExtensions.kt (1)
app/src/main/java/com/philkes/notallyx/data/imports/markdown/MarkdownUtils.kt (1)
  • createMarkdownFromBodyAndSpans (138-193)
app/src/main/java/com/philkes/notallyx/presentation/viewmodel/BaseNoteModel.kt (2)
app/src/main/java/com/philkes/notallyx/utils/backup/ExportExtensions.kt (4)
  • exportPdfFile (560-600)
  • exportPlainTextFile (642-668)
  • exportPdfFileFolder (523-558)
  • exportPlainTextFileFolder (602-640)
app/src/main/java/com/philkes/notallyx/presentation/UiExtensions.kt (2)
  • showSnackbar (931-935)
  • showSnackbar (937-944)
app/src/test/java/com/philkes/notallyx/data/model/MarkdownExportTest.kt (1)
app/src/test/java/com/philkes/notallyx/data/imports/markdown/MarkdownUtilsTest.kt (2)
  • note (9-110)
  • note (11-32)
🔇 Additional comments (22)
app/src/main/java/com/philkes/notallyx/utils/AndroidExtensions.kt (1)

375-386: Remove the redundant FLAG_ACTIVITY_NEW_TASK from the base intent.

Adding FLAG_ACTIVITY_NEW_TASK to both the base intent and the chooser intent is non-standard. The codebase already has a similar file-viewing function (around line 407-419) that works without either flag. The standard Android pattern is:

  • Grant flags (like FLAG_GRANT_READ_URI_PERMISSION) on the base intent
  • Launch flags (like FLAG_ACTIVITY_NEW_TASK) only on the outer intent being started

Remove line 380 (addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) on the base intent) and keep only the flag on the chooser (line 384), matching the pattern used elsewhere in the codebase.

app/src/main/res/values-cs/strings.xml (1)

252-253: Localization change appropriately reflects Markdown support.

The label prefix aligns with the help text mentioning Markdown syntax detection, and matches updates across other localized strings.

app/src/main/res/values-zh-rTW/strings.xml (1)

235-235: Consistent localization update across supported languages.

This change mirrors updates to other locale files and aligns with Markdown support messaging in help text.

app/src/main/res/values-es/strings.xml (1)

251-252: Localization correctly reflects Markdown support in Spanish.

Translation maintains consistency with other language variants and aligns with updated help messaging.

app/src/main/res/values-zh-rCN/strings.xml (1)

244-244: Localization update aligns with Markdown support across all locales.

Simplified Chinese translation maintains consistency and clarity.

app/src/main/res/values-it/strings.xml (1)

230-231: Italian localization properly updated to reflect Markdown support.

Translation is idiomatic and consistent with other language versions.

app/src/main/res/values-de/strings.xml (1)

251-252: German localization correctly indicates Markdown support.

Translation maintains consistency with translations in other languages.

app/src/test/resources/markdown/all_spans_and_image.md (1)

1-5: Test resource appropriately covers Markdown formatting scenarios.

The file tests a comprehensive range of inline spans (bold, italic, code, strikethrough, links, images) and nested formatting, which aligns well with the Markdown parsing/rendering utility test cases mentioned in the PR context.

app/build.gradle.kts (1)

270-271: Dependencies verified as appropriate and secure.

Version 0.27.0 is current on javadoc.io and available on Maven Central, with no published security advisories for commonmark-java. GFM strikethrough extension is fully supported in this version. The dependencies are suitable for the Markdown parsing use case.

app/src/main/res/values-ro/strings.xml (1)

238-238: LGTM! Localization update aligns with Markdown support.

The Romanian translation correctly reflects the new Markdown/plain text file support introduced in this PR.

app/src/main/res/values-pl/strings.xml (1)

249-249: LGTM! Polish localization correctly updated.

The string update properly reflects the Markdown/plain text labeling consistent with the PR's objectives.

app/src/main/res/values-ru/strings.xml (1)

252-252: LGTM! Russian localization updated consistently.

The translation correctly includes the Markdown prefix consistent with other locale updates in this PR.

app/src/main/res/values-fr/strings.xml (1)

238-238: LGTM! French localization updated consistently.

The translation appropriately reflects the Markdown/plain text file support.

app/src/main/res/values/strings.xml (1)

253-254: LGTM! Base string resources updated consistently.

Both the label and help text correctly reflect the new Markdown support introduced in this PR.

README.md (1)

93-94: Minor formatting change - two trailing blank lines added.

This appears to be an incidental formatting change, possibly from editor auto-formatting. No functional impact.

app/src/main/java/com/philkes/notallyx/presentation/activity/note/EditActivity.kt (3)

91-91: LGTM! New import for refactored export flow.

The import of the exportNote extension function supports the refactored single-note export workflow.


405-412: LGTM! Result handler correctly updated for single-note export.

The activity result handler now directly calls baseModel.exportNoteToFile with the selected URI, note, and root view for snackbar feedback. This aligns with the refactored export API.


958-960: LGTM! Export method correctly uses new extension function.

The export method now invokes the exportNote extension function which handles note selection and file picker launch. This is consistent with the refactored export flow.

app/src/main/java/com/philkes/notallyx/data/imports/txt/PlainTextImporter.kt (4)

9-9: LGTM! New imports support Markdown functionality.

The imports for Markdown parsing, MIME type handling, and logging are all properly utilized in the updated import logic.

Also applies to: 15-15, 17-17


55-70: LGTM! Well-structured Markdown parsing with proper error handling.

The code appropriately:

  • Only attempts Markdown parsing when the file is identified as Markdown and no list items are detected
  • Includes exception handling with detailed logging for debugging
  • Falls back to plain text content if parsing fails
  • Returns an empty spans list for non-Markdown content

This defensive approach ensures robust handling of edge cases.


83-84: LGTM! Conditional body/spans assignment is correct.

The logic appropriately clears body and spans for list notes (when listItems.isEmpty() is false) and uses the parsed Markdown body/spans for text notes.


108-117: LGTM! Markdown file detection is correctly implemented.

The isMarkdownFile() helper properly identifies Markdown files by checking both:

  • MIME type matching (case-insensitive)
  • File extension matching (case-insensitive)

The companion object with TAG constant follows standard Android logging practices.

Comment on lines +574 to +576
fun exportSelectedNoteToFile(fileUri: Uri, snackbarView: View) {
exportNoteToFile(fileUri, actionMode.selectedNotes.values.first(), snackbarView)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix single-note export path to avoid crash

After launching the document picker we store the note in selectedExportBaseNote, but here we still pull from actionMode.selectedNotes. If the export was triggered outside of multi-select (or the selection was cleared/config-changed before the result returns), this throws NoSuchElementException and the export fails. Use the saved note as the primary source and clear it once consumed.

Apply this diff to use the stored note safely:

 fun exportSelectedNoteToFile(fileUri: Uri, snackbarView: View) { - exportNoteToFile(fileUri, actionMode.selectedNotes.values.first(), snackbarView) + val note = + selectedExportBaseNote ?: actionMode.selectedNotes.values.firstOrNull() + ?: throw IllegalStateException("No note selected for export") + selectedExportBaseNote = null + exportNoteToFile(fileUri, note, snackbarView) }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fun exportSelectedNoteToFile(fileUri: Uri, snackbarView: View) {
exportNoteToFile(fileUri, actionMode.selectedNotes.values.first(), snackbarView)
}
fun exportSelectedNoteToFile(fileUri: Uri, snackbarView: View) {
val note =
selectedExportBaseNote ?: actionMode.selectedNotes.values.firstOrNull()
?: throw IllegalStateException("No note selected for export")
selectedExportBaseNote = null
exportNoteToFile(fileUri, note, snackbarView)
}
🤖 Prompt for AI Agents
In app/src/main/java/com/philkes/notallyx/presentation/viewmodel/BaseNoteModel.kt around lines 574-576, the exportSelectedNoteToFile currently pulls the note from actionMode.selectedNotes which can throw NoSuchElementException if selection was cleared; instead use the stored selectedExportBaseNote as the primary source (fallback to actionMode.selectedNotes only if that is null), pass that note into exportNoteToFile, and clear selectedExportBaseNote after consuming it so subsequent operations don't reuse stale state. 
@Crustack Crustack force-pushed the feature/markdown-export-import branch from 83ecab4 to 4c8e78b Compare November 9, 2025 18:35
@Crustack Crustack merged commit 277d1e9 into main Nov 9, 2025
@Crustack Crustack deleted the feature/markdown-export-import branch November 9, 2025 18:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

1 participant