许愿式编程:我把 Typability 改成了能日常顶替 Typora 的样子 – 树欲静·而风不止

许愿式编程:我把 Typability 改成了能日常顶替 Typora 的样子 – 树欲静·而风不止


Why fork

I have been using Typora for a long time. It is really easy to use, but there are two problems that I can’t get around.

  1. cost money. Those “activated versions” on the Internet are not completely cracked – after a while, the trial prompt will appear again, and the cache needs to be cleared, and the process is repeated.

  2. Can’t change. There are always some small things that are not smooth after using it (pop-up window logic, window behavior, menu status), but in closed-source software, you can only tolerate it.

So I looked for the open source base:Typability Just right for my taste. It is a WYSIWYG editor based on Milkdown, Tauri packaging (small size, low memory usage), Fluent UI interface, and Mica/acrylic window effects. The last upstream version was 0.4.1, and I started to change it from here – the functional skeleton is there, but it is full of “little awkwardness” in daily use.

I have to say a word of thanks first: the foundation of Typability is really good, thanks to the original author Simon Shiki . Milkdown’s WYSIWYG core, Tauri’s packaging link, Fluent’s component system and Nord/Tokyo themes, multi-language, search and replace, dual-column editing, LaTeX and Mermaid, have all been set up upstream. What I do is more about “filling in the details and optimizing operations” rather than building a Markdown editor from scratch.

So fork, and then change all the way to 0.6.3. Below is a complete record of what these versions have done, what pitfalls they have encountered, and whether they can replace Typora now.


Version list

Version

theme

0.5.0

Window memory, save/close semantics, drag and drop to open, title bar path, source code view, check for updates, build repairs

0.6.1

Dual-column editor and source code view are mutually exclusive

0.6.2

The link bubble is widened + OPEN button, and the default document link points to this repository.

0.6.3

Find and replace highlighting, match position correction, auto focus


0.5.0: First solve the awkwardness of “touching it every day”

1. Remember window size and position

It turns out that every time I start it, there is a fixed 540×680 centered window, and I have to manually drag and move it to the secondary screen every time.

Now there is a switch in the preferences. After checking it:

  • when closedThe window geometry is synchronously read from the Rust side and placed in the application configuration directory (localStorage is not placed to avoid front-end timing issues)

  • Only record the size of the “normal window”: skip it when maximizing/minimizing, so next time you restore the window size you commonly use

  • Protected during loading: If the recorded size is larger than all displays, ignore the size and use the default value; if the recorded position is not visible on any display (such as unplugging the secondary screen), ignore the position and use the default center

This one is essentially “Don’t let me be locked in an invisible window just because I changed my monitor.”

2. Only when the content is actually changed will you be prompted to save it.

This is the most annoying one:When I open the file, nothing is changed. When I close it, I get the message “You haven’t saved it yet.”。

The root is in the editor rather than the pop-up logic – it will be called when opening the file replaceAll Pour the content into Milkdown, and this time the programmatic document replacement will trigger markdownUpdated event, so the “just opened file” is marked as modified.

There are two amendments:

  1. To record the “original content when opening/saving”, use dirty = 当前内容 ≠ 原始内容 judgment, rather than using one that can be flipped randomly saved logo

  2. Differentiate in the editorprogrammatic replacementanduser input:replaceAll A mark is set during this period. The markdown update caused by it is only used to synchronize the baseline and is not regarded as a modification.

By the way, the situation of undoing and reverting to the original state has also been straightened out – after making changes and then changing them back, it is not considered a modification and no pop-up window will occur.

At the same time, the semantics of “cancel” are unified: the confirmation box only determinesDo you want to saveundecidedDo you want to continue the operation?. Click Cancel when closing = exit directly without saving, click Cancel when opening/creating/drag-and-drop = do not save but continue switching. Otherwise, there will be a dead end of “you will never be able to exit the software if you don’t want to save it.”

3. Save item status and save as pop-up window

  • The File menu’s “Save” now follows the change status:Available only with changes;Except for blank documents that have never been saved, they are always available (otherwise the new document cannot be saved for the first time)

  • The previous implementation of “Save As” was “first make the current path blank → trigger a save confirmation → then pop up the system save dialog box”. It is ridiculous to overlap the two dialog boxes. Now it follows an independent process: it only pops up the system save dialog box, and switches to the new path after the save is successful.

  • After saving a new document that does not yet have a path, the path will be written back and you will not be asked where to save it every time you press Ctrl+S.

4. Drag to open

Supports directly dragging md/txt files into the window to open them. When there are unsaved changes, ask whether to save them first, and follow the same path as “Open” in the file menu.

5. The title bar displays the current file name

The empty space to the right of the edit menu displays the current file name, and hovering displays the full path. When the width is not enoughOmit the middle(a-very-...e-name.md), which is easier to identify than truncating the tail. Unsaved documents displayed Untitled-1、Untitled-2…Increments with each new creation.

6. Source code view (Typora’s source mode)

The edit menu finally adds a separator bar and a “Source Code View” checkbox to switch between WYSIWYG and Markdown source code editing. In source code mode, it is a textarea with a fixed-width font, and the same content can be read and written directly, so saving, dirty marks, and automatic saving are still in effect.

A few details:

  • and “Double Column Editor”mutually exclusivecheck one and automatically cancel the other

  • When opening or creating a new document, return to WYSIWYG (checked state is automatically canceled)

  • In source code mode, menu items that rely on Milkdown instances (find/replace/insert tables, charts, formulas) are automatically disabled to avoid operating destroyed instances.

7. “Check for Updates” changes from placeholder to functional

The button on the About page was originally hard-coded disabled Placeholder, Tauri updater is not enabled at all in the project, and there is no target address. Now it uses the default browser to open the GitHub Releases page of this repository – no version comparison is done, and no API current limiting is involved, which is enough.

8. A build problem

  react-intl of @types/react The dependency range includes 19, and yarn installs two types, 18.3.19 and 19.2.0, resulting in tsc newspaper FormattedMessage Not available as a JSX component. use resolutions Unify to 18 to solve.


0.6.1: Dual columns and source code view returned to their original positions

The “Dual Column Editor” menu item was originally sandwiched between “Insert Table” and “Insert Chart”. Semantically they are a pair (both are view switching), so it was moved down above the “Source Code View” and made mutually exclusive. Both are restored to unchecked when opening/creating a new one.

A detail of Fluent UI has also been fixed here:MenuItemCheckbox default persistOnClick: truethe menu does not close after clicking, and must be turned off explicitly; in addition, the checked status is originally maintained internally by the component, and the UI will not change when it is reset externally, but will be changed to controlled checkedValues。


0.6.2: Add an OPEN to the link bubble

Clicking on a link in the document will pop up an editing bubble with only one APPLY button – just change the address and you’re done. If you want to actually access the link, you can only copy it and paste it into the browser.

Now the bubble is widened to 560px, and the same style is added after APPLY OPEN button, use the default browser to open the address in the input box; no agreement is written (for example github.com) will be automatically added https://。

This bubble is actually not in the source code of this project, but @milkdown/theme-pack-helper provided input-chip——The width and button copy are hard-coded in the theme. So the approach is: listen to the node after the editor is ready, widen it, and insert the same button after APPLY (inherited .tooltip-input button style). The placeholder of the image link bubble is different and OPEN will not be added.

Finally, the link pointing to the original author’s warehouse in the default document was changed to the address of this warehouse.


0.6.3: Find and Replace is finally “visible”

Matches are not displayed in reverse, but floating bubbles pop up.

The phenomenon is very strange: Ctrl+F search clearly prompts “Item 1 of 1”, but the selected text cannot be seen in the editor; instead, a floating tool window pops up that only appears when the text is selected.

The reason is the superposition of two layers:

  • The search just changes the matching interval into ProseMirrorconstituency(setSelection),and ProseMirror only when editor has focusOnly then the internal selection is synchronized to the DOM selection. When searching, the focus is in the search box, so the browser does not highlight it at all.

  • Milkdown’s tooltip only looks at the editor stateSelect changesdoes not look at the focus, so it pops up first when the selection changes.

The result is the fragmentation of “there is a selection in the state but not visible on the screen”.

Change the law: Do not rely on native selection, use ProseMirror instead Decoration Draw highlights. bring PluginKey The plug-in saves the matching interval,Decoration.inline Add to all matches find-matchadd the current item find-match-currentclear when doc changes, otherwise follow mapping.

Overall offset of highlight range

After connecting it, I found that the highlight was misplaced: Search Designthe highlighted one is esig Two more words.

original Finder is recursive accumulation nodeSize To calculate the absolute position of the text block,Each time a block-level node is entered, the starting offset of the node itself is omitted.. So the deeper the nesting, the more bias –Fluent Design exist bullet_list > list_item > paragraph exactly 2 characters off.

This bug doesn’t only affect highlighting:setSelection The same batch of locations are used.The replacement function will also replace the wrong position before。

  Change the law: Use directly doc.descendants Get the true position of each text block (text from pos + 1 Start), and easily deleted more than 50 lines of recursive code.

Auto focus on opening

After Ctrl+F opens the search bar, the focus falls on the search box and you can just type. Pressing Ctrl+F again while already in the search state will also bring the focus back.


A few pitfalls worth remembering

  • ProseMirror’s selection is only synchronized to the DOM when focused: All “programmed selection” scenes (search, position, jump) must use Decoration for visual feedback

  • Milkdown’s theme package will generate UI but not in your project: Link bubbles and toolbars are all there theme-pack-helperyou can only change it by intercepting the DOM or overriding the theme feature.

  • Fluent’s MenuItemCheckbox does not close the menu after clicking by defaultand the checked state needs to be controlled before it will follow the external state

  • Tauri v1 updater is turned off by defaulta written to death disabled The button looks like “the function is broken”, but in fact it is not implemented at all

  • Colored emoji will become monochrome line drawings in 16-bit color sessions: I checked this for a long time, and finally found that the same character is rendered as a gray notepad in RDP 16-bit color, and as a yellow Fluent emoji in 32-bit color. It has nothing to do with the code.

  • Don’t just rely on the front end to store window status: Move/zoom event + asynchronous writing when closing will race with window closing, leave it to Rust CloseRequested Synchronous placement is more reliable


Release pipeline

  • beat v* Tags automatically trigger builds (manual triggering is also retained), three-platform matrix

  • Verify tag sum before publishing package.json If the version is consistent, if it is inconsistent, it will directly fail.

  • Windows except setup.exe / msian additional installation-free Typability_<版本>_x64_windows.zip(There are only executable files inside)

  • Linux does not provide official builds yet. Students who need it can compile it by themselves (there is a line of commands in the README)


How was all this written?

What needs to be explained is:All these changes were completed under the “wishing programming” of CodeBuddy + DeepSeek-4.1-flash (including the first draft of this article).

There are basically only two things I do: describe the requirements and phenomena clearly in Chinese (“The window pops up without changing it when closing the window, which is very annoying”, “The highlighted position is wrong”), and I look at the compiled program and feed back the problems. The code itself – Rust’s window state placement and monitor out-of-bounds protection, Tauri configuration and permissions, ProseMirror plug-ins, React components, GitHub Actions pipeline – were all written by it.

I’ve never written Rust myself. Before this time, my knowledge of Rust was limited to being safer than C++. But in the whole process, from environment construction (rustup, domestic mirroring, MSVC tool chain) to src-tauri/setup.rs The window recovery logic in it, and then adding the Windows portable package to the release workflow, all ran through, and every step could be compiled and verified.

What’s even more “magical” is that it can withstand troubleshooting – for example, looking for the highlight offset bug, it can push it all the way from the phenomenon (the highlight is offset by two characters) to the root cause (the offset of the block-level node is omitted when recursively calculating the position, and the offset of the nested two levels is exactly 2), and then directly correct it and explain why the original implementation was wrong.

For users like me who “don’t know the language but know what they want”, this development method is probably the best form:I am responsible for making wishes and acceptance, and it is responsible for realizing and explaining。

Of course, we have to admit the other side:The prerequisite for wishful programming is that the foundation is good enough. The “heavy work” of the editor kernel, packaging, themes, and i18n has been completed upstream, so that AI can focus on specific issues such as window status, saving semantics, and search highlighting. If I were to start from scratch, just running Milkdown would probably be enough to drink a bottle.


Can it now replace Typora?

For my own usage scenarios (writing notes, writing documents, writing blog drafts),able. Daily high-frequency stuff is covered:

Function

state

What you see is what you getEdit

✅

Source code view switch

✅ 0.6.0 added

Title/List/Quote/Code Block/Table

✅

LaTeX formula

✅

Mermaid Chart

✅

Double column editing

✅

Find/Replace

✅ 0.6.3 fixed

Auto save

✅

Drag and drop files to open

✅

Dark and light color themes, window frosted glass

✅Better looking than Typora

Multilingual interface

✅

HTML export

✅

I also know the missing parts:

Function

state

Outline/Document Tree Sidebar

❌ The one I want to make up for the most

Picture pasting/picture bed

❌ Currently only links can be inserted

PDF/Word export

❌

Multiple tabs, multiple windows

❌ Single document

Automatic updates

⚠️ Only jump to Releases page

Linux official package

⚠️Compile by yourself

But put it another way:90% of it is better than the original “Typora that was not completely cracked”——No need to deal with activation failure, no need to endure pop-ups blocking the way, the window behavior is as expected, and you can change the code yourself if there is a problem. For a tool I open a dozen times a day, this is far more important than a few more export formats.

The next step is to add outline sidebars and image insertions. Once that is done, I can probably completely uninstall Typora.


*The changes recorded in this article are all on towerbit/Typability: starting from the last 0.4.1 in the upstream, all the way to 0.6.3.



Source link

Leave a Reply

Your email address will not be published. Required fields are marked *