From 8f6a57e71d9e2d051bfb32bba0e479e4704d0930 Mon Sep 17 00:00:00 2001 From: "Md. Fahim Bin Amin" Date: Sat, 4 Jul 2026 07:36:33 +0600 Subject: [PATCH 1/3] Add Bengali README and links (#4823) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add Bengali README and links Add a Bengali (bn) translation at docs/i18n/README-bn.md and update the main README and i18n README files to include a Bangladesh (🇧🇩) link/icon. * fix(i18n): replace Bangladesh flag emoji with text representation in README files --- README.md | 3 + docs/i18n/README-ar.md | 3 + docs/i18n/README-bn.md | 201 ++++++++++++++++++++++++++++++++++++++ docs/i18n/README-de.md | 3 + docs/i18n/README-es.md | 3 + docs/i18n/README-fr.md | 3 + docs/i18n/README-jp.md | 3 + docs/i18n/README-kr.md | 3 + docs/i18n/README-pt.md | 3 + docs/i18n/README-tr.md | 3 + docs/i18n/README-zh_cn.md | 3 + docs/i18n/README-zh_tw.md | 3 + 12 files changed, 234 insertions(+) create mode 100644 docs/i18n/README-bn.md diff --git a/README.md b/README.md index 016b00be78..b1a14e6164 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,9 @@ :kr: + + :bangladesh: +
diff --git a/docs/i18n/README-ar.md b/docs/i18n/README-ar.md index ea2f0ccf84..3a7f044a1b 100644 --- a/docs/i18n/README-ar.md +++ b/docs/i18n/README-ar.md @@ -90,6 +90,9 @@ :kr: + + :bangladesh: +
diff --git a/docs/i18n/README-bn.md b/docs/i18n/README-bn.md new file mode 100644 index 0000000000..991d115142 --- /dev/null +++ b/docs/i18n/README-bn.md @@ -0,0 +1,201 @@ +

MarkText

+ +

MarkText

+ +
+ + twitter + +
+
+ :high_brightness: āφāϧ⧁āύāĻŋāĻ• āĻĒā§āϰāϜāĻ¨ā§āĻŽā§‡āϰ Markdown āĻāĻĄāĻŋāϟāϰ :crescent_moon:
+ āĻāĻ•āϟāĻŋ āϏāĻžāϧāĻžāϰāĻŖ āĻāĻŦāĻ‚ āĻ…āĻ¤ā§āϝāĻžāϧ⧁āύāĻŋāĻ• āĻ“āĻĒ⧇āύ-āϏ⧋āĻ°ā§āϏ Markdown āĻāĻĄāĻŋāϟāϰ āϝāĻž āĻ—āϤāĻŋ āĻāĻŦāĻ‚ āĻŦā§āϝāĻŦāĻšāĻžāϰāϝ⧋āĻ—ā§āϝāϤāĻžāϰ āĻĻāĻŋāϕ⧇ āϗ⧁āϰ⧁āĻ¤ā§āϤ āĻĻā§‡ā§ŸāĨ¤
+ Linux, macOS āĻāĻŦāĻ‚ Windows-āĻāϰ āϜāĻ¨ā§āϝāĨ¤ +
+ +
+ +
+ + + LICENSE + + + + total download + + + + latest download + + + + sponsors + +
+ +
+

+ + Website + + | + + āĻŦ⧈āĻļāĻŋāĻˇā§āĻŸā§āϝ + + | + + āĻĄāĻžāωāύāϞ⧋āĻĄ + + | + + āĻĄā§‡āϭ⧇āϞāĻĒāĻŽā§‡āĻ¨ā§āϟ + + | + + āĻ…āĻŦāĻĻāĻžāύ + +

+
+ +
+ āĻ…āύ⧁āĻŦāĻžāĻĻāϏāĻŽā§‚āĻš: + + :uk: + + + :cn: + + + :taiwan: + + + :de: + + + :jp: + + + :fr: + + + :tr: + + + :es: + + + :portugal: + + + :kr: + + + :saudi_arabia: + +
+ +
+ āĻāχ Markdown āĻāĻĄāĻŋāϟāϰāϟāĻŋ â¤ī¸Ž āĻĻāĻŋā§Ÿā§‡ āϤ⧈āϰāĻŋ āĻ•āϰ⧇āϛ⧇āύ + Jocs āĻāĻŦāĻ‚ + + āĻ…āĻŦāĻĻāĻžāύāĻ•āĻžāϰ⧀āϰāĻž + + āĨ¤ + +
+ +
+ +

āĻ¸ā§āĻĒāύāϏāϰ

+ +MarkText āĻāĻ•āϟāĻŋ āĻ“āĻĒ⧇āύ-āϏ⧋āĻ°ā§āϏ Markdown āĻāĻĄāĻŋāϟāϰ, āϝāĻž āĻāϰ āĻ•āĻŽāĻŋāωāύāĻŋāϟāĻŋāϰ āϏāĻšāĻžā§ŸāϤāĻžā§Ÿ āĻāĻ—āĻŋā§Ÿā§‡ āϚāϞāϛ⧇āĨ¤ MarkText āϝāĻĻāĻŋ āφāĻĒāύāĻžāϰ āĻ•āĻžāĻœā§‡āϰ āϧāĻžāϰāĻž āωāĻ¨ā§āύāϤ āĻ•āϰ⧇ āĻĨāĻžāϕ⧇, āϤāĻžāĻšāϞ⧇ āĻ…āύ⧁āĻ—ā§āϰāĻš āĻ•āϰ⧇ [āĻĒā§āϰāĻœā§‡āĻ•ā§āϟāϟāĻŋāϕ⧇ āĻ¸ā§āĻĒāύāϏāϰ āĻ•āϰāĻžāϰ](https://github.com/sponsors/marktext) āĻ•āĻĨāĻž āĻŦāĻŋāĻŦ⧇āϚāύāĻž āĻ•āϰ⧁āύāĨ¤ āϏāĻŦ āĻ¸ā§āĻĒāύāϏāϰāϕ⧇ āϧāĻ¨ā§āϝāĻŦāĻžāĻĻ â¤ī¸ + +**āĻŦāĻŋāĻļ⧇āώ āĻ¸ā§āĻĒāύāϏāϰ** + +| [](https://serpapi.com/?utm_source=marktext) | [āφāĻŽāĻžāĻĻ⧇āϰ āĻĻā§āϰ⧁āϤ, āϏāĻšāϜ āĻāĻŦāĻ‚ āĻĒā§‚āĻ°ā§āĻŖāĻžāĻ™ā§āĻ— API āĻĻāĻŋā§Ÿā§‡ Google āĻ“ āĻ…āĻ¨ā§āϝāĻžāĻ¨ā§āϝ āϏāĻžāĻ°ā§āϚ āχāĻžā§āϜāĻŋāύ āĻĨ⧇āϕ⧇ āĻĢāϞāĻžāĻĢāϞ āϏāĻ‚āĻ—ā§āϰāĻš āĻ•āϰ⧁āύāĨ¤](https://serpapi.com/?utm_source=marktext) | +| ------------- |:-------------| + +## āĻ¸ā§āĻ•ā§āϰāĻŋāύāĻļāϟ + +![](../assets/marktext.png?raw=true) + +## āĻŦ⧈āĻļāĻŋāĻˇā§āĻŸā§āϝ + +- āϰāĻŋā§Ÿā§‡āϞāϟāĻžāχāĻŽ āĻĒā§āϰāĻŋāĻ­āĻŋāω (WYSIWYG) āĻāĻŦāĻ‚ āĻĒāϰāĻŋāĻšā§āĻ›āĻ¨ā§āύ, āφāϧ⧁āύāĻŋāĻ• āχāĻ¨ā§āϟāĻžāϰāĻĢ⧇āϏ āφāĻĒāύāĻžāϕ⧇ āĻ—āĻ­ā§€āϰ āĻŽāύ⧋āϝ⧋āϗ⧇āϰ āϏāĻžāĻĨ⧇ āϞ⧇āĻ–āĻžāϰ āĻ…āĻ­āĻŋāĻœā§āĻžāϤāĻž āĻĻā§‡ā§ŸāĨ¤ +- [CommonMark Spec](https://spec.commonmark.org), [GitHub Flavored Markdown Spec](https://github.github.com/gfm/) āĻāĻŦāĻ‚ āφāĻ‚āĻļāĻŋāĻ•āĻ­āĻžāĻŦ⧇ [Pandoc markdown](https://pandoc.org/MANUAL.html#pandocs-markdown) āϏāĻŽāĻ°ā§āĻĨāύ āĻ•āϰ⧇āĨ¤ +- Markdown āĻāĻ•ā§āϏāĻŸā§‡āύāĻļāύ āϝ⧇āĻŽāύ āĻ—āĻŖāĻŋāϤ⧇āϰ āĻ…āĻ­āĻŋāĻŦā§āϝāĻ•ā§āϤāĻŋ (KaTeX), front matter āĻāĻŦāĻ‚ emoji āϏāĻŽāĻ°ā§āĻĨāύ āĻ•āϰ⧇āĨ¤ +- āĻĻā§āϰ⧁āϤ āϞ⧇āĻ–āĻžāϰ āϜāĻ¨ā§āϝ paragraph āĻ“ inline style shortcut āϏāĻŽāĻ°ā§āĻĨāύ āĻ•āϰ⧇āĨ¤ +- **HTML** āĻāĻŦāĻ‚ **PDF** āĻĢāĻžāχāϞ⧇ āĻāĻ•ā§āϏāĻĒā§‹āĻ°ā§āϟ āĻ•āϰāĻž āϝāĻžā§ŸāĨ¤ +- āĻŦāĻŋāĻ­āĻŋāĻ¨ā§āύ [theme](https://marktext.me/docs/themes): **Cadmium Light**, **Material Dark** āχāĻ¤ā§āϝāĻžāĻĻāĻŋāĨ¤ +- āĻŦāĻŋāĻ­āĻŋāĻ¨ā§āύ āĻāĻĄāĻŋāϟāĻŋāĻ‚ āĻŽā§‹āĻĄ: **Source Code mode**, **Typewriter mode**, **Focus mode**āĨ¤ +- āĻ•ā§āϞāĻŋāĻĒāĻŦā§‹āĻ°ā§āĻĄ āĻĨ⧇āϕ⧇ āϏāϰāĻžāϏāϰāĻŋ āĻ›āĻŦāĻŋ āĻĒ⧇āĻ¸ā§āϟ āĻ•āϰāĻž āϝāĻžā§ŸāĨ¤ + +## āĻĄāĻžāωāύāϞ⧋āĻĄ āĻ“ āχāύāĻ¸ā§āϟāϞ⧇āĻļāύ + +![platform](https://img.shields.io/static/v1.svg?label=Platform&message=Linux%20x64%20|%20macOS%20x64%2Farm64%20|%20Windows%20x64%2Farm64&style=for-the-badge) + +| ![](https://raw.githubusercontent.com/wiki/ryanoasis/nerd-fonts/screenshots/v1.0.x/mac-pass-sm.png) | ![](https://raw.githubusercontent.com/wiki/ryanoasis/nerd-fonts/screenshots/v1.0.x/windows-pass-sm.png) | ![](https://raw.githubusercontent.com/wiki/ryanoasis/nerd-fonts/screenshots/v1.0.x/linux-pass-sm.png) | +|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:| +| [![Download for macOS](https://img.shields.io/badge/macOS-Download-blue)](https://github.com/marktext/marktext/releases/latest) | [![Download for Windows](https://img.shields.io/badge/Windows-Download-blue)](https://github.com/marktext/marktext/releases/latest) | [![Download for Linux](https://img.shields.io/badge/Linux-Download-blue)](https://github.com/marktext/marktext/releases/latest) | + +āϏāĻ°ā§āĻŦāĻļ⧇āώ āϏāĻ‚āĻ¸ā§āĻ•āϰāϪ⧇āϰ āύāϤ⧁āύ āĻĢāĻŋāϚāĻžāϰāϗ⧁āϞ⧋ āĻĻ⧇āĻ–āϤ⧇ [CHANGELOG](https://marktext.me/docs/changelog) āĻĻ⧇āϖ⧁āύāĨ¤ + +#### macOS + +macOS 11 (Big Sur) āĻŦāĻž āĻĒāϰāĻŦāĻ°ā§āϤ⧀ āϏāĻ‚āĻ¸ā§āĻ•āϰāĻŖ āĻĒā§āĻ°ā§Ÿā§‹āϜāύāĨ¤ Universal build āĻĒā§āϰāĻ•āĻžāĻļ āĻ•āϰāĻž āĻšā§Ÿ āύāĻž, āϤāĻžāχ āφāĻĒāύāĻžāϰ āĻŽā§‡āĻļāĻŋāύ⧇āϰ āϏāĻ™ā§āϗ⧇ āĻŽāĻŋāϞ āĻĨāĻžāĻ•āĻž `arm64` āĻŦāĻž `x64` installer āĻŦ⧇āϛ⧇ āύāĻŋāύāĨ¤ + +āφāĻĒāύāĻŋ [release page](https://github.com/marktext/marktext/releases/latest) āĻĨ⧇āϕ⧇ āϏāĻ°ā§āĻŦāĻļ⧇āώ `marktext-mac-(arm64|x64)-%version%.dmg` āĻĄāĻžāωāύāϞ⧋āĻĄ āĻ•āϰāϤ⧇ āĻĒāĻžāϰ⧇āύ, āĻ…āĻĨāĻŦāĻž [**homebrew cask**](https://github.com/caskroom/homebrew-cask) āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰ⧇ MarkText āχāύāĻ¸ā§āϟāϞ āĻ•āϰāϤ⧇ āĻĒāĻžāϰ⧇āύāĨ¤ Homebrew-Cask āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰāϤ⧇ āĻšāϞ⧇ āφāϗ⧇ [Homebrew](https://brew.sh/) āχāύāĻ¸ā§āϟāϞ āĻ•āϰāĻž āĻĨāĻžāĻ•āϤ⧇ āĻšāĻŦ⧇āĨ¤ + +```bash +brew install --cask mark-text +``` + +#### Windows + +Windows 10 āĻ…āĻĨāĻŦāĻž 11 āĻĒā§āĻ°ā§Ÿā§‹āϜāύāĨ¤ x64 āĻāĻŦāĻ‚ arm64 - āωāϭ⧟ installer āĻĒā§āϰāĻ•āĻžāĻļ āĻ•āϰāĻž āĻšā§Ÿ, āϤāĻžāχ āφāĻĒāύāĻžāϰ āĻŽā§‡āĻļāĻŋāύ⧇āϰ architecture āĻ…āύ⧁āϝāĻžā§Ÿā§€āϟāĻŋ āĻŦ⧇āϛ⧇ āύāĻŋāύāĨ¤ + +āĻļ⧁āϧ⧁ setup wizard (`marktext-win-(x64|arm64)-%version%-setup.exe`) āĻĄāĻžāωāύāϞ⧋āĻĄ āĻ•āϰ⧇ āχāύāĻ¸ā§āϟāϞ āĻ•āϰ⧁āύ āĻāĻŦāĻ‚ user-only āύāĻžāĻ•āĻŋ machine-wide āχāύāĻ¸ā§āϟāϞ āĻ•āϰāĻŦ⧇āύ āϤāĻž āĻŦ⧇āϛ⧇ āύāĻŋāύāĨ¤ āĻŦāĻŋāĻ•āĻ˛ā§āĻĒāĻ­āĻžāĻŦ⧇, [Chocolatey](https://chocolatey.org/) āĻŦāĻž [Winget](https://docs.microsoft.com/en-us/windows/package-manager/winget/) āĻāϰ āĻŽāϤ⧋ package manager āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰ⧇āĻ“ MarkText āχāύāĻ¸ā§āϟāϞ āĻ•āϰāϤ⧇ āĻĒāĻžāϰ⧇āύāĨ¤ + +Chocolatey āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰāϤ⧇ āϚāĻžāχāϞ⧇ āφāϗ⧇ [Chocolatey](https://chocolatey.org/install) āχāύāĻ¸ā§āϟāϞ āĻ•āϰāĻž āĻĨāĻžāĻ•āϤ⧇ āĻšāĻŦ⧇: + +```bash +choco install marktext +``` + +Winget āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰāϤ⧇ āϚāĻžāχāϞ⧇ āφāϗ⧇ [Winget](https://docs.microsoft.com/en-us/windows/package-manager/winget/#install-winget) āχāύāĻ¸ā§āϟāϞ āĻ•āϰāĻž āĻĨāĻžāĻ•āϤ⧇ āĻšāĻŦ⧇: + +```bash +winget install marktext +``` + +#### Linux + +āĻ…āύ⧁āĻ—ā§āϰāĻš āĻ•āϰ⧇ [Linux installation instructions](https://marktext.me/docs/installation) āĻ…āύ⧁āϏāϰāĻŖ āĻ•āϰ⧁āύāĨ¤ + +#### āĻ…āĻ¨ā§āϝāĻžāĻ¨ā§āϝ + +Linux, macOS āĻāĻŦāĻ‚ Windows-āĻāϰ āϏāĻŦ binary [release page](https://github.com/marktext/marktext/releases/latest) āĻĨ⧇āϕ⧇ āĻĄāĻžāωāύāϞ⧋āĻĄ āĻ•āϰāĻž āϝāĻžāĻŦ⧇āĨ¤ āφāĻĒāύāĻžāϰ āϏāĻŋāĻ¸ā§āĻŸā§‡āĻŽā§‡āϰ āϜāĻ¨ā§āϝ āϕ⧋āύ⧋ āϏāĻ‚āĻ¸ā§āĻ•āϰāĻŖ āύāĻž āĻĨāĻžāĻ•āϞ⧇ āĻāĻ•āϟāĻŋ [issue](https://github.com/marktext/marktext/issues) āϖ⧁āϞ⧁āύāĨ¤ + +## āĻĄā§‡āϭ⧇āϞāĻĒāĻŽā§‡āĻ¨ā§āϟ + +āφāĻĒāύāĻŋ āϝāĻĻāĻŋ āύāĻŋāĻœā§‡ MarkText build āĻ•āϰāϤ⧇ āϚāĻžāύ, āϤāĻžāĻšāϞ⧇ āφāĻŽāĻžāĻĻ⧇āϰ [build instructions](https://marktext.me/docs/dev/build) āĻĻ⧇āϖ⧁āύāĨ¤ + +- [āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀ āĻĄāϕ⧁āĻŽā§‡āĻ¨ā§āĻŸā§‡āĻļāύ](https://marktext.me/docs/introduction) +- [āĻĄā§‡āϭ⧇āϞāĻĒāĻžāϰ āĻĄāϕ⧁āĻŽā§‡āĻ¨ā§āĻŸā§‡āĻļāύ](https://marktext.me/docs/dev/overview) + +MarkText āϏāĻŽā§āĻĒāĻ°ā§āϕ⧇ āφāĻĒāύāĻžāϰ āϕ⧋āύ⧋ āĻĒā§āϰāĻļā§āύ āĻĨāĻžāĻ•āϞ⧇ issue āϖ⧁āϞāϤ⧇ āĻĒāĻžāϰ⧇āύāĨ¤ Issue āĻ–ā§‹āϞāĻžāϰ āϏāĻŽā§Ÿ āĻ…āύ⧁āĻ—ā§āϰāĻš āĻ•āϰ⧇ āĻĄāĻŋāĻĢāĻ˛ā§āϟ āĻĢāϰāĻŽā§āϝāĻžāϟāϟāĻŋ āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰ⧁āύāĨ¤ āĻ…āĻŦāĻļā§āϝāχ, āϏāϰāĻžāϏāϰāĻŋ PR āĻĒāĻžāĻ āĻžāϞ⧇ āϏ⧇āϟāĻŋāĻ“ āĻ…āĻ¤ā§āϝāĻ¨ā§āϤ āĻĒā§āϰāĻļāĻ‚āϏāĻŋāϤ āĻšāĻŦ⧇āĨ¤ + +## āĻ…āĻŦāĻĻāĻžāύ + +MarkText āĻāĻ–āύ⧋ āωāĻ¨ā§āύ⧟āύāĻžāϧ⧀āύ, āϤāĻžāχ pull request āĻ•āϰāĻžāϰ āφāϗ⧇ [Contributing Guide](../../.github/CONTRIBUTING.md) āĻĒā§œā§‡ āύāĻŋāύāĨ¤ MarkText-āĻ āύāϤ⧁āύ āĻĢāĻŋāϚāĻžāϰ āϝ⧋āĻ— āĻ•āϰāϤ⧇ āϚāĻžāύ? āφāĻŽāĻžāĻĻ⧇āϰ [roadmap](https://github.com/marktext/marktext/projects) āĻāĻŦāĻ‚ open issue āĻĻ⧇āϖ⧁āύāĨ¤ + +## āĻ…āĻŦāĻĻāĻžāύāĻ•āĻžāϰ⧀āϰāĻž + +āϝāĻžāϰāĻž āχāϤāĻŋāĻŽāĻ§ā§āϝ⧇āχ MarkText-āĻ āĻ…āĻŦāĻĻāĻžāύ āϰ⧇āϖ⧇āϛ⧇āύ, āϤāĻžāĻĻ⧇āϰ āϏāĻŦāĻžāχāϕ⧇ āϧāĻ¨ā§āϝāĻŦāĻžāĻĻ [[contributors](https://github.com/marktext/marktext/graphs/contributors)]āĨ¤ + + + +## āϞāĻžāχāϏ⧇āĻ¨ā§āϏ + +[**MIT**](../../LICENSE)āĨ¤ diff --git a/docs/i18n/README-de.md b/docs/i18n/README-de.md index 0ca1d68fdd..c5acaad42b 100644 --- a/docs/i18n/README-de.md +++ b/docs/i18n/README-de.md @@ -90,6 +90,9 @@ :saudi_arabia: + + :bangladesh: +
diff --git a/docs/i18n/README-es.md b/docs/i18n/README-es.md index 4c4f0b8afe..7780e76563 100644 --- a/docs/i18n/README-es.md +++ b/docs/i18n/README-es.md @@ -90,6 +90,9 @@ :saudi_arabia: + + :bangladesh: +
diff --git a/docs/i18n/README-fr.md b/docs/i18n/README-fr.md index 27be6e8e80..98180a1549 100644 --- a/docs/i18n/README-fr.md +++ b/docs/i18n/README-fr.md @@ -90,6 +90,9 @@ :saudi_arabia: + + :bangladesh: +
diff --git a/docs/i18n/README-jp.md b/docs/i18n/README-jp.md index 4394d0b26e..768910774c 100644 --- a/docs/i18n/README-jp.md +++ b/docs/i18n/README-jp.md @@ -90,6 +90,9 @@ :saudi_arabia: + + :bangladesh: +
diff --git a/docs/i18n/README-kr.md b/docs/i18n/README-kr.md index 676c72e3c3..4750cefa29 100644 --- a/docs/i18n/README-kr.md +++ b/docs/i18n/README-kr.md @@ -90,6 +90,9 @@ :saudi_arabia: + + :bangladesh: +
diff --git a/docs/i18n/README-pt.md b/docs/i18n/README-pt.md index 4f6af02f12..c0dc805f2e 100644 --- a/docs/i18n/README-pt.md +++ b/docs/i18n/README-pt.md @@ -90,6 +90,9 @@ :saudi_arabia: + + :bangladesh: +
diff --git a/docs/i18n/README-tr.md b/docs/i18n/README-tr.md index 3ed94b8782..bdefccbbf0 100644 --- a/docs/i18n/README-tr.md +++ b/docs/i18n/README-tr.md @@ -90,6 +90,9 @@ :saudi_arabia: + + :bangladesh: +
diff --git a/docs/i18n/README-zh_cn.md b/docs/i18n/README-zh_cn.md index b8202f1157..c2e64e3161 100644 --- a/docs/i18n/README-zh_cn.md +++ b/docs/i18n/README-zh_cn.md @@ -90,6 +90,9 @@ :saudi_arabia: + + :bangladesh: +
diff --git a/docs/i18n/README-zh_tw.md b/docs/i18n/README-zh_tw.md index 2fdb91fb71..000fed1c99 100644 --- a/docs/i18n/README-zh_tw.md +++ b/docs/i18n/README-zh_tw.md @@ -90,6 +90,9 @@ :saudi_arabia: + + :bangladesh: +
From 54a65e7fef4e90b632c5ef294e492bd7d53561fc Mon Sep 17 00:00:00 2001 From: Taekhun Nam Date: Sat, 4 Jul 2026 11:07:51 +0900 Subject: [PATCH 2/3] fix(muya): skip identity operations during deferred flush (#4815) ot-json1.compose() may legitimately return the identity operation (null) when queued operations cancel each other out, such as during IME composition. JSONState._flushOperationCache() assumed that any non-empty operation batch would compose to a non-null operation and emitted it through the json-change event. Downstream consumers such as History expected a JSONOpList and crashed when receiving null. Skip emitting json-change for identity operations to preserve the ot-json1 contract and avoid forwarding no-op operations. --- packages/muya/src/state/index.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/muya/src/state/index.ts b/packages/muya/src/state/index.ts index d7515f1b4f..39bcdf7bf6 100644 --- a/packages/muya/src/state/index.ts +++ b/packages/muya/src/state/index.ts @@ -272,8 +272,9 @@ class JSONState { // (acc, current, index, array) to the callback, but // `json1.type.compose` only accepts (op1, op2). Without the // wrapper TS rejects the signature mismatch. - // `compose` returns JSONOp (= null | JSONOpList); a non-empty cache - // (guarded above) always composes to a non-null op. + // `compose` returns JSONOp (= null | JSONOpList). Multiple queued + // operations may cancel each other out (for example during IME + // composition), producing the identity operation (`null`). const op = this._operationCache.reduce( (acc, curr) => json1.type.compose(acc, curr) as JSONOpList, ); @@ -284,6 +285,10 @@ class JSONState { // Clear before emitting: a listener that edits synchronously then starts // a fresh batch instead of mutating the one being flushed. this._operationCache = []; + + if (op === null) + return; + this._muya.eventCenter.emit('json-change', { op, source: 'user', From 2b9c18e7aec67e0a104bebe16e85e4ac4a214929 Mon Sep 17 00:00:00 2001 From: Ran Luo Date: Sat, 4 Jul 2026 10:18:47 +0800 Subject: [PATCH 3/3] fix(muya): harden History against identity (null) json-change ops (#4838) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(muya): tolerate identity (null) json-change ops in History `ot-json1.compose()` legitimately returns the identity operation (`null`) when queued operations cancel each other out (for example during IME composition). PR #4815 stopped the deferred-flush emitter from forwarding that null, but `dispatch()` — used by `Editor.updateContents` and `rebuildContents` — still forwards it deliberately, and the `updateContents` contract explicitly routes the no-op through `json-change`. `History`'s `json-change` listener passed the op straight to `_record` (and `_transform`), both of which read `op.length` and crash on `null` with `TypeError: Cannot read properties of null (reading 'length')`. Ignore identity ops in the listener: a no-op has nothing to record or transform. This closes the crash at the consumer regardless of which emitter produced the null. Refs #4806 Co-Authored-By: Claude Opus 4.8 (1M context) * test(muya): cover identity-op json-change crash (#4806) Two regressions for the null-op crash: - `updateContents(null)` drives the `dispatch` path that forwards the identity op through `json-change`; asserts History neither crashes nor records an undo entry. - Flushing a compose-to-null batch (an op and its exact inverse) asserts the deferred-flush emitter stays silent, locking the PR #4815 fix. Co-Authored-By: Claude Opus 4.8 (1M context) --------- Co-authored-by: Claude Opus 4.8 (1M context) --- .../__tests__/identityOpJsonChange.spec.ts | 77 +++++++++++++++++++ packages/muya/src/history/index.ts | 9 ++- 2 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 packages/muya/src/history/__tests__/identityOpJsonChange.spec.ts diff --git a/packages/muya/src/history/__tests__/identityOpJsonChange.spec.ts b/packages/muya/src/history/__tests__/identityOpJsonChange.spec.ts new file mode 100644 index 0000000000..55e9f6a26e --- /dev/null +++ b/packages/muya/src/history/__tests__/identityOpJsonChange.spec.ts @@ -0,0 +1,77 @@ +// @vitest-environment happy-dom + +// Regression for #4806: an identity (`null`) ot-json1 op must never crash +// History. `compose()` legitimately yields `null` when queued ops cancel out +// (e.g. IME edits), and both the deferred-flush emitter and the `dispatch` +// path can surface it through `json-change`. `History` reads `op.length`, so a +// forwarded `null` threw `Cannot read properties of null (reading 'length')`. + +import * as json1 from 'ot-json1'; +import { afterEach, beforeEach, describe, expect, it } from 'vitest'; +import { Muya } from '../../muya'; +import { asDoc } from '../../state'; + +const bootedHosts: HTMLElement[] = []; + +beforeEach(() => { + window.MUYA_VERSION = 'test'; +}); + +afterEach(() => { + while (bootedHosts.length) + bootedHosts.pop()!.remove(); + document.getSelection()?.removeAllRanges(); + delete (window as Partial).MUYA_VERSION; +}); + +function bootMuya(markdown: string): Muya { + const host = document.createElement('div'); + document.body.appendChild(host); + const muya = new Muya(host, { markdown } as ConstructorParameters[1]); + muya.init(); + bootedHosts.push(muya.domNode); + return muya; +} + +function undoDepth(muya: Muya): number { + // @ts-expect-error — reach into the private stack for test assertions. + return muya.editor.history._stack.undo.length; +} + +describe('identity (null) op via json-change — #4806', () => { + it('updateContents(null) does not crash History and records nothing', () => { + const muya = bootMuya('hello\n'); + + // `Editor.updateContents` deliberately forwards the identity op to + // `dispatch`, which emits a `json-change` carrying `op: null`. + expect(() => muya.editor.updateContents(null, null, 'user')).not.toThrow(); + + expect(muya.getMarkdown().trim()).toBe('hello'); + expect(undoDepth(muya)).toBe(0); + }); + + it('flushing a compose-to-null batch is a no-op (locks #4815)', () => { + const muya = bootMuya('hello\n'); + const jsonState = muya.editor.jsonState; + + // A real op and its exact inverse compose to the identity op — the + // op-level shape of an IME insert cancelled by a delete in one frame. + const op = json1.insertOp([1], asDoc({ name: 'paragraph', text: 'x' } as never))!; + const inverse = json1.type.invert(op); + expect(json1.type.compose(op, inverse)).toBe(null); + + let changes = 0; + muya.eventCenter.on('json-change', () => { + changes += 1; + }); + + // @ts-expect-error — drive the private deferred-flush path directly. + jsonState._operationCache.push(op, inverse); + // @ts-expect-error — the function the crash stack named. + expect(() => jsonState._flushOperationCache()).not.toThrow(); + + expect(changes).toBe(0); + expect(muya.getMarkdown().trim()).toBe('hello'); + expect(undoDepth(muya)).toBe(0); + }); +}); diff --git a/packages/muya/src/history/index.ts b/packages/muya/src/history/index.ts index e7033fc523..cce428ce57 100644 --- a/packages/muya/src/history/index.ts +++ b/packages/muya/src/history/index.ts @@ -130,7 +130,7 @@ class History { source, prevDoc, }: { - op: JSONOpList; + op: Nullable; source: string; prevDoc: TState[]; doc: TState[]; @@ -138,6 +138,13 @@ class History { if (this._ignoreChange) return; + // The identity op (`null`) carries no change to record or + // transform. It can still reach here through `json-change` when + // queued ops compose away (e.g. IME edits, #4806); `_record` + // would otherwise crash reading `op.length`. + if (op == null) + return; + if (!this._options.userOnly || source === 'user') this._record(op, prevDoc); else