As it turns out, making a simple markdown editor is not simple at all.
Simple Markdown Editor is my pathetic attempt at trying to replicate Obsidian's fabulous WYSIWYG Markdown Editor using CodeMirror. It's currently using an implementation based on @segphault's work from his segphault/codemirror-rich-markdoc WYSIWYG MarkDoc-Based CM6 editor, and I'll switch the MarkDoc Implementation to a pure [lezer-parser/markdown] implementation once I get the markdown editing done.
Updates on June 3rd, 2025: I've integrated @erykwalder's work from his erykwalder/lezer-markdown-obsidian repository to add internal links, embeds, frontmatter parsing, better task list parsing, LaTeX parsing, footnote parsing, marks parsing, highlight parsing, comment parsing, and better hashtag parsing. Huge thanks to his work and effort on his implementation to Obsidian-Flavored Markdown in CM6.
Updates on June 5th, 2025: I've integrated @ebullient's work from his ebullient/markdown-it-obsidian-callouts repository to add nested callouts. Underneath the hood, now I use a Lezer-Parser to detect whether a Blockquote is a Callout, and if it is, replace that occuring lines with a Decoration Widget. The Widget's toDOM() function uses @ebullient's markdown-it-obsidian-callouts package for the markdown-it package to convert the markdown parts of the Callout into HTML. This method does solve some nesting issues, where some prose components works in callouts now, but nested callouts is still half-supported. While nested Callouts does show correctly, some functionality may be missing, like Callout Icons and Callout Fold Buttons. This is still heavily WIP, but it should mark around the 70% progress of this project.
The styling of the codeblock is weird. When you only have ```, the next line is falsely recognized as a FencedCode mark, which means after your cursor leaves that line, the editor will hide it. Same goes for the last line of the codeblock. Though, this only applies when the codeblock is not closed. When the codeblock is closed, everything works fine.- Fixed.
The selection highlights of this editor works badly. In select-all cases, it's possible that the selection highlight won't even show up at all because of all the marks.- Fixed.
The current editor is not even fully GFM-compatible. Quotes aren't working, which is being actively worked on. Obsidian does it in a weird way, and it seems to extend its parsing with callouts with a few "magic tricks". The current quotes editing of this editor is also weird; if you add a header node in a quote block, it some how stops functioning. No idea why, and I'm working on it.- Fixed.
- The core of the WYSIWYG implementation is that it uses [@lezer-parser/markdown] to parse the entire document or a recognized node into an AST, then hiding the Markdown Mark of that node or replacing the node with a
RenderBlockWidget. Obsidian does the similar thing, in fact, I believe they have their own fork of CodeMirror 6-Compatible-HyperMD. The cliche of that implementation in this editor is that theDecoration Markor theRenderBlockWidgetgets replaced when the mouse clicks down on the editor, whereas in Obsidian, they get replaced when the mouse button is released. The nuisance of a small teensy mechanic is that this allows more precise markdown editing in a WYSIWYG line, but without it, you'd have less precision and control over the editor. I doubt that Obsidian has a specific impl. to add in this mechanic (because it probably came built in with HyperMD), but it does wreck the User Experience a bit without it. TheRenderBlockWidgetimplementation is currently using MarkDoc (based on the implementation from @segphault), which is great, but MarkDoc does not allow custom markdown parsing (I'm referring to wikilinks, callouts that conforms to the Obsidian-Flavored Markdown Syntax, etc.). I'm going to switch MarkDoc for remark, which is its own problem, but Markdown parsing is a huge pain in my arse and trying to parse Obsidian-Flavored Markdown Syntax isn't that easy. I believe LLMs handles regexes well, but goddamn stuff like trying to get a wikilink recognized as a wikilink instead of a link with a pair of brackets surrounding it is magical. In fact, @segphault expressed the same frustration in his original implementation, which really does not boost confidence a lot.- Temporarily resolved, see Updates on June 3rd, 2025, and Updates on June 5th, 2025.
- Some cliche issues are still being worked on. Internal Wiki Links and External Links are not yet clickable, and External Links are currently not rendered as a clickable element.
- Indentation and Leveled Lists (both Ordered and Unordered Lists) are weirdly... unsupported. I suspect this is due to some handling on my part and indentation level handling on the
@lezer-parser/markdownpart. Looking at the Obsidian DOM, it seems like they have a better level system in their parsing and it also seems like their parser handles lists similar to howATXHeadingswere handled, in terms of a "parent" starting position to check for a list to initiate an AST Tree Block marked with "Ordered/UnorderedList" and then from that point onwards, parses for the counts of indentations in subsequent elements of the list and marks them in the AST as"Ordered/UnorderedList${indentNumber}. Might be that, might not, but it's the only way I'm thinking that it can be implemented. For the continuity of the order in Ordered Lists in Obsidian, i.e. ordered list elements on the same level uses the same number counter even when an unordered list is separating them (not by a new line, but just a list), it's probably using the system I referred above, which I will try in the future. Maybe Obsidian has an entire Indentation parsing mark and then uses that with the list... Possible as well. - Widget/Deco Marks Hiding with Task Lists are broken. When you try to click on the line of the task list item, the cursor gets resetted to the start of the line. The Task Checker Mark only reveals when the cursor is on the
[ ]mark, instead of at the line or like in Obsidian, at the- [ ]range. I'll have to deal with the parsing issue of this in the future.
- Before they (hopes and dreams) get crushed, I really aspire to make this an implementation that I can use in my other projects, like a plug-and-play. Hopefully this goes for the same for other people.
- Parsing Obsidian-Flavored Markdown is a huge pain. It's brilliant and props to the Obsidian team for that, but to the one who nailed Markdown parsing, damn.... you're a genius. Hopefully, I can achieve most of the aspects of OFM parsing and try to make it look good in the editor.
- Since this project is originally meant to be a markdown editor, I don't want to make this into a full-fledged Obsidian-Alternative Note-taking app. Obsidian combines technology in a way that's super great and efficient. It's like... the neovim of note taking apps, except it's more user-friendly. Instead, I want to make the editor look, work, and run similar to Obsidian's WYSIWYG editor, and have it plug-and-playable/modular and have people customize it to their own needs.