Merge current main into PR 1039

This commit is contained in:
vorotamoroz
2026-08-09 08:45:16 +00:00
1036 changed files with 87150 additions and 45505 deletions
@@ -1,5 +1,5 @@
import type { UXFileInfoStub, UXFolderInfo } from "@lib/common/types";
import type { IConversionAdapter } from "@lib/serviceModules/adapters";
import type { UXFileInfoStub, UXFolderInfo } from "@vrtmrz/livesync-commonlib/compat/common/types";
import type { IConversionAdapter } from "@vrtmrz/livesync-commonlib/compat/serviceModules/adapters";
import { TFileToUXFileInfoStub, TFolderToUXFileInfoStub } from "@/modules/coreObsidian/storageLib/utilObsidian";
import type { TFile, TFolder } from "obsidian";
@@ -1,4 +1,4 @@
import type { FilePath, UXStat } from "@lib/common/types";
import type { FilePath, UXStat } from "@vrtmrz/livesync-commonlib/compat/common/types";
import type {
IFileSystemAdapter,
IPathAdapter,
@@ -6,7 +6,7 @@ import type {
IConversionAdapter,
IStorageAdapter,
IVaultAdapter,
} from "@lib/serviceModules/adapters";
} from "@vrtmrz/livesync-commonlib/compat/serviceModules/adapters";
import type { TAbstractFile, TFile, TFolder, Stat, App } from "obsidian";
import { ObsidianConversionAdapter } from "./ObsidianConversionAdapter";
import { ObsidianPathAdapter } from "./ObsidianPathAdapter";
@@ -54,6 +54,11 @@ export class ObsidianFileSystemAdapter implements IFileSystemAdapter<TAbstractFi
return Promise.resolve(this.app.vault.getFiles());
}
async renameFile(file: TFile, newPath: string): Promise<TFile> {
await this.vault.rename(file, newPath);
return file;
}
statFromNative(file: TFile): Promise<UXStat> {
return Promise.resolve({ ...file.stat, type: "file" });
}
@@ -1,6 +1,6 @@
import { type TAbstractFile, normalizePath } from "@/deps";
import type { FilePath } from "@lib/common/types";
import type { IPathAdapter } from "@lib/serviceModules/adapters";
import type { FilePath } from "@vrtmrz/livesync-commonlib/compat/common/types";
import type { IPathAdapter } from "@vrtmrz/livesync-commonlib/compat/serviceModules/adapters";
/**
* Path adapter implementation for Obsidian
@@ -1,6 +1,6 @@
import type { UXDataWriteOptions } from "@lib/common/types";
import type { IStorageAdapter } from "@lib/serviceModules/adapters";
import { toArrayBuffer } from "@lib/serviceModules/FileAccessBase";
import type { UXDataWriteOptions } from "@vrtmrz/livesync-commonlib/compat/common/types";
import type { IStorageAdapter } from "@vrtmrz/livesync-commonlib/compat/serviceModules/adapters";
import { toArrayBuffer } from "@vrtmrz/livesync-commonlib/compat/serviceModules/FileAccessBase";
import type { Stat, App } from "obsidian";
import { toIntegerTimestamps } from "./sanitizeWriteOptions";
@@ -1,4 +1,4 @@
import type { ITypeGuardAdapter } from "@lib/serviceModules/adapters";
import type { ITypeGuardAdapter } from "@vrtmrz/livesync-commonlib/compat/serviceModules/adapters";
import { TFile, TFolder } from "obsidian";
/**
@@ -1,6 +1,6 @@
import type { UXDataWriteOptions } from "@lib/common/types";
import type { IVaultAdapter } from "@lib/serviceModules/adapters";
import { toArrayBuffer } from "@lib/serviceModules/FileAccessBase";
import type { UXDataWriteOptions } from "@vrtmrz/livesync-commonlib/compat/common/types";
import type { IVaultAdapter } from "@vrtmrz/livesync-commonlib/compat/serviceModules/adapters";
import { toArrayBuffer } from "@vrtmrz/livesync-commonlib/compat/serviceModules/FileAccessBase";
import type { TFile, App, TFolder } from "obsidian";
import { toIntegerTimestamps } from "./sanitizeWriteOptions";
@@ -11,7 +11,8 @@ export class ObsidianVaultAdapter implements IVaultAdapter<TFile, TFolder> {
constructor(private app: App) {}
async read(file: TFile): Promise<string> {
return await this.app.vault.read(file);
// Vault.read strips a leading UTF-8 BOM, leaving the content size inconsistent with TFile.stat.
return await this.app.vault.adapter.read(file.path);
}
async cachedRead(file: TFile): Promise<string> {
@@ -38,22 +39,16 @@ export class ObsidianVaultAdapter implements IVaultAdapter<TFile, TFolder> {
return await this.app.vault.createBinary(path, toArrayBuffer(data), toIntegerTimestamps(options));
}
async delete(file: TFile | TFolder, force = false): Promise<void> {
if ("trashFile" in this.app.fileManager) {
// eslint-disable-next-line obsidianmd/no-unsupported-api
return await this.app.fileManager.trashFile(file);
}
// eslint-disable-next-line obsidianmd/prefer-file-manager-trash-file -- Fallback for older versions of Obsidian without trashFile support
return await this.app.vault.delete(file, force);
async rename(file: TFile, newPath: string): Promise<void> {
return await this.app.vault.rename(file, newPath);
}
async trash(file: TFile | TFolder, force = false): Promise<void> {
if ("trashFile" in this.app.fileManager) {
// eslint-disable-next-line obsidianmd/no-unsupported-api
return await this.app.fileManager.trashFile(file);
}
// eslint-disable-next-line obsidianmd/prefer-file-manager-trash-file -- Fallback for older versions of Obsidian without trashFile support
return await this.app.vault.trash(file, force);
async delete(file: TFile | TFolder): Promise<void> {
return await this.app.fileManager.trashFile(file);
}
async trash(file: TFile | TFolder): Promise<void> {
return await this.app.fileManager.trashFile(file);
}
trigger(name: string, ...data: unknown[]): void {
@@ -0,0 +1,37 @@
import { describe, expect, it, vi } from "vitest";
import type { App, TFile } from "obsidian";
import { ObsidianVaultAdapter } from "./ObsidianVaultAdapter";
describe("ObsidianVaultAdapter.read", () => {
it("preserves a UTF-8 BOM so the content size matches the file stat", async () => {
const path = "Transcripts/字幕.md";
const contentWithoutBom = "字幕の検証行です。\n";
const contentWithBom = `\ufeff${contentWithoutBom}`;
const read = vi.fn().mockResolvedValue(contentWithoutBom);
const adapterRead = vi.fn().mockResolvedValue(contentWithBom);
const app = {
vault: {
read,
adapter: {
read: adapterRead,
},
},
} as unknown as App;
const file = {
path,
stat: {
ctime: 1,
mtime: 2,
size: new Blob([contentWithBom]).size,
},
} as TFile;
const adapter = new ObsidianVaultAdapter(app);
const result = await adapter.read(file);
expect(new Blob([result]).size).toBe(file.stat.size);
expect(result.charCodeAt(0)).toBe(0xfeff);
expect(adapterRead).toHaveBeenCalledWith(path);
expect(read).not.toHaveBeenCalled();
});
});