fix(desktop): sanitize Obsidian file path separators

This commit is contained in:
DIYgod 2026-05-14 14:45:44 +08:00
parent ed1cc36af6
commit 9bfcfc805b
2 changed files with 75 additions and 1 deletions

View File

@ -0,0 +1,74 @@
import fsp from "node:fs/promises"
import os from "node:os"
import type { IpcContext } from "electron-ipc-decorator"
import path from "pathe"
import { afterEach, describe, expect, it, vi } from "vitest"
import { IntegrationService } from "./integration"
vi.mock("electron", () => ({
ipcMain: {
handle: vi.fn(),
},
shell: {
openExternal: vi.fn(),
},
}))
vi.mock("electron-ipc-decorator", () => ({
IpcMethod: () => (_target: unknown, _propertyKey: string, descriptor: PropertyDescriptor) =>
descriptor,
IpcService: class {},
}))
vi.mock("~/lib/store", () => ({
store: {
get: vi.fn(),
set: vi.fn(),
},
}))
vi.mock("~/logger", () => ({
logger: {
debug: vi.fn(),
error: vi.fn(),
info: vi.fn(),
warn: vi.fn(),
},
}))
describe("IntegrationService", () => {
let vaultPath: string | undefined
afterEach(async () => {
if (!vaultPath) return
await fsp.rm(vaultPath, { force: true, recursive: true })
vaultPath = undefined
})
it("saves Obsidian titles with path separators as one markdown file", async () => {
vaultPath = await fsp.mkdtemp(path.join(os.tmpdir(), "folo-obsidian-"))
const service = new IntegrationService()
const context = {} as IpcContext
await expect(
service.saveToObsidian(context, {
url: "https://example.com",
title: "KAWA DESIGN 少女前线2追放 索米·雪兔献礼 1/6比例手办",
content: "content",
author: "Folo",
publishedAt: "2026-05-14T04:20:44.405Z",
vaultPath,
}),
).resolves.toEqual({ success: true })
await expect(fsp.readdir(vaultPath)).resolves.toEqual([
"KAWA DESIGN 少女前线2追放 索米·雪兔献礼 1_6比例手办.md",
])
await expect(
fsp.stat(path.join(vaultPath, "KAWA DESIGN 少女前线2追放 索米·雪兔献礼 1")),
).rejects.toThrow()
})
})

View File

@ -14,7 +14,7 @@ import { createObsidianFrontmatter } from "./obsidian-frontmatter"
// Taken from https://github.com/rollup/rollup/blob/4f69d33af3b2ec9320c43c9e6c65ea23a02bdde3/src/utils/sanitizeFileName.ts
// https://datatracker.ietf.org/doc/html/rfc2396
// eslint-disable-next-line no-control-regex
const INVALID_CHAR_REGEX = /[\u0000-\u001F"#$%&*+,:;<=>?[\]^`{|}\u007F]/g
const INVALID_CHAR_REGEX = /[\u0000-\u001F"#$%&*+,:;<=>?[\]^`{|}\u007F/\\]/g
const DRIVE_LETTER_REGEX = /^[a-z]:/i
function sanitizeFileName(name: string): string {