Skip to content

Commit 9bfcfc8

Browse files
committed
fix(desktop): sanitize Obsidian file path separators
1 parent ed1cc36 commit 9bfcfc8

2 files changed

Lines changed: 75 additions & 1 deletion

File tree

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import fsp from "node:fs/promises"
2+
import os from "node:os"
3+
4+
import type { IpcContext } from "electron-ipc-decorator"
5+
import path from "pathe"
6+
import { afterEach, describe, expect, it, vi } from "vitest"
7+
8+
import { IntegrationService } from "./integration"
9+
10+
vi.mock("electron", () => ({
11+
ipcMain: {
12+
handle: vi.fn(),
13+
},
14+
shell: {
15+
openExternal: vi.fn(),
16+
},
17+
}))
18+
19+
vi.mock("electron-ipc-decorator", () => ({
20+
IpcMethod: () => (_target: unknown, _propertyKey: string, descriptor: PropertyDescriptor) =>
21+
descriptor,
22+
IpcService: class {},
23+
}))
24+
25+
vi.mock("~/lib/store", () => ({
26+
store: {
27+
get: vi.fn(),
28+
set: vi.fn(),
29+
},
30+
}))
31+
32+
vi.mock("~/logger", () => ({
33+
logger: {
34+
debug: vi.fn(),
35+
error: vi.fn(),
36+
info: vi.fn(),
37+
warn: vi.fn(),
38+
},
39+
}))
40+
41+
describe("IntegrationService", () => {
42+
let vaultPath: string | undefined
43+
44+
afterEach(async () => {
45+
if (!vaultPath) return
46+
47+
await fsp.rm(vaultPath, { force: true, recursive: true })
48+
vaultPath = undefined
49+
})
50+
51+
it("saves Obsidian titles with path separators as one markdown file", async () => {
52+
vaultPath = await fsp.mkdtemp(path.join(os.tmpdir(), "folo-obsidian-"))
53+
const service = new IntegrationService()
54+
const context = {} as IpcContext
55+
56+
await expect(
57+
service.saveToObsidian(context, {
58+
url: "https://example.com",
59+
title: "KAWA DESIGN 少女前线2:追放 索米·雪兔献礼 1/6比例手办",
60+
content: "content",
61+
author: "Folo",
62+
publishedAt: "2026-05-14T04:20:44.405Z",
63+
vaultPath,
64+
}),
65+
).resolves.toEqual({ success: true })
66+
67+
await expect(fsp.readdir(vaultPath)).resolves.toEqual([
68+
"KAWA DESIGN 少女前线2:追放 索米·雪兔献礼 1_6比例手办.md",
69+
])
70+
await expect(
71+
fsp.stat(path.join(vaultPath, "KAWA DESIGN 少女前线2:追放 索米·雪兔献礼 1")),
72+
).rejects.toThrow()
73+
})
74+
})

apps/desktop/layer/main/src/ipc/services/integration.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { createObsidianFrontmatter } from "./obsidian-frontmatter"
1414
// Taken from https://github.com/rollup/rollup/blob/4f69d33af3b2ec9320c43c9e6c65ea23a02bdde3/src/utils/sanitizeFileName.ts
1515
// https://datatracker.ietf.org/doc/html/rfc2396
1616
// eslint-disable-next-line no-control-regex
17-
const INVALID_CHAR_REGEX = /[\u0000-\u001F"#$%&*+,:;<=>?[\]^`{|}\u007F]/g
17+
const INVALID_CHAR_REGEX = /[\u0000-\u001F"#$%&*+,:;<=>?[\]^`{|}\u007F/\\]/g
1818
const DRIVE_LETTER_REGEX = /^[a-z]:/i
1919

2020
function sanitizeFileName(name: string): string {

0 commit comments

Comments
 (0)