diff --git a/README.md b/README.md
index b1a14e6164..7cdbc71b6d 100644
--- a/README.md
+++ b/README.md
@@ -109,7 +109,6 @@ MarkText is an open-source Markdown editor powered by the support of its communi
| [
](https://serpapi.com/?utm_source=marktext) | [Scrape Google and other search engines from our fast, easy, and complete API.](https://serpapi.com/?utm_source=marktext) |
| ------------- |:-------------|
-| [
](https://www.ukey.com) | [Secure hardware wallet made simple.](https://www.ukey.com) |
## Screenshot
diff --git a/docs/assets/sponsors/ukey.png b/docs/assets/sponsors/ukey.png
deleted file mode 100644
index 8ffd2f90a3..0000000000
Binary files a/docs/assets/sponsors/ukey.png and /dev/null differ
diff --git a/packages/desktop/src/main/filesystem/index.ts b/packages/desktop/src/main/filesystem/index.ts
index 2180b3f819..2148ffe8f7 100644
--- a/packages/desktop/src/main/filesystem/index.ts
+++ b/packages/desktop/src/main/filesystem/index.ts
@@ -35,7 +35,14 @@ export const writeFile = async(
// write-file-atomic does not create parent directories; recreate a moved or
// deleted folder first so an (auto)save into it still succeeds (#3509).
- await ensureDir(path.dirname(pathname))
+ // Only do this when the parent does not already exist. On Windows fs.mkdir
+ // returns EPERM (not EEXIST) for an existing volume root (e.g. "E:\"), so for
+ // an existing parent — drive roots included — ensureDir would throw and block
+ // saving directly to a drive root instead of being a harmless no-op (#5150).
+ const dir = path.dirname(pathname)
+ if (!isDirectory(dir)) {
+ await ensureDir(dir)
+ }
// Durable atomic save: write to a temp file in the target's directory, fsync
// it, then rename it over the target. This survives an application crash AND
diff --git a/packages/desktop/test/e2e/task-list-autocheck.spec.ts b/packages/desktop/test/e2e/task-list-autocheck.spec.ts
index ab1d5bcb1e..14d76c24df 100644
--- a/packages/desktop/test/e2e/task-list-autocheck.spec.ts
+++ b/packages/desktop/test/e2e/task-list-autocheck.spec.ts
@@ -31,6 +31,11 @@ import {
// proven nesting fixture from the unit parity spec.
const NESTED_TASKS = '- [ ] parent\n\n - [ ] child1\n - [ ] child2\n'
+const SCROLLED_TASKS = `${Array.from(
+ { length: 80 },
+ (_, index) => `Paragraph before task ${index + 1}`
+).join('\n\n')}\n\n- [ ] review decision\n`
+
const setAutoCheck = async(app: ElectronApplication, value: boolean): Promise => {
await sendIpcToRenderer(app, 'mt::user-preference', { autoCheck: value })
}
@@ -129,3 +134,35 @@ test.describe('Checklist 32 — task list autoCheck cascade via a real checkbox
await expectNoRendererErrors(app)
})
})
+
+test.describe('Task-list checkbox preserves the viewport', () => {
+ test('clicking a visible checkbox in a long document does not jump to the top', async() => {
+ const { app, page } = await launchWithMarkdown(SCROLLED_TASKS, {
+ suppressErrorDialog: true
+ })
+
+ try {
+ const checkbox = page.locator('.editor-component input[type=checkbox]')
+ await checkbox.scrollIntoViewIfNeeded()
+
+ const before = await page.evaluate(() => {
+ const editor = document.querySelector('.editor-component') as HTMLElement | null
+ return editor?.scrollTop ?? 0
+ })
+ expect(before).toBeGreaterThan(500)
+
+ await checkbox.click()
+ await expect(checkbox).toBeChecked()
+ await page.waitForTimeout(250)
+
+ const after = await page.evaluate(() => {
+ const editor = document.querySelector('.editor-component') as HTMLElement | null
+ return editor?.scrollTop ?? 0
+ })
+ expect(after).toBeGreaterThan(before - 100)
+ await expectNoRendererErrors(app)
+ } finally {
+ await app.close()
+ }
+ })
+})
diff --git a/packages/muya/src/block/gfm/taskListCheckbox/index.ts b/packages/muya/src/block/gfm/taskListCheckbox/index.ts
index 8004d1c0aa..384750fedb 100644
--- a/packages/muya/src/block/gfm/taskListCheckbox/index.ts
+++ b/packages/muya/src/block/gfm/taskListCheckbox/index.ts
@@ -174,6 +174,11 @@ class TaskListCheckbox extends TreeNode {
this._checked = checked;
this.update(checked, 'user');
}
+
+ // The control is not editable, so clicking it does not move the
+ // text selection. Seat the caret in this item before the desktop's
+ // selection-follow scroll can reveal a stale off-screen cursor.
+ (this.parent as TaskListItem).firstContentInDescendant()?.setCursor(0, 0, true);
};
const eventIds = [
diff --git a/packages/website/content/docs/end-user/FAQ.md b/packages/website/content/docs/end-user/FAQ.md
index 9b78db247f..4c7803abee 100644
--- a/packages/website/content/docs/end-user/FAQ.md
+++ b/packages/website/content/docs/end-user/FAQ.md
@@ -14,7 +14,7 @@ Yes, MarkText is licensed under the [MIT](https://github.com/marktext/marktext/b
### Can I use MarkText as note management/taking app?
-MarkText is a pure markdown editor without feature such as knowledge management and tags but yes, you can do this via the integrated filesystem explorer and task lists.
+MarkText is a pure markdown editor without features such as knowledge management and tags but yes, you can do this via the integrated filesystem explorer and task lists.
### Where can I find documentation?
@@ -28,7 +28,7 @@ The full documentation is available on the MarkText website:
Yes, please see [here](PORTABLE.md) for further information.
-### How can I report bugs and problems
+### How can I report bugs and problems?
You can report bugs and problems via our [GitHub issue tracker](https://github.com/marktext/marktext/issues). Please provide a detailed description of the problem to better solve the issue.
@@ -36,7 +36,7 @@ You can report bugs and problems via our [GitHub issue tracker](https://github.c
> _The SUID sandbox helper binary was found, but is not configured correctly._
-Normally, you should never get this error but if you disabled user namespaces, this error message may appears in the command output when launching MarkText. To solve the issue, that Chromium cannot start the sandbox (process), you can choose one of the following steps:
+Normally, you should never get this error but if you disabled user namespaces, this error message may appear in the command output when launching MarkText. To solve the issue, that Chromium cannot start the sandbox (process), you can choose one of the following steps:
- Enable Linux kernel user namespaces to use the preferred sandbox: `sudo sysctl kernel.unprivileged_userns_clone=1`.
- Set correct SUID sandbox helper binary permissions: `sudo chown root /chrome-sandbox && sudo chmod 4755 /chrome-sandbox`. This is preferred if you don't want to enable user namespaces.
diff --git a/packages/website/public/assets/ukey.png b/packages/website/public/assets/ukey.png
deleted file mode 100644
index 8ffd2f90a3..0000000000
Binary files a/packages/website/public/assets/ukey.png and /dev/null differ
diff --git a/packages/website/src/components/Support.tsx b/packages/website/src/components/Support.tsx
index f391b9dd57..6f15c85d26 100644
--- a/packages/website/src/components/Support.tsx
+++ b/packages/website/src/components/Support.tsx
@@ -28,9 +28,6 @@ export default function Support() {
-
-
-
diff --git a/packages/website/src/lib/downloads.ts b/packages/website/src/lib/downloads.ts
index af65f19c42..1abafa5978 100644
--- a/packages/website/src/lib/downloads.ts
+++ b/packages/website/src/lib/downloads.ts
@@ -9,6 +9,5 @@ export const DOWNLOAD = {
sponsor: 'https://github.com/sponsors/marktext',
twitter:
'https://twitter.com/intent/tweet?via=marktextme&url=https://github.com/marktext/marktext/&text=What%20do%20you%20want%20to%20say%20to%20app?&hashtags=happyMarkText',
- serpapi: 'https://serpapi.com/?utm_source=marktext',
- ukey: 'https://www.ukey.com'
+ serpapi: 'https://serpapi.com/?utm_source=marktext'
}