mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2025-03-03 15:32:25 +02:00
Add translation ids
This commit is contained in:
parent
f2b667d75e
commit
73782c5389
93
README_es.md
Normal file
93
README_es.md
Normal file
@ -0,0 +1,93 @@
|
||||
<!-- For translation: 20240227r0 -->
|
||||
# Self-hosted LiveSync
|
||||
[Documentación en inglés](./README_ja.md) - [Documentación en japonés](./README_ja.md) - [Documentación en chino](./README_cn.md).
|
||||
|
||||
Self-hosted LiveSync es un plugin de sincronización implementado por la comunidad, disponible en todas las plataformas compatibles con Obsidian y utiliza CouchDB o Almacenamiento de Objetos (por ejemplo, MinIO, S3, R2, etc.) como servidor.
|
||||
|
||||

|
||||
|
||||
Nota: Este plugin no puede sincronizarse con el "Obsidian Sync" oficial.
|
||||
|
||||
## Características
|
||||
|
||||
- Sincroniza bóvedas de manera eficiente con menos tráfico.
|
||||
- Buen manejo de modificaciones en conflicto.
|
||||
- Fusión automática para conflictos simples.
|
||||
- Uso de soluciones de código abierto para el servidor.
|
||||
- Pueden usarse soluciones compatibles.
|
||||
- Soporte de cifrado de extremo a extremo.
|
||||
- Sincronización de configuraciones, fragmentos, temas y complementos a través de [Sincronización de personalización \(Beta\)](#customization-sync) o [Sincronización de archivos ocultos](#hiddenfilesync)
|
||||
- WebClip de [obsidian-livesync-webclip](https://chrome.google.com/webstore/detail/obsidian-livesync-webclip/jfpaflmpckblieefkegjncjoceapakdf)
|
||||
|
||||
Este plugin puede ser útil para investigadores, ingenieros y desarrolladores que necesitan mantener sus notas totalmente autoalojadas por razones de seguridad, o para aquellos que deseen tener la tranquilidad de saber que sus notas son totalmente privadas.
|
||||
|
||||
>[!IMPORTANTE]
|
||||
> - Antes de instalar o actualizar este plugin, realice un respaldo de su bóveda.
|
||||
> - No active este plugin junto con otra solución de sincronización al mismo tiempo (incluyendo iCloud y Obsidian Sync).
|
||||
> - Este es un plugin de sincronización, no una solución de respaldo. No confíe en él para realizar respaldos.
|
||||
|
||||
## Cómo usar
|
||||
|
||||
### Configuración en 3 minutos - CouchDB en fly.io
|
||||
|
||||
**Recomendado para principiantes**
|
||||
|
||||
[](https://www.youtube.com/watch?v=7sa_I1832Xc)
|
||||
|
||||
1. [Configurar CouchDB en fly.io](docs/setup_flyio_es.md)
|
||||
2. Configurar el plugin en [Configuración rápida](docs/quick_setup_es.md)
|
||||
|
||||
### Configuración manual
|
||||
|
||||
1. Configurar el servidor
|
||||
1. [Configurar CouchDB en fly.io](docs/setup_flyio_es.md)
|
||||
2. [Configurar su CouchDB](docs/setup_own_server_es.md)
|
||||
2. Configura el plugin en [Configuración rápida](docs/quick_setup_es.md)
|
||||
|
||||
> [!CONSEJO]
|
||||
> Actualmente, fly.io ya no es gratuito. Afortunadamente, aunque hay algunos problemas, aún podemos usar IBM Cloudant. Aquí está como [Configurar IBM Cloudant](docs/setup_cloudant.md). ¡Se actualizará pronto!
|
||||
|
||||
|
||||
## Información en la barra de estado
|
||||
|
||||
El estado de sincronización se muestra en la barra de estado con los siguientes iconos.
|
||||
|
||||
- Indicador de actividad
|
||||
- 📲 Solicitud de red
|
||||
- Estado
|
||||
- ⏹️ Detenido
|
||||
- 💤 LiveSync activado. Esperando cambios
|
||||
- ⚡️ Sincronización en progreso
|
||||
- ⚠ Ocurrió un error
|
||||
- Indicador estadístico
|
||||
- ↑ Chunks y metadatos subidos
|
||||
- ↓ Chunks y metadatos descargados
|
||||
- Indicador de progreso
|
||||
- 📥 Elementos transferidos sin procesar
|
||||
- 📄 Operación de base de datos en curso
|
||||
- 💾 Procesos de escritura en almacenamiento en curso
|
||||
- ⏳ Procesos de lectura en almacenamiento en curso
|
||||
- 🛫 Procesos de lectura en almacenamiento pendientes
|
||||
- 📬 Procesos de lectura en almacenamiento por lotes
|
||||
- ⚙️ Procesos de almacenamiento de archivos ocultos en curso o pendientes
|
||||
- 🧩 Chunks en espera
|
||||
- 🔌 Elementos de personalización en curso (Configuración, fragmentos y plugins)
|
||||
|
||||
Para prevenir la corrupción de archivos y bases de datos, antes de detener Obsidian espere hasta que todos los indicadores de progreso hayan desaparecido (el plugin también intentará reanudar, sin embargo). Especialmente en caso de que haya eliminado o renombrado archivos.
|
||||
|
||||
|
||||
## Consejos y Solución de Problemas
|
||||
Si tienes problemas para hacer funcionar el plugin, consulta: [Consejos y solución de problemas](docs/troubleshooting_es.md).
|
||||
|
||||
## Agradecimientos
|
||||
|
||||
El proyecto ha progresado y mantenido en armonía gracias a:
|
||||
- Muchos [Colaboradores](https://github.com/vrtmrz/obsidian-livesync/graphs/contributors)
|
||||
- Muchos [Patrocinadores de GitHub](https://github.com/sponsors/vrtmrz#sponsors)
|
||||
- Programas comunitarios de JetBrains / Soporte para Proyectos de Código Abierto <img src="https://resources.jetbrains.com/storage/products/company/brand/logos/jetbrains.png" alt="JetBrains logo." height="24">
|
||||
|
||||
Que aquellos que han contribuido sean honrados y recordados por su amabilidad y generosidad.
|
||||
|
||||
## Licencia
|
||||
|
||||
Licenciado bajo la Licencia MIT.
|
@ -25,10 +25,10 @@ npm run buildDev
|
||||
## Make messages to be translated
|
||||
|
||||
1. Find the message that you want to be translated.
|
||||
2. Change the literal to a tagged template literal using `$f`, like below.
|
||||
2. Change the literal to use `$tf`, like below.
|
||||
```diff
|
||||
- Logger("Could not determine passphrase to save data.json! You probably make the configuration sure again!", LOG_LEVEL_URGENT);
|
||||
+ Logger($f`Could not determine passphrase to save data.json! You probably make the configuration sure again!`, LOG_LEVEL_URGENT);
|
||||
+ Logger($tf('someKeyForPassphraseError'), LOG_LEVEL_URGENT);
|
||||
```
|
||||
3. Make the PR to `https://github.com/vrtmrz/obsidian-livesync`.
|
||||
4. Follow the steps of "Add translations for already defined terms" to add the translations.
|
@ -71,6 +71,7 @@ import { Semaphore } from "octagonal-wheels/concurrency/semaphore";
|
||||
import type { IObsidianModule } from "../../modules/AbstractObsidianModule.ts";
|
||||
import { EVENT_REQUEST_OPEN_PLUGIN_SYNC_DIALOG, eventHub } from "../../common/events.ts";
|
||||
import { PluginDialogModal } from "./PluginDialogModal.ts";
|
||||
import { $tf } from "src/lib/src/common/i18n.ts";
|
||||
|
||||
const d = "\u200b";
|
||||
const d2 = "\n";
|
||||
@ -446,7 +447,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
||||
this.showPluginSyncModal();
|
||||
},
|
||||
});
|
||||
this.addRibbonIcon("custom-sync", "Show Customization sync", () => {
|
||||
this.addRibbonIcon("custom-sync", $tf("cmdConfigSync.showCustomizationSync"), () => {
|
||||
this.showPluginSyncModal();
|
||||
}).addClass("livesync-ribbon-showcustom");
|
||||
eventHub.onEvent(EVENT_REQUEST_OPEN_PLUGIN_SYNC_DIALOG, () => this.showPluginSyncModal());
|
||||
|
@ -211,7 +211,7 @@ export class ModuleFileHandler extends AbstractModule implements ICoreModule {
|
||||
? await this.db.fetchEntryMeta(entryInfo, undefined, true)
|
||||
: await this.db.fetchEntryMeta(entryInfo.path, undefined, true);
|
||||
if (!docEntry) {
|
||||
this._log(`File ${file?.path} is not exist on the database`, LOG_LEVEL_VERBOSE);
|
||||
this._log(`File ${entryInfo} is not exist on the database`, LOG_LEVEL_VERBOSE);
|
||||
return false;
|
||||
}
|
||||
const path = getPath(docEntry);
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { $f } from "../../lib/src/common/i18n";
|
||||
import { $tf } from "../../lib/src/common/i18n";
|
||||
import { LiveSyncLocalDB } from "../../lib/src/pouchdb/LiveSyncLocalDB.ts";
|
||||
import { initializeStores } from "../../common/stores.ts";
|
||||
import { AbstractModule } from "../AbstractModule.ts";
|
||||
@ -13,7 +13,7 @@ export class ModuleLocalDatabaseObsidian extends AbstractModule implements ICore
|
||||
await this.localDatabase.close();
|
||||
}
|
||||
const vaultName = this.core.$$getVaultName();
|
||||
this._log($f`Waiting for ready...`);
|
||||
this._log($tf("moduleLocalDatabase.logWaitingForReady"));
|
||||
this.core.localDatabase = new LiveSyncLocalDB(vaultName, this.core);
|
||||
initializeStores(vaultName);
|
||||
return await this.localDatabase.initializeDatabase();
|
||||
|
@ -2,34 +2,24 @@ import { LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "octagonal-w
|
||||
import { AbstractModule } from "../AbstractModule.ts";
|
||||
import { sizeToHumanReadable } from "octagonal-wheels/number";
|
||||
import type { ICoreModule } from "../ModuleTypes.ts";
|
||||
import { $tf } from "src/lib/src/common/i18n.ts";
|
||||
|
||||
export class ModuleCheckRemoteSize extends AbstractModule implements ICoreModule {
|
||||
async $allScanStat(): Promise<boolean> {
|
||||
this._log(`Checking storage sizes`, LOG_LEVEL_VERBOSE);
|
||||
this._log($tf("moduleCheckRemoteSize.logCheckingStorageSizes"), LOG_LEVEL_VERBOSE);
|
||||
if (this.settings.notifyThresholdOfRemoteStorageSize < 0) {
|
||||
const message = `We can set a maximum database capacity warning, **to take action before running out of space on the remote storage**.
|
||||
Do you want to enable this?
|
||||
|
||||
> [!MORE]-
|
||||
> - 0: Do not warn about storage size.
|
||||
> This is recommended if you have enough space on the remote storage especially you have self-hosted. And you can check the storage size and rebuild manually.
|
||||
> - 800: Warn if the remote storage size exceeds 800MB.
|
||||
> This is recommended if you are using fly.io with 1GB limit or IBM Cloudant.
|
||||
> - 2000: Warn if the remote storage size exceeds 2GB.
|
||||
|
||||
If we have reached the limit, we will be asked to enlarge the limit step by step.
|
||||
`;
|
||||
const ANSWER_0 = "No, never warn please";
|
||||
const ANSWER_800 = "800MB (Cloudant, fly.io)";
|
||||
const ANSWER_2000 = "2GB (Standard)";
|
||||
const ASK_ME_NEXT_TIME = "Ask me later";
|
||||
const message = $tf("moduleCheckRemoteSize.msgSetDBCapacity");
|
||||
const ANSWER_0 = $tf("moduleCheckRemoteSize.optionNoWarn");
|
||||
const ANSWER_800 = $tf("moduleCheckRemoteSize.option800MB");
|
||||
const ANSWER_2000 = $tf("moduleCheckRemoteSize.option2GB");
|
||||
const ASK_ME_NEXT_TIME = $tf("moduleCheckRemoteSize.optionAskMeLater");
|
||||
|
||||
const ret = await this.core.confirm.askSelectStringDialogue(
|
||||
message,
|
||||
[ANSWER_0, ANSWER_800, ANSWER_2000, ASK_ME_NEXT_TIME],
|
||||
{
|
||||
defaultAction: ASK_ME_NEXT_TIME,
|
||||
title: "Setting up database size notification",
|
||||
title: $tf("moduleCheckRemoteSize.titleDatabaseSizeNotify"),
|
||||
timeout: 40,
|
||||
}
|
||||
);
|
||||
@ -51,40 +41,28 @@ If we have reached the limit, we will be asked to enlarge the limit step by step
|
||||
if (estimatedSize) {
|
||||
const maxSize = this.settings.notifyThresholdOfRemoteStorageSize * 1024 * 1024;
|
||||
if (estimatedSize > maxSize) {
|
||||
const message = `**Your database is getting larger!** But do not worry, we can address it now. The time before running out of space on the remote storage.
|
||||
|
||||
| Measured size | Configured size |
|
||||
| --- | --- |
|
||||
| ${sizeToHumanReadable(estimatedSize)} | ${sizeToHumanReadable(maxSize)} |
|
||||
|
||||
> [!MORE]-
|
||||
> If you have been using it for many years, there may be unreferenced chunks - that is, garbage - accumulating in the database. Therefore, we recommend rebuilding everything. It will probably become much smaller.
|
||||
>
|
||||
> If the volume of your vault is simply increasing, it is better to rebuild everything after organizing the files. Self-hosted LiveSync does not delete the actual data even if you delete it to speed up the process. It is roughly [documented](https://github.com/vrtmrz/obsidian-livesync/blob/main/docs/tech_info.md).
|
||||
>
|
||||
> If you don't mind the increase, you can increase the notification limit by 100MB. This is the case if you are running it on your own server. However, it is better to rebuild everything from time to time.
|
||||
>
|
||||
|
||||
> [!WARNING]
|
||||
> If you perform rebuild everything, make sure all devices are synchronised. The plug-in will merge as much as possible, though.
|
||||
|
||||
\n`;
|
||||
const message = $tf("moduleCheckRemoteSize.msgDatabaseGrowing", {
|
||||
estimatedSize: sizeToHumanReadable(estimatedSize),
|
||||
maxSize: sizeToHumanReadable(maxSize),
|
||||
});
|
||||
const newMax = ~~(estimatedSize / 1024 / 1024) + 100;
|
||||
const ANSWER_ENLARGE_LIMIT = `increase to ${newMax}MB`;
|
||||
const ANSWER_REBUILD = "Rebuild Everything Now";
|
||||
const ANSWER_IGNORE = "Dismiss";
|
||||
const ANSWER_ENLARGE_LIMIT = $tf("moduleCheckRemoteSize.optionIncreaseLimit", {
|
||||
newMax: newMax.toString(),
|
||||
});
|
||||
const ANSWER_REBUILD = $tf("moduleCheckRemoteSize.optionRebuildAll");
|
||||
const ANSWER_IGNORE = $tf("moduleCheckRemoteSize.optionDismiss");
|
||||
const ret = await this.core.confirm.askSelectStringDialogue(
|
||||
message,
|
||||
[ANSWER_ENLARGE_LIMIT, ANSWER_REBUILD, ANSWER_IGNORE],
|
||||
{
|
||||
defaultAction: ANSWER_IGNORE,
|
||||
title: "Remote storage size exceeded the limit",
|
||||
title: $tf("moduleCheckRemoteSize.titleDatabaseSizeLimitExceeded"),
|
||||
timeout: 60,
|
||||
}
|
||||
);
|
||||
if (ret == ANSWER_REBUILD) {
|
||||
const ret = await this.core.confirm.askYesNoDialog(
|
||||
"This may take a bit of a long time. Do you really want to rebuild everything now?",
|
||||
$tf("moduleCheckRemoteSize.msgConfirmRebuild"),
|
||||
{ defaultOption: "No" }
|
||||
);
|
||||
if (ret == "yes") {
|
||||
@ -95,7 +73,9 @@ If we have reached the limit, we will be asked to enlarge the limit step by step
|
||||
} else if (ret == ANSWER_ENLARGE_LIMIT) {
|
||||
this.settings.notifyThresholdOfRemoteStorageSize = ~~(estimatedSize / 1024 / 1024) + 100;
|
||||
this._log(
|
||||
`Threshold has been enlarged to ${this.settings.notifyThresholdOfRemoteStorageSize}MB`,
|
||||
$tf("moduleCheckRemoteSize.logThresholdEnlarged", {
|
||||
size: this.settings.notifyThresholdOfRemoteStorageSize.toString(),
|
||||
}),
|
||||
LOG_LEVEL_NOTICE
|
||||
);
|
||||
await this.core.saveSettings();
|
||||
@ -104,11 +84,18 @@ If we have reached the limit, we will be asked to enlarge the limit step by step
|
||||
}
|
||||
|
||||
this._log(
|
||||
`Remote storage size: ${sizeToHumanReadable(estimatedSize)} exceeded ${sizeToHumanReadable(this.settings.notifyThresholdOfRemoteStorageSize * 1024 * 1024)} `,
|
||||
$tf("moduleCheckRemoteSize.logExceededWarning", {
|
||||
measuredSize: sizeToHumanReadable(estimatedSize),
|
||||
notifySize: sizeToHumanReadable(
|
||||
this.settings.notifyThresholdOfRemoteStorageSize * 1024 * 1024
|
||||
),
|
||||
}),
|
||||
LOG_LEVEL_INFO
|
||||
);
|
||||
} else {
|
||||
this._log(`Remote storage size: ${sizeToHumanReadable(estimatedSize)}`, LOG_LEVEL_INFO);
|
||||
this._log($tf("moduleCheckRemoteSize.logCurrentStorageSize", {
|
||||
measuredSize: sizeToHumanReadable(estimatedSize),
|
||||
}), LOG_LEVEL_INFO);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
// ModuleInputUIObsidian.ts
|
||||
import { AbstractObsidianModule, type IObsidianModule } from "../AbstractObsidianModule.ts";
|
||||
import { scheduleTask } from "octagonal-wheels/concurrency/task";
|
||||
import { disposeMemoObject, memoIfNotExist, memoObject, retrieveMemoObject } from "../../common/utils.ts";
|
||||
@ -10,6 +11,7 @@ import {
|
||||
} from "./UILib/dialogs.ts";
|
||||
import { Notice } from "../../deps.ts";
|
||||
import type { Confirm } from "../interfaces/Confirm.ts";
|
||||
import { $tf } from "src/lib/src/common/i18n.ts";
|
||||
|
||||
// This module cannot be a common module because it depends on Obsidian's API.
|
||||
// However, we have to make compatible one for other platform.
|
||||
@ -23,23 +25,28 @@ export class ModuleInputUIObsidian extends AbstractObsidianModule implements IOb
|
||||
askYesNo(message: string): Promise<"yes" | "no"> {
|
||||
return askYesNo(this.app, message);
|
||||
}
|
||||
|
||||
askString(title: string, key: string, placeholder: string, isPassword: boolean = false): Promise<string | false> {
|
||||
return askString(this.app, title, key, placeholder, isPassword);
|
||||
}
|
||||
|
||||
async askYesNoDialog(
|
||||
message: string,
|
||||
opt: { title?: string; defaultOption?: "Yes" | "No"; timeout?: number } = { title: "Confirmation" }
|
||||
opt: { title?: string; defaultOption?: "Yes" | "No"; timeout?: number } = {}
|
||||
): Promise<"yes" | "no"> {
|
||||
const defaultTitle = $tf("moduleInputUIObsidian.defaultTitleConfirmation");
|
||||
const yesLabel = $tf("moduleInputUIObsidian.optionYes");
|
||||
const noLabel = $tf("moduleInputUIObsidian.optionNo");
|
||||
const defaultOption = opt.defaultOption === "Yes" ? yesLabel : noLabel;
|
||||
const ret = await confirmWithMessageWithWideButton(
|
||||
this.plugin,
|
||||
opt.title || "Confirmation",
|
||||
opt.title || defaultTitle,
|
||||
message,
|
||||
["Yes", "No"],
|
||||
opt.defaultOption ?? "No",
|
||||
[yesLabel, noLabel],
|
||||
defaultOption,
|
||||
opt.timeout
|
||||
);
|
||||
return ret == "Yes" ? "yes" : "no";
|
||||
return ret === yesLabel ? "yes" : "no";
|
||||
}
|
||||
|
||||
askSelectString(message: string, items: string[]): Promise<string> {
|
||||
@ -51,9 +58,10 @@ export class ModuleInputUIObsidian extends AbstractObsidianModule implements IOb
|
||||
buttons: string[],
|
||||
opt: { title?: string; defaultAction: (typeof buttons)[number]; timeout?: number }
|
||||
): Promise<(typeof buttons)[number] | false> {
|
||||
const defaultTitle = $tf("moduleInputUIObsidian.defaultTitleSelect");
|
||||
return confirmWithMessageWithWideButton(
|
||||
this.plugin,
|
||||
opt.title || "Select",
|
||||
opt.title || defaultTitle,
|
||||
message,
|
||||
buttons,
|
||||
opt.defaultAction,
|
||||
@ -91,6 +99,7 @@ export class ModuleInputUIObsidian extends AbstractObsidianModule implements IOb
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
confirmWithMessage(
|
||||
title: string,
|
||||
contentMd: string,
|
||||
|
@ -8,16 +8,12 @@ import {
|
||||
} from "../../common/events.ts";
|
||||
import { AbstractModule } from "../AbstractModule.ts";
|
||||
import type { ICoreModule } from "../ModuleTypes.ts";
|
||||
|
||||
const URI_DOC = "https://github.com/vrtmrz/obsidian-livesync/blob/main/README.md#how-to-use";
|
||||
import { $tf } from "src/lib/src/common/i18n.ts";
|
||||
|
||||
export class ModuleMigration extends AbstractModule implements ICoreModule {
|
||||
async migrateDisableBulkSend() {
|
||||
if (this.settings.sendChunksBulk) {
|
||||
this._log(
|
||||
"Send chunks in bulk has been enabled, however, this feature had been corrupted. Sorry for your inconvenience. Automatically disabled.",
|
||||
LOG_LEVEL_NOTICE
|
||||
);
|
||||
this._log($tf('moduleMigration.logBulkSendCorrupted'), LOG_LEVEL_NOTICE);
|
||||
this.settings.sendChunksBulk = false;
|
||||
this.settings.sendChunksBulkMaxSize = 1;
|
||||
await this.saveSettings();
|
||||
@ -28,7 +24,10 @@ export class ModuleMigration extends AbstractModule implements ICoreModule {
|
||||
const current = SETTING_VERSION_SUPPORT_CASE_INSENSITIVE;
|
||||
// Check each migrations(old -> current)
|
||||
if (!(await this.migrateToCaseInsensitive(old, current))) {
|
||||
this._log(`Migration failed or cancelled from ${old} to ${current}`, LOG_LEVEL_NOTICE);
|
||||
this._log($tf('moduleMigration.logMigrationFailed', {
|
||||
old: old.toString(),
|
||||
current: current.toString()
|
||||
}), LOG_LEVEL_NOTICE);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -68,10 +67,10 @@ export class ModuleMigration extends AbstractModule implements ICoreModule {
|
||||
remoteChecked = true;
|
||||
}
|
||||
} else {
|
||||
this._log("Failed to fetch remote tweak values", LOG_LEVEL_INFO);
|
||||
this._log($tf('moduleMigration.logFetchRemoteTweakFailed'), LOG_LEVEL_INFO);
|
||||
}
|
||||
} catch (ex) {
|
||||
this._log("Could not get remote tweak values", LOG_LEVEL_INFO);
|
||||
this._log($tf('moduleMigration.logRemoteTweakUnavailable'), LOG_LEVEL_INFO);
|
||||
this._log(ex, LOG_LEVEL_VERBOSE);
|
||||
}
|
||||
|
||||
@ -82,27 +81,21 @@ export class ModuleMigration extends AbstractModule implements ICoreModule {
|
||||
this.settings.handleFilenameCaseSensitive = true;
|
||||
this.settings.doNotUseFixedRevisionForChunks = true;
|
||||
this.settings.settingVersion = SETTING_VERSION_SUPPORT_CASE_INSENSITIVE;
|
||||
this._log(`Migrated to db:${current} with the same behaviour as before`, LOG_LEVEL_INFO);
|
||||
this._log($tf('moduleMigration.logMigratedSameBehaviour', {
|
||||
current: current.toString()
|
||||
}), LOG_LEVEL_INFO);
|
||||
await this.saveSettings();
|
||||
return true;
|
||||
}
|
||||
const message = `As you may already know, the self-hosted LiveSync has changed its default behaviour and database structure.
|
||||
|
||||
And thankfully, with your time and efforts, the remote database appears to have already been migrated. Congratulations!
|
||||
|
||||
However, we need a bit more. The configuration of this device is not compatible with the remote database. We will need to fetch the remote database again. Should we fetch from the remote again now?
|
||||
|
||||
___Note: We cannot synchronise until the configuration has been changed and the database has been fetched again.___
|
||||
___Note2: The chunks are completely immutable, we can fetch only the metadata and difference.___
|
||||
`;
|
||||
const OPTION_FETCH = "Yes, fetch again";
|
||||
const DISMISS = "No, please ask again";
|
||||
const message = $tf('moduleMigration.msgFetchRemoteAgain');
|
||||
const OPTION_FETCH = $tf('moduleMigration.optionYesFetchAgain');
|
||||
const DISMISS = $tf('moduleMigration.optionNoAskAgain');
|
||||
const options = [OPTION_FETCH, DISMISS];
|
||||
const ret = await this.core.confirm.confirmWithMessage(
|
||||
"Case Sensitivity",
|
||||
$tf('moduleMigration.titleCaseSensitivity'),
|
||||
message,
|
||||
options,
|
||||
"No, please ask again",
|
||||
DISMISS,
|
||||
40
|
||||
);
|
||||
if (ret == OPTION_FETCH) {
|
||||
@ -114,7 +107,7 @@ ___Note2: The chunks are completely immutable, we can fetch only the metadata an
|
||||
await this.core.rebuilder.scheduleFetch();
|
||||
return;
|
||||
} catch (ex) {
|
||||
this._log("Failed to create redflag2", LOG_LEVEL_VERBOSE);
|
||||
this._log($tf('moduleMigration.logRedflag2CreationFail'), LOG_LEVEL_VERBOSE);
|
||||
this._log(ex, LOG_LEVEL_VERBOSE);
|
||||
}
|
||||
return false;
|
||||
@ -123,33 +116,19 @@ ___Note2: The chunks are completely immutable, we can fetch only the metadata an
|
||||
}
|
||||
}
|
||||
|
||||
const ENABLE_BOTH = "Enable both";
|
||||
const ENABLE_FILENAME_CASE_INSENSITIVE = "Enable only #1";
|
||||
const ENABLE_FIXED_REVISION_FOR_CHUNKS = "Enable only #2";
|
||||
const ADJUST_TO_REMOTE = "Adjust to remote";
|
||||
const DISMISS = "Decide it later";
|
||||
const KEEP = "Keep previous behaviour";
|
||||
const message = `Since v0.23.21, the self-hosted LiveSync has changed the default behaviour and database structure. The following changes have been made:
|
||||
|
||||
1. **Case sensitivity of filenames**
|
||||
The handling of filenames is now case-insensitive. This is a beneficial change for most platforms, other than Linux and iOS, which do not manage filename case sensitivity effectively.
|
||||
(On These, a warning will be displayed for files with the same name but different cases).
|
||||
|
||||
2. **Revision handling of the chunks**
|
||||
Chunks are immutable, which allows their revisions to be fixed. This change will enhance the performance of file saving.
|
||||
|
||||
___However, to enable either of these changes, both remote and local databases need to be rebuilt. This process takes a few minutes, and we recommend doing it when you have ample time.___
|
||||
|
||||
- If you wish to maintain the previous behaviour, you can skip this process by using \`${KEEP}\`.
|
||||
- If you do not have enough time, please choose \`${DISMISS}\`. You will be prompted again later.
|
||||
- If you have rebuilt the database on another device, please select \`${DISMISS}\` and try synchronizing again. Since a difference has been detected, you will be prompted again.
|
||||
`;
|
||||
const ENABLE_BOTH = $tf('moduleMigration.optionEnableBoth');
|
||||
const ENABLE_FILENAME_CASE_INSENSITIVE = $tf('moduleMigration.optionEnableFilenameCaseInsensitive');
|
||||
const ENABLE_FIXED_REVISION_FOR_CHUNKS = $tf('moduleMigration.optionEnableFixedRevisionForChunks');
|
||||
const ADJUST_TO_REMOTE = $tf('moduleMigration.optionAdjustRemote');
|
||||
const KEEP = $tf('moduleMigration.optionKeepPreviousBehaviour');
|
||||
const DISMISS = $tf('moduleMigration.optionDecideLater');
|
||||
const message = $tf('moduleMigration.msgSinceV02321');
|
||||
const options = [ENABLE_BOTH, ENABLE_FILENAME_CASE_INSENSITIVE, ENABLE_FIXED_REVISION_FOR_CHUNKS];
|
||||
if (remoteChecked) {
|
||||
options.push(ADJUST_TO_REMOTE);
|
||||
}
|
||||
options.push(KEEP, DISMISS);
|
||||
const ret = await this.core.confirm.confirmWithMessage("Case Sensitivity", message, options, DISMISS, 40);
|
||||
const ret = await this.core.confirm.confirmWithMessage($tf('moduleMigration.titleCaseSensitivity'), message, options, DISMISS, 40);
|
||||
console.dir(ret);
|
||||
switch (ret) {
|
||||
case ENABLE_BOTH:
|
||||
@ -181,20 +160,14 @@ ___However, to enable either of these changes, both remote and local databases n
|
||||
}
|
||||
|
||||
async initialMessage() {
|
||||
const message = `Your device has **not been set up yet**. Let me guide you through the setup process.
|
||||
|
||||
Please keep in mind that every dialogue content can be copied to the clipboard. If you need to refer to it later, you can paste it into a note in Obsidian. You can also translate it into your language using a translation tool.
|
||||
|
||||
First, do you have **Setup URI**?
|
||||
|
||||
Note: If you do not know what it is, please refer to the [documentation](${URI_DOC}).
|
||||
`;
|
||||
|
||||
const USE_SETUP = "Yes, I have";
|
||||
const NEXT = "No, I do not have";
|
||||
const message = $tf('moduleMigration.msgInitialSetup', {
|
||||
URI_DOC: $tf('moduleMigration.docUri'),
|
||||
});
|
||||
const USE_SETUP = $tf('moduleMigration.optionHaveSetupUri');
|
||||
const NEXT = $tf('moduleMigration.optionNoSetupUri');
|
||||
|
||||
const ret = await this.core.confirm.askSelectStringDialogue(message, [USE_SETUP, NEXT], {
|
||||
title: "Welcome to Self-hosted LiveSync",
|
||||
title: $tf('moduleMigration.titleWelcome'),
|
||||
defaultAction: USE_SETUP,
|
||||
});
|
||||
if (ret === USE_SETUP) {
|
||||
@ -207,17 +180,13 @@ Note: If you do not know what it is, please refer to the [documentation](${URI_D
|
||||
}
|
||||
|
||||
async askAgainForSetupURI() {
|
||||
const message = `We strongly recommend that you generate a set-up URI and use it.
|
||||
If you do not have knowledge about it, please refer to the [documentation](${URI_DOC}) (Sorry again, but it is important).
|
||||
|
||||
How do you want to set it up manually?`;
|
||||
|
||||
const USE_MINIMAL = "Take me into the setup wizard";
|
||||
const USE_SETUP = "Set it up all manually";
|
||||
const NEXT = "Remind me at the next launch";
|
||||
const message = $tf('moduleMigration.msgRecommendSetupUri');
|
||||
const USE_MINIMAL = $tf('moduleMigration.optionSetupWizard');
|
||||
const USE_SETUP = $tf('moduleMigration.optionManualSetup');
|
||||
const NEXT = $tf('moduleMigration.optionRemindNextLaunch');
|
||||
|
||||
const ret = await this.core.confirm.askSelectStringDialogue(message, [USE_MINIMAL, USE_SETUP, NEXT], {
|
||||
title: "Recommendation to use Setup URI",
|
||||
title: $tf('moduleMigration.titleRecommendSetupUri'),
|
||||
defaultAction: USE_MINIMAL,
|
||||
});
|
||||
if (ret === USE_MINIMAL) {
|
||||
@ -235,7 +204,7 @@ How do you want to set it up manually?`;
|
||||
|
||||
async $everyOnFirstInitialize(): Promise<boolean> {
|
||||
if (!this.localDatabase.isReady) {
|
||||
this._log(`Something went wrong! The local database is not ready`, LOG_LEVEL_NOTICE);
|
||||
this._log($tf('moduleMigration.logLocalDatabaseNotReady'), LOG_LEVEL_NOTICE);
|
||||
return false;
|
||||
}
|
||||
if (this.settings.isConfigured) {
|
||||
@ -245,10 +214,7 @@ How do you want to set it up manually?`;
|
||||
if (!this.settings.isConfigured) {
|
||||
// Case sensitivity
|
||||
if (!(await this.initialMessage()) || !(await this.askAgainForSetupURI())) {
|
||||
this._log(
|
||||
"The setup has been cancelled, Self-hosted LiveSync waiting for your setup!",
|
||||
LOG_LEVEL_NOTICE
|
||||
);
|
||||
this._log($tf('moduleMigration.logSetupCancelled'), LOG_LEVEL_NOTICE);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ import { fireAndForget } from "octagonal-wheels/promises";
|
||||
import { addIcon, type Editor, type MarkdownFileInfo, type MarkdownView } from "../../deps.ts";
|
||||
import { LOG_LEVEL_NOTICE, type FilePathWithPrefix } from "../../lib/src/common/types.ts";
|
||||
import { AbstractObsidianModule, type IObsidianModule } from "../AbstractObsidianModule.ts";
|
||||
import { $tf } from "src/lib/src/common/i18n.ts";
|
||||
|
||||
export class ModuleObsidianMenu extends AbstractObsidianModule implements IObsidianModule {
|
||||
$everyOnloadStart(): Promise<boolean> {
|
||||
@ -16,7 +17,7 @@ export class ModuleObsidianMenu extends AbstractObsidianModule implements IObsid
|
||||
</g>`
|
||||
);
|
||||
|
||||
this.addRibbonIcon("replicate", "Replicate", async () => {
|
||||
this.addRibbonIcon("replicate", $tf("moduleObsidianMenu.replicate"), async () => {
|
||||
await this.core.$$replicate(true);
|
||||
}).addClass("livesync-ribbon-replicate");
|
||||
|
||||
|
@ -1,83 +1,93 @@
|
||||
<script lang="ts">
|
||||
import { onDestroy, onMount } from "svelte";
|
||||
import { logMessages } from "../../../lib/src/mock_and_interop/stores";
|
||||
import { reactive, type ReactiveInstance } from "../../../lib/src/dataobject/reactive";
|
||||
import { Logger } from "../../../lib/src/common/logger";
|
||||
import { onDestroy, onMount } from "svelte";
|
||||
import { logMessages } from "../../../lib/src/mock_and_interop/stores";
|
||||
import { reactive, type ReactiveInstance } from "../../../lib/src/dataobject/reactive";
|
||||
import { Logger } from "../../../lib/src/common/logger";
|
||||
import { $tf as tf, currentLang as lang } from "../../../lib/src/common/i18n.ts";
|
||||
|
||||
let unsubscribe: () => void;
|
||||
let messages = [] as string[];
|
||||
let wrapRight = false;
|
||||
let autoScroll = true;
|
||||
let suspended = false;
|
||||
function updateLog(logs: ReactiveInstance<string[]>) {
|
||||
const e = logs.value;
|
||||
if (!suspended) {
|
||||
messages = [...e];
|
||||
setTimeout(() => {
|
||||
if (scroll) scroll.scrollTop = scroll.scrollHeight;
|
||||
}, 10);
|
||||
}
|
||||
}
|
||||
onMount(async () => {
|
||||
const _logMessages = reactive(() => logMessages.value);
|
||||
_logMessages.onChanged(updateLog);
|
||||
Logger("Log window opened");
|
||||
unsubscribe = () => _logMessages.offChanged(updateLog);
|
||||
});
|
||||
onDestroy(() => {
|
||||
if (unsubscribe) unsubscribe();
|
||||
});
|
||||
let scroll: HTMLDivElement;
|
||||
let unsubscribe: () => void;
|
||||
let messages = [] as string[];
|
||||
let wrapRight = false;
|
||||
let autoScroll = true;
|
||||
let suspended = false;
|
||||
function updateLog(logs: ReactiveInstance<string[]>) {
|
||||
const e = logs.value;
|
||||
if (!suspended) {
|
||||
messages = [...e];
|
||||
setTimeout(() => {
|
||||
if (scroll) scroll.scrollTop = scroll.scrollHeight;
|
||||
}, 10);
|
||||
}
|
||||
}
|
||||
onMount(async () => {
|
||||
const _logMessages = reactive(() => logMessages.value);
|
||||
_logMessages.onChanged(updateLog);
|
||||
Logger(tf("logPane.logWindowOpened", {}, lang));
|
||||
unsubscribe = () => _logMessages.offChanged(updateLog);
|
||||
});
|
||||
onDestroy(() => {
|
||||
if (unsubscribe) unsubscribe();
|
||||
});
|
||||
let scroll: HTMLDivElement;
|
||||
</script>
|
||||
|
||||
<div class="logpane">
|
||||
<!-- <h1>Self-hosted LiveSync Log</h1> -->
|
||||
<div class="control">
|
||||
<div class="row">
|
||||
<label><input type="checkbox" bind:checked={wrapRight} /><span>Wrap</span></label>
|
||||
<label><input type="checkbox" bind:checked={autoScroll} /><span>Auto scroll</span></label>
|
||||
<label><input type="checkbox" bind:checked={suspended} /><span>Pause</span></label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="log" bind:this={scroll}>
|
||||
{#each messages as line}
|
||||
<pre class:wrap-right={wrapRight}>{line}</pre>
|
||||
{/each}
|
||||
</div>
|
||||
<!-- <h1>{tf("logPane.title", {}, lang)}</h1> -->
|
||||
<div class="control">
|
||||
<div class="row">
|
||||
<label>
|
||||
<input type="checkbox" bind:checked={wrapRight} />
|
||||
<span>{tf("logPane.wrap", {}, lang)}</span>
|
||||
</label>
|
||||
<label>
|
||||
<input type="checkbox" bind:checked={autoScroll} />
|
||||
<span>{tf("logPane.autoScroll", {}, lang)}</span>
|
||||
</label>
|
||||
<label>
|
||||
<input type="checkbox" bind:checked={suspended} />
|
||||
<span>{tf("logPane.pause", {}, lang)}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="log" bind:this={scroll}>
|
||||
{#each messages as line}
|
||||
<pre class:wrap-right={wrapRight}>{line}</pre>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.logpane {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
flex-direction: column;
|
||||
}
|
||||
.log {
|
||||
overflow-y: scroll;
|
||||
user-select: text;
|
||||
padding-bottom: 2em;
|
||||
}
|
||||
.log > pre {
|
||||
margin: 0;
|
||||
}
|
||||
.log > pre.wrap-right {
|
||||
word-break: break-all;
|
||||
max-width: 100%;
|
||||
width: 100%;
|
||||
white-space: normal;
|
||||
}
|
||||
.row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.row > label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-width: 5em;
|
||||
margin-right: 1em;
|
||||
}
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.logpane {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
flex-direction: column;
|
||||
}
|
||||
.log {
|
||||
overflow-y: scroll;
|
||||
user-select: text;
|
||||
padding-bottom: 2em;
|
||||
}
|
||||
.log > pre {
|
||||
margin: 0;
|
||||
}
|
||||
.log > pre.wrap-right {
|
||||
word-break: break-all;
|
||||
max-width: 100%;
|
||||
width: 100%;
|
||||
white-space: normal;
|
||||
}
|
||||
.row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.row > label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-width: 5em;
|
||||
margin-right: 1em;
|
||||
}
|
||||
</style>
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { ItemView, WorkspaceLeaf } from "obsidian";
|
||||
import LogPaneComponent from "./LogPane.svelte";
|
||||
import type ObsidianLiveSyncPlugin from "../../../main.ts";
|
||||
import { $tf } from "src/lib/src/common/i18n.ts";
|
||||
export const VIEW_TYPE_LOG = "log-log";
|
||||
//Log view
|
||||
export class LogPaneView extends ItemView {
|
||||
@ -24,7 +25,8 @@ export class LogPaneView extends ItemView {
|
||||
}
|
||||
|
||||
getDisplayText() {
|
||||
return "Self-hosted LiveSync Log";
|
||||
// TODO: This function is not reactive and does not update the title based on the current language
|
||||
return $tf("logPane.title");
|
||||
}
|
||||
|
||||
async onOpen() {
|
||||
|
@ -26,6 +26,7 @@ import { LOG_LEVEL_NOTICE, setGlobalLogFunction } from "octagonal-wheels/common/
|
||||
import { QueueProcessor } from "octagonal-wheels/concurrency/processor";
|
||||
import { LogPaneView, VIEW_TYPE_LOG } from "./Log/LogPaneView.ts";
|
||||
import { serialized } from "octagonal-wheels/concurrency/lock";
|
||||
import { $tf } from "src/lib/src/common/i18n.ts";
|
||||
|
||||
// This module cannot be a core module because it depends on the Obsidian UI.
|
||||
|
||||
@ -292,7 +293,7 @@ export class ModuleLog extends AbstractObsidianModule implements IObsidianModule
|
||||
<path d="m106 346v44h70v-44zm45 16h-20v-8h20z"/>
|
||||
</g>`
|
||||
);
|
||||
this.addRibbonIcon("view-log", "Show log", () => {
|
||||
this.addRibbonIcon("view-log", $tf("moduleLog.showLog"), () => {
|
||||
void this.core.$$showView(VIEW_TYPE_LOG);
|
||||
}).addClass("livesync-ribbon-showlog");
|
||||
|
||||
|
@ -30,6 +30,7 @@ import {
|
||||
type AllNumericItemKey,
|
||||
type AllBooleanItemKey,
|
||||
} from "./settingConstants.ts";
|
||||
import { $tf } from "src/lib/src/common/i18n.ts";
|
||||
|
||||
export class LiveSyncSetting extends Setting {
|
||||
autoWiredComponent?: TextComponent | ToggleComponent | DropdownComponent | ButtonComponent | TextAreaComponent;
|
||||
@ -86,7 +87,7 @@ export class LiveSyncSetting extends Setting {
|
||||
autoWireSetting(key: AllSettingItemKey, opt?: AutoWireOption) {
|
||||
const conf = getConfig(key);
|
||||
if (!conf) {
|
||||
// throw new Error(`No such setting item :${key}`)
|
||||
// throw new Error($tf("liveSyncSetting.errorNoSuchSettingItem", { key }));
|
||||
return;
|
||||
}
|
||||
const name = `${conf.name}${statusDisplay(conf.status)}`;
|
||||
@ -216,7 +217,15 @@ export class LiveSyncSetting extends Setting {
|
||||
text.inputEl.toggleClass("sls-item-invalid-value", false);
|
||||
await this.commitValue(value);
|
||||
} else {
|
||||
this.setTooltip(`The value should ${opt.clampMin || "~"} < value < ${opt.clampMax || "~"}`);
|
||||
this.setTooltip(
|
||||
$tf(
|
||||
"liveSyncSetting.valueShouldBeInRange",
|
||||
{
|
||||
min: opt.clampMin?.toString() || "~",
|
||||
max: opt.clampMax?.toString() || "~",
|
||||
}
|
||||
)
|
||||
);
|
||||
text.inputEl.toggleClass("sls-item-invalid-value", true);
|
||||
lastError = true;
|
||||
return false;
|
||||
@ -268,7 +277,7 @@ export class LiveSyncSetting extends Setting {
|
||||
this.addButton((button) => {
|
||||
this.applyButtonComponent = button;
|
||||
this.watchDirtyKeys = unique([...keys, ...this.watchDirtyKeys]);
|
||||
button.setButtonText(text ?? "Apply");
|
||||
button.setButtonText(text ?? $tf("liveSyncSettings.btnApply"));
|
||||
button.onClick(async () => {
|
||||
await LiveSyncSetting.env.saveSettings(keys);
|
||||
LiveSyncSetting.env.reloadAllSettings();
|
||||
@ -363,7 +372,9 @@ export class LiveSyncSetting extends Setting {
|
||||
}
|
||||
if (this.holdValue && this.selfKey) {
|
||||
const isDirty = LiveSyncSetting.env.isDirty(this.selfKey);
|
||||
const alt = isDirty ? `Original: ${LiveSyncSetting.env.initialSettings![this.selfKey]}` : "";
|
||||
const alt = isDirty
|
||||
? $tf("liveSyncSetting.originalValue", { value: String(LiveSyncSetting.env.initialSettings?.[this.selfKey] ?? "") })
|
||||
: "";
|
||||
this.controlEl.toggleClass("sls-item-dirty", isDirty);
|
||||
if (!this.hasPassword) {
|
||||
this.nameEl.toggleClass("sls-item-dirty-help", isDirty);
|
||||
|
@ -60,7 +60,7 @@ import {
|
||||
getConfName,
|
||||
} from "./settingConstants.ts";
|
||||
import { SUPPORTED_I18N_LANGS, type I18N_LANGS } from "../../../lib/src/common/rosetta.ts";
|
||||
import { $t } from "../../../lib/src/common/i18n.ts";
|
||||
import { $t, $tf } from "../../../lib/src/common/i18n.ts";
|
||||
import { Semaphore } from "octagonal-wheels/concurrency/semaphore";
|
||||
import { LiveSyncSetting as Setting } from "./LiveSyncSetting.ts";
|
||||
import { fireAndForget, yieldNextAnimationFrame } from "octagonal-wheels/promises";
|
||||
@ -117,11 +117,11 @@ type OnSavedHandler<T extends AllSettingItemKey> = {
|
||||
|
||||
function getLevelStr(level: ConfigLevel) {
|
||||
return level == LEVEL_POWER_USER
|
||||
? " (Power User)"
|
||||
? $tf("obsidianLiveSyncSettingTab.levelPowerUser")
|
||||
: level == LEVEL_ADVANCED
|
||||
? " (Advanced)"
|
||||
? $tf("obsidianLiveSyncSettingTab.levelAdvanced")
|
||||
: level == LEVEL_EDGE_CASE
|
||||
? " (Edge Case)"
|
||||
? $tf("obsidianLiveSyncSettingTab.levelEdgeCase")
|
||||
: "";
|
||||
}
|
||||
|
||||
@ -384,7 +384,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
||||
const status = await replicator.getRemoteStatus(trialSetting);
|
||||
if (status) {
|
||||
if (status.estimatedSize) {
|
||||
Logger(`Estimated size: ${sizeToHumanReadable(status.estimatedSize)}`, LOG_LEVEL_NOTICE);
|
||||
Logger($tf("obsidianLiveSyncSettingTab.logEstimatedSize", { size: sizeToHumanReadable(status.estimatedSize) }), LOG_LEVEL_NOTICE);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -464,9 +464,9 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
||||
// And modified.
|
||||
this.plugin.confirm.askInPopup(
|
||||
`config-reloaded-${k}`,
|
||||
`The setting "${getConfName(k as AllSettingItemKey)}" was modified from another device. Click {HERE} to reload settings. Click elsewhere to ignore changes`,
|
||||
$tf("obsidianLiveSyncSettingTab.msgSettingModified", { setting: getConfName(k as AllSettingItemKey) }),
|
||||
(anchor) => {
|
||||
anchor.text = "HERE";
|
||||
anchor.text = $tf("obsidianLiveSyncSettingTab.optionHere");
|
||||
anchor.addEventListener("click", () => {
|
||||
this.refreshSetting(k as AllSettingItemKey);
|
||||
this.display();
|
||||
@ -611,35 +611,16 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
||||
Logger(`Passphrase is not valid, please fix it.`, LOG_LEVEL_NOTICE);
|
||||
return;
|
||||
}
|
||||
const OPTION_FETCH = `Fetch from Remote`;
|
||||
const OPTION_REBUILD_BOTH = `Rebuild Both from This Device`;
|
||||
const OPTION_ONLY_SETTING = `(Danger) Save Only Settings`;
|
||||
const OPTION_CANCEL = `Cancel`;
|
||||
const title = `Rebuild Required`;
|
||||
const note = `Rebuilding Databases are required to apply the changes.. Please select the method to apply the changes.
|
||||
|
||||
<details>
|
||||
<summary>Legends</summary>
|
||||
|
||||
| Symbol | Meaning |
|
||||
|: ------ :| ------- |
|
||||
| ⇔ | Up to Date |
|
||||
| ⇄ | Synchronise to balance |
|
||||
| ⇐,⇒ | Transfer to overwrite |
|
||||
| ⇠,⇢ | Transfer to overwrite from other side |
|
||||
|
||||
</details>
|
||||
|
||||
## ${OPTION_REBUILD_BOTH}
|
||||
At a glance: 📄 ⇒¹ 💻 ⇒² 🛰️ ⇢ⁿ 💻 ⇄ⁿ⁺¹ 📄
|
||||
Reconstruct both the local and remote databases using existing files from this device.
|
||||
This causes a lockout other devices, and they need to perform fetching.
|
||||
## ${OPTION_FETCH}
|
||||
At a glance: 📄 ⇄² 💻 ⇐¹ 🛰️ ⇔ 💻 ⇔ 📄
|
||||
Initialise the local database and reconstruct it using data fetched from the remote database.
|
||||
This case includes the case which you have rebuilt the remote database.
|
||||
## ${OPTION_ONLY_SETTING}
|
||||
Store only the settings. **Caution: This may lead to data corruption**; database reconstruction is generally necessary.`;
|
||||
const OPTION_FETCH = $tf("obsidianLiveSyncSettingTab.optionFetchFromRemote");
|
||||
const OPTION_REBUILD_BOTH = $tf("obsidianLiveSyncSettingTab.optionRebuildBoth");
|
||||
const OPTION_ONLY_SETTING = $tf("obsidianLiveSyncSettingTab.optionSaveOnlySettings");
|
||||
const OPTION_CANCEL = $tf("obsidianLiveSyncSettingTab.optionCancel");
|
||||
const title = $tf("obsidianLiveSyncSettingTab.titleRebuildRequired");
|
||||
const note = $tf("obsidianLiveSyncSettingTab.msgRebuildRequired", {
|
||||
OPTION_REBUILD_BOTH,
|
||||
OPTION_FETCH,
|
||||
OPTION_ONLY_SETTING,
|
||||
});
|
||||
const buttons = [
|
||||
OPTION_FETCH,
|
||||
OPTION_REBUILD_BOTH, // OPTION_REBUILD_REMOTE,
|
||||
@ -651,7 +632,7 @@ Store only the settings. **Caution: This may lead to data corruption**; database
|
||||
if (result == OPTION_FETCH) {
|
||||
if (!(await checkWorkingPassphrase())) {
|
||||
if (
|
||||
(await this.plugin.confirm.askYesNoDialog("Are you sure to proceed?", {
|
||||
(await this.plugin.confirm.askYesNoDialog($tf("obsidianLiveSyncSettingTab.msgAreYouSureProceed"), {
|
||||
defaultOption: "No",
|
||||
})) != "yes"
|
||||
)
|
||||
@ -682,8 +663,8 @@ Store only the settings. **Caution: This may lead to data corruption**; database
|
||||
{ cls: "sls-setting-menu-buttons" },
|
||||
(el) => {
|
||||
el.addClass("wizardHidden");
|
||||
el.createEl("label", { text: "Changes need to be applied!" });
|
||||
void this.addEl(el, "button", { text: "Apply", cls: "mod-warning" }, (buttonEl) => {
|
||||
el.createEl("label", { text: $tf("obsidianLiveSyncSettingTab.msgChangesNeedToBeApplied") });
|
||||
void this.addEl(el, "button", { text: $tf("obsidianLiveSyncSettingTab.optionApply"), cls: "mod-warning" }, (buttonEl) => {
|
||||
buttonEl.addEventListener("click", () => fireAndForget(async () => await confirmRebuild()));
|
||||
});
|
||||
},
|
||||
@ -840,15 +821,15 @@ Store only the settings. **Caution: This may lead to data corruption**; database
|
||||
true
|
||||
);
|
||||
if (typeof db === "string") {
|
||||
Logger(`ERROR: Failed to check passphrase with the remote server: \n${db}.`, LOG_LEVEL_NOTICE);
|
||||
Logger($tf("obsidianLiveSyncSettingTab.logCheckPassphraseFailed", { db }), LOG_LEVEL_NOTICE);
|
||||
return false;
|
||||
} else {
|
||||
if (await checkSyncInfo(db.db)) {
|
||||
// Logger("Database connected", LOG_LEVEL_NOTICE);
|
||||
// Logger($tf("obsidianLiveSyncSettingTab.logDatabaseConnected"), LOG_LEVEL_NOTICE);
|
||||
return true;
|
||||
} else {
|
||||
Logger(
|
||||
"ERROR: Passphrase is not compatible with the remote server! Please check it again!",
|
||||
$tf("obsidianLiveSyncSettingTab.logPassphraseNotCompatible"),
|
||||
LOG_LEVEL_NOTICE
|
||||
);
|
||||
return false;
|
||||
@ -857,11 +838,11 @@ Store only the settings. **Caution: This may lead to data corruption**; database
|
||||
};
|
||||
const isPassphraseValid = async () => {
|
||||
if (this.editingSettings.encrypt && this.editingSettings.passphrase == "") {
|
||||
Logger("You cannot enable encryption without a passphrase", LOG_LEVEL_NOTICE);
|
||||
Logger($tf("obsidianLiveSyncSettingTab.logEncryptionNoPassphrase"), LOG_LEVEL_NOTICE);
|
||||
return false;
|
||||
}
|
||||
if (this.editingSettings.encrypt && !(await testCrypt())) {
|
||||
Logger("Your device does not support encryption.", LOG_LEVEL_NOTICE);
|
||||
Logger($tf("obsidianLiveSyncSettingTab.logEncryptionNoSupport"), LOG_LEVEL_NOTICE);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@ -871,11 +852,11 @@ Store only the settings. **Caution: This may lead to data corruption**; database
|
||||
method: "localOnly" | "remoteOnly" | "rebuildBothByThisDevice" | "localOnlyWithChunks"
|
||||
) => {
|
||||
if (this.editingSettings.encrypt && this.editingSettings.passphrase == "") {
|
||||
Logger("You cannot enable encryption without a passphrase", LOG_LEVEL_NOTICE);
|
||||
Logger($tf("obsidianLiveSyncSettingTab.logEncryptionNoPassphrase"), LOG_LEVEL_NOTICE);
|
||||
return;
|
||||
}
|
||||
if (this.editingSettings.encrypt && !(await testCrypt())) {
|
||||
Logger("Your device does not support encryption.", LOG_LEVEL_NOTICE);
|
||||
Logger($tf("obsidianLiveSyncSettingTab.logEncryptionNoSupport"), LOG_LEVEL_NOTICE);
|
||||
return;
|
||||
}
|
||||
if (!this.editingSettings.encrypt) {
|
||||
@ -886,7 +867,7 @@ Store only the settings. **Caution: This may lead to data corruption**; database
|
||||
await this.plugin.$allSuspendExtraSync();
|
||||
this.reloadAllSettings();
|
||||
this.editingSettings.isConfigured = true;
|
||||
Logger("Syncing has been disabled, fetch and re-enabled if desired.", LOG_LEVEL_NOTICE);
|
||||
Logger($tf("obsidianLiveSyncSettingTab.logRebuildNote"), LOG_LEVEL_NOTICE);
|
||||
await this.saveAllDirtySettings();
|
||||
this.closeSetting();
|
||||
await delay(2000);
|
||||
@ -894,14 +875,14 @@ Store only the settings. **Caution: This may lead to data corruption**; database
|
||||
};
|
||||
// Panes
|
||||
|
||||
void addPane(containerEl, "Change Log", "💬", 100, false).then((paneEl) => {
|
||||
void addPane(containerEl, $tf("obsidianLiveSyncSettingTab.panelChangeLog"), "💬", 100, false).then((paneEl) => {
|
||||
const informationDivEl = this.createEl(paneEl, "div", { text: "" });
|
||||
|
||||
const tmpDiv = createDiv();
|
||||
// tmpDiv.addClass("sls-header-button");
|
||||
tmpDiv.addClass("op-warn-info");
|
||||
|
||||
tmpDiv.innerHTML = `<p>Here due to an upgrade notification? Please review the version history. If you're satisfied, click the button. A new update will prompt this again.</p><button> OK, I have read everything. </button>`;
|
||||
tmpDiv.innerHTML = `<p>${$tf("obsidianLiveSyncSettingTab.msgNewVersionNote")}</p><button>${$tf("obsidianLiveSyncSettingTab.optionOkReadEverything")}</button>`
|
||||
if (lastVersion > (this.editingSettings?.lastReadUpdates || 0)) {
|
||||
const informationButtonDiv = informationDivEl.appendChild(tmpDiv);
|
||||
informationButtonDiv.querySelector("button")?.addEventListener("click", () => {
|
||||
@ -917,35 +898,33 @@ Store only the settings. **Caution: This may lead to data corruption**; database
|
||||
);
|
||||
});
|
||||
|
||||
void addPane(containerEl, "Setup", "🧙♂️", 110, false).then((paneEl) => {
|
||||
void addPanel(paneEl, "Quick Setup").then((paneEl) => {
|
||||
void addPane(containerEl, $tf("obsidianLiveSyncSettingTab.panelSetup"), "🧙♂️", 110, false).then((paneEl) => {
|
||||
void addPanel(paneEl, $tf("obsidianLiveSyncSettingTab.titleQuickSetup")).then((paneEl) => {
|
||||
new Setting(paneEl)
|
||||
.setName("Connect with Setup URI")
|
||||
.setDesc("This is the recommended method to set up Self-hosted LiveSync with a Setup URI.")
|
||||
.setName($tf("obsidianLiveSyncSettingTab.nameConnectSetupURI"))
|
||||
.setDesc($tf("obsidianLiveSyncSettingTab.descConnectSetupURI"))
|
||||
.addButton((text) => {
|
||||
text.setButtonText("Use").onClick(() => {
|
||||
text.setButtonText($tf("obsidianLiveSyncSettingTab.btnUse")).onClick(() => {
|
||||
this.closeSetting();
|
||||
eventHub.emitEvent(EVENT_REQUEST_OPEN_SETUP_URI);
|
||||
});
|
||||
});
|
||||
|
||||
new Setting(paneEl)
|
||||
.setName("Manual setup")
|
||||
.setDesc("Not recommended, but useful if you don't have a Setup URI")
|
||||
.setName($tf("obsidianLiveSyncSettingTab.nameManualSetup"))
|
||||
.setDesc($tf("obsidianLiveSyncSettingTab.descManualSetup"))
|
||||
.addButton((text) => {
|
||||
text.setButtonText("Start").onClick(async () => {
|
||||
text.setButtonText($tf("obsidianLiveSyncSettingTab.btnStart")).onClick(async () => {
|
||||
await this.enableMinimalSetup();
|
||||
});
|
||||
});
|
||||
|
||||
new Setting(paneEl)
|
||||
.setName("Enable LiveSync")
|
||||
.setDesc(
|
||||
"Only enable this after configuring either of the above two options or completing all configuration manually."
|
||||
)
|
||||
.setName($tf("obsidianLiveSyncSettingTab.nameEnableLiveSync"))
|
||||
.setDesc($tf("obsidianLiveSyncSettingTab.descEnableLiveSync"))
|
||||
.addOnUpdate(visibleOnly(() => !this.isConfiguredAs("isConfigured", true)))
|
||||
.addButton((text) => {
|
||||
text.setButtonText("Enable").onClick(async () => {
|
||||
text.setButtonText($tf("obsidianLiveSyncSettingTab.btnEnable")).onClick(async () => {
|
||||
this.editingSettings.isConfigured = true;
|
||||
await this.saveAllDirtySettings();
|
||||
this.plugin.$$askReload();
|
||||
@ -955,29 +934,29 @@ Store only the settings. **Caution: This may lead to data corruption**; database
|
||||
|
||||
void addPanel(
|
||||
paneEl,
|
||||
"To setup other devices",
|
||||
$tf("obsidianLiveSyncSettingTab.titleSetupOtherDevices"),
|
||||
undefined,
|
||||
visibleOnly(() => this.isConfiguredAs("isConfigured", true))
|
||||
).then((paneEl) => {
|
||||
new Setting(paneEl)
|
||||
.setName("Copy the current settings to a Setup URI")
|
||||
.setDesc("Perfect for setting up a new device!")
|
||||
.setName($tf("obsidianLiveSyncSettingTab.nameCopySetupURI"))
|
||||
.setDesc($tf("obsidianLiveSyncSettingTab.descCopySetupURI"))
|
||||
.addButton((text) => {
|
||||
text.setButtonText("Copy").onClick(() => {
|
||||
text.setButtonText($tf("obsidianLiveSyncSettingTab.btnCopy")).onClick(() => {
|
||||
// await this.plugin.addOnSetup.command_copySetupURI();
|
||||
eventHub.emitEvent(EVENT_REQUEST_COPY_SETUP_URI);
|
||||
});
|
||||
});
|
||||
});
|
||||
void addPanel(paneEl, "Reset").then((paneEl) => {
|
||||
void addPanel(paneEl, $tf("obsidianLiveSyncSettingTab.titleReset")).then((paneEl) => {
|
||||
new Setting(paneEl)
|
||||
.setName("Discard existing settings and databases")
|
||||
.setName($tf("obsidianLiveSyncSettingTab.nameDiscardSettings"))
|
||||
.addButton((text) => {
|
||||
text.setButtonText("Discard")
|
||||
text.setButtonText($tf("obsidianLiveSyncSettingTab.btnDiscard"))
|
||||
.onClick(async () => {
|
||||
if (
|
||||
(await this.plugin.confirm.askYesNoDialog(
|
||||
"Do you really want to discard existing settings and databases?",
|
||||
$tf("obsidianLiveSyncSettingTab.msgDiscardConfirmation"),
|
||||
{ defaultOption: "No" }
|
||||
)) == "yes"
|
||||
) {
|
||||
@ -993,9 +972,9 @@ Store only the settings. **Caution: This may lead to data corruption**; database
|
||||
.setWarning();
|
||||
})
|
||||
.addOnUpdate(visibleOnly(() => this.isConfiguredAs("isConfigured", true)));
|
||||
// }
|
||||
});
|
||||
void addPanel(paneEl, "Enable extra and advanced features").then((paneEl) => {
|
||||
|
||||
void addPanel(paneEl, $tf("obsidianLiveSyncSettingTab.titleExtraFeatures")).then((paneEl) => {
|
||||
new Setting(paneEl).autoWireToggle("useAdvancedMode");
|
||||
|
||||
new Setting(paneEl).autoWireToggle("usePowerUserMode");
|
||||
@ -1005,17 +984,18 @@ Store only the settings. **Caution: This may lead to data corruption**; database
|
||||
this.addOnSaved("usePowerUserMode", () => this.display());
|
||||
this.addOnSaved("useEdgeCaseMode", () => this.display());
|
||||
});
|
||||
void addPanel(paneEl, "Online Tips").then((paneEl) => {
|
||||
// this.createEl(paneEl, "h3", { text: "Online Tips" });
|
||||
|
||||
void addPanel(paneEl, $tf("obsidianLiveSyncSettingTab.titleOnlineTips")).then((paneEl) => {
|
||||
// this.createEl(paneEl, "h3", { text: $tf("obsidianLiveSyncSettingTab.titleOnlineTips") });
|
||||
const repo = "vrtmrz/obsidian-livesync";
|
||||
const topPath = "/docs/troubleshooting.md";
|
||||
const topPath = $tf("obsidianLiveSyncSettingTab.linkTroubleshooting");
|
||||
const rawRepoURI = `https://raw.githubusercontent.com/${repo}/main`;
|
||||
this.createEl(
|
||||
paneEl,
|
||||
"div",
|
||||
"",
|
||||
(el) =>
|
||||
(el.innerHTML = `<a href='https://github.com/${repo}/blob/main${topPath}' target="_blank">Open in browser</a>`)
|
||||
(el.innerHTML = `<a href='https://github.com/${repo}/blob/main${topPath}' target="_blank">${$tf("obsidianLiveSyncSettingTab.linkOpenInBrowser")}</a>`)
|
||||
);
|
||||
const troubleShootEl = this.createEl(paneEl, "div", {
|
||||
text: "",
|
||||
@ -1035,7 +1015,7 @@ Store only the settings. **Caution: This may lead to data corruption**; database
|
||||
try {
|
||||
remoteTroubleShootMDSrc = await request(`${rawRepoURI}${basePath}/${filename}`);
|
||||
} catch (ex: any) {
|
||||
remoteTroubleShootMDSrc = "An error occurred!!\n" + ex.toString();
|
||||
remoteTroubleShootMDSrc = `${$tf("obsidianLiveSyncSettingTab.logErrorOccurred")}\n${ex.toString()}`;
|
||||
}
|
||||
const remoteTroubleShootMD = remoteTroubleShootMDSrc.replace(
|
||||
/\((.*?(.png)|(.jpg))\)/g,
|
||||
@ -1044,7 +1024,7 @@ Store only the settings. **Caution: This may lead to data corruption**; database
|
||||
// Render markdown
|
||||
await MarkdownRenderer.render(
|
||||
this.plugin.app,
|
||||
`<a class='sls-troubleshoot-anchor'></a> [Tips and Troubleshooting](${topPath}) [PageTop](${filename})\n\n${remoteTroubleShootMD}`,
|
||||
`<a class='sls-troubleshoot-anchor'></a> [${$tf("obsidianLiveSyncSettingTab.linkTipsAndTroubleshooting")}](${topPath}) [${$tf("obsidianLiveSyncSettingTab.linkPageTop")}](${filename})\n\n${remoteTroubleShootMD}`,
|
||||
troubleShootEl,
|
||||
`${rawRepoURI}`,
|
||||
this.plugin
|
||||
@ -1097,10 +1077,10 @@ Store only the settings. **Caution: This may lead to data corruption**; database
|
||||
void loadMarkdownPage(topPath);
|
||||
});
|
||||
});
|
||||
void addPane(containerEl, "General Settings", "⚙️", 20, false).then((paneEl) => {
|
||||
void addPanel(paneEl, "Appearance").then((paneEl) => {
|
||||
void addPane(containerEl, $tf("obsidianLiveSyncSettingTab.panelGeneralSettings"), "⚙️", 20, false).then((paneEl) => {
|
||||
void addPanel(paneEl, $tf("obsidianLiveSyncSettingTab.titleAppearance")).then((paneEl) => {
|
||||
const languages = Object.fromEntries([
|
||||
["", "Default"],
|
||||
["", $tf("obsidianLiveSyncSettingTab.defaultLanguage")],
|
||||
...SUPPORTED_I18N_LANGS.map((e) => [e, $t(`lang-${e}`)]),
|
||||
]) as Record<I18N_LANGS, string>;
|
||||
new Setting(paneEl).autoWireDropDown("displayLanguage", {
|
||||
@ -1113,7 +1093,7 @@ Store only the settings. **Caution: This may lead to data corruption**; database
|
||||
});
|
||||
new Setting(paneEl).autoWireToggle("showStatusOnStatusbar");
|
||||
});
|
||||
void addPanel(paneEl, "Logging").then((paneEl) => {
|
||||
void addPanel(paneEl, $tf("obsidianLiveSyncSettingTab.titleLogging")).then((paneEl) => {
|
||||
paneEl.addClass("wizardHidden");
|
||||
|
||||
new Setting(paneEl).autoWireToggle("lessInformationInLog");
|
||||
@ -1124,7 +1104,7 @@ Store only the settings. **Caution: This may lead to data corruption**; database
|
||||
});
|
||||
new Setting(paneEl).setClass("wizardOnly").addButton((button) =>
|
||||
button
|
||||
.setButtonText("Next")
|
||||
.setButtonText($tf("obsidianLiveSyncSettingTab.btnNext"))
|
||||
.setCta()
|
||||
.onClick(() => {
|
||||
this.changeDisplay("0");
|
||||
@ -1133,7 +1113,7 @@ Store only the settings. **Caution: This may lead to data corruption**; database
|
||||
});
|
||||
let checkResultDiv: HTMLDivElement;
|
||||
const checkConfig = async (checkResultDiv: HTMLDivElement | undefined) => {
|
||||
Logger(`Checking database configuration`, LOG_LEVEL_INFO);
|
||||
Logger($tf("obsidianLiveSyncSettingTab.logCheckingDbConfig"), LOG_LEVEL_INFO);
|
||||
let isSuccessful = true;
|
||||
const emptyDiv = createDiv();
|
||||
emptyDiv.innerHTML = "<span></span>";
|
||||
@ -1149,9 +1129,10 @@ Store only the settings. **Caution: This may lead to data corruption**; database
|
||||
};
|
||||
try {
|
||||
if (isCloudantURI(this.editingSettings.couchDB_URI)) {
|
||||
Logger("This feature cannot be used with IBM Cloudant.", LOG_LEVEL_NOTICE);
|
||||
Logger($tf("obsidianLiveSyncSettingTab.logCannotUseCloudant"), LOG_LEVEL_NOTICE);
|
||||
return;
|
||||
}
|
||||
// Tip: Add log for cloudant as Logger($tf("obsidianLiveSyncSettingTab.logServerConfigurationCheck"));
|
||||
const r = await requestToCouchDB(
|
||||
this.editingSettings.couchDB_URI,
|
||||
this.editingSettings.couchDB_USER,
|
||||
@ -1164,11 +1145,11 @@ Store only the settings. **Caution: This may lead to data corruption**; database
|
||||
if (!checkResultDiv) return;
|
||||
const tmpDiv = createDiv();
|
||||
tmpDiv.addClass("ob-btn-config-fix");
|
||||
tmpDiv.innerHTML = `<label>${title}</label><button>Fix</button>`;
|
||||
tmpDiv.innerHTML = `<label>${title}</label><button>${$tf("obsidianLiveSyncSettingTab.btnFix")}</button>`;
|
||||
const x = checkResultDiv.appendChild(tmpDiv);
|
||||
x.querySelector("button")?.addEventListener("click", () => {
|
||||
fireAndForget(async () => {
|
||||
Logger(`CouchDB Configuration: ${title} -> Set ${key} to ${value}`);
|
||||
Logger($tf("obsidianLiveSyncSettingTab.logCouchDbConfigSet", { title, key, value }));
|
||||
const res = await requestToCouchDB(
|
||||
this.editingSettings.couchDB_URI,
|
||||
this.editingSettings.couchDB_USER,
|
||||
@ -1178,96 +1159,104 @@ Store only the settings. **Caution: This may lead to data corruption**; database
|
||||
value
|
||||
);
|
||||
if (res.status == 200) {
|
||||
Logger(`CouchDB Configuration: ${title} successfully updated`, LOG_LEVEL_NOTICE);
|
||||
Logger($tf("obsidianLiveSyncSettingTab.logCouchDbConfigUpdated", { title }), LOG_LEVEL_NOTICE);
|
||||
checkResultDiv.removeChild(x);
|
||||
await checkConfig(checkResultDiv);
|
||||
} else {
|
||||
Logger(`CouchDB Configuration: ${title} failed`, LOG_LEVEL_NOTICE);
|
||||
Logger($tf("obsidianLiveSyncSettingTab.logCouchDbConfigFail", { title }), LOG_LEVEL_NOTICE);
|
||||
Logger(res.text, LOG_LEVEL_VERBOSE);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
addResult("---Notice---", ["ob-btn-config-head"]);
|
||||
addResult(
|
||||
"If the server configuration is not persistent (e.g., running on docker), the values here may change. Once you are able to connect, please update the settings in the server's local.ini.",
|
||||
["ob-btn-config-info"]
|
||||
);
|
||||
|
||||
addResult("--Config check--", ["ob-btn-config-head"]);
|
||||
addResult($tf("obsidianLiveSyncSettingTab.msgNotice"), ["ob-btn-config-head"]);
|
||||
addResult($tf("obsidianLiveSyncSettingTab.msgIfConfigNotPersistent"), ["ob-btn-config-info"]);
|
||||
addResult($tf("obsidianLiveSyncSettingTab.msgConfigCheck"), ["ob-btn-config-head"]);
|
||||
|
||||
// Admin check
|
||||
// for database creation and deletion
|
||||
if (!(this.editingSettings.couchDB_USER in responseConfig.admins)) {
|
||||
addResult(`⚠ You do not have administrator privileges.`);
|
||||
addResult($tf("obsidianLiveSyncSettingTab.warnNoAdmin"));
|
||||
} else {
|
||||
addResult("✔ You have administrator privileges.");
|
||||
addResult($tf("obsidianLiveSyncSettingTab.okAdminPrivileges"));
|
||||
}
|
||||
// HTTP user-authorization check
|
||||
if (responseConfig?.chttpd?.require_valid_user != "true") {
|
||||
isSuccessful = false;
|
||||
addResult("❗ chttpd.require_valid_user is wrong.");
|
||||
addConfigFixButton("Set chttpd.require_valid_user = true", "chttpd/require_valid_user", "true");
|
||||
addResult($tf("obsidianLiveSyncSettingTab.errRequireValidUser"));
|
||||
addConfigFixButton(
|
||||
$tf("obsidianLiveSyncSettingTab.msgSetRequireValidUser"),
|
||||
"chttpd/require_valid_user",
|
||||
"true"
|
||||
);
|
||||
} else {
|
||||
addResult("✔ chttpd.require_valid_user is ok.");
|
||||
addResult($tf("obsidianLiveSyncSettingTab.okRequireValidUser"));
|
||||
}
|
||||
if (responseConfig?.chttpd_auth?.require_valid_user != "true") {
|
||||
isSuccessful = false;
|
||||
addResult("❗ chttpd_auth.require_valid_user is wrong.");
|
||||
addResult($tf("obsidianLiveSyncSettingTab.errRequireValidUserAuth"));
|
||||
addConfigFixButton(
|
||||
"Set chttpd_auth.require_valid_user = true",
|
||||
$tf("obsidianLiveSyncSettingTab.msgSetRequireValidUserAuth"),
|
||||
"chttpd_auth/require_valid_user",
|
||||
"true"
|
||||
);
|
||||
} else {
|
||||
addResult("✔ chttpd_auth.require_valid_user is ok.");
|
||||
addResult($tf("obsidianLiveSyncSettingTab.okRequireValidUserAuth"));
|
||||
}
|
||||
// HTTPD check
|
||||
// Check Authentication header
|
||||
if (!responseConfig?.httpd["WWW-Authenticate"]) {
|
||||
isSuccessful = false;
|
||||
addResult("❗ httpd.WWW-Authenticate is missing");
|
||||
addConfigFixButton("Set httpd.WWW-Authenticate", "httpd/WWW-Authenticate", 'Basic realm="couchdb"');
|
||||
addResult($tf("obsidianLiveSyncSettingTab.errMissingWwwAuth"));
|
||||
addConfigFixButton(
|
||||
$tf("obsidianLiveSyncSettingTab.msgSetWwwAuth"),
|
||||
"httpd/WWW-Authenticate",
|
||||
'Basic realm="couchdb"'
|
||||
);
|
||||
} else {
|
||||
addResult("✔ httpd.WWW-Authenticate is ok.");
|
||||
addResult($tf("obsidianLiveSyncSettingTab.okWwwAuth"));
|
||||
}
|
||||
if (responseConfig?.httpd?.enable_cors != "true") {
|
||||
isSuccessful = false;
|
||||
addResult("❗ httpd.enable_cors is wrong");
|
||||
addConfigFixButton("Set httpd.enable_cors", "httpd/enable_cors", "true");
|
||||
addResult($tf("obsidianLiveSyncSettingTab.errEnableCors"));
|
||||
addConfigFixButton($tf("obsidianLiveSyncSettingTab.msgEnableCors"), "httpd/enable_cors", "true");
|
||||
} else {
|
||||
addResult("✔ httpd.enable_cors is ok.");
|
||||
addResult($tf("obsidianLiveSyncSettingTab.okEnableCors"));
|
||||
}
|
||||
// If the server is not cloudant, configure request size
|
||||
if (!isCloudantURI(this.editingSettings.couchDB_URI)) {
|
||||
// REQUEST SIZE
|
||||
if (Number(responseConfig?.chttpd?.max_http_request_size ?? 0) < 4294967296) {
|
||||
isSuccessful = false;
|
||||
addResult("❗ chttpd.max_http_request_size is low)");
|
||||
addResult($tf("obsidianLiveSyncSettingTab.errMaxRequestSize"));
|
||||
addConfigFixButton(
|
||||
"Set chttpd.max_http_request_size",
|
||||
$tf("obsidianLiveSyncSettingTab.msgSetMaxRequestSize"),
|
||||
"chttpd/max_http_request_size",
|
||||
"4294967296"
|
||||
);
|
||||
} else {
|
||||
addResult("✔ chttpd.max_http_request_size is ok.");
|
||||
addResult($tf("obsidianLiveSyncSettingTab.okMaxRequestSize"));
|
||||
}
|
||||
if (Number(responseConfig?.couchdb?.max_document_size ?? 0) < 50000000) {
|
||||
isSuccessful = false;
|
||||
addResult("❗ couchdb.max_document_size is low)");
|
||||
addConfigFixButton("Set couchdb.max_document_size", "couchdb/max_document_size", "50000000");
|
||||
addResult($tf("obsidianLiveSyncSettingTab.errMaxDocumentSize"));
|
||||
addConfigFixButton(
|
||||
$tf("obsidianLiveSyncSettingTab.msgSetMaxDocSize"),
|
||||
"couchdb/max_document_size",
|
||||
"50000000"
|
||||
);
|
||||
} else {
|
||||
addResult("✔ couchdb.max_document_size is ok.");
|
||||
addResult($tf("obsidianLiveSyncSettingTab.okMaxDocumentSize"));
|
||||
}
|
||||
}
|
||||
// CORS check
|
||||
// checking connectivity for mobile
|
||||
if (responseConfig?.cors?.credentials != "true") {
|
||||
isSuccessful = false;
|
||||
addResult("❗ cors.credentials is wrong");
|
||||
addConfigFixButton("Set cors.credentials", "cors/credentials", "true");
|
||||
addResult($tf("obsidianLiveSyncSettingTab.errCorsCredentials"));
|
||||
addConfigFixButton($tf("obsidianLiveSyncSettingTab.msgSetCorsCredentials"), "cors/credentials", "true");
|
||||
} else {
|
||||
addResult("✔ cors.credentials is ok.");
|
||||
addResult($tf("obsidianLiveSyncSettingTab.okCorsCredentials"));
|
||||
}
|
||||
const ConfiguredOrigins = ((responseConfig?.cors?.origins ?? "") + "").split(",");
|
||||
if (
|
||||
@ -1276,18 +1265,18 @@ Store only the settings. **Caution: This may lead to data corruption**; database
|
||||
ConfiguredOrigins.indexOf("capacitor://localhost") !== -1 &&
|
||||
ConfiguredOrigins.indexOf("http://localhost") !== -1)
|
||||
) {
|
||||
addResult("✔ cors.origins is ok.");
|
||||
addResult($tf("obsidianLiveSyncSettingTab.okCorsOrigins"));
|
||||
} else {
|
||||
addResult("❗ cors.origins is wrong");
|
||||
addResult($tf("obsidianLiveSyncSettingTab.errCorsOrigins"));
|
||||
addConfigFixButton(
|
||||
"Set cors.origins",
|
||||
$tf("obsidianLiveSyncSettingTab.msgSetCorsOrigins"),
|
||||
"cors/origins",
|
||||
"app://obsidian.md,capacitor://localhost,http://localhost"
|
||||
);
|
||||
isSuccessful = false;
|
||||
}
|
||||
addResult("--Connection check--", ["ob-btn-config-head"]);
|
||||
addResult(`Current origin:${window.location.origin}`);
|
||||
addResult($tf("obsidianLiveSyncSettingTab.msgConnectionCheck"), ["ob-btn-config-head"]);
|
||||
addResult($tf("obsidianLiveSyncSettingTab.msgCurrentOrigin", { origin: window.location.origin }));
|
||||
|
||||
// Request header check
|
||||
const origins = ["app://obsidian.md", "capacitor://localhost", "http://localhost"];
|
||||
@ -1304,35 +1293,35 @@ Store only the settings. **Caution: This may lead to data corruption**; database
|
||||
return e;
|
||||
})
|
||||
);
|
||||
addResult(`Origin check:${org}`);
|
||||
addResult($tf("obsidianLiveSyncSettingTab.msgOriginCheck", { org }));
|
||||
if (responseHeaders["access-control-allow-credentials"] != "true") {
|
||||
addResult("❗ CORS is not allowing credentials");
|
||||
addResult($tf("obsidianLiveSyncSettingTab.errCorsNotAllowingCredentials"));
|
||||
isSuccessful = false;
|
||||
} else {
|
||||
addResult("✔ CORS credentials OK");
|
||||
addResult($tf("obsidianLiveSyncSettingTab.okCorsCredentialsForOrigin"));
|
||||
}
|
||||
if (responseHeaders["access-control-allow-origin"] != org) {
|
||||
addResult(
|
||||
`⚠ CORS Origin is unmatched:${origin}->${responseHeaders["access-control-allow-origin"]}`
|
||||
$tf("obsidianLiveSyncSettingTab.warnCorsOriginUnmatched", {
|
||||
from: origin,
|
||||
to: responseHeaders["access-control-allow-origin"],
|
||||
})
|
||||
);
|
||||
} else {
|
||||
addResult("✔ CORS origin OK");
|
||||
addResult($tf("obsidianLiveSyncSettingTab.okCorsOriginMatched"));
|
||||
}
|
||||
}
|
||||
addResult("--Done--", ["ob-btn-config-head"]);
|
||||
addResult(
|
||||
"If you're having trouble with the Connection-check (even after checking config), please check your reverse proxy configuration.",
|
||||
["ob-btn-config-info"]
|
||||
);
|
||||
Logger(`Checking configuration done`, LOG_LEVEL_INFO);
|
||||
addResult($tf("obsidianLiveSyncSettingTab.msgDone"), ["ob-btn-config-head"]);
|
||||
addResult($tf("obsidianLiveSyncSettingTab.msgConnectionProxyNote"), ["ob-btn-config-info"]);
|
||||
Logger($tf("obsidianLiveSyncSettingTab.logCheckingConfigDone"), LOG_LEVEL_INFO);
|
||||
} catch (ex: any) {
|
||||
if (ex?.status == 401) {
|
||||
isSuccessful = false;
|
||||
addResult(`❗ Access forbidden.`);
|
||||
addResult(`We could not continue the test.`);
|
||||
Logger(`Checking configuration done`, LOG_LEVEL_INFO);
|
||||
addResult($tf("obsidianLiveSyncSettingTab.errAccessForbidden"));
|
||||
addResult($tf("obsidianLiveSyncSettingTab.errCannotContinueTest"));
|
||||
Logger($tf("obsidianLiveSyncSettingTab.logCheckingConfigDone"), LOG_LEVEL_INFO);
|
||||
} else {
|
||||
Logger(`Checking configuration failed`, LOG_LEVEL_NOTICE);
|
||||
Logger($tf("obsidianLiveSyncSettingTab.logCheckingConfigFailed"), LOG_LEVEL_NOTICE);
|
||||
Logger(ex);
|
||||
isSuccessful = false;
|
||||
}
|
||||
@ -1340,31 +1329,23 @@ Store only the settings. **Caution: This may lead to data corruption**; database
|
||||
return isSuccessful;
|
||||
};
|
||||
|
||||
void addPane(containerEl, "Remote Configuration", "🛰️", 0, false).then((paneEl) => {
|
||||
void addPanel(paneEl, "Remote Server").then((paneEl) => {
|
||||
void addPane(containerEl, $tf("obsidianLiveSyncSettingTab.panelRemoteConfiguration"), "🛰️", 0, false).then((paneEl) => {
|
||||
void addPanel(paneEl, $tf("obsidianLiveSyncSettingTab.titleRemoteServer")).then((paneEl) => {
|
||||
// const containerRemoteDatabaseEl = containerEl.createDiv();
|
||||
new Setting(paneEl).autoWireDropDown("remoteType", {
|
||||
holdValue: true,
|
||||
options: {
|
||||
[REMOTE_COUCHDB]: "CouchDB",
|
||||
[REMOTE_MINIO]: "Minio,S3,R2",
|
||||
[REMOTE_COUCHDB]: $tf("obsidianLiveSyncSettingTab.optionCouchDB"),
|
||||
[REMOTE_MINIO]: $tf("obsidianLiveSyncSettingTab.optionMinioS3R2"),
|
||||
},
|
||||
onUpdate: enableOnlySyncDisabled,
|
||||
});
|
||||
|
||||
void addPanel(paneEl, "Minio,S3,R2", undefined, onlyOnMinIO).then((paneEl) => {
|
||||
void addPanel(paneEl, $tf("obsidianLiveSyncSettingTab.titleMinioS3R2"), undefined, onlyOnMinIO).then((paneEl) => {
|
||||
const syncWarnMinio = this.createEl(paneEl, "div", {
|
||||
text: "",
|
||||
});
|
||||
const ObjectStorageMessage = `WARNING: This feature is a Work In Progress, so please keep in mind the following:
|
||||
- Append only architecture. A rebuild is required to shrink the storage.
|
||||
- A bit fragile.
|
||||
- When first syncing, all history will be transferred from the remote. Be mindful of data caps and slow speeds.
|
||||
- Only differences are synced live.
|
||||
|
||||
If you run into any issues, or have ideas about this feature, please create a issue on GitHub.
|
||||
I appreciate you for your great dedication.
|
||||
`;
|
||||
const ObjectStorageMessage = $tf("obsidianLiveSyncSettingTab.msgObjectStorageWarning");
|
||||
|
||||
void MarkdownRenderer.render(
|
||||
this.plugin.app,
|
||||
@ -1388,16 +1369,16 @@ I appreciate you for your great dedication.
|
||||
new Setting(paneEl).autoWireText("bucket", { holdValue: true });
|
||||
|
||||
new Setting(paneEl).autoWireToggle("useCustomRequestHandler", { holdValue: true });
|
||||
new Setting(paneEl).setName("Test Connection").addButton((button) =>
|
||||
new Setting(paneEl).setName($tf("obsidianLiveSyncSettingTab.nameTestConnection")).addButton((button) =>
|
||||
button
|
||||
.setButtonText("Test")
|
||||
.setButtonText($tf("obsidianLiveSyncSettingTab.btnTest"))
|
||||
.setDisabled(false)
|
||||
.onClick(async () => {
|
||||
await this.testConnection(this.editingSettings);
|
||||
})
|
||||
);
|
||||
new Setting(paneEl)
|
||||
.setName("Apply Settings")
|
||||
.setName($tf("obsidianLiveSyncSettingTab.nameApplySettings"))
|
||||
.setClass("wizardHidden")
|
||||
.addApplyButton([
|
||||
"remoteType",
|
||||
@ -1411,13 +1392,13 @@ I appreciate you for your great dedication.
|
||||
.addOnUpdate(onlyOnMinIO);
|
||||
});
|
||||
|
||||
void addPanel(paneEl, "CouchDB", undefined, onlyOnCouchDB).then((paneEl) => {
|
||||
void addPanel(paneEl, $tf("obsidianLiveSyncSettingTab.titleCouchDB"), undefined, onlyOnCouchDB).then((paneEl) => {
|
||||
if (this.plugin.$$isMobile()) {
|
||||
this.createEl(
|
||||
paneEl,
|
||||
"div",
|
||||
{
|
||||
text: `Cannot connect to non-HTTPS URI. Please update your config and try again.`,
|
||||
text:$tf("obsidianLiveSyncSettingTab.msgNonHTTPSWarning"),
|
||||
},
|
||||
undefined,
|
||||
visibleOnly(() => !this.editingSettings.couchDB_URI.startsWith("https://"))
|
||||
@ -1427,7 +1408,7 @@ I appreciate you for your great dedication.
|
||||
paneEl,
|
||||
"div",
|
||||
{
|
||||
text: `Configured as non-HTTPS URI. Be warned that this may not work on mobile devices.`,
|
||||
text: $tf("obsidianLiveSyncSettingTab.msgNonHTTPSInfo"),
|
||||
},
|
||||
undefined,
|
||||
visibleOnly(() => !this.editingSettings.couchDB_URI.startsWith("https://"))
|
||||
@ -1438,7 +1419,7 @@ I appreciate you for your great dedication.
|
||||
paneEl,
|
||||
"div",
|
||||
{
|
||||
text: `These settings are unable to be changed during synchronization. Please disable all syncing in the "Sync Settings" to unlock.`,
|
||||
text: $tf("obsidianLiveSyncSettingTab.msgSettingsUnchangeableDuringSync"),
|
||||
},
|
||||
undefined,
|
||||
visibleOnly(() => isAnySyncEnabled())
|
||||
@ -1463,14 +1444,12 @@ I appreciate you for your great dedication.
|
||||
});
|
||||
|
||||
new Setting(paneEl)
|
||||
.setName("Test Database Connection")
|
||||
.setName($tf("obsidianLiveSyncSettingTab.nameTestDatabaseConnection"))
|
||||
.setClass("wizardHidden")
|
||||
.setDesc(
|
||||
"Open database connection. If the remote database is not found and you have permission to create a database, the database will be created."
|
||||
)
|
||||
.setDesc($tf("obsidianLiveSyncSettingTab.descTestDatabaseConnection"))
|
||||
.addButton((button) =>
|
||||
button
|
||||
.setButtonText("Test")
|
||||
.setButtonText($tf("obsidianLiveSyncSettingTab.btnTest"))
|
||||
.setDisabled(false)
|
||||
.onClick(async () => {
|
||||
await this.testConnection();
|
||||
@ -1478,11 +1457,11 @@ I appreciate you for your great dedication.
|
||||
);
|
||||
|
||||
new Setting(paneEl)
|
||||
.setName("Validate Database Configuration")
|
||||
.setDesc("Checks and fixes any potential issues with the database config.")
|
||||
.setName($tf("obsidianLiveSyncSettingTab.nameValidateDatabaseConfig"))
|
||||
.setDesc($tf("obsidianLiveSyncSettingTab.descValidateDatabaseConfig"))
|
||||
.addButton((button) =>
|
||||
button
|
||||
.setButtonText("Check")
|
||||
.setButtonText($tf("obsidianLiveSyncSettingTab.btnCheck"))
|
||||
.setDisabled(false)
|
||||
.onClick(async () => {
|
||||
await checkConfig(checkResultDiv);
|
||||
@ -1493,7 +1472,7 @@ I appreciate you for your great dedication.
|
||||
});
|
||||
|
||||
new Setting(paneEl)
|
||||
.setName("Apply Settings")
|
||||
.setName($tf("obsidianLiveSyncSettingTab.nameApplySettings"))
|
||||
.setClass("wizardHidden")
|
||||
.addApplyButton([
|
||||
"remoteType",
|
||||
@ -1505,12 +1484,12 @@ I appreciate you for your great dedication.
|
||||
.addOnUpdate(onlyOnCouchDB);
|
||||
});
|
||||
});
|
||||
void addPanel(paneEl, "Notification").then((paneEl) => {
|
||||
void addPanel(paneEl, $tf("obsidianLiveSyncSettingTab.titleNotification")).then((paneEl) => {
|
||||
paneEl.addClass("wizardHidden");
|
||||
new Setting(paneEl).autoWireNumeric("notifyThresholdOfRemoteStorageSize", {}).setClass("wizardHidden");
|
||||
});
|
||||
|
||||
void addPanel(paneEl, "Privacy & Encryption").then((paneEl) => {
|
||||
void addPanel(paneEl, $tf("obsidianLiveSyncSettingTab.panelPrivacyEncryption")).then((paneEl) => {
|
||||
new Setting(paneEl).autoWireToggle("encrypt", { holdValue: true });
|
||||
|
||||
const isEncryptEnabled = visibleOnly(() => this.isConfiguredAs("encrypt", true));
|
||||
@ -1533,13 +1512,13 @@ I appreciate you for your great dedication.
|
||||
.setClass("wizardHidden");
|
||||
});
|
||||
|
||||
void addPanel(paneEl, "Fetch settings").then((paneEl) => {
|
||||
void addPanel(paneEl, $tf("obsidianLiveSyncSettingTab.titleFetchSettings")).then((paneEl) => {
|
||||
new Setting(paneEl)
|
||||
.setName("Fetch config from remote server")
|
||||
.setDesc("Fetch necessary settings from already configured remote server.")
|
||||
.setName($tf("obsidianLiveSyncSettingTab.titleFetchConfigFromRemote"))
|
||||
.setDesc($tf("obsidianLiveSyncSettingTab.descFetchConfigFromRemote"))
|
||||
.addButton((button) =>
|
||||
button
|
||||
.setButtonText("Fetch")
|
||||
.setButtonText($tf("obsidianLiveSyncSettingTab.buttonFetch"))
|
||||
.setDisabled(false)
|
||||
.onClick(async () => {
|
||||
const trialSetting = { ...this.initialSettings, ...this.editingSettings };
|
||||
@ -1553,15 +1532,15 @@ I appreciate you for your great dedication.
|
||||
});
|
||||
new Setting(paneEl).setClass("wizardOnly").addButton((button) =>
|
||||
button
|
||||
.setButtonText("Next")
|
||||
.setButtonText($tf("obsidianLiveSyncSettingTab.buttonNext"))
|
||||
.setCta()
|
||||
.setDisabled(false)
|
||||
.onClick(async () => {
|
||||
if (!(await checkConfig(checkResultDiv))) {
|
||||
if (
|
||||
(await this.plugin.confirm.askYesNoDialog(
|
||||
"The configuration check has failed. Do you want to continue anyway?",
|
||||
{ defaultOption: "No", title: "Remote Configuration Check Failed" }
|
||||
$tf("obsidianLiveSyncSettingTab.msgConfigCheckFailed"),
|
||||
{ defaultOption: "No", title: $tf("obsidianLiveSyncSettingTab.titleRemoteConfigCheckFailed") }
|
||||
)) == "no"
|
||||
) {
|
||||
return;
|
||||
@ -1572,8 +1551,8 @@ I appreciate you for your great dedication.
|
||||
if (isEncryptionFullyEnabled) {
|
||||
if (
|
||||
(await this.plugin.confirm.askYesNoDialog(
|
||||
"We recommend enabling End-To-End Encryption, and Path Obfuscation. Are you sure you want to continue without encryption?",
|
||||
{ defaultOption: "No", title: "Encryption is not enabled" }
|
||||
$tf("obsidianLiveSyncSettingTab.msgEnableEncryptionRecommendation"),
|
||||
{ defaultOption: "No", title: $tf("obsidianLiveSyncSettingTab.titleEncryptionNotEnabled") }
|
||||
)) == "no"
|
||||
) {
|
||||
return;
|
||||
@ -1585,8 +1564,8 @@ I appreciate you for your great dedication.
|
||||
if (!(await isPassphraseValid())) {
|
||||
if (
|
||||
(await this.plugin.confirm.askYesNoDialog(
|
||||
"Your encryption passphrase might be invalid. Are you sure you want to continue?",
|
||||
{ defaultOption: "No", title: "Encryption Passphrase Invalid?" }
|
||||
$tf("obsidianLiveSyncSettingTab.msgInvalidPassphrase"),
|
||||
{ defaultOption: "No", title: $tf("obsidianLiveSyncSettingTab.titleEncryptionPassphraseInvalid") }
|
||||
)) == "no"
|
||||
) {
|
||||
return;
|
||||
@ -1601,8 +1580,8 @@ I appreciate you for your great dedication.
|
||||
}
|
||||
if (
|
||||
(await this.plugin.confirm.askYesNoDialog(
|
||||
"Do you want to fetch the config from the remote server?",
|
||||
{ defaultOption: "Yes", title: "Fetch config" }
|
||||
$tf("obsidianLiveSyncSettingTab.msgFetchConfigFromRemote"),
|
||||
{ defaultOption: "Yes", title: $tf("obsidianLiveSyncSettingTab.titleFetchConfig") }
|
||||
)) == "yes"
|
||||
) {
|
||||
const trialSetting = { ...this.initialSettings, ...this.editingSettings };
|
||||
@ -1618,7 +1597,7 @@ I appreciate you for your great dedication.
|
||||
})
|
||||
);
|
||||
});
|
||||
void addPane(containerEl, "Sync Settings", "🔄", 30, false).then((paneEl) => {
|
||||
void addPane(containerEl, $tf("obsidianLiveSyncSettingTab.titleSyncSettings"), "🔄", 30, false).then((paneEl) => {
|
||||
if (this.editingSettings.versionUpFlash != "") {
|
||||
const c = this.createEl(
|
||||
paneEl,
|
||||
@ -1628,7 +1607,7 @@ I appreciate you for your great dedication.
|
||||
cls: "op-warn sls-setting-hidden",
|
||||
},
|
||||
(el) => {
|
||||
this.createEl(el, "button", { text: "I got it and updated." }, (e) => {
|
||||
this.createEl(el, "button", { text: $tf("obsidianLiveSyncSettingTab.btnGotItAndUpdated") }, (e) => {
|
||||
e.addClass("mod-cta");
|
||||
e.addEventListener("click", () => {
|
||||
fireAndForget(async () => {
|
||||
@ -1644,23 +1623,23 @@ I appreciate you for your great dedication.
|
||||
}
|
||||
|
||||
this.createEl(paneEl, "div", {
|
||||
text: `Please select and apply any preset item to complete the wizard.`,
|
||||
text: $tf("obsidianLiveSyncSettingTab.msgSelectAndApplyPreset"),
|
||||
cls: "wizardOnly",
|
||||
}).addClasses(["op-warn-info"]);
|
||||
|
||||
void addPanel(paneEl, "Synchronization Preset").then((paneEl) => {
|
||||
void addPanel(paneEl, $tf("obsidianLiveSyncSettingTab.titleSynchronizationPreset")).then((paneEl) => {
|
||||
const options: Record<string, string> =
|
||||
this.editingSettings.remoteType == REMOTE_COUCHDB
|
||||
? {
|
||||
NONE: "",
|
||||
LIVESYNC: "LiveSync",
|
||||
PERIODIC: "Periodic w/ batch",
|
||||
DISABLE: "Disable all automatic",
|
||||
LIVESYNC: $tf("obsidianLiveSyncSettingTab.optionLiveSync"),
|
||||
PERIODIC: $tf("obsidianLiveSyncSettingTab.optionPeriodicWithBatch"),
|
||||
DISABLE: $tf("obsidianLiveSyncSettingTab.optionDisableAllAutomatic"),
|
||||
}
|
||||
: {
|
||||
NONE: "",
|
||||
PERIODIC: "Periodic w/ batch",
|
||||
DISABLE: "Disable all automatic",
|
||||
PERIODIC: $tf("obsidianLiveSyncSettingTab.optionPeriodicWithBatch"),
|
||||
DISABLE: $tf("obsidianLiveSyncSettingTab.optionDisableAllAutomatic"),
|
||||
};
|
||||
|
||||
new Setting(paneEl)
|
||||
@ -1669,7 +1648,7 @@ I appreciate you for your great dedication.
|
||||
holdValue: true,
|
||||
})
|
||||
.addButton((button) => {
|
||||
button.setButtonText("Apply");
|
||||
button.setButtonText($tf("obsidianLiveSyncSettingTab.btnApply"));
|
||||
button.onClick(async () => {
|
||||
// await this.saveSettings(["preset"]);
|
||||
await this.saveAllDirtySettings();
|
||||
@ -1678,7 +1657,7 @@ I appreciate you for your great dedication.
|
||||
|
||||
this.addOnSaved("preset", async (currentPreset) => {
|
||||
if (currentPreset == "") {
|
||||
Logger("Select any preset.", LOG_LEVEL_NOTICE);
|
||||
Logger($tf("obsidianLiveSyncSettingTab.logSelectAnyPreset"), LOG_LEVEL_NOTICE);
|
||||
return;
|
||||
}
|
||||
const presetAllDisabled = {
|
||||
@ -1711,15 +1690,15 @@ I appreciate you for your great dedication.
|
||||
...this.editingSettings,
|
||||
...presetLiveSync,
|
||||
};
|
||||
Logger("Configured synchronization mode: LiveSync", LOG_LEVEL_NOTICE);
|
||||
Logger($tf("obsidianLiveSyncSettingTab.logConfiguredLiveSync"), LOG_LEVEL_NOTICE);
|
||||
} else if (currentPreset == "PERIODIC") {
|
||||
this.editingSettings = {
|
||||
...this.editingSettings,
|
||||
...presetPeriodic,
|
||||
};
|
||||
Logger("Configured synchronization mode: Periodic", LOG_LEVEL_NOTICE);
|
||||
Logger($tf("obsidianLiveSyncSettingTab.logConfiguredPeriodic"), LOG_LEVEL_NOTICE);
|
||||
} else {
|
||||
Logger("Configured synchronization mode: DISABLED", LOG_LEVEL_NOTICE);
|
||||
Logger($tf("obsidianLiveSyncSettingTab.logConfiguredDisabled"), LOG_LEVEL_NOTICE);
|
||||
this.editingSettings = {
|
||||
...this.editingSettings,
|
||||
...presetAllDisabled,
|
||||
@ -1737,8 +1716,8 @@ I appreciate you for your great dedication.
|
||||
// this.resetEditingSettings();
|
||||
if (
|
||||
(await this.plugin.confirm.askYesNoDialog(
|
||||
"All done! Do you want to generate a setup URI to set up other devices?",
|
||||
{ defaultOption: "Yes", title: "Congratulations!" }
|
||||
$tf("obsidianLiveSyncSettingTab.msgGenerateSetupURI"),
|
||||
{ defaultOption: "Yes", title: $tf("obsidianLiveSyncSettingTab.titleCongratulations") }
|
||||
)) == "yes"
|
||||
) {
|
||||
eventHub.emitEvent(EVENT_REQUEST_COPY_SETUP_URI);
|
||||
@ -1758,7 +1737,7 @@ I appreciate you for your great dedication.
|
||||
}
|
||||
});
|
||||
});
|
||||
void addPanel(paneEl, "Synchronization Method").then((paneEl) => {
|
||||
void addPanel(paneEl, $tf("obsidianLiveSyncSettingTab.titleSynchronizationMethod")).then((paneEl) => {
|
||||
paneEl.addClass("wizardHidden");
|
||||
|
||||
// const onlyOnLiveSync = visibleOnly(() => this.isConfiguredAs("syncMode", "LIVESYNC"));
|
||||
@ -1768,11 +1747,14 @@ I appreciate you for your great dedication.
|
||||
const optionsSyncMode =
|
||||
this.editingSettings.remoteType == REMOTE_COUCHDB
|
||||
? {
|
||||
ONEVENTS: "On events",
|
||||
PERIODIC: "Periodic and on events",
|
||||
LIVESYNC: "LiveSync",
|
||||
ONEVENTS: $tf("obsidianLiveSyncSettingTab.optionOnEvents"),
|
||||
PERIODIC: $tf("obsidianLiveSyncSettingTab.optionPeriodicAndEvents"),
|
||||
LIVESYNC: $tf("obsidianLiveSyncSettingTab.optionLiveSync"),
|
||||
}
|
||||
: { ONEVENTS: "On events", PERIODIC: "Periodic and on events" };
|
||||
: {
|
||||
ONEVENTS: $tf("obsidianLiveSyncSettingTab.optionOnEvents"),
|
||||
PERIODIC: $tf("obsidianLiveSyncSettingTab.optionPeriodicAndEvents"),
|
||||
};
|
||||
|
||||
new Setting(paneEl)
|
||||
.autoWireDropDown("syncMode", {
|
||||
@ -1817,7 +1799,7 @@ I appreciate you for your great dedication.
|
||||
.autoWireToggle("syncAfterMerge", { onUpdate: onlyOnNonLiveSync });
|
||||
});
|
||||
|
||||
void addPanel(paneEl, "Update thinning").then((paneEl) => {
|
||||
void addPanel(paneEl, $tf("obsidianLiveSyncSettingTab.titleUpdateThinning")).then((paneEl) => {
|
||||
paneEl.addClass("wizardHidden");
|
||||
new Setting(paneEl).setClass("wizardHidden").autoWireToggle("batchSave");
|
||||
new Setting(paneEl).setClass("wizardHidden").autoWireNumeric("batchSaveMinimumDelay", {
|
||||
@ -1830,13 +1812,13 @@ I appreciate you for your great dedication.
|
||||
});
|
||||
});
|
||||
|
||||
void addPanel(paneEl, "Deletion Propagation", undefined, undefined, LEVEL_ADVANCED).then((paneEl) => {
|
||||
void addPanel(paneEl, $tf("obsidianLiveSyncSettingTab.titleDeletionPropagation"), undefined, undefined, LEVEL_ADVANCED).then((paneEl) => {
|
||||
paneEl.addClass("wizardHidden");
|
||||
new Setting(paneEl).setClass("wizardHidden").autoWireToggle("trashInsteadDelete");
|
||||
|
||||
new Setting(paneEl).setClass("wizardHidden").autoWireToggle("doNotDeleteFolder");
|
||||
});
|
||||
void addPanel(paneEl, "Conflict resolution", undefined, undefined, LEVEL_ADVANCED).then((paneEl) => {
|
||||
void addPanel(paneEl, $tf("obsidianLiveSyncSettingTab.titleConflictResolution"), undefined, undefined, LEVEL_ADVANCED).then((paneEl) => {
|
||||
paneEl.addClass("wizardHidden");
|
||||
|
||||
new Setting(paneEl).setClass("wizardHidden").autoWireToggle("resolveConflictsByNewerFile");
|
||||
@ -1846,7 +1828,7 @@ I appreciate you for your great dedication.
|
||||
new Setting(paneEl).setClass("wizardHidden").autoWireToggle("showMergeDialogOnlyOnActive");
|
||||
});
|
||||
|
||||
void addPanel(paneEl, "Sync settings via markdown", undefined, undefined, LEVEL_ADVANCED).then((paneEl) => {
|
||||
void addPanel(paneEl, $tf("obsidianLiveSyncSettingTab.titleSyncSettingsViaMarkdown"), undefined, undefined, LEVEL_ADVANCED).then((paneEl) => {
|
||||
paneEl.addClass("wizardHidden");
|
||||
|
||||
new Setting(paneEl)
|
||||
@ -1858,14 +1840,14 @@ I appreciate you for your great dedication.
|
||||
new Setting(paneEl).autoWireToggle("notifyAllSettingSyncFile");
|
||||
});
|
||||
|
||||
void addPanel(paneEl, "Hidden Files", undefined, undefined, LEVEL_ADVANCED).then((paneEl) => {
|
||||
void addPanel(paneEl, $tf("obsidianLiveSyncSettingTab.titleHiddenFiles"), undefined, undefined, LEVEL_ADVANCED).then((paneEl) => {
|
||||
paneEl.addClass("wizardHidden");
|
||||
|
||||
const LABEL_ENABLED = "🔁 : Enabled";
|
||||
const LABEL_DISABLED = "⏹️ : Disabled";
|
||||
const LABEL_ENABLED = $tf("obsidianLiveSyncSettingTab.labelEnabled");
|
||||
const LABEL_DISABLED = $tf("obsidianLiveSyncSettingTab.labelDisabled");
|
||||
|
||||
const hiddenFileSyncSetting = new Setting(paneEl)
|
||||
.setName("Hidden file synchronization")
|
||||
.setName($tf("obsidianLiveSyncSettingTab.nameHiddenFileSynchronization"))
|
||||
.setClass("wizardHidden");
|
||||
const hiddenFileSyncSettingEl = hiddenFileSyncSetting.settingEl;
|
||||
const hiddenFileSyncSettingDiv = hiddenFileSyncSettingEl.createDiv("");
|
||||
@ -1874,10 +1856,10 @@ I appreciate you for your great dedication.
|
||||
: LABEL_DISABLED;
|
||||
if (this.editingSettings.syncInternalFiles) {
|
||||
new Setting(paneEl)
|
||||
.setName("Disable Hidden files sync")
|
||||
.setName($tf("obsidianLiveSyncSettingTab.nameDisableHiddenFileSync"))
|
||||
.setClass("wizardHidden")
|
||||
.addButton((button) => {
|
||||
button.setButtonText("Disable").onClick(async () => {
|
||||
button.setButtonText($tf("obsidianLiveSyncSettingTab.btnDisable")).onClick(async () => {
|
||||
this.editingSettings.syncInternalFiles = false;
|
||||
await this.saveAllDirtySettings();
|
||||
this.display();
|
||||
@ -1885,7 +1867,7 @@ I appreciate you for your great dedication.
|
||||
});
|
||||
} else {
|
||||
new Setting(paneEl)
|
||||
.setName("Enable Hidden files sync")
|
||||
.setName($tf("obsidianLiveSyncSettingTab.nameEnableHiddenFileSync"))
|
||||
.setClass("wizardHidden")
|
||||
.addButton((button) => {
|
||||
button.setButtonText("Merge").onClick(async () => {
|
||||
@ -2881,7 +2863,6 @@ ${stringifyYaml(pluginConfig)}`;
|
||||
})
|
||||
)
|
||||
.addOnUpdate(onlyOnCouchDB);
|
||||
|
||||
new Setting(paneEl)
|
||||
.setName("Reset journal received history")
|
||||
.setDesc(
|
||||
|
@ -8,7 +8,7 @@ import {
|
||||
EVENT_SETTING_SAVED,
|
||||
eventHub,
|
||||
} from "../../common/events.ts";
|
||||
import { $f, setLang } from "../../lib/src/common/i18n.ts";
|
||||
import { $tf, setLang } from "../../lib/src/common/i18n.ts";
|
||||
import { versionNumberString2Number } from "../../lib/src/string_and_binary/convert.ts";
|
||||
import { cancelAllPeriodicTask, cancelAllTasks } from "octagonal-wheels/concurrency/task";
|
||||
import { stopAllRunningProcessors } from "octagonal-wheels/concurrency/processor";
|
||||
@ -20,25 +20,16 @@ export class ModuleLiveSyncMain extends AbstractModule implements ICoreModule {
|
||||
if (!(await this.core.$everyOnLayoutReady())) return;
|
||||
eventHub.emitEvent(EVENT_LAYOUT_READY);
|
||||
if (this.settings.suspendFileWatching || this.settings.suspendParseReplicationResult) {
|
||||
const ANSWER_KEEP = "Keep LiveSync disabled";
|
||||
const ANSWER_RESUME = "Resume and restart Obsidian";
|
||||
const message = `Self-hosted LiveSync has been configured to ignore some events. Is this correct?
|
||||
|
||||
| Type | Status | Note |
|
||||
|:---:|:---:|---|
|
||||
| Storage Events | ${this.settings.suspendFileWatching ? "suspended" : "active"} | Every modification will be ignored |
|
||||
| Database Events | ${this.settings.suspendParseReplicationResult ? "suspended" : "active"} | Every synchronised change will be postponed |
|
||||
|
||||
Do you want to resume them and restart Obsidian?
|
||||
|
||||
> [!DETAILS]-
|
||||
> These flags are set by the plug-in while rebuilding, or fetching. If the process ends abnormally, it may be kept unintended.
|
||||
> If you are not sure, you can try to rerun these processes. Make sure to back your vault up.
|
||||
`;
|
||||
const ANSWER_KEEP = $tf("moduleLiveSyncMain.optionKeepLiveSyncDisabled");
|
||||
const ANSWER_RESUME = $tf("moduleLiveSyncMain.optionResumeAndRestart");
|
||||
const message = $tf("moduleLiveSyncMain.msgScramEnabled", {
|
||||
fileWatchingStatus: this.settings.suspendFileWatching ? "suspended" : "active",
|
||||
parseReplicationStatus: this.settings.suspendParseReplicationResult ? "suspended" : "active"
|
||||
});
|
||||
if (
|
||||
(await this.core.confirm.askSelectStringDialogue(message, [ANSWER_KEEP, ANSWER_RESUME], {
|
||||
defaultAction: ANSWER_KEEP,
|
||||
title: "Scram Enabled",
|
||||
title: $tf("moduleLiveSyncMain.titleScramEnabled"),
|
||||
})) == ANSWER_RESUME
|
||||
) {
|
||||
this.settings.suspendFileWatching = false;
|
||||
@ -56,11 +47,11 @@ Do you want to resume them and restart Obsidian?
|
||||
if (!(await this.core.$everyOnFirstInitialize())) return;
|
||||
await this.core.$$realizeSettingSyncMode();
|
||||
fireAndForget(async () => {
|
||||
this._log(`Additional safety scan..`, LOG_LEVEL_VERBOSE);
|
||||
this._log($tf("moduleLiveSyncMain.logAdditionalSafetyScan"), LOG_LEVEL_VERBOSE);
|
||||
if (!(await this.core.$allScanStat())) {
|
||||
this._log(`Additional safety scan has failed on a module`, LOG_LEVEL_NOTICE);
|
||||
this._log($tf("moduleLiveSyncMain.logSafetyScanFailed"), LOG_LEVEL_NOTICE);
|
||||
} else {
|
||||
this._log(`Additional safety scan completed`, LOG_LEVEL_VERBOSE);
|
||||
this._log($tf("moduleLiveSyncMain.logSafetyScanCompleted"), LOG_LEVEL_VERBOSE);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -80,9 +71,9 @@ Do you want to resume them and restart Obsidian?
|
||||
this.$$wireUpEvents();
|
||||
// debugger;
|
||||
eventHub.emitEvent(EVENT_PLUGIN_LOADED, this.core);
|
||||
this._log("loading plugin");
|
||||
this._log($tf("moduleLiveSyncMain.logLoadingPlugin"));
|
||||
if (!(await this.core.$everyOnloadStart())) {
|
||||
this._log("Plugin initialisation was cancelled by a module", LOG_LEVEL_NOTICE);
|
||||
this._log($tf("moduleLiveSyncMain.logPluginInitCancelled"), LOG_LEVEL_NOTICE);
|
||||
return;
|
||||
}
|
||||
// this.addUIs();
|
||||
@ -91,10 +82,10 @@ Do you want to resume them and restart Obsidian?
|
||||
//@ts-ignore
|
||||
const packageVersion: string = PACKAGE_VERSION || "0.0.0";
|
||||
|
||||
this._log($f`Self-hosted LiveSync${" v"}${manifestVersion} ${packageVersion}`);
|
||||
this._log($tf("moduleLiveSyncMain.logPluginVersion", { manifestVersion, packageVersion }));
|
||||
await this.core.$$loadSettings();
|
||||
if (!(await this.core.$everyOnloadAfterLoadSettings())) {
|
||||
this._log("Plugin initialisation was cancelled by a module", LOG_LEVEL_NOTICE);
|
||||
this._log($tf("moduleLiveSyncMain.logPluginInitCancelled"), LOG_LEVEL_NOTICE);
|
||||
return;
|
||||
}
|
||||
const lsKey = "obsidian-live-sync-ver" + this.core.$$getVaultName();
|
||||
@ -102,7 +93,7 @@ Do you want to resume them and restart Obsidian?
|
||||
|
||||
const lastVersion = ~~(versionNumberString2Number(manifestVersion) / 1000);
|
||||
if (lastVersion > this.settings.lastReadUpdates && this.settings.isConfigured) {
|
||||
this._log($f`LiveSync has updated, please read the changelog!`, LOG_LEVEL_NOTICE);
|
||||
this._log($tf("moduleLiveSyncMain.logReadChangelog"), LOG_LEVEL_NOTICE);
|
||||
}
|
||||
|
||||
//@ts-ignore
|
||||
@ -117,7 +108,7 @@ Do you want to resume them and restart Obsidian?
|
||||
this.settings.syncOnFileOpen = false;
|
||||
this.settings.syncAfterMerge = false;
|
||||
this.settings.periodicReplication = false;
|
||||
this.settings.versionUpFlash = $f`LiveSync has been updated, In case of breaking updates, all automatic synchronization has been temporarily disabled. Ensure that all devices are up to date before enabling.`;
|
||||
this.settings.versionUpFlash = $tf("moduleLiveSyncMain.logVersionUpdate");
|
||||
await this.saveSettings();
|
||||
}
|
||||
localStorage.setItem(lsKey, `${VER}`);
|
||||
@ -148,7 +139,7 @@ Do you want to resume them and restart Obsidian?
|
||||
}
|
||||
await this.localDatabase.close();
|
||||
}
|
||||
this._log($f`unloading plugin`);
|
||||
this._log($tf("moduleLiveSyncMain.logUnloadingPlugin"));
|
||||
}
|
||||
|
||||
async $$realizeSettingSyncMode(): Promise<void> {
|
||||
|
Loading…
x
Reference in New Issue
Block a user