Skip to content
zyz954489346Public

About

Offline Word-to-Markdown desktop converter that gets merged cells and multi-page tables right.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

13 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Markdown Express

Markdown Express

English | 简体中文

Offline Word (.docx) → Markdown desktop converter that gets merged cells and multi-page tables right.
“Markdown 快转” in the app UI.

MIT platforms Flutter Python


Why

Almost every docx → Markdown tool falls apart on the tables found in real business documents:

  • Merged cells: headers merged horizontally or vertically get split up or dropped, so columns shift;
  • Multi-page tables: a table split by a page break in Word comes out as two broken tables;
  • Line breaks, empty cells and nested paragraphs inside cells: the whole table usually collapses into a single line.

The conversion engine post-processes these cases specifically. Vertically merged cells are padded with placeholders across the rows they span, so every row has the same number of columns and nothing shifts. Horizontally merged cells collapse into a single cell of text. Table fragments separated by a page break are stitched back into one table, with the duplicated header/separator rows at the break removed so only one proper header row remains. The resulting Markdown tables can be pasted straight into a docs site or knowledge base. Documents are converted locally; only images are uploaded, and only to object storage you configure.

Features

  • Batch conversion: drag and drop or click + to add multiple .docx files; conversions run concurrently with live progress.
  • Lossless tables: merged cells, multi-page tables and line breaks inside cells are all restored correctly.
  • Image hosting: images in the document are uploaded automatically to your own Alibaba Cloud OSS bucket (via STS temporary credentials, no long-lived keys stored on disk) and appear in the Markdown as accessible links. OSS currently has to be configured in Settings before converting.
  • Preview / source / save as: results render as a preview as soon as conversion finishes, with a toggle to view the Markdown source. Both the source document and the result can be saved anywhere.
  • History: conversion records are stored locally and listed on launch, with fuzzy search, deletion, retention period and storage location settings.
  • Dark / light / follow system: switchable from Settings.
  • Local conversion: documents never pass through any third-party service. No network requests are made apart from uploading images to your OSS bucket.

Install

Download the package for your platform from Releases.

  • macOS: unzip and drag doc_shift_app.app into Applications. The app is not notarized by Apple, so the first launch is blocked: on macOS 14 and earlier, right-click and choose "Open"; on macOS 15 and later, double-click once, then go to System Settings → Privacy & Security and click "Open Anyway" at the bottom. Alternatively run xattr -cr /Applications/doc_shift_app.app and double-click as usual.
  • Linux: unzip and run bundle/doc_shift_app.
  • Windows: unzip and run doc_shift_app.exe. The binary is not code-signed, so SmartScreen shows "Windows protected your PC"; click "More info → Run anyway".

Development

Prerequisites

  • Flutter 3.47 (stable)
  • Python ≥ 3.12
  • On Linux desktop, the GTK development libraries required by Flutter

Setup

git clone <this repo> && cd doc_shift

# Python conversion engine: venv or conda, pick one
cd engine
python3 -m venv .venv && .venv/bin/pip install -e ".[dev]"     # venv
# or, inside an activated conda environment:
pip install -e ".[dev]" && export DOCMD_PYTHON="$(which python)"

# Flutter app
cd ../app && flutter pub get

The app and the packaging scripts look for a Python with docmd installed in this order: DOCMD_PYTHON → engine/.venv → python3 on PATH. With conda, the easiest option is to put DOCMD_PYTHON in your shell config.

Run

cd app
flutter run -d macos      # or linux / windows

In development the app launches the conversion engine as a subprocess using the Python above, so engine changes do not require repackaging.

Test

# Flutter: widget tests plus integration tests that spawn the real engine subprocess
cd app && flutter analyze && flutter test

# Python: conversion engine and sidecar unit tests
cd engine && pytest          # .venv/bin/pytest when using venv

Packaging

The engine is bundled with PyInstaller (onedir) into a standalone directory shipped alongside the app, so end users do not need Python.

scripts/build_macos_release.sh       # run on macOS, produces the .app and a distributable zip
scripts/build_linux_release.sh       # run on Linux, produces build/linux/x64/release/bundle/
.\scripts\build_windows_release.ps1  # run in Windows PowerShell, produces the Release directory and a zip

All three scripts build the engine first, then the Flutter app, and finally verify that the engine is actually inside the output. Windows requires the Visual Studio 2022 "Desktop development with C++" workload (the official requirement for Flutter Windows desktop builds). If PowerShell refuses to run the script, run Set-ExecutionPolicy -Scope Process Bypass first. The Windows packaging flow has not yet been verified on real hardware; feedback is welcome.

macOS notes:

  • Ad-hoc signing is used, so no Apple developer account is needed. To use your own certificate, copy app/macos/Runner/Configs/Signing.local.xcconfig.example to Signing.local.xcconfig in the same directory and fill in the certificate name (that file is git-ignored).
  • The engine directory lives under .app/Contents/Resources/ rather than Contents/MacOS/, because codesign requires every file in the latter to be signable as code, and the PyInstaller output contains data files.
  • OSS credentials are not stored in the system keychain (ad-hoc signing would trigger a keychain authorization prompt on every build). Instead they are encrypted with AES-256-GCM and stored in the app data directory with file permissions 0600. The key is derived from a hardware identifier of the machine plus a random salt, so the ciphertext cannot be decrypted on another machine; the app will ask you to enter the credentials again.

Icons

The logo source is app/assets/logo/logo.svg. After changing it, run scripts/generate_icons.sh (requires rsvg-convert and Pillow) to regenerate the platform icons and the in-app logo.

Release

The version number is maintained only in the version field of app/pubspec.yaml. To release, bump it, commit, then create and push a tag with the same name:

git tag v1.0.1 && git push origin v1.0.1

GitHub Actions (.github/workflows/release.yml) packages the app on Linux, macOS and Windows in parallel and attaches the artifacts to the Release of the same name, with auto-generated release notes. The pipeline fails if the tag does not match the pubspec version. Triggering the workflow manually only builds without publishing, which is useful for verification.

Architecture

┌──────────────────────────┐   HTTP / WebSocket (127.0.0.1)   ┌──────────────────────────────┐
│  Flutter desktop (app/)  │ ───────────────────────────────▶ │  Python engine (engine/)      │
│  UI · queue · history ·  │ ◀─────────────────────────────── │  docx→md · table post-        │
│  settings                │        progress / results        │  processing · OSS upload      │
└──────────────────────────┘                                  └──────────────────────────────┘

On startup the app spawns the engine subprocess. The engine listens on a random local port and hands the port plus a one-time token back through the first line of stdout; every subsequent request carries the token, so other processes on the same machine cannot reach it. Each file maps to one conversion job, and progress is pushed over WebSocket.

Layer Technology
Desktop app Flutter + Riverpod, drift (SQLite) for history
Conversion engine Python: mammoth + BeautifulSoup + markdownify + custom table post-processing
IPC FastAPI + uvicorn
Image upload Alibaba Cloud STS + alibabacloud_oss_v2
Packaging PyInstaller + Flutter desktop

Contributing

Issues and PRs are welcome. Before submitting, make sure flutter analyze, flutter test and pytest pass, and that Dart code is formatted with dart format. For changes touching macOS packaging or signing, please state which macOS version you verified on. If you have a docx that converts poorly, attaching it to an Issue (with sensitive content removed) is the most valuable contribution you can make.

License

MIT © 2026 zed.Zhao

About

Offline Word-to-Markdown desktop converter that gets merged cells and multi-page tables right.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages