.: Configuration filespackage.json: Project settingsbuild/: Contains generated binariesdist/: Build files for deploymentdocs/: Documentation and assetsresources/: Application assets using at build timenode_modules/: Dependenciessrc: MarkText source codecommon/: Common source files that only require Node.js APIs. Code from this folder can be used in all other folders exceptmuya.main/: Main process source files that require Electron main-process APIs.mainfiles can usecommonsource code.muya/: MarkTexts backend that only allow pure JavaScript, BOM and DOM APIs. Don't use Electron or Node.js APIs!renderer: Frontend for the editor UI. Renderer code may usecommonormuya, but system and Electron capabilities must be accessed throughsrc/renderer/services/nativeApi/*.
static/: Application assets (images, themes, etc)test/: Contains (unit) tests
MarkText is a realtime preview (WYSIWYG) editor for markdown with various markdown extensions and our philosophy is to keep things clean, simple and minimal. The application is build with HTML, JS and CSS on top of Electron. The renderer UI now runs on Vue 3 with Pinia. MarkText can be split in three parts: the core called Muya, the main- and renderer process.
Muya provides realtime preview and markdown editing via multiple modules based on a block structure. You can imagine it as the editor backend with modules for markdown parsing, data store as block structure, markdown document transformations according CommonMark and GitHub Flavored Markdown specification with some extra specifications, event listeners and an exporter to generate standalone HTML and markdown files but also to generate the WYSIWYG editor. Muya is single threaded as well as MarkText but use asynchronous functions to boost performance.
NOTE: MarkText's source-code editor is provided by CodeMirror and not well optimized nor feature rich. It's not part of Muya and an editor (renderer process) feature that load the markdown text from Muya (export), operate on it and re-import the text into Muya when switching to preview mode.
NOTE: Muya requires a core refactoring to provide better modularization, APIs and plugins. Furthermore, the data structure need improvements for better performance and stability.
The editor represents the view and is split into two parts. The first is the main process that have full access to Electron and all OS features. It's mainly used for IO, user interaction with native dialogs and controlls the editor windows. The main process should not (be long) blocked by synchronous operations. The renderer process is the real editor and also a host for Muya. It's responsible for all graphical elements (src/renderer/components), state (src/renderer/stores) and data synchronization. A renderer process is spawned for each window, operates on its own and is controlled by the main process. It contains two text editors: the realtime preview editor provided by Muya and the source-code one by CodeMirror with special features such as tabs, sidebar and editing features.
Renderer code must not import electron or @electron/remote directly. Native interactions go through the renderer facade in src/renderer/services/nativeApi/*, which keeps renderer call sites decoupled from the underlying Electron IPC details.
See VERSION_POLICY.md for the supported Node/Electron baseline and the official developer commands.
There are three runtime entry points to the application:
src/main/index.jsfor the main process that is executed first and only once per instance. It is bundled bytools/build/bun-pack.mjstodist/electron/main.js. Once the application is initialized, it's safe to access all the environment variables and single-instances and the application (App) is started (src/main/app/index.js). You can use the application afterApp::init()is run successfully.src/main/preload/index.jsfor the preload bridge. It is bundled bytools/build/bun-pack.mjstodist/electron/preload.jsand exposes the renderer-facing native contract onwindow.mtNative.src/renderer/index.htmlfor each editor window. It is bundled bytools/build/bun-pack.mjs, together withsrc/renderer/main.js, into the renderer assets underdist/electron/.
In development, tools/dev/bun-dev-runner.mjs rebuilds the Bun bundles, serves dist/electron on 127.0.0.1:9091, and restarts Electron when the app bundles change.
TBD
- Overview about Muya components
- How Muya work internal
- Data structure
Main- and renderer process communicate asynchronously via inter-process communication (IPC) and it's mainly used for IO and user interaction with native dialogs.
The boundary is now:
- renderer component/store/command
src/renderer/services/nativeApi/*- preload bridge in
src/main/preload/index.js - explicit IPC handlers in
src/main/ipc/renderer/*
Renderer code should call the facade, not Electron directly. The preload bridge owns access to ipcRenderer, webFrame, and the window.mtNative contract, while the main process owns the matching IPC handlers and BrowserWindow state.
The preload bridge now runs with contextIsolation: true, sandbox: true, and nodeIntegration: false. Native modules such as keytar, native-keymap, and fontmanager-redux are loaded lazily in main-process adapters under src/main/native/*, and renderer code can only access those capabilities through typed facades in src/renderer/services/nativeApi/*.
Unit tests now run through Vitest via vitest.config.js and test/unit/setup.js. The test/unit/specs/*.spec.js suite imports runtime dependencies directly and no longer depends on Karma globals, webpack preprocessors, or require.context.
End-to-end smoke coverage continues to use Playwright against the packaged dist/electron/main.js contract.
TBD
MarkdownDocument is a document that represents a markdown file on disk or an untitled document. To get a markdown document you can use the loadMarkdownFile function that asynchronously returns a RawMarkdownDocument (= MarkdownDocument with some additional information) in the main process.
Overall steps to open a file:
- Click
File -> Open Fileand a file dialog is shown that emitapp-open-file-by-idwith the editor window id to open the file in and resolved absolute file path. - The application (
Appinstance) tries to find the specified editor and callopenTabon the editor window. A new editor window is created if no editor window exists. - The editor window tries to load the markdown file via
loadMarkdownFileand send the result via themt::open-new-tabevent to the renderer process.
- Each opened file is also added to the filesystem watcher and the full path is saved to track opened file in the current editor window.
- The event is handled in
src/renderer/stores/editor.jsthrough the Pinia-backed editor store (renderer process), does some checks and creates a new document state that represents a markdown document and tab state. - The new created tab is either opened and the
file-changedevent is emitted or just added to the tab state. - Both Muya and the source-code editor listen on this event and change the markdown document accordingly.
NOTE: We currently have no high level APIs to make changes to the document text or lines automatically. All modifications need user interaction!