A Typora-style desktop Markdown editor: you write plain Markdown, and the editor renders it in
place — headings look like headings, tables look like tables, and the #, **, ```, $$
markers only reappear on the line your caret is on.
Built with Electron + CodeMirror 6 + markdown-it + KaTeX + highlight.js + Mermaid.
pnpm install # once (see the Electron note below for this machine)
pnpm start # build the renderer bundle, then launch Electron
pnpm dev # rebuild on change (renderer)Package manager is pnpm 11 (packageManager pinned in package.json, build scripts allow-listed
in pnpm-workspace.yaml). On this machine pnpm's script gate and Electron's binary download both
needed handling once:
pnpm approve-builds --all # let esbuild run its postinstall
export ELECTRON_SKIP_BINARY_DOWNLOAD=1 # don't let electron's install.js hit the network
pnpm installThen drop the official electron-v<version>-win32-x64.zip (from
https://registry.npmmirror.com/-/binary/electron/<version>/) into
node_modules/.pnpm/electron@<version>/node_modules/electron/dist/ and write electron.exe into
path.txt next to it. pnpm exec electron --version should echo the version you placed.
pnpm test # unit tests (node:test): Markdown renderer, paths, images, math, diagrams, outline, word count, drafts, user themes
pnpm dist # package an installer for the current OS into release/
pnpm dist:store # Microsoft Store package (MSIX/.appx) into release/
pnpm icons # regenerate every icon from build/icon.svgCI runs the tests and the renderer build on Linux, Windows and macOS for every push and pull
request. To publish a release, bump version in package.json, commit, then push a matching tag:
git tag v0.2.1 && git push origin v0.2.1The release workflow builds the Windows (NSIS installer + portable zip), macOS (dmg, x64 + arm64) and Linux (AppImage, deb)
packages and attaches them to a GitHub Release. Builds are not code-signed yet. A separate store job
builds the Microsoft Store package (.appx) and keeps it as a workflow artifact for upload to Partner
Center (which signs it); its package identity comes from the repository variables
APPX_IDENTITY_NAME, APPX_PUBLISHER and APPX_PUBLISHER_DISPLAY_NAME.
The NSIS installer also adds two Explorer context menu entries, "Open with Posemark" on files and
"Open Folder with Posemark" on folders and folder backgrounds (labels in Chinese when the installer
runs in Chinese). They are registry keys under Software\Classes in the hive the install went to
(build/installer.nsh) and are removed by the uninstaller. A folder passed on the command line opens
in the sidebar, a file opens in the editor; the portable zip and the Store package get no menu entries.
Running Electron 45.0.0 (Chromium 156, Node 22).
In-place table editing — click a table and you get the grid, not pipes:
Rows, columns and alignment from a right-click menu on any cell:
Cells render their inline Markdown until you focus them:
Code block while editing: fences stay visible, the code stays highlighted:
Math and diagram blocks while editing — the formula is a field with its render below, the diagram keeps its source and re-renders underneath:
Images too — the picture appears under the ![]() line you are editing:
Dropping an image file onto the editor:
Click an image to get a resize frame ( is what ends up in the file):
[TOC] on its own line becomes a clickable contents block; [^ref] footnotes render as superscript
numbers with their definitions collected at the end:
Right-click anything in the file tree for new file / new folder / rename / trash:
Editing (live preview)
- ATX / setext headings with per-level typography, horizontal rules, quotes, bullet/ordered lists
- Bold, italic, strikethrough,
==highlight==,H~2~O,X^2^, inline code, links (bracket + URL hidden) - Reference-style links:
[text][label]and bare[label]render as links once a[label]: urlline exists; the definition lines are dimmed, andCtrl+click follows them like any other link - Emoji shortcodes:
:smile:renders as 😄 (GitHub names); typing:plus a few letters pops up an autocomplete list - GitHub-style alerts: a quote starting with
> [!NOTE],[!TIP],[!IMPORTANT],[!WARNING]or[!CAUTION]gets an icon, a label and a coloured bar, in the editor and in every export (Paragraph › Alertwraps the selection) - Strict mode switch (Preferences › Markdown): off,
#Titlewithout the space still counts as a heading, like Typora; preserve single line breaks carries the editor's line breaks into exports - Task lists with clickable checkboxes (
- [ ]/- [x]) - Fenced code blocks rendered with highlight.js, optional line numbers and soft wrap
- Putting the caret inside a fence un-renders it to source with the code still syntax-highlighted (JS/TS, Python, JSON via CodeMirror's language parsers; everything else falls back to our palette)
- GFM tables rendered as real tables that open as an editable grid when you click or put the caret in them:
type into a cell,
Tab/Shift-Tabwalks the cells,Enterjumps down a row,Tabon the last cell appends a row,Escdrops the caret out below the table. Every keystroke is written back to the Markdown source (pipes escaped), andCtrl+Zinside a cell undoes the edit. Right-click any cell (or hit the⋮at the end of the hint line) for rows/columns: insert row above/below, delete row, insert column left/right, delete column, and left/center/right alignment — each one rewrites the Markdown, delimiter row included. Cells show their inline Markdown rendered (**bold**,`code`,$x^2$,==mark==) while you are not typing in them, and the raw source the moment a cell takes focus. - Inline
$…$and block$$…$$math via KaTeX — a$$block opens as a math field: the TeX in a monospace input, the formula rendered below a dashed rule, updated on every keystroke.EscorCtrl/Cmd+Enterleaves the field and drops the caret after the block. ```mermaidblocks rendered as diagrams — same deal while editing: source on top, the diagram re-renders below after a short pause, and a syntax error shows up in the preview instead of replacing your text.```sequence(js-sequence-diagrams syntax) and```flow(flowchart.js syntax) blocks are translated to Mermaid and drawn the same way.- Diagram look: Mermaid follows the editor theme (dark themes get Mermaid's dark theme), and a
theme's CSS variables override it Typora-style —
--mermaid-theme,--mermaid-font-family,--mermaid-sequence-numbers: on,--mermaid-flowchart-curve: linear— with the same choices in Preferences › Markdown › Diagrams. Right-click a diagram to save it as SVG or PNG, copy it as a PNG, or copy the SVG code. - Math extras:
\(…\)inline and\[…\]display delimiters and```mathfences work next to$…$/$$…$$; mhchem (\ce{H2O}) is loaded, and a KaTeX-friendly subset of thephysicspackage (\dv,\pdv,\abs,\norm,\bra,\ket,\braket,\expval,\vb,\va,\grad,\curl, …) is on by default. - Equation numbering (Preferences › Markdown › Math): none, AMS style (
equation/align/gatherenvironments and anything with a\label), or every display equation.\label{eq:x}plus\eqref{eq:x}/\ref{eq:x}— inside math or in the text — show the number (clickable in the editor, an anchor link in the export). - Relative image paths resolved against the current document; the picture renders inline, and while
you edit its
![]()line the image shows underneath the source (with its alt text as caption, and amissing: …panel when the path does not resolve) - Drop an image file onto the editor and it is copied into the document's image folder
(
assets/by default, unique name if it already exists) withinserted at the drop point; with no file open it goes toPictures/posemark. A dashed overlay shows the drop zone while you drag. Dropping a video or audio file inserts<video controls src="…">/<audio controls src="…">, rendered as a player in place. - Image insert policy (Preferences › Images): copy into the image folder, keep the original path,
or upload straight away. The folder is relative to the document (
assets,${filename}.assets) or absolute, and atypora-copy-images-to: ./imgline in the front matter overrides it per file. Written paths are relative by default (./prefix and URL-escaping are switches); withtypora-root-url: /pathin the front matter,/images/x.pngresolves against that root and newly inserted images under it are written as/…site-root paths. <img>tags work like Markdown images:<img src="a.png" width="320">andstyle="zoom:50%"(what Typora writes) render at that size, inline or on a line of their own, the resize handle rewrites thewidthattribute, and the live preview under the edited line shows them- Right-click a rendered image for: open, reveal in Explorer, copy image / path, zoom 25 / 50 / 75 %
or original size, upload (with an uploader set up), move or copy the file to another folder, download
a remote image into the image folder, delete (file to the recycle bin, Markdown removed), plus
Move All / Copy All Images To… (remote ones are downloaded) and Upload All Local Images,
all rewriting the document in one undo step. The same batch commands live under
Format › Image. - Image uploaders: PicGo-Core (
picgo upload), PicList (its HTTP server) or any custom command that gets the file paths as arguments and prints one URL per line; Preferences › Images has a "Test Upload" button that sends a 1×1 PNG and shows the URL that came back - Click an image to resize it: a frame + corner handle appear, dragging scales it with the aspect
ratio locked, and the width is written into the Markdown as
. Hold Shift while dragging to unlock the ratio — that writes(both attributes land in the exported<img>). Double-click the image to jump into its![]()source instead. - YAML front matter kept visible but visually de-emphasised
- Source code mode (
Ctrl+/) shows the raw Markdown in a monospace font with syntax highlighting: headings sized by level, code fences and front matter shaded, quotes with their bar, inline code, links, task markers and all the markers greyed - Links work: clicking a rendered link (or
Ctrl+click on one in the source) openshttp(s)/mailtoin the system browser, a relative.mdin the editor (jumping to#anchorif there is one), any other relative file in its default app, and#fooscrolls to that heading [TOC]becomes a contents block built from the headings below it; clicking an entry jumps there (with## Title {#custom-id}respected for anchors, in the block and in the exported HTML)- Footnotes:
[^label]renders as a superscript number that jumps to its definition, and[^label]: textlines are dimmed with afn nbadge — the export collects them into a<section class="footnotes">with back-links - Paste a screenshot (
Ctrl+Vwith an image on the clipboard) and it lands inassets/and is inserted, same as the file drop - Smart typography while typing:
--→ —,...→ …, straight quotes → curly quotes, skipped inside code and at the start of a line (so---still makes a rule). Toggle off in Preferences. - Auto pair:
(,[,{(and quotes while smart typography is off) close themselves, typing the closer steps over it, Backspace removes an empty pair. The Markdown markers pair too:*,_,`and$close themselves (**gives**|**), and typing*,_,~,=,^,`or$with a selection wraps it. Both halves can be switched off in Preferences › Editor. - Smart paste: rich text on the clipboard (a web page, Word, Google Docs, another editor) is
converted to Markdown on
Ctrl+V(headings, lists, tables, code, links, images, alerts…). Copies from code editors are detected and pasted as-is;Ctrl+Shift+Valways pastes the plain text. Shift+Enterinside a table cell inserts<br>instead of ending the cell
Workspace
- Open file / open folder, file tree with expand-collapse, quick open (
Ctrl+P), recent files - File tree sorting and filters (the
⋯button or the sidebar's right-click menu): natural / name / modified / created order, ascending or descending, show hidden files, show all file types (other files open in their default app) - The folder is watched: files created, renamed or removed outside the app show up in the tree, and if the open document is rewritten on disk it is reloaded in place (unless you have unsaved edits, in which case you get a notice instead)
- Search in folder (
Ctrl+Shift+F, the Search tab): matches grouped by file with the line number, case / whole word / regex toggles; clicking a hit opens the file and selects the match - File tree operations: right-click a row (or the sidebar background) for New File, New Folder,
Rename (
F2), Move to Trash (Del, goes to the recycle bin), Reveal in File Explorer, Copy Path. Renaming is inline, keeps the extension, refuses to overwrite, and follows the open document - Drag and drop in the file tree moves a file or folder into another folder (never
overwrites); Duplicate (
Ctrl+D) makesname copy.md; Undo Last File Operation (Ctrl+Zwith the tree focused, or the right-click menu) reverses the last rename, move, duplicate or new file; arrow keys /Enter/Home/Endwalk the tree,→/←expand and collapse folders - File list mode (the
☰button,Ctrl+Shift+4, or Preferences › Appearance › Sidebar): every note under the folder in one flat list, newest first, with its subfolder and date - Pinned folders: pin the open folder from the
⋯menu and it stays at the top ofFile › Open Recentand of Quick Open (Ctrl+P, which also lists recent files from outside the folder). Preferences › General can stop recording recent files and folders altogether. - Outline panel: headings nest and collapse (the fold state is remembered per file), a filter
box narrows the list with the matches highlighted, the
☰button flattens it, and the heading the caret is under is marked and kept in view - Find / replace (
Ctrl+F,Ctrl+H) with regex / case / whole-word, jump to line (Ctrl+G) - Word count: the status bar shows words and characters (plus "n words selected"); click it or
press
Ctrl+Shift+Wfor the panel with characters with/without spaces, lines, paragraphs and reading time (speed configurable in Preferences), for the document and for the selection - Auto save, dirty indicator, save-on-open-another prompt
- Unsaved-draft backup: a dirty document is mirrored into the app's
drafts/folder a moment after every edit and dropped once saved, so a crash or a forced shutdown loses nothing;File › Recover Unsaved Drafts…(also Preferences › General) lists what was left behind with a preview, and restores it into the window or discards it - Multiple windows:
Ctrl+Shift+Nopens another window (one document per window, like Typora),Ctrl+Tab/Ctrl+Shift+Tabcycle through them, the Window menu lists them, and a second launch with a file opens it in a new window.Ctrl+Shift+Treopens the last closed file. - Launch behaviour (Preferences › General › Startup): start with a new document, restore the last folder, restore the last file and folder, or always open a fixed folder
- Focus mode, typewriter mode, zoom, fullscreen, always on top
Output
- Export HTML (self-contained: theme CSS, highlight colours, KaTeX CSS with its fonts and rendered diagrams inlined), HTML without styles (bare markup), and Image (PNG) — the whole document as one picture at a configurable width
- Export PDF (rendered through a hidden window and Chromium
printToPDF, so it matches the page): paper size (A3/A4/A5/B5/Letter/Legal/Tabloid or a custom148x210 mm), landscape, margins, header and footer (left | center | right, with${title},${author},${today},${pageNo},${pageCount}and any front matter key), a page break before every#heading, andtitle/author/description/keywordsfrom the front matter written into the PDF's document properties - HTML options: a collapsible outline side panel (pure CSS), raw HTML appended to
<head>and<body>with the same${…}variables - Preferences › Export: theme for exports, default export folder, open / reveal after exporting;
File › Export › Export with Previous(Ctrl+Alt+E) repeats the last format into the last folder without a dialog - Front matter overrides the export settings per document:
export-theme,pdf-paper,pdf-landscape,pdf-margin(20/20 15/t r b l, mm) andpdf-margin-top…,pdf-header,pdf-footer,pdf-h1-page-break,html-outline,html-head/html-body(|block scalars work),image-width - Copy as HTML, copy as Markdown
- Copy for WeChat (
Ctrl+Alt+C, also in the editor's right-click menu) — see below
公众号 throws away every stylesheet, so a normal rich copy arrives as unstyled text. This command
rebuilds the document with inline styles only and puts it on the clipboard as a text/html
flavour (the plain-text flavour carries the Markdown source, for pasting into a plain editor).
- Every element gets a
styleattribute from a small WeChat-safe profile, coloured from the theme you are looking at; noclass, noid, no<style>, nohref. - The style is configurable in Preferences › Export (also
File › Export › Export Settings…), next to a live preview that runs the real copy pipeline on the open document (or a built-in sample) inside a stylesheet-free frame, at phone or desktop width: colour theme cards (follow the editor, or any light theme — dark ones fall back to Default), accent colour, H2 style (bar / underline / badge / plain), font size, line height, letter and paragraph spacing, justified text, first-line indent, code language labels, and the 参考链接 list. - Code blocks keep their highlight colours as inline
colors on<span>s. - Tables are rebuilt with visible borders, a shaded header row and padding on every cell.
- Local images are embedded as
data:URIs (WeChat cannot readfile://); remote ones are left alone. - Formula blocks and mermaid diagrams are rasterised to PNG — the 公众号 editor would show raw
KaTeX spans and drop the
<svg>. They are drawn once in an offscreen window and pinned to their CSS size, so an inline formula stays inline-sized. - Links cannot be clicked inside 公众号, so each one keeps its text and gains a superscript number, with a 参考链接 list of the real URLs at the end.
What you see above is the pasted result rendered with every stylesheet stripped — exactly the worst case the editor can do to it.
Caveats: 公众号 strips border-radius on some elements and rewrites <table> widths; wide code
blocks rely on overflow-x: auto, which the app-side editor honours but the phone-side preview may
scroll less gracefully. Nothing that needs remote hosting (image uploads, 图床) is attempted.
Interface language: English or 简体中文 (Preferences › General › Language, or follow the OS).
Menus, dialogs, preferences, messages and editor hints are translated; switching applies at once.
The English text is the translation key (src/main/locales/zh-CN.js), with {name} slots for
messages that carry a value.
Themes: Default, Night, Pixyll, Whitey, Newsprint — plain CSS variable files, drop your own in
src/renderer/styles/themes/.
- Follow the system dark mode (Preferences › Appearance): pick one theme for light and one for dark and the editor switches with the OS; the Themes menu then changes whichever slot is active.
- Custom CSS without touching the app:
Themes › Open Theme Folder(or the button in Preferences) opens the user theme folder, Typora-style.base.user.cssis appended to every theme,<theme>.user.cssto that theme, and a whole<name>.cssshows up in the Themes menu as a theme of its own (start it with/* dark */to mark it dark). Exports pick the overrides up too. Edits apply when the theme is re-selected or the window is reloaded.
The installed app doubles as a command line tool (src/main/cli-args.js parses it; in development
pnpm exec electron . … takes the same arguments):
posemark notes/ # a folder opens in the sidebar, files in the editor
posemark -n todo.md # always a new window
posemark --wait draft.md # return when that window closes (git: core.editor="posemark --wait")
posemark export a.md b.md -f pdf -o out/ # html | html-plain | pdf | png | wechat
posemark export a.md --theme night --paper Letter --landscape --margin "18 14" --set html-outline=true
posemark --helpexport renders in a hidden window and writes next to each file unless -o names a file (one
input) or a folder; the options are the same front matter keys a document can carry, so
--set key=value takes any of them. A running Posemark is reused for open, never for export.
How posemark gets on PATH: the Windows installer has a page for it (it adds bin\posemark.cmd's
folder to the user or machine PATH and the uninstaller removes it again); on macOS use
File › Install "posemark" Shell Command, which links Contents/Resources/bin/posemark into
/usr/local/bin; the deb package installs /usr/bin/posemark; for the AppImage or the portable zip,
alias the executable yourself. The Store (MSIX) build declares an app execution alias
(build/appx/extensions.xml), so posemark is available right after installing from the Store.
The main process has three verification modes (they are dev-only flags, not user features):
# render samples/demo.md once and save a screenshot
pnpm exec electron . samples/demo.md --screenshot=tmp/shot.png --wait=4000
# run every editor command and print the resulting document
pnpm exec electron . samples/demo.md --selftest
# build the exported HTML + PDF through the real pipeline
pnpm exec electron . samples/demo.md --export-test=tmp/exp
# build the WeChat fragment, render it with every stylesheet stripped, audit the computed
# styles, and print what actually landed on the clipboard
pnpm exec electron . samples/demo.md --wx-test=tmp/wxPOSEMARK_DEBUG=1 forwards renderer console output to the terminal, POSEMARK_DUMP=1|2|3 dumps the
syntax tree / visible line texts / block widgets, and POSEMARK_SCROLL=<px> scrolls before the capture.
POSEMARK_JS=<script> runs renderer code before the capture, POSEMARK_CLIP=<script> runs code that
returns { x, y, width, height } (CSS px) so only that region is captured, and POSEMARK_TYPE=<text>
types real keystrokes into the editor (that is how the smart-typography input handler is verified).
src/main/ main process: windows, menu, IPC, config store, PDF pipeline
src/main/menu.js application menu (the Typora-like menu tree)
src/main/drafts.js unsaved-draft backups under userData/drafts
src/main/user-themes.js user theme folder (base.user.css, <theme>.user.css, whole themes)
src/main/i18n.js UI translation (shared with the renderer); locales/zh-CN.js is the dictionary
src/main/pdf-info.js PDF document properties (incremental update of the Info dictionary)
src/main/images.js image files: import / copy / move / download, uploaders (PicGo-Core, PicList, command)
src/renderer/ renderer bundle
main.js app shell: files, sidebar, outline, status bar, command dispatch, export
editor.js CodeMirror 6 setup + paragraph/format commands
live-preview.js decorations: hide marks off-caret, replace blocks with widgets
blocks.js block scanner + block widgets (code, table, math, diagram, image, task)
markdown.js markdown-it instance for export / copy-as-HTML / table rendering
export-settings.js export options: front matter overrides, ${var} expansion, PDF page / header / footer, outline
wechat.js copy-for-WeChat: inline style profile + rasteriser for maths and diagrams
syntax.js highlight.js wrapper
paths.js path helpers: file:// resolution, typora-root-url, Markdown image paths, <img> parsing
outline.js outline tree / filter / current-heading helpers
math.js KaTeX options (mhchem, physics macros), equation numbering, \label / \ref
diagrams.js sequence (js-sequence) and flow (flowchart.js) → Mermaid translation
wordcount.js word count panel figures and reading time
styles/ app.css, editor.css, content.css (export), themes/*.css
build/ app icon: icon.svg (source) → `pnpm icons` → icon.png / icon.ico / appx/*.png
samples/demo.md a document that exercises everything
website/ static promo site (home, docs, privacy policy); see website/README.md
docs/ screenshots
| Action | Keys |
|---|---|
| New / Open / Save / Save As | Ctrl+N / Ctrl+O / Ctrl+S / Ctrl+Shift+S |
| New window / Next, previous window / Reopen closed file | Ctrl+Shift+N / Ctrl+Tab, Ctrl+Shift+Tab / Ctrl+Shift+T |
| Quick open | Ctrl+P |
| Find / Replace | Ctrl+F / Ctrl+H |
| Source code mode | Ctrl+/ |
| Focus mode / Typewriter mode | F8 / F9 |
| Heading 1–6 / Paragraph | Ctrl+1…6 / Ctrl+0 |
| Bold / Italic / Code / Strike / Highlight | Ctrl+B / Ctrl+I / Ctrl+Shift+\`` / Alt+Shift+5/Ctrl+Shift+H` |
| Insert link / image | Ctrl+K / Ctrl+Shift+I |
| Table / Code fence / Math block | Ctrl+T / — / Ctrl+Shift+M |
| Sidebar / Outline / Search / Files / File list | Ctrl+Shift+L / Ctrl+Shift+1 / Ctrl+Shift+2 / Ctrl+Shift+3 / Ctrl+Shift+4 |
| Word count panel | Ctrl+Shift+W |
| Find in folder / Paste as plain text | Ctrl+Shift+F / Ctrl+Shift+V |
| Export PDF / Print | Ctrl+P |
| Export with Previous | Ctrl+Alt+E |
| Copy for WeChat | Ctrl+Alt+C |
| Rename / Duplicate / Move to trash / Undo file operation (file tree) | F2 / Ctrl+D / Del / Ctrl+Z |
- Editing the inside of a rendered formula (picking a sub-expression in the KaTeX tree) — the field is whole-TeX, not structural. Tables, code, mermaid and images are all editable in place now.
- Pandoc export (docx / EPUB / LaTeX) and custom export commands, spell-check dictionaries beyond Chromium's.
- Strict mode only relaxes headings; list indentation still follows GFM.
- Tabs: one document per window, like Typora (
Ctrl+Tabswitches windows instead).
















