Skip to content
whyun-pagesPublic

About

A Typora-like live-preview Markdown editor for the desktop.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Posemark

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.

Run it

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 install

Then 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.

Test and release

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.svg

CI 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.1

The 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).

editor

In-place table editing — click a table and you get the grid, not pipes:

table editing

Rows, columns and alignment from a right-click menu on any cell:

table menu

Cells render their inline Markdown until you focus them:

table cell formatting

Code block while editing: fences stay visible, the code stays highlighted:

code editing

Math and diagram blocks while editing — the formula is a field with its render below, the diagram keeps its source and re-renders underneath:

math field

diagram live preview

Images too — the picture appears under the ![]() line you are editing:

image live preview

Dropping an image file onto the editor:

drop hint

Click an image to get a resize frame (![alt|320](pic.png) is what ends up in the file):

image resize

[TOC] on its own line becomes a clickable contents block; [^ref] footnotes render as superscript numbers with their definitions collected at the end:

table of contents

footnotes

Right-click anything in the file tree for new file / new folder / rename / trash:

file tree menu

code blocks

night theme

preferences

What works

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]: url line exists; the definition lines are dimmed, and Ctrl+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 › Alert wraps the selection)
  • Strict mode switch (Preferences › Markdown): off, #Title without 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-Tab walks the cells, Enter jumps down a row, Tab on the last cell appends a row, Esc drops the caret out below the table. Every keystroke is written back to the Markdown source (pipes escaped), and Ctrl+Z inside 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. Esc or Ctrl/Cmd+Enter leaves the field and drops the caret after the block.
  • ```mermaid blocks 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 ```math fences work next to $…$ / $$…$$; mhchem (\ce{H2O}) is loaded, and a KaTeX-friendly subset of the physics package (\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 / gather environments 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 a missing: … 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) with ![name](assets/name.ext) inserted at the drop point; with no file open it goes to Pictures/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 a typora-copy-images-to: ./img line in the front matter overrides it per file. Written paths are relative by default (./ prefix and URL-escaping are switches); with typora-root-url: /path in the front matter, /images/x.png resolves 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"> and style="zoom:50%" (what Typora writes) render at that size, inline or on a line of their own, the resize handle rewrites the width attribute, 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 ![alt|320](pic.png). Hold Shift while dragging to unlock the ratio — that writes ![alt|320x140](pic.png) (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) opens http(s)/mailto in the system browser, a relative .md in the editor (jumping to #anchor if there is one), any other relative file in its default app, and #foo scrolls 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]: text lines are dimmed with a fn n badge — the export collects them into a <section class="footnotes"> with back-links
  • Paste a screenshot (Ctrl+V with an image on the clipboard) and it lands in assets/ 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+V always pastes the plain text.
  • Shift+Enter inside 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) makes name copy.md; Undo Last File Operation (Ctrl+Z with the tree focused, or the right-click menu) reverses the last rename, move, duplicate or new file; arrow keys / Enter / Home / End walk 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 of File › Open Recent and 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+W for 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+N opens another window (one document per window, like Typora), Ctrl+Tab / Ctrl+Shift+Tab cycle through them, the Window menu lists them, and a second launch with a file opens it in a new window. Ctrl+Shift+T reopens 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 custom 148x210 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, and title / author / description / keywords from 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) and pdf-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

Copy for WeChat

公众号 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 style attribute from a small WeChat-safe profile, coloured from the theme you are looking at; no class, no id, no <style>, no href.
  • 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 read file://); 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.

copy for wechat

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.css is appended to every theme, <theme>.user.css to that theme, and a whole <name>.css shows 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.

Command line

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 --help

export 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.

Verifying without clicking

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/wx

POSEMARK_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).

Layout

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

Keyboard shortcuts

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

Not implemented (and why it shows)

  • 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+Tab switches windows instead).

License

MIT

About

A Typora-like live-preview Markdown editor for the desktop.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages