mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2025-12-30 10:01:31 +00:00
Merge branch 'main' into per-file-sync-grammar
This commit is contained in:
16
src/common/events.ts
Normal file
16
src/common/events.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
export const EVENT_LAYOUT_READY = "layout-ready";
|
||||
export const EVENT_PLUGIN_LOADED = "plugin-loaded";
|
||||
export const EVENT_PLUGIN_UNLOADED = "plugin-unloaded";
|
||||
export const EVENT_SETTING_SAVED = "setting-saved";
|
||||
export const EVENT_FILE_RENAMED = "file-renamed";
|
||||
|
||||
export const EVENT_LEAF_ACTIVE_CHANGED = "leaf-active-changed";
|
||||
|
||||
|
||||
// export const EVENT_FILE_CHANGED = "file-changed";
|
||||
|
||||
import { eventHub } from "../lib/src/hub/hub";
|
||||
// TODO: Add overloads for the emit method to allow for type checking
|
||||
|
||||
export { eventHub };
|
||||
|
||||
@@ -15,14 +15,14 @@ export { scheduleTask, setPeriodicTask, cancelTask, cancelAllTasks, cancelPeriod
|
||||
// For backward compatibility, using the path for determining id.
|
||||
// Only CouchDB unacceptable ID (that starts with an underscore) has been prefixed with "/".
|
||||
// The first slash will be deleted when the path is normalized.
|
||||
export async function path2id(filename: FilePathWithPrefix | FilePath, obfuscatePassphrase: string | false): Promise<DocumentID> {
|
||||
export async function path2id(filename: FilePathWithPrefix | FilePath, obfuscatePassphrase: string | false, caseInsensitive: boolean): Promise<DocumentID> {
|
||||
const temp = filename.split(":");
|
||||
const path = temp.pop();
|
||||
const normalizedPath = normalizePath(path as FilePath);
|
||||
temp.push(normalizedPath);
|
||||
const fixedPath = temp.join(":") as FilePathWithPrefix;
|
||||
|
||||
const out = await path2id_base(fixedPath, obfuscatePassphrase);
|
||||
const out = await path2id_base(fixedPath, obfuscatePassphrase, caseInsensitive);
|
||||
return out;
|
||||
}
|
||||
export function id2path(id: DocumentID, entry?: EntryHasPath): FilePathWithPrefix {
|
||||
@@ -134,7 +134,13 @@ export function generatePatchObj(from: Record<string | number | symbol, any>, to
|
||||
//if type is not match, replace completely.
|
||||
ret[key] = { [MARK_SWAPPED]: value };
|
||||
} else {
|
||||
if (typeof (v) == "object" && typeof (value) == "object" && !Array.isArray(v) && !Array.isArray(value)) {
|
||||
if (v === null && value === null) {
|
||||
// NO OP.
|
||||
} else if (v === null && value !== null) {
|
||||
ret[key] = { [MARK_SWAPPED]: value };
|
||||
} else if (v !== null && value === null) {
|
||||
ret[key] = { [MARK_SWAPPED]: value };
|
||||
} else if (typeof (v) == "object" && typeof (value) == "object" && !Array.isArray(v) && !Array.isArray(value)) {
|
||||
const wk = generatePatchObj(v, value);
|
||||
if (Object.keys(wk).length > 0) ret[key] = wk;
|
||||
} else if (typeof (v) == "object" && typeof (value) == "object" && Array.isArray(v) && Array.isArray(value)) {
|
||||
@@ -169,6 +175,10 @@ export function applyPatch(from: Record<string | number | symbol, any>, patch: R
|
||||
delete ret[key];
|
||||
continue;
|
||||
}
|
||||
if (value === null) {
|
||||
ret[key] = null;
|
||||
continue;
|
||||
}
|
||||
if (typeof (value) == "object") {
|
||||
if (MARK_SWAPPED in value) {
|
||||
ret[key] = value[MARK_SWAPPED];
|
||||
@@ -251,6 +261,7 @@ export function mergeObject(
|
||||
|
||||
export function flattenObject(obj: Record<string | number | symbol, any>, path: string[] = []): [string, any][] {
|
||||
if (typeof (obj) != "object") return [[path.join("."), obj]];
|
||||
if (obj === null) return [[path.join("."), null]];
|
||||
if (Array.isArray(obj)) return [[path.join("."), JSON.stringify(obj)]];
|
||||
const e = Object.entries(obj);
|
||||
const ret = []
|
||||
@@ -465,3 +476,63 @@ export function compareFileFreshness(baseFile: TFile | AnyEntry | undefined, che
|
||||
return compareMTime(modifiedBase, modifiedTarget);
|
||||
}
|
||||
|
||||
const _cached = new Map<string, {
|
||||
value: any;
|
||||
context: Map<string, any>;
|
||||
}>();
|
||||
|
||||
export type MemoOption = {
|
||||
key: string;
|
||||
forceUpdate?: boolean;
|
||||
validator?: () => boolean;
|
||||
}
|
||||
|
||||
export function useMemo<T>({ key, forceUpdate, validator }: MemoOption, updateFunc: (context: Map<string, any>, prev: T) => T): T {
|
||||
const cached = _cached.get(key);
|
||||
if (cached && !forceUpdate && (!validator || validator && !validator())) {
|
||||
return cached.value;
|
||||
}
|
||||
const context = cached?.context || new Map<string, any>();
|
||||
const value = updateFunc(context, cached?.value);
|
||||
if (value !== cached?.value) {
|
||||
_cached.set(key, { value, context });
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
// const _static = new Map<string, any>();
|
||||
const _staticObj = new Map<string, {
|
||||
value: any
|
||||
}>();
|
||||
|
||||
export function useStatic<T>(key: string): { value: (T | undefined) };
|
||||
export function useStatic<T>(key: string, initial: T): { value: T };
|
||||
export function useStatic<T>(key: string, initial?: T) {
|
||||
// if (!_static.has(key) && initial) {
|
||||
// _static.set(key, initial);
|
||||
// }
|
||||
const obj = _staticObj.get(key);
|
||||
if (obj !== undefined) {
|
||||
return obj;
|
||||
} else {
|
||||
// let buf = initial;
|
||||
const obj = {
|
||||
_buf: initial,
|
||||
get value() {
|
||||
return this._buf as T;
|
||||
},
|
||||
set value(value: T) {
|
||||
this._buf = value
|
||||
}
|
||||
}
|
||||
_staticObj.set(key, obj);
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
export function disposeMemo(key: string) {
|
||||
_cached.delete(key);
|
||||
}
|
||||
|
||||
export function disposeAllMemo() {
|
||||
_cached.clear();
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import { writable } from 'svelte/store';
|
||||
import { Notice, type PluginManifest, parseYaml, normalizePath, type ListedFiles, diff_match_patch } from "../deps.ts";
|
||||
|
||||
import type { EntryDoc, LoadedEntry, InternalFileEntry, FilePathWithPrefix, FilePath, AnyEntry, SavingEntry, diff_result } from "../lib/src/common/types.ts";
|
||||
import { CANCELLED, LEAVE_TO_SUBSEQUENT, LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE, MODE_SELECTIVE, MODE_SHINY } from "../lib/src/common/types.ts";
|
||||
import { CANCELLED, LEAVE_TO_SUBSEQUENT, LOG_LEVEL_DEBUG, LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE, MODE_SELECTIVE, MODE_SHINY } from "../lib/src/common/types.ts";
|
||||
import { ICXHeader, PERIODIC_PLUGIN_SWEEP, } from "../common/types.ts";
|
||||
import { createBlob, createSavingEntryFromLoadedEntry, createTextBlob, delay, fireAndForget, getDocData, getDocDataAsArray, isDocContentSame, isLoadedEntry, isObjectDifferent } from "../lib/src/common/utils.ts";
|
||||
import { Logger } from "../lib/src/common/logger.ts";
|
||||
@@ -11,7 +11,7 @@ import { arrayBufferToBase64, decodeBinary, readString } from 'src/lib/src/strin
|
||||
import { serialized, shareRunningResult } from "../lib/src/concurrency/lock.ts";
|
||||
import { LiveSyncCommands } from "./LiveSyncCommands.ts";
|
||||
import { stripAllPrefixes } from "../lib/src/string_and_binary/path.ts";
|
||||
import { PeriodicProcessor, disposeMemoObject, memoIfNotExist, memoObject, retrieveMemoObject, scheduleTask } from "../common/utils.ts";
|
||||
import { EVEN, PeriodicProcessor, disposeMemoObject, isMarkedAsSameChanges, markChangesAreSame, memoIfNotExist, memoObject, retrieveMemoObject, scheduleTask } from "../common/utils.ts";
|
||||
import { PluginDialogModal } from "../common/dialogs.ts";
|
||||
import { JsonResolveModal } from "../ui/JsonResolveModal.ts";
|
||||
import { QueueProcessor } from '../lib/src/concurrency/processor.ts';
|
||||
@@ -272,8 +272,10 @@ export class PluginDataExDisplayV2 {
|
||||
this.confKey = `${categoryToFolder(this.category, this.term)}${this.name}`;
|
||||
this.applyLoadedManifest();
|
||||
}
|
||||
setFile(file: LoadedEntryPluginDataExFile) {
|
||||
if (this.files.find(e => e.filename == file.filename)) {
|
||||
async setFile(file: LoadedEntryPluginDataExFile) {
|
||||
const old = this.files.find(e => e.filename == file.filename);
|
||||
if (old) {
|
||||
if (old.mtime == file.mtime && await isDocContentSame(old.data, file.data)) return;
|
||||
this.files = this.files.filter(e => e.filename != file.filename);
|
||||
}
|
||||
this.files.push(file);
|
||||
@@ -319,6 +321,7 @@ export type PluginDataEx = {
|
||||
version?: string,
|
||||
mtime: number,
|
||||
};
|
||||
|
||||
export class ConfigSync extends LiveSyncCommands {
|
||||
constructor(plugin: ObsidianLiveSyncPlugin) {
|
||||
super(plugin);
|
||||
@@ -637,7 +640,7 @@ export class ConfigSync extends LiveSyncCommands {
|
||||
if (!entry) return;
|
||||
const file = await this.createPluginDataExFileV2(unifiedFilenameWithKey);
|
||||
if (file) {
|
||||
entry.setFile(file);
|
||||
await entry.setFile(file);
|
||||
} else {
|
||||
entry.deleteFile(unifiedFilenameWithKey);
|
||||
if (entry.files.length == 0) {
|
||||
@@ -841,22 +844,46 @@ export class ConfigSync extends LiveSyncCommands {
|
||||
Logger(`Applying ${filename} of ${data.displayName || data.name}..`);
|
||||
const path = `${baseDir}/${filename}` as FilePath;
|
||||
await this.vaultAccess.ensureDirectory(path);
|
||||
// If the content has applied, modified time will be updated to the current time.
|
||||
await this.vaultAccess.adapterWrite(path, content);
|
||||
await this.storeCustomisationFileV2(path, this.plugin.deviceAndVaultName);
|
||||
|
||||
} else {
|
||||
const files = data.files;
|
||||
for (const f of files) {
|
||||
// If files have applied, modified time will be updated to the current time.
|
||||
const stat = { mtime: f.mtime, ctime: f.ctime };
|
||||
const path = `${baseDir}/${f.filename}` as FilePath;
|
||||
Logger(`Applying ${f.filename} of ${data.displayName || data.name}..`);
|
||||
// const contentEach = createBlob(f.data);
|
||||
this.vaultAccess.ensureDirectory(path);
|
||||
|
||||
if (f.datatype == "newnote") {
|
||||
let oldData;
|
||||
try {
|
||||
oldData = await this.vaultAccess.adapterReadBinary(path);
|
||||
} catch (ex) {
|
||||
oldData = new ArrayBuffer(0);
|
||||
}
|
||||
const content = base64ToArrayBuffer(f.data);
|
||||
await this.vaultAccess.adapterWrite(path, content);
|
||||
if (await isDocContentSame(oldData, content)) {
|
||||
Logger(`The file ${f.filename} is already up-to-date`, LOG_LEVEL_VERBOSE);
|
||||
continue;
|
||||
}
|
||||
await this.vaultAccess.adapterWrite(path, content, stat);
|
||||
} else {
|
||||
let oldData;
|
||||
try {
|
||||
oldData = await this.vaultAccess.adapterRead(path);
|
||||
} catch (ex) {
|
||||
oldData = "";
|
||||
}
|
||||
const content = getDocData(f.data);
|
||||
await this.vaultAccess.adapterWrite(path, content);
|
||||
if (await isDocContentSame(oldData, content)) {
|
||||
Logger(`The file ${f.filename} is already up-to-date`, LOG_LEVEL_VERBOSE);
|
||||
continue;
|
||||
}
|
||||
await this.vaultAccess.adapterWrite(path, content, stat);
|
||||
}
|
||||
Logger(`Applied ${f.filename} of ${data.displayName || data.name}..`);
|
||||
await this.storeCustomisationFileV2(path, this.plugin.deviceAndVaultName);
|
||||
@@ -1063,7 +1090,7 @@ export class ConfigSync extends LiveSyncCommands {
|
||||
}
|
||||
|
||||
|
||||
async storeCustomisationFileV2(path: FilePath, term: string, saveRelatives = false) {
|
||||
async storeCustomisationFileV2(path: FilePath, term: string, force = false) {
|
||||
const vf = this.filenameWithUnifiedKey(path, term);
|
||||
return await serialized(`plugin-${vf}`, async () => {
|
||||
const prefixedFileName = vf;
|
||||
@@ -1095,8 +1122,21 @@ export class ConfigSync extends LiveSyncCommands {
|
||||
eden: {}
|
||||
};
|
||||
} else {
|
||||
if (old.mtime == mtime) {
|
||||
// Logger(`STORAGE --> DB:${prefixedFileName}: (config) Skipped (Same time)`, LOG_LEVEL_VERBOSE);
|
||||
if (isMarkedAsSameChanges(prefixedFileName, [old.mtime, mtime + 1]) == EVEN) {
|
||||
Logger(`STORAGE --> DB:${prefixedFileName}: (config) Skipped (Already checked the same)`, LOG_LEVEL_DEBUG);
|
||||
return;
|
||||
}
|
||||
const docXDoc = await this.localDatabase.getDBEntryFromMeta(old, {}, false, false);
|
||||
if (docXDoc == false) {
|
||||
throw "Could not load the document";
|
||||
}
|
||||
const dataSrc = getDocData(docXDoc.data);
|
||||
const dataStart = dataSrc.indexOf(DUMMY_END);
|
||||
const oldContent = dataSrc.substring(dataStart + DUMMY_END.length);
|
||||
const oldContentArray = base64ToArrayBuffer(oldContent);
|
||||
if (await isDocContentSame(oldContentArray, content)) {
|
||||
Logger(`STORAGE --> DB:${prefixedFileName}: (config) Skipped (the same content)`, LOG_LEVEL_VERBOSE);
|
||||
markChangesAreSame(prefixedFileName, old.mtime, mtime + 1);
|
||||
return true;
|
||||
}
|
||||
saveData =
|
||||
|
||||
@@ -374,7 +374,7 @@ Of course, we are able to disable these features.`
|
||||
await this.plugin.openDatabase();
|
||||
this.plugin.isReady = true;
|
||||
if (makeLocalChunkBeforeSync) {
|
||||
await this.plugin.initializeDatabase(true);
|
||||
await this.plugin.createAllChunks(true);
|
||||
}
|
||||
await this.plugin.markRemoteResolved();
|
||||
await delay(500);
|
||||
@@ -390,6 +390,7 @@ Of course, we are able to disable these features.`
|
||||
async rebuildRemote() {
|
||||
this.suspendExtraSync();
|
||||
this.plugin.settings.isConfigured = true;
|
||||
|
||||
await this.plugin.realizeSettingSyncMode();
|
||||
await this.plugin.markRemoteLocked();
|
||||
await this.plugin.tryResetRemoteDatabase();
|
||||
|
||||
234
src/features/CmdStatusInsideEditor.ts
Normal file
234
src/features/CmdStatusInsideEditor.ts
Normal file
@@ -0,0 +1,234 @@
|
||||
import { computed, reactive, reactiveSource, type ReactiveValue } from "octagonal-wheels/dataobject/reactive";
|
||||
import type { DatabaseConnectingStatus, EntryDoc } from "../lib/src/common/types";
|
||||
import { LiveSyncCommands } from "./LiveSyncCommands";
|
||||
import { scheduleTask } from "octagonal-wheels/concurrency/task";
|
||||
import { isDirty, throttle } from "../lib/src/common/utils";
|
||||
import { collectingChunks, pluginScanningCount, hiddenFilesEventCount, hiddenFilesProcessingCount } from "../lib/src/mock_and_interop/stores";
|
||||
import { eventHub } from "../lib/src/hub/hub";
|
||||
import { EVENT_FILE_RENAMED, EVENT_LAYOUT_READY, EVENT_LEAF_ACTIVE_CHANGED } from "../common/events";
|
||||
|
||||
export class LogAddOn extends LiveSyncCommands {
|
||||
|
||||
statusBar?: HTMLElement;
|
||||
|
||||
statusDiv?: HTMLElement;
|
||||
statusLine?: HTMLDivElement;
|
||||
logMessage?: HTMLDivElement;
|
||||
logHistory?: HTMLDivElement;
|
||||
messageArea?: HTMLDivElement;
|
||||
|
||||
statusBarLabels!: ReactiveValue<{ message: string, status: string }>;
|
||||
|
||||
observeForLogs() {
|
||||
const padSpaces = `\u{2007}`.repeat(10);
|
||||
// const emptyMark = `\u{2003}`;
|
||||
function padLeftSpComputed(numI: ReactiveValue<number>, mark: string) {
|
||||
const formatted = reactiveSource("");
|
||||
let timer: ReturnType<typeof setTimeout> | undefined = undefined;
|
||||
let maxLen = 1;
|
||||
numI.onChanged(numX => {
|
||||
const num = numX.value;
|
||||
const numLen = `${Math.abs(num)}`.length + 1;
|
||||
maxLen = maxLen < numLen ? numLen : maxLen;
|
||||
if (timer) clearTimeout(timer);
|
||||
if (num == 0) {
|
||||
timer = setTimeout(() => {
|
||||
formatted.value = "";
|
||||
maxLen = 1;
|
||||
}, 3000);
|
||||
}
|
||||
formatted.value = ` ${mark}${`${padSpaces}${num}`.slice(-(maxLen))}`;
|
||||
})
|
||||
return computed(() => formatted.value);
|
||||
}
|
||||
const labelReplication = padLeftSpComputed(this.plugin.replicationResultCount, `📥`);
|
||||
const labelDBCount = padLeftSpComputed(this.plugin.databaseQueueCount, `📄`);
|
||||
const labelStorageCount = padLeftSpComputed(this.plugin.storageApplyingCount, `💾`);
|
||||
const labelChunkCount = padLeftSpComputed(collectingChunks, `🧩`);
|
||||
const labelPluginScanCount = padLeftSpComputed(pluginScanningCount, `🔌`);
|
||||
const labelConflictProcessCount = padLeftSpComputed(this.plugin.conflictProcessQueueCount, `🔩`);
|
||||
const hiddenFilesCount = reactive(() => hiddenFilesEventCount.value + hiddenFilesProcessingCount.value);
|
||||
const labelHiddenFilesCount = padLeftSpComputed(hiddenFilesCount, `⚙️`)
|
||||
const queueCountLabelX = reactive(() => {
|
||||
return `${labelReplication()}${labelDBCount()}${labelStorageCount()}${labelChunkCount()}${labelPluginScanCount()}${labelHiddenFilesCount()}${labelConflictProcessCount()}`;
|
||||
})
|
||||
const queueCountLabel = () => queueCountLabelX.value;
|
||||
|
||||
const requestingStatLabel = computed(() => {
|
||||
const diff = this.plugin.requestCount.value - this.plugin.responseCount.value;
|
||||
return diff != 0 ? "📲 " : "";
|
||||
})
|
||||
|
||||
const replicationStatLabel = computed(() => {
|
||||
const e = this.plugin.replicationStat.value;
|
||||
const sent = e.sent;
|
||||
const arrived = e.arrived;
|
||||
const maxPullSeq = e.maxPullSeq;
|
||||
const maxPushSeq = e.maxPushSeq;
|
||||
const lastSyncPullSeq = e.lastSyncPullSeq;
|
||||
const lastSyncPushSeq = e.lastSyncPushSeq;
|
||||
let pushLast = "";
|
||||
let pullLast = "";
|
||||
let w = "";
|
||||
const labels: Partial<Record<DatabaseConnectingStatus, string>> = {
|
||||
"CONNECTED": "⚡",
|
||||
"JOURNAL_SEND": "📦↑",
|
||||
"JOURNAL_RECEIVE": "📦↓",
|
||||
}
|
||||
switch (e.syncStatus) {
|
||||
case "CLOSED":
|
||||
case "COMPLETED":
|
||||
case "NOT_CONNECTED":
|
||||
w = "⏹";
|
||||
break;
|
||||
case "STARTED":
|
||||
w = "🌀";
|
||||
break;
|
||||
case "PAUSED":
|
||||
w = "💤";
|
||||
break;
|
||||
case "CONNECTED":
|
||||
case "JOURNAL_SEND":
|
||||
case "JOURNAL_RECEIVE":
|
||||
w = labels[e.syncStatus] || "⚡";
|
||||
pushLast = ((lastSyncPushSeq == 0) ? "" : (lastSyncPushSeq >= maxPushSeq ? " (LIVE)" : ` (${maxPushSeq - lastSyncPushSeq})`));
|
||||
pullLast = ((lastSyncPullSeq == 0) ? "" : (lastSyncPullSeq >= maxPullSeq ? " (LIVE)" : ` (${maxPullSeq - lastSyncPullSeq})`));
|
||||
break;
|
||||
case "ERRORED":
|
||||
w = "⚠";
|
||||
break;
|
||||
default:
|
||||
w = "?";
|
||||
}
|
||||
return { w, sent, pushLast, arrived, pullLast };
|
||||
})
|
||||
const labelProc = padLeftSpComputed(this.plugin.vaultManager.processing, `⏳`);
|
||||
const labelPend = padLeftSpComputed(this.plugin.vaultManager.totalQueued, `🛫`);
|
||||
const labelInBatchDelay = padLeftSpComputed(this.plugin.vaultManager.batched, `📬`);
|
||||
const waitingLabel = computed(() => {
|
||||
return `${labelProc()}${labelPend()}${labelInBatchDelay()}`;
|
||||
})
|
||||
const statusLineLabel = computed(() => {
|
||||
const { w, sent, pushLast, arrived, pullLast } = replicationStatLabel();
|
||||
const queued = queueCountLabel();
|
||||
const waiting = waitingLabel();
|
||||
const networkActivity = requestingStatLabel();
|
||||
return {
|
||||
message: `${networkActivity}Sync: ${w} ↑ ${sent}${pushLast} ↓ ${arrived}${pullLast}${waiting}${queued}`,
|
||||
};
|
||||
})
|
||||
const statusBarLabels = reactive(() => {
|
||||
const scheduleMessage = this.plugin.isReloadingScheduled ? `WARNING! RESTARTING OBSIDIAN IS SCHEDULED!\n` : "";
|
||||
const { message } = statusLineLabel();
|
||||
const status = scheduleMessage + this.plugin.statusLog.value;
|
||||
|
||||
return {
|
||||
message, status
|
||||
}
|
||||
})
|
||||
this.statusBarLabels = statusBarLabels;
|
||||
|
||||
const applyToDisplay = throttle((label: typeof statusBarLabels.value) => {
|
||||
// const v = label;
|
||||
this.applyStatusBarText();
|
||||
|
||||
}, 20);
|
||||
statusBarLabels.onChanged(label => applyToDisplay(label.value))
|
||||
}
|
||||
|
||||
adjustStatusDivPosition() {
|
||||
const mdv = this.app.workspace.getMostRecentLeaf();
|
||||
if (mdv && this.statusDiv) {
|
||||
this.statusDiv.remove();
|
||||
// this.statusDiv.pa();
|
||||
const container = mdv.view.containerEl;
|
||||
container.insertBefore(this.statusDiv, container.lastChild);
|
||||
}
|
||||
}
|
||||
|
||||
onunload() {
|
||||
if (this.statusDiv) {
|
||||
this.statusDiv.remove();
|
||||
}
|
||||
document.querySelectorAll(`.livesync-status`)?.forEach(e => e.remove());
|
||||
}
|
||||
async setFileStatus() {
|
||||
this.messageArea!.innerText = await this.plugin.getActiveFileStatus();
|
||||
}
|
||||
onActiveLeafChange() {
|
||||
this.adjustStatusDivPosition();
|
||||
this.setFileStatus();
|
||||
|
||||
}
|
||||
onload(): void | Promise<void> {
|
||||
eventHub.onEvent(EVENT_FILE_RENAMED, (evt: CustomEvent<{ oldPath: string, newPath: string }>) => {
|
||||
this.setFileStatus();
|
||||
});
|
||||
eventHub.onEvent(EVENT_LEAF_ACTIVE_CHANGED, () => this.onActiveLeafChange());
|
||||
const w = document.querySelectorAll(`.livesync-status`);
|
||||
w.forEach(e => e.remove());
|
||||
|
||||
this.observeForLogs();
|
||||
this.adjustStatusDivPosition();
|
||||
this.statusDiv = this.app.workspace.containerEl.createDiv({ cls: "livesync-status" });
|
||||
this.statusLine = this.statusDiv.createDiv({ cls: "livesync-status-statusline" });
|
||||
this.messageArea = this.statusDiv.createDiv({ cls: "livesync-status-messagearea" });
|
||||
this.logMessage = this.statusDiv.createDiv({ cls: "livesync-status-logmessage" });
|
||||
this.logHistory = this.statusDiv.createDiv({ cls: "livesync-status-loghistory" });
|
||||
eventHub.onEvent(EVENT_LAYOUT_READY, () => this.adjustStatusDivPosition());
|
||||
if (this.settings.showStatusOnStatusbar) {
|
||||
this.statusBar = this.plugin.addStatusBarItem();
|
||||
this.statusBar.addClass("syncstatusbar");
|
||||
}
|
||||
}
|
||||
nextFrameQueue: ReturnType<typeof requestAnimationFrame> | undefined = undefined;
|
||||
logLines: { ttl: number, message: string }[] = [];
|
||||
|
||||
applyStatusBarText() {
|
||||
if (this.nextFrameQueue) {
|
||||
return;
|
||||
}
|
||||
this.nextFrameQueue = requestAnimationFrame(() => {
|
||||
this.nextFrameQueue = undefined;
|
||||
const { message, status } = this.statusBarLabels.value;
|
||||
// const recent = logMessages.value;
|
||||
const newMsg = message;
|
||||
const newLog = this.settings.showOnlyIconsOnEditor ? "" : status;
|
||||
|
||||
this.statusBar?.setText(newMsg.split("\n")[0]);
|
||||
if (this.settings.showStatusOnEditor && this.statusDiv) {
|
||||
// const root = activeDocument.documentElement;
|
||||
// root.style.setProperty("--sls-log-text", "'" + (newMsg + "\\A " + newLog) + "'");
|
||||
// this.statusDiv.innerText = newMsg + "\\A " + newLog;
|
||||
if (this.settings.showLongerLogInsideEditor) {
|
||||
const now = new Date().getTime();
|
||||
this.logLines = this.logLines.filter(e => e.ttl > now);
|
||||
const minimumNext = this.logLines.reduce((a, b) => a < b.ttl ? a : b.ttl, Number.MAX_SAFE_INTEGER);
|
||||
if (this.logLines.length > 0) setTimeout(() => this.applyStatusBarText(), minimumNext - now);
|
||||
const recent = this.logLines.map(e => e.message);
|
||||
const recentLogs = recent.reverse().join("\n");
|
||||
if (isDirty("recentLogs", recentLogs)) this.logHistory!.innerText = recentLogs;
|
||||
}
|
||||
if (isDirty("newMsg", newMsg)) this.statusLine!.innerText = newMsg;
|
||||
if (isDirty("newLog", newLog)) this.logMessage!.innerText = newLog;
|
||||
} else {
|
||||
// const root = activeDocument.documentElement;
|
||||
// root.style.setProperty("--log-text", "'" + (newMsg + "\\A " + newLog) + "'");
|
||||
}
|
||||
});
|
||||
|
||||
scheduleTask("log-hide", 3000, () => { this.plugin.statusLog.value = "" });
|
||||
}
|
||||
|
||||
|
||||
onInitializeDatabase(showNotice: boolean) { }
|
||||
beforeReplicate(showNotice: boolean) { }
|
||||
onResume() { }
|
||||
parseReplicationResultItem(docs: PouchDB.Core.ExistingDocument<EntryDoc>): boolean | Promise<boolean> {
|
||||
return false;
|
||||
}
|
||||
async realizeSettingSyncMode() { }
|
||||
|
||||
|
||||
}
|
||||
|
||||
2
src/lib
2
src/lib
Submodule src/lib updated: f0253a8548...3108e3e3db
807
src/main.ts
807
src/main.ts
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@ import { type App, TFile, type DataWriteOptions, TFolder, TAbstractFile } from "
|
||||
import { serialized } from "../lib/src/concurrency/lock.ts";
|
||||
import { Logger } from "../lib/src/common/logger.ts";
|
||||
import { isPlainText } from "../lib/src/string_and_binary/path.ts";
|
||||
import type { FilePath } from "../lib/src/common/types.ts";
|
||||
import type { FilePath, HasSettings } from "../lib/src/common/types.ts";
|
||||
import { createBinaryBlob, isDocContentSame } from "../lib/src/common/utils.ts";
|
||||
import type { InternalFileInfo } from "../common/types.ts";
|
||||
import { markChangesAreSame } from "../common/utils.ts";
|
||||
@@ -31,8 +31,10 @@ async function processWriteFile<T>(file: TFile | TFolder | string, proc: () => P
|
||||
}
|
||||
export class SerializedFileAccess {
|
||||
app: App
|
||||
constructor(app: App) {
|
||||
plugin: HasSettings<{ handleFilenameCaseSensitive: boolean }>
|
||||
constructor(app: App, plugin: typeof this["plugin"]) {
|
||||
this.app = app;
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
async adapterStat(file: TFile | string) {
|
||||
@@ -138,17 +140,23 @@ export class SerializedFileAccess {
|
||||
return await processWriteFile(file, () => this.app.vault.trash(file, force));
|
||||
}
|
||||
|
||||
|
||||
|
||||
isStorageInsensitive(): boolean {
|
||||
//@ts-ignore
|
||||
return this.app.vault.adapter.insensitive ?? true;
|
||||
}
|
||||
|
||||
getAbstractFileByPathInsensitive(path: FilePath | string): TAbstractFile | null {
|
||||
//@ts-ignore
|
||||
return app.vault.getAbstractFileByPathInsensitive(path);
|
||||
}
|
||||
|
||||
getAbstractFileByPath(path: FilePath | string): TAbstractFile | null {
|
||||
// Disabled temporary.
|
||||
if (!this.plugin.settings.handleFilenameCaseSensitive || this.isStorageInsensitive()) {
|
||||
return this.getAbstractFileByPathInsensitive(path);
|
||||
}
|
||||
return this.app.vault.getAbstractFileByPath(path);
|
||||
// // Hidden API but so useful.
|
||||
// // @ts-ignore
|
||||
// if ("getAbstractFileByPathInsensitive" in app.vault && (app.vault.adapter?.insensitive ?? false)) {
|
||||
// // @ts-ignore
|
||||
// return app.vault.getAbstractFileByPathInsensitive(path);
|
||||
// } else {
|
||||
// return app.vault.getAbstractFileByPath(path);
|
||||
// }
|
||||
}
|
||||
|
||||
getFiles() {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
339
src/ui/components/LiveSyncSetting.ts
Normal file
339
src/ui/components/LiveSyncSetting.ts
Normal file
@@ -0,0 +1,339 @@
|
||||
import { Setting, TextComponent, type ToggleComponent, type DropdownComponent, ButtonComponent, type TextAreaComponent, type ValueComponent } from "obsidian";
|
||||
import { unique } from "octagonal-wheels/collection";
|
||||
import { LEVEL_ADVANCED, LEVEL_POWER_USER, statusDisplay, type ConfigurationItem } from "../../lib/src/common/types";
|
||||
import { type ObsidianLiveSyncSettingTab, type AutoWireOption, wrapMemo, type OnUpdateResult, createStub, findAttrFromParent } from "../ObsidianLiveSyncSettingTab";
|
||||
import { type AllSettingItemKey, getConfig, type AllSettings, type AllStringItemKey, type AllNumericItemKey, type AllBooleanItemKey } from "../settingConstants";
|
||||
|
||||
|
||||
export class LiveSyncSetting extends Setting {
|
||||
autoWiredComponent?: TextComponent | ToggleComponent | DropdownComponent | ButtonComponent | TextAreaComponent;
|
||||
applyButtonComponent?: ButtonComponent;
|
||||
selfKey?: AllSettingItemKey;
|
||||
watchDirtyKeys = [] as AllSettingItemKey[];
|
||||
holdValue: boolean = false;
|
||||
static env: ObsidianLiveSyncSettingTab;
|
||||
|
||||
descBuf: string | DocumentFragment = "";
|
||||
nameBuf: string | DocumentFragment = "";
|
||||
placeHolderBuf: string = "";
|
||||
hasPassword: boolean = false;
|
||||
|
||||
invalidateValue?: () => void;
|
||||
setValue?: (value: any) => void;
|
||||
constructor(containerEl: HTMLElement) {
|
||||
super(containerEl);
|
||||
LiveSyncSetting.env.settingComponents.push(this);
|
||||
}
|
||||
|
||||
_createDocStub(key: string, value: string | DocumentFragment) {
|
||||
DEV: {
|
||||
const paneName = findAttrFromParent(this.settingEl, "data-pane");
|
||||
const panelName = findAttrFromParent(this.settingEl, "data-panel");
|
||||
const itemName = typeof this.nameBuf == "string" ? this.nameBuf : this.nameBuf.textContent?.toString() ?? "";
|
||||
const strValue = typeof value == "string" ? value : value.textContent?.toString() ?? "";
|
||||
|
||||
createStub(itemName, key, strValue, panelName, paneName);
|
||||
}
|
||||
}
|
||||
|
||||
setDesc(desc: string | DocumentFragment): this {
|
||||
this.descBuf = desc;
|
||||
DEV: {
|
||||
this._createDocStub("desc", desc);
|
||||
}
|
||||
super.setDesc(desc);
|
||||
return this;
|
||||
}
|
||||
setName(name: string | DocumentFragment): this {
|
||||
this.nameBuf = name;
|
||||
DEV: {
|
||||
this._createDocStub("name", name);
|
||||
}
|
||||
super.setName(name);
|
||||
return this;
|
||||
}
|
||||
setAuto(key: AllSettingItemKey, opt?: AutoWireOption) {
|
||||
this.autoWireSetting(key, opt);
|
||||
return this;
|
||||
}
|
||||
autoWireSetting(key: AllSettingItemKey, opt?: AutoWireOption) {
|
||||
const conf = getConfig(key);
|
||||
if (!conf) {
|
||||
// throw new Error(`No such setting item :${key}`)
|
||||
return;
|
||||
}
|
||||
const name = `${conf.name}${statusDisplay(conf.status)}`;
|
||||
this.setName(name);
|
||||
if (conf.desc) {
|
||||
this.setDesc(conf.desc);
|
||||
}
|
||||
DEV: {
|
||||
this._createDocStub("key", key);
|
||||
if (conf.obsolete) this._createDocStub("is_obsolete", "true");
|
||||
if (conf.level) this._createDocStub("level", conf.level);
|
||||
}
|
||||
|
||||
this.holdValue = opt?.holdValue || this.holdValue;
|
||||
this.selfKey = key;
|
||||
if (conf.obsolete || opt?.obsolete) {
|
||||
this.settingEl.toggleClass("sls-setting-obsolete", true);
|
||||
}
|
||||
if (opt?.onUpdate) this.addOnUpdate(opt.onUpdate);
|
||||
const stat = this._getComputedStatus();
|
||||
if (stat.visibility === false) {
|
||||
this.settingEl.toggleClass("sls-setting-hidden", !stat.visibility);
|
||||
}
|
||||
return conf;
|
||||
}
|
||||
autoWireComponent(component: ValueComponent<any>, conf?: ConfigurationItem, opt?: AutoWireOption) {
|
||||
this.placeHolderBuf = conf?.placeHolder || opt?.placeHolder || "";
|
||||
if (conf?.level == LEVEL_ADVANCED) {
|
||||
this.settingEl.toggleClass("sls-setting-advanced", true);
|
||||
} else if (conf?.level == LEVEL_POWER_USER) {
|
||||
this.settingEl.toggleClass("sls-setting-poweruser", true);
|
||||
}
|
||||
if (this.placeHolderBuf && component instanceof TextComponent) {
|
||||
component.setPlaceholder(this.placeHolderBuf);
|
||||
}
|
||||
if (opt?.onUpdate) this.addOnUpdate(opt.onUpdate);
|
||||
}
|
||||
async commitValue<T extends AllSettingItemKey>(value: AllSettings[T]) {
|
||||
const key = this.selfKey as T;
|
||||
if (key !== undefined) {
|
||||
if (value != LiveSyncSetting.env.editingSettings[key]) {
|
||||
LiveSyncSetting.env.editingSettings[key] = value;
|
||||
if (!this.holdValue) {
|
||||
await LiveSyncSetting.env.saveSettings([key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
LiveSyncSetting.env.requestUpdate();
|
||||
}
|
||||
autoWireText(key: AllStringItemKey, opt?: AutoWireOption) {
|
||||
const conf = this.autoWireSetting(key, opt);
|
||||
this.addText(text => {
|
||||
this.autoWiredComponent = text;
|
||||
const setValue = wrapMemo((value: string) => text.setValue(value));
|
||||
this.invalidateValue = () => setValue(`${LiveSyncSetting.env.editingSettings[key]}`);
|
||||
this.invalidateValue();
|
||||
text.onChange(async (value) => {
|
||||
await this.commitValue(value);
|
||||
});
|
||||
if (opt?.isPassword) {
|
||||
text.inputEl.setAttribute("type", "password");
|
||||
this.hasPassword = true;
|
||||
}
|
||||
this.autoWireComponent(this.autoWiredComponent, conf, opt);
|
||||
});
|
||||
return this;
|
||||
}
|
||||
autoWireTextArea(key: AllStringItemKey, opt?: AutoWireOption) {
|
||||
const conf = this.autoWireSetting(key, opt);
|
||||
this.addTextArea(text => {
|
||||
this.autoWiredComponent = text;
|
||||
const setValue = wrapMemo((value: string) => text.setValue(value));
|
||||
this.invalidateValue = () => setValue(`${LiveSyncSetting.env.editingSettings[key]}`);
|
||||
this.invalidateValue();
|
||||
text.onChange(async (value) => {
|
||||
await this.commitValue(value);
|
||||
});
|
||||
if (opt?.isPassword) {
|
||||
text.inputEl.setAttribute("type", "password");
|
||||
this.hasPassword = true;
|
||||
}
|
||||
this.autoWireComponent(this.autoWiredComponent, conf, opt);
|
||||
});
|
||||
return this;
|
||||
}
|
||||
autoWireNumeric(key: AllNumericItemKey, opt: AutoWireOption & { clampMin?: number; clampMax?: number; acceptZero?: boolean; }) {
|
||||
const conf = this.autoWireSetting(key, opt);
|
||||
this.addText(text => {
|
||||
this.autoWiredComponent = text;
|
||||
if (opt.clampMin) {
|
||||
text.inputEl.setAttribute("min", `${opt.clampMin}`);
|
||||
}
|
||||
if (opt.clampMax) {
|
||||
text.inputEl.setAttribute("max", `${opt.clampMax}`);
|
||||
}
|
||||
let lastError = false;
|
||||
const setValue = wrapMemo((value: string) => text.setValue(value));
|
||||
this.invalidateValue = () => {
|
||||
if (!lastError) setValue(`${LiveSyncSetting.env.editingSettings[key]}`);
|
||||
};
|
||||
this.invalidateValue();
|
||||
text.onChange(async (TextValue) => {
|
||||
const parsedValue = Number(TextValue);
|
||||
const value = parsedValue;
|
||||
let hasError = false;
|
||||
if (isNaN(value)) hasError = true;
|
||||
if (opt.clampMax && opt.clampMax < value) hasError = true;
|
||||
if (opt.clampMin && opt.clampMin > value) {
|
||||
if (opt.acceptZero && value == 0) {
|
||||
// This is ok.
|
||||
} else {
|
||||
hasError = true;
|
||||
}
|
||||
}
|
||||
if (!hasError) {
|
||||
lastError = false;
|
||||
this.setTooltip(``);
|
||||
text.inputEl.toggleClass("sls-item-invalid-value", false);
|
||||
await this.commitValue(value);
|
||||
} else {
|
||||
this.setTooltip(`The value should ${opt.clampMin || "~"} < value < ${opt.clampMax || "~"}`);
|
||||
text.inputEl.toggleClass("sls-item-invalid-value", true);
|
||||
lastError = true;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
text.inputEl.setAttr("type", "number");
|
||||
this.autoWireComponent(this.autoWiredComponent, conf, opt);
|
||||
});
|
||||
return this;
|
||||
}
|
||||
autoWireToggle(key: AllBooleanItemKey, opt?: AutoWireOption) {
|
||||
const conf = this.autoWireSetting(key, opt);
|
||||
this.addToggle(toggle => {
|
||||
this.autoWiredComponent = toggle;
|
||||
const setValue = wrapMemo((value: boolean) => toggle.setValue(opt?.invert ? !value : value));
|
||||
this.invalidateValue = () => setValue(LiveSyncSetting.env.editingSettings[key] ?? false);
|
||||
this.invalidateValue();
|
||||
|
||||
toggle.onChange(async (value) => {
|
||||
await this.commitValue(opt?.invert ? !value : value);
|
||||
});
|
||||
|
||||
this.autoWireComponent(this.autoWiredComponent, conf, opt);
|
||||
});
|
||||
return this;
|
||||
}
|
||||
autoWireDropDown<T extends string>(key: AllStringItemKey, opt: AutoWireOption & { options: Record<T, string>; }) {
|
||||
const conf = this.autoWireSetting(key, opt);
|
||||
this.addDropdown(dropdown => {
|
||||
this.autoWiredComponent = dropdown;
|
||||
const setValue = wrapMemo((value: string) => {
|
||||
dropdown.setValue(value);
|
||||
});
|
||||
|
||||
dropdown
|
||||
.addOptions(opt.options);
|
||||
|
||||
this.invalidateValue = () => setValue(LiveSyncSetting.env.editingSettings[key] || "");
|
||||
this.invalidateValue();
|
||||
dropdown.onChange(async (value) => {
|
||||
await this.commitValue(value);
|
||||
});
|
||||
this.autoWireComponent(this.autoWiredComponent, conf, opt);
|
||||
});
|
||||
return this;
|
||||
}
|
||||
addApplyButton(keys: AllSettingItemKey[], text?: string) {
|
||||
this.addButton((button) => {
|
||||
this.applyButtonComponent = button;
|
||||
this.watchDirtyKeys = unique([...keys, ...this.watchDirtyKeys]);
|
||||
button.setButtonText(text ?? "Apply");
|
||||
button.onClick(async () => {
|
||||
await LiveSyncSetting.env.saveSettings(keys);
|
||||
LiveSyncSetting.env.reloadAllSettings();
|
||||
});
|
||||
LiveSyncSetting.env.requestUpdate();
|
||||
});
|
||||
return this;
|
||||
}
|
||||
addOnUpdate(func: () => OnUpdateResult) {
|
||||
this.updateHandlers.add(func);
|
||||
// this._applyOnUpdateHandlers();
|
||||
return this;
|
||||
}
|
||||
updateHandlers = new Set<() => OnUpdateResult>();
|
||||
|
||||
prevStatus: OnUpdateResult = {};
|
||||
|
||||
_getComputedStatus() {
|
||||
let newConf = {} as OnUpdateResult;
|
||||
for (const handler of this.updateHandlers) {
|
||||
newConf = {
|
||||
...newConf,
|
||||
...handler(),
|
||||
};
|
||||
}
|
||||
return newConf;
|
||||
}
|
||||
_applyOnUpdateHandlers() {
|
||||
if (this.updateHandlers.size > 0) {
|
||||
const newConf = this._getComputedStatus();
|
||||
const keys = Object.keys(newConf) as [keyof OnUpdateResult];
|
||||
for (const k of keys) {
|
||||
|
||||
if (k in this.prevStatus && this.prevStatus[k] == newConf[k]) {
|
||||
continue;
|
||||
}
|
||||
// const newValue = newConf[k];
|
||||
switch (k) {
|
||||
case "visibility":
|
||||
this.settingEl.toggleClass("sls-setting-hidden", !(newConf[k] || false));
|
||||
this.prevStatus[k] = newConf[k];
|
||||
break;
|
||||
case "classes":
|
||||
break;
|
||||
case "disabled":
|
||||
this.setDisabled((newConf[k] || false));
|
||||
this.settingEl.toggleClass("sls-setting-disabled", (newConf[k] || false));
|
||||
this.prevStatus[k] = newConf[k];
|
||||
break;
|
||||
case "isCta":
|
||||
{
|
||||
const component = this.autoWiredComponent;
|
||||
if (component instanceof ButtonComponent) {
|
||||
if (newConf[k]) {
|
||||
component.setCta();
|
||||
} else {
|
||||
component.removeCta();
|
||||
}
|
||||
}
|
||||
this.prevStatus[k] = newConf[k];
|
||||
}
|
||||
break;
|
||||
case "isWarning":
|
||||
{
|
||||
const component = this.autoWiredComponent;
|
||||
if (component instanceof ButtonComponent) {
|
||||
if (newConf[k]) {
|
||||
component.setWarning();
|
||||
} else {
|
||||
//TODO:IMPLEMENT
|
||||
// component.removeCta();
|
||||
}
|
||||
}
|
||||
this.prevStatus[k] = newConf[k];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_onUpdate() {
|
||||
if (this.applyButtonComponent) {
|
||||
const isDirty = LiveSyncSetting.env.isSomeDirty(this.watchDirtyKeys);
|
||||
this.applyButtonComponent.setDisabled(!isDirty);
|
||||
if (isDirty) {
|
||||
this.applyButtonComponent.setCta();
|
||||
} else {
|
||||
this.applyButtonComponent.removeCta();
|
||||
}
|
||||
}
|
||||
if (this.selfKey && !LiveSyncSetting.env.isDirty(this.selfKey) && this.invalidateValue) {
|
||||
this.invalidateValue();
|
||||
}
|
||||
if (this.holdValue && this.selfKey) {
|
||||
const isDirty = LiveSyncSetting.env.isDirty(this.selfKey);
|
||||
const alt = isDirty ? `Original: ${LiveSyncSetting.env.initialSettings![this.selfKey]}` : "";
|
||||
this.controlEl.toggleClass("sls-item-dirty", isDirty);
|
||||
if (!this.hasPassword) {
|
||||
this.nameEl.toggleClass("sls-item-dirty-help", isDirty);
|
||||
this.setTooltip(alt, { delay: 10, placement: "right" });
|
||||
}
|
||||
}
|
||||
this._applyOnUpdateHandlers();
|
||||
}
|
||||
}
|
||||
@@ -53,7 +53,7 @@
|
||||
canApply = true;
|
||||
} else {
|
||||
const dtDiff = (local?.mtime ?? 0) - (remote?.mtime ?? 0);
|
||||
const diff = timeDeltaToHumanReadable(Math.abs(dtDiff / 1000));
|
||||
const diff = timeDeltaToHumanReadable(Math.abs(dtDiff));
|
||||
if (dtDiff / 1000 < -10) {
|
||||
// freshness = "✓ Newer";
|
||||
freshness = `Newer (${diff})`;
|
||||
|
||||
@@ -198,8 +198,9 @@ export const SettingInformation: Partial<Record<keyof AllSettings, Configuration
|
||||
"name": "Do not keep metadata of deleted files."
|
||||
},
|
||||
"useIndexedDBAdapter": {
|
||||
"name": "Use an old adapter for compatibility",
|
||||
"desc": "Before v0.17.16, we used an old adapter for the local database. Now the new adapter is preferred. However, it needs local database rebuilding. Please disable this toggle when you have enough time. If leave it enabled, also while fetching from the remote database, you will be asked to disable this."
|
||||
"name": "(Obsolete) Use an old adapter for compatibility",
|
||||
"desc": "Before v0.17.16, we used an old adapter for the local database. Now the new adapter is preferred. However, it needs local database rebuilding. Please disable this toggle when you have enough time. If leave it enabled, also while fetching from the remote database, you will be asked to disable this.",
|
||||
"obsolete": true
|
||||
},
|
||||
"watchInternalFileChanges": {
|
||||
"name": "Scan changes on customization sync",
|
||||
@@ -329,6 +330,30 @@ export const SettingInformation: Partial<Record<keyof AllSettings, Configuration
|
||||
name: "Enable per-file customization sync",
|
||||
desc: "If enabled, efficient per-file customization sync will be used. A minor migration is required when enabling this feature, and all devices must be updated to v0.23.18. Enabling this feature will result in losing compatibility with older versions."
|
||||
}
|
||||
"handleFilenameCaseSensitive": {
|
||||
name: "Handle files as Case-Sensitive",
|
||||
desc: "If this enabled, All files are handled as case-Sensitive (Previous behaviour)."
|
||||
},
|
||||
"doNotUseFixedRevisionForChunks": {
|
||||
name: "Compute revisions for chunks (Previous behaviour)",
|
||||
desc: "If this enabled, all chunks will be stored with the revision made from its content. (Previous behaviour)"
|
||||
},
|
||||
"sendChunksBulkMaxSize": {
|
||||
name: "Maximum size of chunks to send in one request",
|
||||
desc: "MB"
|
||||
},
|
||||
"useAdvancedMode": {
|
||||
name: "Enable advanced features",
|
||||
// desc: "Enable advanced mode"
|
||||
},
|
||||
usePowerUserMode: {
|
||||
name: "Enable power user features",
|
||||
// desc: "Enable power user mode",
|
||||
// level: LEVEL_ADVANCED
|
||||
},
|
||||
useEdgeCaseMode: {
|
||||
name: "Enable edge case treatment features",
|
||||
},
|
||||
}
|
||||
function translateInfo(infoSrc: ConfigurationItem | undefined | false) {
|
||||
if (!infoSrc) return false;
|
||||
|
||||
Reference in New Issue
Block a user