## 0.24.26

### New Features

- Automatic display-language changing according to the Obsidian language
  setting.
- Now we can limit files to be synchronised even in the hidden files.
- "Use Request API to avoid `inevitable` CORS problem" has been implemented.
- `Show status icon instead of file warnings banner` has been implemented.

### Improved

- All regular expressions can be inverted by prefixing `!!` now.

### Fixed

- No longer unexpected files will be gathered during hidden file sync.
- No longer broken `\n` and new-line characters during the bucket
  synchronisation.
- We can purge the remote bucket again if we using MinIO instead of AWS S3 or
  Cloudflare R2.
- Purging the remote bucket is now more reliable.
- Some wrong messages have been fixed.

### Behaviour changed

- Entering into the deeper directories to gather the hidden files is now limited
  by `/` or `\/` prefixed ignore filters.

### Etcetera

- Some code has been tidied up.
- Trying less warning-suppressing and be more safer-coding.
- Dependent libraries have been updated to the latest version.
- Some build processes have been separated to `pre` and `post` processes.
This commit is contained in:
vorotamoroz
2025-05-14 13:11:03 +01:00
parent d0e92cff7a
commit 3b8d03a189
15 changed files with 3069 additions and 2198 deletions

4819
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -6,8 +6,10 @@
"type": "module", "type": "module",
"scripts": { "scripts": {
"bakei18n": "npx tsx ./src/lib/_tools/bakei18n.ts", "bakei18n": "npx tsx ./src/lib/_tools/bakei18n.ts",
"postbakei18n": "prettier --config ./.prettierrc ./src/lib/src/common/messages/*.ts --write --log-level error",
"dev": "node esbuild.config.mjs", "dev": "node esbuild.config.mjs",
"build": "npm run bakei18n && node esbuild.config.mjs production", "prebuild": "npm run bakei18n",
"build": "node esbuild.config.mjs production",
"buildDev": "node esbuild.config.mjs dev", "buildDev": "node esbuild.config.mjs dev",
"lint": "eslint src", "lint": "eslint src",
"svelte-check": "svelte-check --tsconfig ./tsconfig.json", "svelte-check": "svelte-check --tsconfig ./tsconfig.json",
@@ -22,12 +24,12 @@
"license": "MIT", "license": "MIT",
"devDependencies": { "devDependencies": {
"@chialab/esbuild-plugin-worker": "^0.18.1", "@chialab/esbuild-plugin-worker": "^0.18.1",
"@eslint/compat": "^1.2.6", "@eslint/compat": "^1.2.7",
"@eslint/eslintrc": "^3.2.0", "@eslint/eslintrc": "^3.3.0",
"@eslint/js": "^9.20.0", "@eslint/js": "^9.21.0",
"@tsconfig/svelte": "^5.0.4", "@tsconfig/svelte": "^5.0.4",
"@types/diff-match-patch": "^1.0.36", "@types/diff-match-patch": "^1.0.36",
"@types/node": "^22.5.4", "@types/node": "^22.13.8",
"@types/pouchdb": "^6.4.2", "@types/pouchdb": "^6.4.2",
"@types/pouchdb-adapter-http": "^6.1.6", "@types/pouchdb-adapter-http": "^6.1.6",
"@types/pouchdb-adapter-idb": "^6.1.7", "@types/pouchdb-adapter-idb": "^6.1.7",
@@ -36,17 +38,17 @@
"@types/pouchdb-mapreduce": "^6.1.10", "@types/pouchdb-mapreduce": "^6.1.10",
"@types/pouchdb-replication": "^6.4.7", "@types/pouchdb-replication": "^6.4.7",
"@types/transform-pouch": "^1.0.6", "@types/transform-pouch": "^1.0.6",
"@typescript-eslint/eslint-plugin": "^8.24.1", "@typescript-eslint/eslint-plugin": "8.25.0",
"@typescript-eslint/parser": "^8.24.1", "@typescript-eslint/parser": "8.25.0",
"builtin-modules": "^4.0.0", "builtin-modules": "5.0.0",
"esbuild": "0.24.2", "esbuild": "0.25.0",
"esbuild-svelte": "^0.9.0", "esbuild-svelte": "^0.9.0",
"eslint": "^9.20.1", "eslint": "^9.21.0",
"eslint-plugin-import": "^2.31.0", "eslint-plugin-import": "^2.31.0",
"eslint-plugin-svelte": "^2.46.1", "eslint-plugin-svelte": "^3.0.2",
"events": "^3.3.0", "events": "^3.3.0",
"obsidian": "^1.7.2", "obsidian": "^1.8.7",
"postcss": "^8.5.2", "postcss": "^8.5.3",
"postcss-load-config": "^6.0.1", "postcss-load-config": "^6.0.1",
"pouchdb-adapter-http": "^9.0.0", "pouchdb-adapter-http": "^9.0.0",
"pouchdb-adapter-idb": "^9.0.0", "pouchdb-adapter-idb": "^9.0.0",
@@ -58,28 +60,30 @@
"pouchdb-merge": "^9.0.0", "pouchdb-merge": "^9.0.0",
"pouchdb-replication": "^9.0.0", "pouchdb-replication": "^9.0.0",
"pouchdb-utils": "^9.0.0", "pouchdb-utils": "^9.0.0",
"prettier": "^3.5.1", "prettier": "3.5.2",
"svelte": "^5.20.1", "svelte": "5.28.6",
"svelte-preprocess": "^6.0.3", "svelte-preprocess": "^6.0.3",
"terser": "^5.39.0", "terser": "^5.39.0",
"transform-pouch": "^2.0.0", "transform-pouch": "^2.0.0",
"tslib": "^2.8.1", "tslib": "^2.8.1",
"tsx": "^4.19.2", "tsx": "^4.19.4",
"typescript": "^5.7.3" "typescript": "5.7.3"
}, },
"dependencies": { "dependencies": {
"@aws-sdk/client-s3": "^3.787.0", "@aws-sdk/client-s3": "^3.808.0",
"@smithy/md5-js": "^4.0.2",
"@smithy/middleware-apply-body-checksum": "^4.1.0",
"@smithy/fetch-http-handler": "^5.0.2", "@smithy/fetch-http-handler": "^5.0.2",
"@smithy/protocol-http": "^5.1.0", "@smithy/protocol-http": "^5.1.0",
"@smithy/querystring-builder": "^4.0.2", "@smithy/querystring-builder": "^4.0.2",
"diff-match-patch": "^1.0.5", "diff-match-patch": "^1.0.5",
"esbuild-plugin-inline-worker": "^0.1.1", "esbuild-plugin-inline-worker": "^0.1.1",
"fflate": "^0.8.2", "fflate": "^0.8.2",
"idb": "^8.0.2", "idb": "^8.0.3",
"minimatch": "^10.0.1", "minimatch": "^10.0.1",
"octagonal-wheels": "^0.1.25", "octagonal-wheels": "^0.1.25",
"qrcode-generator": "^1.4.4", "qrcode-generator": "^1.4.4",
"svelte-check": "^4.1.4", "svelte-check": "^4.1.7",
"trystero": "^0.21.3", "trystero": "^0.21.3",
"xxhash-wasm-102": "npm:xxhash-wasm@^1.0.2" "xxhash-wasm-102": "npm:xxhash-wasm@^1.0.2"
} }

View File

@@ -25,6 +25,8 @@ import {
readContent, readContent,
createBlob, createBlob,
fireAndForget, fireAndForget,
type CustomRegExp,
getFileRegExp,
} from "../../lib/src/common/utils.ts"; } from "../../lib/src/common/utils.ts";
import { import {
compareMTime, compareMTime,
@@ -164,12 +166,11 @@ export class HiddenFileSync extends LiveSyncCommands implements IObsidianModule
return Promise.resolve(true); return Promise.resolve(true);
} }
updateSettingCache() { updateSettingCache() {
const ignorePatterns = this.settings.syncInternalFilesIgnorePatterns const ignorePatterns = getFileRegExp(this.plugin.settings, "syncInternalFilesIgnorePatterns");
.replace(/\n| /g, "")
.split(",")
.filter((e) => e)
.map((e) => new RegExp(e, "i"));
this.ignorePatterns = ignorePatterns; this.ignorePatterns = ignorePatterns;
const targetFilter = getFileRegExp(this.plugin.settings, "syncInternalFilesTargetPatterns");
this.targetPatterns = targetFilter;
this.shouldSkipFile = [] as FilePathWithPrefixLC[]; this.shouldSkipFile = [] as FilePathWithPrefixLC[];
// Exclude files handled by customization sync // Exclude files handled by customization sync
const configDir = normalizePath(this.app.vault.configDir); const configDir = normalizePath(this.app.vault.configDir);
@@ -219,12 +220,10 @@ export class HiddenFileSync extends LiveSyncCommands implements IObsidianModule
? this.settings.syncInternalFilesInterval * 1000 ? this.settings.syncInternalFilesInterval * 1000
: 0 : 0
); );
const ignorePatterns = this.settings.syncInternalFilesIgnorePatterns const ignorePatterns = getFileRegExp(this.plugin.settings, "syncInternalFilesIgnorePatterns");
.replace(/\n| /g, "")
.split(",")
.filter((e) => e)
.map((e) => new RegExp(e, "i"));
this.ignorePatterns = ignorePatterns; this.ignorePatterns = ignorePatterns;
const targetFilter = getFileRegExp(this.plugin.settings, "syncInternalFilesTargetPatterns");
this.targetPatterns = targetFilter;
return Promise.resolve(true); return Promise.resolve(true);
} }
@@ -1683,14 +1682,12 @@ ${messageFetch}${messageOverwrite}${messageMerge}
// <-- Configuration handling // <-- Configuration handling
// --> Local Storage SubFunctions // --> Local Storage SubFunctions
ignorePatterns: RegExp[] = []; ignorePatterns: CustomRegExp[] = [];
targetPatterns: CustomRegExp[] = [];
async scanInternalFileNames() { async scanInternalFileNames() {
const configDir = normalizePath(this.app.vault.configDir); const configDir = normalizePath(this.app.vault.configDir);
const ignoreFilter = this.settings.syncInternalFilesIgnorePatterns const ignoreFilter = getFileRegExp(this.plugin.settings, "syncInternalFilesIgnorePatterns");
.replace(/\n| /g, "") const targetFilter = getFileRegExp(this.plugin.settings, "syncInternalFilesTargetPatterns");
.split(",")
.filter((e) => e)
.map((e) => new RegExp(e, "i"));
const synchronisedInConfigSync = !this.settings.usePluginSync const synchronisedInConfigSync = !this.settings.usePluginSync
? [] ? []
: Object.values(this.settings.pluginSyncExtendedSetting) : Object.values(this.settings.pluginSyncExtendedSetting)
@@ -1701,7 +1698,7 @@ ${messageFetch}${messageOverwrite}${messageMerge}
const root = this.app.vault.getRoot(); const root = this.app.vault.getRoot();
const findRoot = root.path; const findRoot = root.path;
const filenames = (await this.getFiles(findRoot, [], undefined, ignoreFilter)) const filenames = (await this.getFiles(findRoot, [], targetFilter, ignoreFilter))
.filter((e) => e.startsWith(".")) .filter((e) => e.startsWith("."))
.filter((e) => !e.startsWith(".trash")); .filter((e) => !e.startsWith(".trash"));
const files = filenames.filter((path) => const files = filenames.filter((path) =>
@@ -1737,7 +1734,7 @@ ${messageFetch}${messageOverwrite}${messageMerge}
return result; return result;
} }
async getFiles(path: string, ignoreList: string[], filter?: RegExp[], ignoreFilter?: RegExp[]) { async getFiles(path: string, ignoreList: string[], filter?: CustomRegExp[], ignoreFilter?: CustomRegExp[]) {
let w: ListedFiles; let w: ListedFiles;
try { try {
w = await this.app.vault.adapter.list(path); w = await this.app.vault.adapter.list(path);
@@ -1746,26 +1743,32 @@ ${messageFetch}${messageOverwrite}${messageMerge}
this._log(ex, LOG_LEVEL_VERBOSE); this._log(ex, LOG_LEVEL_VERBOSE);
return []; return [];
} }
const filesSrc = [
...w.files
.filter((e) => !ignoreList.some((ee) => e.endsWith(ee)))
.filter((e) => !filter || filter.some((ee) => e.match(ee)))
.filter((e) => !ignoreFilter || ignoreFilter.every((ee) => !e.match(ee))),
];
let files = [] as string[]; let files = [] as string[];
for (const file of filesSrc) { for (const file of w.files) {
if (!(await this.plugin.$$isIgnoredByIgnoreFiles(file))) { if (ignoreList && ignoreList.length > 0) {
files.push(file); if (ignoreList.some((e) => file.endsWith(e))) continue;
} }
if (filter && filter.length > 0) {
if (!filter.some((e) => e.test(file))) {
continue;
}
}
if (ignoreFilter && ignoreFilter.some((ee) => ee.test(file))) {
continue;
}
if (await this.plugin.$$isIgnoredByIgnoreFiles(file)) continue;
files.push(file);
} }
L1: for (const v of w.folders) { L1: for (const v of w.folders) {
for (const ignore of ignoreList) { for (const ignore of ignoreList) {
if (v.endsWith(ignore)) { if (v.endsWith(ignore)) {
continue L1; continue L1;
} }
} }
if (ignoreFilter && ignoreFilter.some((e) => v.match(e))) { if (
ignoreFilter &&
ignoreFilter.some((e) => (e.pattern.startsWith("/") || e.pattern.startsWith("\\/")) && e.test(v))
) {
continue L1; continue L1;
} }
if (await this.plugin.$$isIgnoredByIgnoreFiles(v)) { if (await this.plugin.$$isIgnoredByIgnoreFiles(v)) {

Submodule src/lib updated: c8bb4fedbb...d53cad1c68

View File

@@ -291,7 +291,8 @@ export default class ObsidianLiveSyncPlugin
performSetup: boolean, performSetup: boolean,
skipInfo: boolean, skipInfo: boolean,
compression: boolean, compression: boolean,
customHeaders: Record<string, string> customHeaders: Record<string, string>,
useRequestAPI: boolean
): Promise< ): Promise<
| string | string
| { | {

View File

@@ -14,7 +14,7 @@ import type {
import { TFileToUXFileInfoStub, TFolderToUXFileInfoStub } from "./storageLib/utilObsidian.ts"; import { TFileToUXFileInfoStub, TFolderToUXFileInfoStub } from "./storageLib/utilObsidian.ts";
import { StorageEventManagerObsidian, type StorageEventManager } from "./storageLib/StorageEventManager"; import { StorageEventManagerObsidian, type StorageEventManager } from "./storageLib/StorageEventManager";
import type { StorageAccess } from "../interfaces/StorageAccess"; import type { StorageAccess } from "../interfaces/StorageAccess";
import { createBlob } from "../../lib/src/common/utils"; import { createBlob, type CustomRegExp } from "../../lib/src/common/utils";
export class ModuleFileAccessObsidian extends AbstractObsidianModule implements IObsidianModule, StorageAccess { export class ModuleFileAccessObsidian extends AbstractObsidianModule implements IObsidianModule, StorageAccess {
vaultAccess!: SerializedFileAccess; vaultAccess!: SerializedFileAccess;
@@ -223,8 +223,8 @@ export class ModuleFileAccessObsidian extends AbstractObsidianModule implements
async getFilesIncludeHidden( async getFilesIncludeHidden(
basePath: string, basePath: string,
includeFilter?: RegExp[], includeFilter?: CustomRegExp[],
excludeFilter?: RegExp[], excludeFilter?: CustomRegExp[],
skipFolder: string[] = [".git", ".trash", "node_modules"] skipFolder: string[] = [".git", ".trash", "node_modules"]
): Promise<FilePath[]> { ): Promise<FilePath[]> {
let w: ListedFiles; let w: ListedFiles;
@@ -239,16 +239,11 @@ export class ModuleFileAccessObsidian extends AbstractObsidianModule implements
let files = [] as string[]; let files = [] as string[];
for (const file of w.files) { for (const file of w.files) {
if (excludeFilter && excludeFilter.some((ee) => file.match(ee))) { if (includeFilter && includeFilter.length > 0) {
// If excludeFilter and includeFilter are both set, the file will be included in the list. if (!includeFilter.some((e) => e.test(file))) continue;
if (includeFilter) {
if (!includeFilter.some((e) => file.match(e))) continue;
} else {
continue;
}
} }
if (includeFilter) { if (excludeFilter && excludeFilter.some((ee) => ee.test(file))) {
if (!includeFilter.some((e) => file.match(e))) continue; continue;
} }
if (await this.plugin.$$isIgnoredByIgnoreFiles(file)) continue; if (await this.plugin.$$isIgnoredByIgnoreFiles(file)) continue;
files.push(file); files.push(file);
@@ -259,16 +254,12 @@ export class ModuleFileAccessObsidian extends AbstractObsidianModule implements
if (skipFolder.some((e) => folderName === e)) { if (skipFolder.some((e) => folderName === e)) {
continue; continue;
} }
if (excludeFilter && excludeFilter.some((e) => v.match(e))) {
if (includeFilter) {
if (!includeFilter.some((e) => v.match(e))) {
continue;
}
}
}
if (includeFilter) { if (excludeFilter && excludeFilter.some((e) => e.test(v))) {
if (!includeFilter.some((e) => v.match(e))) continue; continue;
}
if (await this.plugin.$$isIgnoredByIgnoreFiles(v)) {
continue;
} }
// OK, deep dive! // OK, deep dive!
files = files.concat(await this.getFilesIncludeHidden(v, includeFilter, excludeFilter, skipFolder)); files = files.concat(await this.getFilesIncludeHidden(v, includeFilter, excludeFilter, skipFolder));

View File

@@ -12,7 +12,7 @@ import {
type UXFileInfoStub, type UXFileInfoStub,
type UXInternalFileInfoStub, type UXInternalFileInfoStub,
} from "../../../lib/src/common/types.ts"; } from "../../../lib/src/common/types.ts";
import { delay, fireAndForget } from "../../../lib/src/common/utils.ts"; import { delay, fireAndForget, getFileRegExp } from "../../../lib/src/common/utils.ts";
import { type FileEventItem, type FileEventType } from "../../../common/types.ts"; import { type FileEventItem, type FileEventType } from "../../../common/types.ts";
import { serialized, skipIfDuplicated } from "../../../lib/src/concurrency/lock.ts"; import { serialized, skipIfDuplicated } from "../../../lib/src/concurrency/lock.ts";
import { import {
@@ -161,12 +161,10 @@ export class StorageEventManagerObsidian extends StorageEventManager {
if (!this.plugin.settings.syncInternalFiles && !this.plugin.settings.usePluginSync) return; if (!this.plugin.settings.syncInternalFiles && !this.plugin.settings.usePluginSync) return;
if (!this.plugin.settings.watchInternalFileChanges) return; if (!this.plugin.settings.watchInternalFileChanges) return;
if (!path.startsWith(this.plugin.app.vault.configDir)) return; if (!path.startsWith(this.plugin.app.vault.configDir)) return;
const ignorePatterns = this.plugin.settings.syncInternalFilesIgnorePatterns const ignorePatterns = getFileRegExp(this.plugin.settings, "syncInternalFilesIgnorePatterns");
.replace(/\n| /g, "") const targetPatterns = getFileRegExp(this.plugin.settings, "syncInternalFilesTargetPatterns");
.split(",") if (ignorePatterns.some((e) => e.test(path))) return;
.filter((e) => e) if (!targetPatterns.some((e) => e.test(path))) return;
.map((e) => new RegExp(e, "i"));
if (ignorePatterns.some((e) => path.match(e))) return;
if (path.endsWith("/")) { if (path.endsWith("/")) {
// Folder // Folder
return; return;

View File

@@ -17,17 +17,8 @@ import { AuthorizationHeaderGenerator } from "../../lib/src/replication/httplib.
setNoticeClass(Notice); setNoticeClass(Notice);
async function fetchByAPI(request: RequestUrlParam): Promise<RequestUrlResponse> { async function fetchByAPI(request: RequestUrlParam, errorAsResult = false): Promise<RequestUrlResponse> {
const ret = await requestUrl(request); const ret = await requestUrl({ ...request, throw: !errorAsResult });
if (ret.status - (ret.status % 100) !== 200) {
const er: Error & { status?: number } = new Error(`Request Error:${ret.status}`);
if (ret.json) {
er.message = ret.json.reason;
er.name = `${ret.json.error ?? ""}:${ret.json.message ?? ""}`;
}
er.status = ret.status;
throw er;
}
return ret; return ret;
} }
@@ -45,13 +36,7 @@ export class ModuleObsidianAPI extends AbstractObsidianModule implements IObsidi
return !this.last_successful_post; return !this.last_successful_post;
} }
async fetchByAPI( async _fetchByAPI(url: string, authHeader: string, opts?: RequestInit): Promise<Response> {
url: string,
localURL: string,
method: string,
authHeader: string,
opts?: RequestInit
): Promise<Response> {
const body = opts?.body as string; const body = opts?.body as string;
const transformedHeaders = { ...(opts?.headers as Record<string, string>) }; const transformedHeaders = { ...(opts?.headers as Record<string, string>) };
@@ -65,25 +50,36 @@ export class ModuleObsidianAPI extends AbstractObsidianModule implements IObsidi
method: opts?.method, method: opts?.method,
body: body, body: body,
headers: transformedHeaders, headers: transformedHeaders,
contentType: "application/json", contentType:
// contentType: opts.headers, transformedHeaders?.["content-type"] ?? transformedHeaders?.["Content-Type"] ?? "application/json",
}; };
const r = await fetchByAPI(requestParam, true);
return new Response(r.arrayBuffer, {
headers: r.headers,
status: r.status,
statusText: `${r.status}`,
});
}
async fetchByAPI(
url: string,
localURL: string,
method: string,
authHeader: string,
opts?: RequestInit
): Promise<Response> {
const body = opts?.body as string;
const size = body ? ` (${body.length})` : ""; const size = body ? ` (${body.length})` : "";
try { try {
const r = await this._fetchByAPI(url, authHeader, opts);
this.plugin.requestCount.value = this.plugin.requestCount.value + 1; this.plugin.requestCount.value = this.plugin.requestCount.value + 1;
const r = await fetchByAPI(requestParam);
if (method == "POST" || method == "PUT") { if (method == "POST" || method == "PUT") {
this.last_successful_post = r.status - (r.status % 100) == 200; this.last_successful_post = r.status - (r.status % 100) == 200;
} else { } else {
this.last_successful_post = true; this.last_successful_post = true;
} }
this._log(`HTTP:${method}${size} to:${localURL} -> ${r.status}`, LOG_LEVEL_DEBUG); this._log(`HTTP:${method}${size} to:${localURL} -> ${r.status}`, LOG_LEVEL_DEBUG);
return r;
return new Response(r.arrayBuffer, {
headers: r.headers,
status: r.status,
statusText: `${r.status}`,
});
} catch (ex) { } catch (ex) {
this._log(`HTTP:${method}${size} to:${localURL} -> failed`, LOG_LEVEL_VERBOSE); this._log(`HTTP:${method}${size} to:${localURL} -> failed`, LOG_LEVEL_VERBOSE);
// limit only in bulk_docs. // limit only in bulk_docs.
@@ -106,7 +102,8 @@ export class ModuleObsidianAPI extends AbstractObsidianModule implements IObsidi
performSetup: boolean, performSetup: boolean,
skipInfo: boolean, skipInfo: boolean,
compression: boolean, compression: boolean,
customHeaders: Record<string, string> customHeaders: Record<string, string>,
useRequestAPI: boolean
): Promise<string | { db: PouchDB.Database<EntryDoc>; info: PouchDB.Core.DatabaseInfo }> { ): Promise<string | { db: PouchDB.Database<EntryDoc>; info: PouchDB.Core.DatabaseInfo }> {
if (!isValidRemoteCouchDBURI(uri)) return "Remote URI is not valid"; if (!isValidRemoteCouchDBURI(uri)) return "Remote URI is not valid";
if (uri.toLowerCase() != uri) return "Remote URI and database name could not contain capital letters."; if (uri.toLowerCase() != uri) return "Remote URI and database name could not contain capital letters.";
@@ -147,15 +144,11 @@ export class ModuleObsidianAPI extends AbstractObsidianModule implements IObsidi
headers.append("authorization", authHeader); headers.append("authorization", authHeader);
} }
if (!disableRequestURI && typeof url == "string" && typeof (opts?.body ?? "") == "string") {
// Deprecated configuration, only for backward compatibility.
return await this.fetchByAPI(url, localURL, method, authHeader, { ...opts, headers });
}
// --> native Fetch API.
try { try {
this.plugin.requestCount.value = this.plugin.requestCount.value + 1; this.plugin.requestCount.value = this.plugin.requestCount.value + 1;
const response: Response = await fetch(url, { ...opts, headers }); const response: Response = await (useRequestAPI
? this._fetchByAPI(url.toString(), authHeader, { ...opts, headers })
: fetch(url, { ...opts, headers }));
if (method == "POST" || method == "PUT") { if (method == "POST" || method == "PUT") {
this.last_successful_post = response.ok; this.last_successful_post = response.ok;
} else { } else {
@@ -188,6 +181,10 @@ export class ModuleObsidianAPI extends AbstractObsidianModule implements IObsidi
return response; return response;
} catch (ex) { } catch (ex) {
if (ex instanceof TypeError) { if (ex instanceof TypeError) {
if (useRequestAPI) {
this._log("Failed to request by API.");
throw ex;
}
this._log( this._log(
"Failed to fetch by native fetch API. Trying to fetch by API to get more information." "Failed to fetch by native fetch API. Trying to fetch by API to get more information."
); );

View File

@@ -7,6 +7,7 @@ import { uint8ArrayToHexString } from "octagonal-wheels/binary/hex";
import { parseYaml, requestUrl, stringifyYaml } from "obsidian"; import { parseYaml, requestUrl, stringifyYaml } from "obsidian";
import type { FilePath } from "../../lib/src/common/types.ts"; import type { FilePath } from "../../lib/src/common/types.ts";
import { scheduleTask } from "octagonal-wheels/concurrency/task"; import { scheduleTask } from "octagonal-wheels/concurrency/task";
import { getFileRegExp } from "../../lib/src/common/utils.ts";
declare global { declare global {
interface LSEvents { interface LSEvents {
@@ -200,13 +201,10 @@ export class ModuleReplicateTest extends AbstractObsidianModule implements IObsi
} }
async _dumpFileListIncludeHidden(outFile?: string) { async _dumpFileListIncludeHidden(outFile?: string) {
const ignorePatterns = this.settings.syncInternalFilesIgnorePatterns const ignorePatterns = getFileRegExp(this.plugin.settings, "syncInternalFilesIgnorePatterns");
.replace(/\n| /g, "") const targetPatterns = getFileRegExp(this.plugin.settings, "syncInternalFilesTargetPatterns");
.split(",")
.filter((e) => e)
.map((e) => new RegExp(e, "i"));
const out = [] as any[]; const out = [] as any[];
const files = await this.core.storageAccess.getFilesIncludeHidden("", undefined, ignorePatterns); const files = await this.core.storageAccess.getFilesIncludeHidden("", targetPatterns, ignorePatterns);
// console.dir(files); // console.dir(files);
const webcrypto = await getWebCrypto(); const webcrypto = await getWebCrypto();
for (const file of files) { for (const file of files) {

View File

@@ -63,6 +63,7 @@ export class ModuleLog extends AbstractObsidianModule implements IObsidianModule
statusBarLabels!: ReactiveValue<{ message: string; status: string }>; statusBarLabels!: ReactiveValue<{ message: string; status: string }>;
statusLog = reactiveSource(""); statusLog = reactiveSource("");
activeFileStatus = reactiveSource("");
notifies: { [key: string]: { notice: Notice; count: number } } = {}; notifies: { [key: string]: { notice: Notice; count: number } } = {};
p2pLogCollector = new P2PLogCollector(); p2pLogCollector = new P2PLogCollector();
@@ -181,10 +182,11 @@ export class ModuleLog extends AbstractObsidianModule implements IObsidianModule
? `WARNING! RESTARTING OBSIDIAN IS SCHEDULED!\n` ? `WARNING! RESTARTING OBSIDIAN IS SCHEDULED!\n`
: ""; : "";
const { message } = statusLineLabel(); const { message } = statusLineLabel();
const fileStatus = this.activeFileStatus.value;
const status = scheduleMessage + this.statusLog.value; const status = scheduleMessage + this.statusLog.value;
const fileStatusIcon = `${fileStatus && this.settings.hideFileWarningNotice ? " ⛔ SKIP" : ""}`;
return { return {
message, message: `${message}${fileStatusIcon}`,
status, status,
}; };
}); });
@@ -229,7 +231,9 @@ export class ModuleLog extends AbstractObsidianModule implements IObsidianModule
return ""; return "";
} }
async setFileStatus() { async setFileStatus() {
this.messageArea!.innerText = await this.getActiveFileStatus(); const fileStatus = await this.getActiveFileStatus();
this.activeFileStatus.value = fileStatus;
this.messageArea!.innerText = this.settings.hideFileWarningNotice ? "" : fileStatus;
} }
onActiveLeafChange() { onActiveLeafChange() {
fireAndForget(async () => { fireAndForget(async () => {

View File

@@ -11,9 +11,45 @@ import {
} from "../../lib/src/common/types"; } from "../../lib/src/common/types";
import { LOG_LEVEL_NOTICE, LOG_LEVEL_URGENT } from "octagonal-wheels/common/logger"; import { LOG_LEVEL_NOTICE, LOG_LEVEL_URGENT } from "octagonal-wheels/common/logger";
import { encrypt, tryDecrypt } from "octagonal-wheels/encryption"; import { encrypt, tryDecrypt } from "octagonal-wheels/encryption";
import { setLang } from "../../lib/src/common/i18n"; import { $msg, setLang } from "../../lib/src/common/i18n";
import { isCloudantURI } from "../../lib/src/pouchdb/utils_couchdb"; import { isCloudantURI } from "../../lib/src/pouchdb/utils_couchdb";
import { getLanguage } from "obsidian";
import { SUPPORTED_I18N_LANGS, type I18N_LANGS } from "../../lib/src/common/rosetta.ts";
export class ModuleObsidianSettings extends AbstractObsidianModule implements IObsidianModule { export class ModuleObsidianSettings extends AbstractObsidianModule implements IObsidianModule {
async $everyOnLayoutReady(): Promise<boolean> {
let isChanged = false;
if (this.settings.displayLanguage == "") {
const obsidianLanguage = getLanguage();
if (
SUPPORTED_I18N_LANGS.indexOf(obsidianLanguage) !== -1 && // Check if the language is supported
obsidianLanguage != this.settings.displayLanguage && // Check if the language is different from the current setting
this.settings.displayLanguage != ""
) {
// Check if the current setting is not empty (Means migrated or installed).
this.settings.displayLanguage = obsidianLanguage as I18N_LANGS;
isChanged = true;
setLang(this.settings.displayLanguage);
} else if (this.settings.displayLanguage == "") {
this.settings.displayLanguage = "def";
setLang(this.settings.displayLanguage);
await this.core.$$saveSettingData();
}
}
if (isChanged) {
const revert = $msg("dialog.yourLanguageAvailable.btnRevertToDefault");
if (
(await this.core.confirm.askSelectStringDialogue($msg(`dialog.yourLanguageAvailable`), ["OK", revert], {
defaultAction: "OK",
title: $msg(`dialog.yourLanguageAvailable.Title`),
})) == revert
) {
this.settings.displayLanguage = "def";
setLang(this.settings.displayLanguage);
}
await this.core.$$saveSettingData();
}
return true;
}
getPassphrase(settings: ObsidianLiveSyncSettings) { getPassphrase(settings: ObsidianLiveSyncSettings) {
const methods: Record<ConfigPassphraseStore, () => Promise<string | false>> = { const methods: Record<ConfigPassphraseStore, () => Promise<string | false>> = {
"": () => Promise.resolve("*"), "": () => Promise.resolve("*"),
@@ -199,6 +235,7 @@ export class ModuleObsidianSettings extends AbstractObsidianModule implements IO
} }
} }
this.settings = settings; this.settings = settings;
setLang(this.settings.displayLanguage); setLang(this.settings.displayLanguage);
if ("workingEncrypt" in this.settings) delete this.settings.workingEncrypt; if ("workingEncrypt" in this.settings) delete this.settings.workingEncrypt;

View File

@@ -1,31 +1,28 @@
<script lang="ts"> <script lang="ts">
export let patterns = [] as string[]; import type { CustomRegExpSource } from "../../../lib/src/common/types";
export let originals = [] as string[]; import { isInvertedRegExp, isValidRegExp } from "../../../lib/src/common/utils";
export let apply: (args: string[]) => Promise<void> = (_: string[]) => Promise.resolve(); export let patterns = [] as CustomRegExpSource[];
export let originals = [] as CustomRegExpSource[];
export let apply: (args: CustomRegExpSource[]) => Promise<void> = (_: CustomRegExpSource[]) => Promise.resolve();
function revert() { function revert() {
patterns = [...originals]; patterns = [...originals];
} }
const CHECK_OK = "✔"; const CHECK_OK = "✔";
const CHECK_NG = "⚠"; const CHECK_NG = "⚠";
const MARK_MODIFIED = "✏ "; const MARK_MODIFIED = "✏ ";
function checkRegExp(pattern: string) { function checkRegExp(pattern: CustomRegExpSource) {
if (pattern.trim() == "") return ""; return isValidRegExp(pattern) ? CHECK_OK : CHECK_NG;
try {
new RegExp(pattern);
return CHECK_OK;
} catch (ex) {
return CHECK_NG;
}
} }
$: statusName = patterns.map((e) => checkRegExp(e)); $: statusName = patterns.map((e) => checkRegExp(e));
$: modified = patterns.map((e, i) => (e != (originals?.[i] ?? "") ? MARK_MODIFIED : "")); $: modified = patterns.map((e, i) => (e != (originals?.[i] ?? "") ? MARK_MODIFIED : ""));
$: isInvertedExp = patterns.map((e) => isInvertedRegExp(e));
function remove(idx: number) { function remove(idx: number) {
patterns[idx] = ""; patterns[idx] = "" as CustomRegExpSource;
} }
function add() { function add() {
patterns = [...patterns, ""]; patterns = [...patterns, "" as CustomRegExpSource];
} }
</script> </script>
@@ -33,7 +30,9 @@
{#each patterns as pattern, idx} {#each patterns as pattern, idx}
<!-- svelte-ignore a11y-label-has-associated-control --> <!-- svelte-ignore a11y-label-has-associated-control -->
<li> <li>
<label>{modified[idx]}{statusName[idx]}</label><input type="text" bind:value={pattern} class={modified[idx]} /> <label>{modified[idx]}{statusName[idx]}</label>
<span class="chip">{isInvertedExp[idx] ? "INVERTED" : ""}</span>
<input type="text" bind:value={pattern} class={modified[idx]} />
<button class="iconbutton" on:click={() => remove(idx)}>🗑</button> <button class="iconbutton" on:click={() => remove(idx)}>🗑</button>
</li> </li>
{/each} {/each}
@@ -43,8 +42,16 @@
</label> </label>
</li> </li>
<li class="buttons"> <li class="buttons">
<button on:click={() => apply(patterns)} disabled={statusName.some((e) => e === CHECK_NG) || modified.every((e) => e === "")}>Apply </button> <button
<button on:click={() => revert()} disabled={statusName.some((e) => e === CHECK_NG) || modified.every((e) => e === "")}>Revert </button> on:click={() => apply(patterns)}
disabled={statusName.some((e) => e === CHECK_NG) || modified.every((e) => e === "")}
>Apply
</button>
<button
on:click={() => revert()}
disabled={statusName.some((e) => e === CHECK_NG) || modified.every((e) => e === "")}
>Revert
</button>
</li> </li>
</ul> </ul>
@@ -85,4 +92,14 @@
button.iconbutton { button.iconbutton {
max-width: 4em; max-width: 4em;
} }
.chip {
background-color: var(--tag-background);
color: var(--tag-color);
padding: var(--size-2-1) var(--size-4-1);
border-radius: 0.5em;
font-size: 0.8em;
}
.chip:empty {
display: none;
}
</style> </style>

View File

@@ -26,15 +26,19 @@ import {
type MetaEntry, type MetaEntry,
type FilePath, type FilePath,
REMOTE_P2P, REMOTE_P2P,
type CustomRegExpSource,
} from "../../../lib/src/common/types.ts"; } from "../../../lib/src/common/types.ts";
import { import {
constructCustomRegExpList,
createBlob, createBlob,
delay, delay,
getFileRegExp,
isDocContentSame, isDocContentSame,
isObjectDifferent, isObjectDifferent,
parseHeaderValues, parseHeaderValues,
readAsBlob, readAsBlob,
sizeToHumanReadable, sizeToHumanReadable,
splitCustomRegExpList,
} from "../../../lib/src/common/utils.ts"; } from "../../../lib/src/common/utils.ts";
import { arrayBufferToBase64Single, versionNumberString2Number } from "../../../lib/src/string_and_binary/convert.ts"; import { arrayBufferToBase64Single, versionNumberString2Number } from "../../../lib/src/string_and_binary/convert.ts";
import { Logger } from "../../../lib/src/common/logger.ts"; import { Logger } from "../../../lib/src/common/logger.ts";
@@ -1132,7 +1136,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
(paneEl) => { (paneEl) => {
void addPanel(paneEl, $msg("obsidianLiveSyncSettingTab.titleAppearance")).then((paneEl) => { void addPanel(paneEl, $msg("obsidianLiveSyncSettingTab.titleAppearance")).then((paneEl) => {
const languages = Object.fromEntries([ const languages = Object.fromEntries([
["", $msg("obsidianLiveSyncSettingTab.defaultLanguage")], // ["", $msg("obsidianLiveSyncSettingTab.defaultLanguage")],
...SUPPORTED_I18N_LANGS.map((e) => [e, $t(`lang-${e}`)]), ...SUPPORTED_I18N_LANGS.map((e) => [e, $t(`lang-${e}`)]),
]) as Record<I18N_LANGS, string>; ]) as Record<I18N_LANGS, string>;
new Setting(paneEl).autoWireDropDown("displayLanguage", { new Setting(paneEl).autoWireDropDown("displayLanguage", {
@@ -1144,6 +1148,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
onUpdate: visibleOnly(() => this.isConfiguredAs("showStatusOnEditor", true)), onUpdate: visibleOnly(() => this.isConfiguredAs("showStatusOnEditor", true)),
}); });
new Setting(paneEl).autoWireToggle("showStatusOnStatusbar"); new Setting(paneEl).autoWireToggle("showStatusOnStatusbar");
new Setting(paneEl).autoWireToggle("hideFileWarningNotice");
}); });
void addPanel(paneEl, $msg("obsidianLiveSyncSettingTab.titleLogging")).then((paneEl) => { void addPanel(paneEl, $msg("obsidianLiveSyncSettingTab.titleLogging")).then((paneEl) => {
paneEl.addClass("wizardHidden"); paneEl.addClass("wizardHidden");
@@ -2218,13 +2223,10 @@ The pane also can be launched by \`P2P Replicator\` command from the Command Pal
mount(MultipleRegExpControl, { mount(MultipleRegExpControl, {
target: syncFilesSetting.controlEl, target: syncFilesSetting.controlEl,
props: { props: {
patterns: this.editingSettings.syncOnlyRegEx.split("|[]|"), patterns: splitCustomRegExpList(this.editingSettings.syncOnlyRegEx, "|[]|"),
originals: [...this.editingSettings.syncOnlyRegEx.split("|[]|")], originals: splitCustomRegExpList(this.editingSettings.syncOnlyRegEx, "|[]|"),
apply: async (newPatterns: string[]) => { apply: async (newPatterns: CustomRegExpSource[]) => {
this.editingSettings.syncOnlyRegEx = newPatterns this.editingSettings.syncOnlyRegEx = constructCustomRegExpList(newPatterns, "|[]|");
.map((e: string) => e.trim())
.filter((e) => e != "")
.join("|[]|");
await this.saveAllDirtySettings(); await this.saveAllDirtySettings();
this.display(); this.display();
}, },
@@ -2241,13 +2243,10 @@ The pane also can be launched by \`P2P Replicator\` command from the Command Pal
mount(MultipleRegExpControl, { mount(MultipleRegExpControl, {
target: nonSyncFilesSetting.controlEl, target: nonSyncFilesSetting.controlEl,
props: { props: {
patterns: this.editingSettings.syncIgnoreRegEx.split("|[]|"), patterns: splitCustomRegExpList(this.editingSettings.syncIgnoreRegEx, "|[]|"),
originals: [...this.editingSettings.syncIgnoreRegEx.split("|[]|")], originals: splitCustomRegExpList(this.editingSettings.syncIgnoreRegEx, "|[]|"),
apply: async (newPatterns: string[]) => { apply: async (newPatterns: CustomRegExpSource[]) => {
this.editingSettings.syncIgnoreRegEx = newPatterns this.editingSettings.syncIgnoreRegEx = constructCustomRegExpList(newPatterns, "|[]|");
.map((e) => e.trim())
.filter((e) => e != "")
.join("|[]|");
await this.saveAllDirtySettings(); await this.saveAllDirtySettings();
this.display(); this.display();
}, },
@@ -2261,14 +2260,32 @@ The pane also can be launched by \`P2P Replicator\` command from the Command Pal
}); });
}); });
void addPanel(paneEl, "Hidden Files", undefined, undefined, LEVEL_ADVANCED).then((paneEl) => { void addPanel(paneEl, "Hidden Files", undefined, undefined, LEVEL_ADVANCED).then((paneEl) => {
const targetPatternSetting = new Setting(paneEl)
.setName("Target patterns")
.setClass("wizardHidden")
.setDesc("Patterns to match files for syncing");
const patTarget = splitCustomRegExpList(this.editingSettings.syncInternalFilesTargetPatterns, ",");
mount(MultipleRegExpControl, {
target: targetPatternSetting.controlEl,
props: {
patterns: patTarget,
originals: [...patTarget],
apply: async (newPatterns: CustomRegExpSource[]) => {
this.editingSettings.syncInternalFilesTargetPatterns = constructCustomRegExpList(
newPatterns,
","
);
await this.saveAllDirtySettings();
this.display();
},
},
});
const defaultSkipPattern = "\\/node_modules\\/, \\/\\.git\\/, ^\\.git\\/, \\/obsidian-livesync\\/"; const defaultSkipPattern = "\\/node_modules\\/, \\/\\.git\\/, ^\\.git\\/, \\/obsidian-livesync\\/";
const defaultSkipPatternXPlat = const defaultSkipPatternXPlat =
defaultSkipPattern + ",\\/workspace$ ,\\/workspace.json$,\\/workspace-mobile.json$"; defaultSkipPattern + ",\\/workspace$ ,\\/workspace.json$,\\/workspace-mobile.json$";
const pat = this.editingSettings.syncInternalFilesIgnorePatterns const pat = splitCustomRegExpList(this.editingSettings.syncInternalFilesIgnorePatterns, ",");
.split(",")
.map((x) => x.trim())
.filter((x) => x != "");
const patSetting = new Setting(paneEl).setName("Ignore patterns").setClass("wizardHidden").setDesc(""); const patSetting = new Setting(paneEl).setName("Ignore patterns").setClass("wizardHidden").setDesc("");
mount(MultipleRegExpControl, { mount(MultipleRegExpControl, {
@@ -2276,11 +2293,11 @@ The pane also can be launched by \`P2P Replicator\` command from the Command Pal
props: { props: {
patterns: pat, patterns: pat,
originals: [...pat], originals: [...pat],
apply: async (newPatterns: string[]) => { apply: async (newPatterns: CustomRegExpSource[]) => {
this.editingSettings.syncInternalFilesIgnorePatterns = newPatterns this.editingSettings.syncInternalFilesIgnorePatterns = constructCustomRegExpList(
.map((e) => e.trim()) newPatterns,
.filter((e) => e != "") ","
.join(", "); );
await this.saveAllDirtySettings(); await this.saveAllDirtySettings();
this.display(); this.display();
}, },
@@ -2288,16 +2305,13 @@ The pane also can be launched by \`P2P Replicator\` command from the Command Pal
}); });
const addDefaultPatterns = async (patterns: string) => { const addDefaultPatterns = async (patterns: string) => {
const oldList = this.editingSettings.syncInternalFilesIgnorePatterns const oldList = splitCustomRegExpList(this.editingSettings.syncInternalFilesIgnorePatterns, ",");
.split(",") const newList = splitCustomRegExpList(
.map((x) => x.trim()) patterns as unknown as typeof this.editingSettings.syncInternalFilesIgnorePatterns,
.filter((x) => x != ""); ","
const newList = patterns );
.split(",") const allSet = new Set<CustomRegExpSource>([...oldList, ...newList]);
.map((x) => x.trim()) this.editingSettings.syncInternalFilesIgnorePatterns = constructCustomRegExpList([...allSet], ",");
.filter((x) => x != "");
const allSet = new Set([...oldList, ...newList]);
this.editingSettings.syncInternalFilesIgnorePatterns = [...allSet].join(", ");
await this.saveAllDirtySettings(); await this.saveAllDirtySettings();
this.display(); this.display();
}; };
@@ -2691,17 +2705,20 @@ ${stringifyYaml(pluginConfig)}`;
.setCta() .setCta()
.onClick(async () => { .onClick(async () => {
Logger("Start verifying all files", LOG_LEVEL_NOTICE, "verify"); Logger("Start verifying all files", LOG_LEVEL_NOTICE, "verify");
const ignorePatterns = this.plugin.settings.syncInternalFilesIgnorePatterns const ignorePatterns = getFileRegExp(
.replace(/\n| /g, "") this.plugin.settings,
.split(",") "syncInternalFilesIgnorePatterns"
.filter((e) => e) );
.map((e) => new RegExp(e, "i")); const targetPatterns = getFileRegExp(
this.plugin.settings,
"syncInternalFilesTargetPatterns"
);
this.plugin.localDatabase.hashCaches.clear(); this.plugin.localDatabase.hashCaches.clear();
Logger("Start verifying all files", LOG_LEVEL_NOTICE, "verify"); Logger("Start verifying all files", LOG_LEVEL_NOTICE, "verify");
const files = this.plugin.settings.syncInternalFiles const files = this.plugin.settings.syncInternalFiles
? await this.plugin.storageAccess.getFilesIncludeHidden( ? await this.plugin.storageAccess.getFilesIncludeHidden(
"/", "/",
undefined, targetPatterns,
ignorePatterns ignorePatterns
) )
: await this.plugin.storageAccess.getFileNames(); : await this.plugin.storageAccess.getFileNames();
@@ -2721,7 +2738,7 @@ ${stringifyYaml(pluginConfig)}`;
i++; i++;
if (i % 25 == 0) if (i % 25 == 0)
Logger( Logger(
`Checking ${i}/${files.length} files \n`, `Checking ${i}/${allPaths.length} files \n`,
LOG_LEVEL_NOTICE, LOG_LEVEL_NOTICE,
"verify-processed" "verify-processed"
); );
@@ -3087,7 +3104,9 @@ ${stringifyYaml(pluginConfig)}`;
onUpdate: visibleOnly(() => this.isConfiguredAs("disableWorkerForGeneratingChunks", false)), onUpdate: visibleOnly(() => this.isConfiguredAs("disableWorkerForGeneratingChunks", false)),
}); });
}); });
void addPanel(paneEl, "Edge case addressing (Networking)").then((paneEl) => {
new Setting(paneEl).autoWireToggle("useRequestAPI");
});
void addPanel(paneEl, "Compatibility (Trouble addressed)").then((paneEl) => { void addPanel(paneEl, "Compatibility (Trouble addressed)").then((paneEl) => {
new Setting(paneEl).autoWireToggle("disableCheckingConfigMismatch"); new Setting(paneEl).autoWireToggle("disableCheckingConfigMismatch");
}); });

View File

@@ -377,6 +377,14 @@ export const SettingInformation: Partial<Record<keyof AllSettings, Configuration
name: "Minimum interval for syncing", name: "Minimum interval for syncing",
desc: "The minimum interval for automatic synchronisation on event.", desc: "The minimum interval for automatic synchronisation on event.",
}, },
useRequestAPI: {
name: "Use Request API to avoid `inevitable` CORS problem",
desc: "If enabled, the request API will be used to avoid `inevitable` CORS problems. This is a workaround and may not work in all cases. PLEASE READ THE DOCUMENTATION BEFORE USING THIS OPTION. This is a less-secure option.",
},
hideFileWarningNotice: {
name: "Show status icon instead of file warnings banner",
desc: "If enabled, the ⛔ icon will be shown inside the status instead of the file warnings banner. No details will be shown.",
},
}; };
function translateInfo(infoSrc: ConfigurationItem | undefined | false) { function translateInfo(infoSrc: ConfigurationItem | undefined | false) {
if (!infoSrc) return false; if (!infoSrc) return false;

View File

@@ -7,6 +7,7 @@ import type {
UXFolderInfo, UXFolderInfo,
UXStat, UXStat,
} from "../../lib/src/common/types"; } from "../../lib/src/common/types";
import type { CustomRegExp } from "../../lib/src/common/utils";
export interface StorageAccess { export interface StorageAccess {
deleteVaultItem(file: FilePathWithPrefix | UXFileInfoStub | UXFolderInfo): Promise<void>; deleteVaultItem(file: FilePathWithPrefix | UXFileInfoStub | UXFolderInfo): Promise<void>;
@@ -48,8 +49,8 @@ export interface StorageAccess {
getFilesIncludeHidden( getFilesIncludeHidden(
basePath: string, basePath: string,
includeFilter?: RegExp[], includeFilter?: CustomRegExp[],
excludeFilter?: RegExp[], excludeFilter?: CustomRegExp[],
skipFolder?: string[] skipFolder?: string[]
): Promise<FilePath[]>; ): Promise<FilePath[]>;
} }