mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-09-04 09:47:06 +00:00
Compare commits
22
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7b4f7bf514 | ||
|
|
ba8f910865 | ||
|
|
c58e462057 | ||
|
|
a09c59aaba | ||
|
|
cbbae33d67 | ||
|
|
be9c328f4a | ||
|
|
a9b146a0ab | ||
|
|
5d5e448c6b | ||
|
|
725db213c3 | ||
|
|
a2441b9870 | ||
|
|
01c38268c7 | ||
|
|
64e17ab920 | ||
|
|
d8fccd6e4b | ||
|
|
50ad4c4bdf | ||
|
|
8cb5d6d87f | ||
|
|
4b47ebbd4d | ||
|
|
b28871ab67 | ||
|
|
2c35f45765 | ||
|
|
caa8c92cbe | ||
|
|
2cefff43bb | ||
|
|
6b37ea8778 | ||
|
|
01c060558a |
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "obsidian-livesync",
|
||||
"name": "Self-hosted LiveSync",
|
||||
"version": "1.0.23",
|
||||
"version": "1.0.24",
|
||||
"minAppVersion": "1.7.2",
|
||||
"description": "Community implementation of self-hosted livesync. Reflect your vault changes to some other devices immediately. Please make sure to disable other synchronize solutions to avoid content corruption or duplication.",
|
||||
"author": "vorotamoroz",
|
||||
|
||||
Generated
+5
-5
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "obsidian-livesync",
|
||||
"version": "1.0.23",
|
||||
"version": "1.0.24",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "obsidian-livesync",
|
||||
"version": "1.0.23",
|
||||
"version": "1.0.24",
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
"src/apps/cli",
|
||||
@@ -12937,7 +12937,7 @@
|
||||
},
|
||||
"src/apps/cli": {
|
||||
"name": "self-hosted-livesync-cli",
|
||||
"version": "1.0.23-cli",
|
||||
"version": "1.0.24-cli",
|
||||
"dependencies": {
|
||||
"chokidar": "^4.0.0",
|
||||
"minimatch": "^10.2.5",
|
||||
@@ -12962,7 +12962,7 @@
|
||||
},
|
||||
"src/apps/webapp": {
|
||||
"name": "livesync-webapp",
|
||||
"version": "1.0.23-webapp",
|
||||
"version": "1.0.24-webapp",
|
||||
"dependencies": {
|
||||
"octagonal-wheels": "^0.1.53"
|
||||
},
|
||||
@@ -12974,7 +12974,7 @@
|
||||
}
|
||||
},
|
||||
"src/apps/webpeer": {
|
||||
"version": "1.0.23-webpeer",
|
||||
"version": "1.0.24-webpeer",
|
||||
"dependencies": {
|
||||
"octagonal-wheels": "^0.1.53"
|
||||
},
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "obsidian-livesync",
|
||||
"version": "1.0.23",
|
||||
"version": "1.0.24",
|
||||
"description": "Reflect your vault changes to some other devices immediately. Please make sure to disable other synchronize solutions to avoid content corruption or duplication.",
|
||||
"main": "main.js",
|
||||
"type": "module",
|
||||
|
||||
+44
-32
@@ -48,7 +48,7 @@ CLI Main
|
||||
- Settings management (JSON file)
|
||||
- Graceful shutdown handling
|
||||
|
||||
## Usage
|
||||
## Command overview
|
||||
|
||||
The CLI operates on a **database directory** which contains PouchDB data and settings.
|
||||
|
||||
@@ -151,6 +151,46 @@ npm run cli -- [database-path] [command] [args...]
|
||||
node src/apps/cli/dist/index.cjs [database-path] [command] [args...]
|
||||
```
|
||||
|
||||
### systemd installation
|
||||
|
||||
The `deploy/` directory contains a systemd unit template and an install script.
|
||||
|
||||
**Automated installation (user service, recommended):**
|
||||
|
||||
```bash
|
||||
bash src/apps/cli/deploy/install.sh --vault /path/to/vault
|
||||
```
|
||||
|
||||
**With a polling interval:**
|
||||
|
||||
```bash
|
||||
bash src/apps/cli/deploy/install.sh --vault /path/to/vault --interval 60
|
||||
```
|
||||
|
||||
**System-wide installation** (requires root or `sudo` for `/etc/systemd/system/`):
|
||||
|
||||
```bash
|
||||
bash src/apps/cli/deploy/install.sh --system --vault /path/to/vault
|
||||
```
|
||||
|
||||
The script:
|
||||
|
||||
1. Installs the repository dependencies and builds the CLI.
|
||||
2. Installs the complete CLI bundle and its production dependencies under `~/.local/lib/livesync-cli` (user) or `/usr/local/lib/livesync-cli` (system), then checks that the installed CLI can start.
|
||||
3. Installs the command wrapper as `~/.local/bin/livesync-cli` (user) or `/usr/local/bin/livesync-cli` (system).
|
||||
4. Writes the unit file to `~/.config/systemd/user/livesync-cli.service` (user) or `/etc/systemd/system/livesync-cli.service` (system).
|
||||
5. Reloads systemd, enables and starts the service, and reports success only after confirming that the service remains active.
|
||||
|
||||
Ensure that `~/.local/bin` for a user installation, or `/usr/local/bin` for a system-wide installation, is on the shell's `PATH` before invoking `livesync-cli` interactively. For example, add the following to the appropriate shell start-up file for a user installation when needed:
|
||||
|
||||
```bash
|
||||
export PATH="$HOME/.local/bin:$PATH"
|
||||
```
|
||||
|
||||
The generated systemd unit uses the wrapper's absolute path and does not depend on the shell's `PATH`.
|
||||
|
||||
**Manual setup** — if you prefer to manage the unit yourself, copy `deploy/livesync-cli.service`, replace `LIVESYNC_BIN` and `LIVESYNC_VAULT_PATH` with the actual binary path and Vault path, then install it in the appropriate systemd directory.
|
||||
|
||||
### Docker
|
||||
|
||||
A Docker image is provided for headless / server deployments. Build from the repository root:
|
||||
@@ -210,7 +250,9 @@ candidate carries the host's public IP and peers can connect normally.
|
||||
|
||||
### Adding `livesync-cli` alias
|
||||
|
||||
To use the `livesync-cli` command globally, you can add an alias to your shell configuration file (e.g., `.zshrc` or `.bashrc`).
|
||||
If you used the [systemd installer](#systemd-installation), no alias is required: it installs the `livesync-cli` wrapper in `~/.local/bin` or `/usr/local/bin`. If the installed command is not found, follow the `PATH` guidance in the systemd installation section.
|
||||
|
||||
The aliases below are only for running the CLI from a source checkout, or from Docker without using the installer. Add the appropriate alias to your shell configuration file, such as `.zshrc` or `.bashrc`.
|
||||
|
||||
If you are using `npm run`, add the following line:
|
||||
|
||||
@@ -506,36 +548,6 @@ Patterns apply in both directions: the chokidar watcher will not emit events for
|
||||
|
||||
Changes to this file require a daemon restart to take effect.
|
||||
|
||||
### Systemd Installation
|
||||
|
||||
The `deploy/` directory contains a systemd unit template and an install script.
|
||||
|
||||
**Automated install (user service, recommended):**
|
||||
|
||||
```bash
|
||||
bash src/apps/cli/deploy/install.sh --vault /path/to/vault
|
||||
```
|
||||
|
||||
**With polling interval:**
|
||||
|
||||
```bash
|
||||
bash src/apps/cli/deploy/install.sh --vault /path/to/vault --interval 60
|
||||
```
|
||||
|
||||
**System-wide install** (requires root / sudo for `/etc/systemd/system/`):
|
||||
|
||||
```bash
|
||||
bash src/apps/cli/deploy/install.sh --system --vault /path/to/vault
|
||||
```
|
||||
|
||||
The script:
|
||||
1. Builds the CLI (`npm install` + `npm run build`).
|
||||
2. Installs the binary to `~/.local/bin/livesync-cli` (user) or `/usr/local/bin/livesync-cli` (system).
|
||||
3. Writes the unit file to `~/.config/systemd/user/livesync-cli.service` (user) or `/etc/systemd/system/livesync-cli.service` (system).
|
||||
4. Runs `systemctl [--user] daemon-reload && systemctl [--user] enable --now livesync-cli`.
|
||||
|
||||
**Manual setup** — if you prefer to manage the unit yourself, copy `deploy/livesync-cli.service`, replace `LIVESYNC_BIN` and `LIVESYNC_VAULT_PATH` with the actual binary path and vault path, then install to the appropriate systemd directory.
|
||||
|
||||
### Planned options:
|
||||
|
||||
- `--immediate`: Perform sync after the command (e.g. `push`, `pull`, `put`, `rm`).
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd -- "$SCRIPT_DIR/../../.." && pwd)"
|
||||
REPO_ROOT="$(cd -- "$SCRIPT_DIR/../../../.." && pwd)"
|
||||
CLI_DIR="$REPO_ROOT/src/apps/cli"
|
||||
SERVICE_TEMPLATE="$SCRIPT_DIR/livesync-cli.service"
|
||||
|
||||
@@ -104,30 +104,70 @@ fi
|
||||
# ── Install binary ───────────────────────────────────────────────────────────
|
||||
if [[ "$INSTALL_MODE" == "user" ]]; then
|
||||
BIN_DIR="$HOME/.local/bin"
|
||||
LIB_DIR="$HOME/.local/lib/livesync-cli"
|
||||
UNIT_DIR="$HOME/.config/systemd/user"
|
||||
SYSTEMCTL_FLAGS="--user"
|
||||
else
|
||||
BIN_DIR="/usr/local/bin"
|
||||
LIB_DIR="/usr/local/lib/livesync-cli"
|
||||
UNIT_DIR="/etc/systemd/system"
|
||||
SYSTEMCTL_FLAGS=""
|
||||
fi
|
||||
|
||||
mkdir -p "$BIN_DIR"
|
||||
LIB_PARENT="$(dirname -- "$LIB_DIR")"
|
||||
mkdir -p "$BIN_DIR" "$LIB_PARENT"
|
||||
|
||||
LIVESYNC_BIN="$BIN_DIR/livesync-cli"
|
||||
LIVESYNC_JS="$BIN_DIR/livesync-cli.js"
|
||||
LIVESYNC_JS="$LIB_DIR/dist/index.cjs"
|
||||
|
||||
# Copy the CJS bundle so the wrapper is self-contained and independent of the
|
||||
# build directory location.
|
||||
cp "$BUILT_CJS" "$LIVESYNC_JS"
|
||||
# Build a complete runtime payload before replacing any previous installation.
|
||||
# The Vite output contains hashed sibling chunks, while some Node dependencies
|
||||
# deliberately remain external and must be installed next to the bundle.
|
||||
PAYLOAD_STAGING="$(mktemp -d "$LIB_PARENT/.livesync-cli.install.XXXXXX")"
|
||||
cleanup_payload() {
|
||||
if [[ -n "$PAYLOAD_STAGING" ]] && [[ -e "$PAYLOAD_STAGING" ]]; then
|
||||
rm -rf -- "$PAYLOAD_STAGING"
|
||||
fi
|
||||
}
|
||||
trap cleanup_payload EXIT
|
||||
|
||||
# Write a bash wrapper that invokes node on the installed bundle.
|
||||
cp "$CLI_DIR/package.json" "$PAYLOAD_STAGING/package.json"
|
||||
npm install --omit=dev --no-audit --no-fund --prefix "$PAYLOAD_STAGING"
|
||||
cp -R "$CLI_DIR/dist" "$PAYLOAD_STAGING/dist"
|
||||
|
||||
if ! node "$PAYLOAD_STAGING/dist/index.cjs" --help >/dev/null; then
|
||||
echo "Error: installed CLI failed its start-up check" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
PAYLOAD_BACKUP=""
|
||||
if [[ -e "$LIB_DIR" ]] || [[ -L "$LIB_DIR" ]]; then
|
||||
PAYLOAD_BACKUP="$(mktemp -d "$LIB_PARENT/.livesync-cli.backup.XXXXXX")"
|
||||
rmdir "$PAYLOAD_BACKUP"
|
||||
mv -- "$LIB_DIR" "$PAYLOAD_BACKUP"
|
||||
fi
|
||||
|
||||
if ! mv -- "$PAYLOAD_STAGING" "$LIB_DIR"; then
|
||||
if [[ -n "$PAYLOAD_BACKUP" ]]; then
|
||||
mv -- "$PAYLOAD_BACKUP" "$LIB_DIR"
|
||||
fi
|
||||
echo "Error: failed to install the CLI files at $LIB_DIR" >&2
|
||||
exit 1
|
||||
fi
|
||||
PAYLOAD_STAGING=""
|
||||
|
||||
if [[ -n "$PAYLOAD_BACKUP" ]]; then
|
||||
rm -rf -- "$PAYLOAD_BACKUP"
|
||||
fi
|
||||
trap - EXIT
|
||||
|
||||
# Write a bash wrapper that invokes Node.js on the installed payload.
|
||||
cat > "$LIVESYNC_BIN" <<WRAPPER
|
||||
#!/usr/bin/env bash
|
||||
exec node "$LIVESYNC_JS" "\$@"
|
||||
WRAPPER
|
||||
chmod +x "$LIVESYNC_BIN"
|
||||
echo "[INFO] Installed bundle: $LIVESYNC_JS"
|
||||
echo "[INFO] Installed CLI files: $LIB_DIR"
|
||||
echo "[INFO] Installed binary: $LIVESYNC_BIN"
|
||||
|
||||
# ── Write systemd unit ───────────────────────────────────────────────────────
|
||||
@@ -180,6 +220,15 @@ systemctl $SYSTEMCTL_FLAGS daemon-reload
|
||||
# shellcheck disable=SC2086
|
||||
systemctl $SYSTEMCTL_FLAGS enable --now livesync-cli
|
||||
|
||||
sleep 1
|
||||
# shellcheck disable=SC2086
|
||||
if ! systemctl $SYSTEMCTL_FLAGS is-active --quiet livesync-cli; then
|
||||
echo "Error: livesync-cli service did not remain active after startup." >&2
|
||||
# shellcheck disable=SC2086
|
||||
systemctl $SYSTEMCTL_FLAGS status livesync-cli --no-pager || true
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "[Done] livesync-cli service installed and started."
|
||||
echo ""
|
||||
|
||||
@@ -0,0 +1,192 @@
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { chmod, copyFile, mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { delimiter, dirname, join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
|
||||
const deploySourceDirectory = dirname(fileURLToPath(import.meta.url));
|
||||
const temporaryDirectories: string[] = [];
|
||||
|
||||
type InstallerFixture = {
|
||||
cliDirectory: string;
|
||||
environment: NodeJS.ProcessEnv;
|
||||
homeDirectory: string;
|
||||
installerPath: string;
|
||||
npmCallLog: string;
|
||||
repositoryRoot: string;
|
||||
systemctlCallLog: string;
|
||||
vaultDirectory: string;
|
||||
};
|
||||
|
||||
async function writeExecutable(path: string, content: string): Promise<void> {
|
||||
await writeFile(path, `${content}\n`, "utf8");
|
||||
await chmod(path, 0o755);
|
||||
}
|
||||
|
||||
async function createInstallerFixture(serviceActive: boolean): Promise<InstallerFixture> {
|
||||
const temporaryDirectory = await mkdtemp(join(tmpdir(), "livesync-cli-installer-"));
|
||||
temporaryDirectories.push(temporaryDirectory);
|
||||
|
||||
const repositoryRoot = join(temporaryDirectory, "repository");
|
||||
const cliDirectory = join(repositoryRoot, "src", "apps", "cli");
|
||||
const deployDirectory = join(cliDirectory, "deploy");
|
||||
const distDirectory = join(cliDirectory, "dist");
|
||||
const fakeBinDirectory = join(temporaryDirectory, "fake-bin");
|
||||
const homeDirectory = join(temporaryDirectory, "home");
|
||||
const vaultDirectory = join(temporaryDirectory, "vault");
|
||||
const npmCallLog = join(temporaryDirectory, "npm-calls.log");
|
||||
const systemctlCallLog = join(temporaryDirectory, "systemctl-calls.log");
|
||||
|
||||
await Promise.all([
|
||||
mkdir(deployDirectory, { recursive: true }),
|
||||
mkdir(distDirectory, { recursive: true }),
|
||||
mkdir(fakeBinDirectory, { recursive: true }),
|
||||
mkdir(homeDirectory, { recursive: true }),
|
||||
mkdir(vaultDirectory, { recursive: true }),
|
||||
]);
|
||||
|
||||
await Promise.all([
|
||||
copyFile(join(deploySourceDirectory, "install.sh"), join(deployDirectory, "install.sh")),
|
||||
copyFile(join(deploySourceDirectory, "livesync-cli.service"), join(deployDirectory, "livesync-cli.service")),
|
||||
writeFile(
|
||||
join(repositoryRoot, "package.json"),
|
||||
JSON.stringify({ private: true, workspaces: ["src/apps/*"] }),
|
||||
"utf8"
|
||||
),
|
||||
writeFile(
|
||||
join(cliDirectory, "package.json"),
|
||||
JSON.stringify({
|
||||
name: "self-hosted-livesync-cli",
|
||||
private: true,
|
||||
version: "0.0.0",
|
||||
dependencies: { "fixture-runtime-dependency": "1.0.0" },
|
||||
}),
|
||||
"utf8"
|
||||
),
|
||||
writeFile(
|
||||
join(distDirectory, "index.cjs"),
|
||||
'const chunk = require("./chunk.cjs");\n' +
|
||||
'const dependency = require("fixture-runtime-dependency");\n' +
|
||||
"process.stdout.write(`${chunk}:${dependency}\\n`);\n",
|
||||
"utf8"
|
||||
),
|
||||
writeFile(join(distDirectory, "chunk.cjs"), 'module.exports = "chunk-ready";\n', "utf8"),
|
||||
]);
|
||||
|
||||
await writeExecutable(
|
||||
join(fakeBinDirectory, "npm"),
|
||||
[
|
||||
"#!/usr/bin/env bash",
|
||||
"set -euo pipefail",
|
||||
'printf \'%s|%s\\n\' "$PWD" "$*" >> "$NPM_CALL_LOG"',
|
||||
'prefix=""',
|
||||
"expect_prefix=0",
|
||||
'for argument in "$@"; do',
|
||||
' if [[ "$expect_prefix" -eq 1 ]]; then',
|
||||
' prefix="$argument"',
|
||||
" expect_prefix=0",
|
||||
' elif [[ "$argument" == "--prefix" ]]; then',
|
||||
" expect_prefix=1",
|
||||
" fi",
|
||||
"done",
|
||||
'if [[ -n "$prefix" ]]; then',
|
||||
' mkdir -p "$prefix/node_modules/fixture-runtime-dependency"',
|
||||
" printf '%s\\n' 'module.exports = \"dependency-ready\";' > \"$prefix/node_modules/fixture-runtime-dependency/index.js\"",
|
||||
"fi",
|
||||
].join("\n")
|
||||
);
|
||||
await writeExecutable(
|
||||
join(fakeBinDirectory, "systemctl"),
|
||||
[
|
||||
"#!/usr/bin/env bash",
|
||||
"set -euo pipefail",
|
||||
'printf \'%s\\n\' "$*" >> "$SYSTEMCTL_CALL_LOG"',
|
||||
'if [[ " $* " == *" is-active "* ]]; then',
|
||||
' [[ "${FAKE_SYSTEMCTL_ACTIVE:-1}" == "1" ]]',
|
||||
" exit",
|
||||
"fi",
|
||||
'if [[ " $* " == *" status "* ]]; then',
|
||||
" printf '%s\\n' \"fixture service status\"",
|
||||
"fi",
|
||||
].join("\n")
|
||||
);
|
||||
await writeExecutable(join(fakeBinDirectory, "sleep"), ["#!/usr/bin/env bash", "exit 0"].join("\n"));
|
||||
|
||||
return {
|
||||
cliDirectory,
|
||||
environment: {
|
||||
...process.env,
|
||||
FAKE_SYSTEMCTL_ACTIVE: serviceActive ? "1" : "0",
|
||||
HOME: homeDirectory,
|
||||
NPM_CALL_LOG: npmCallLog,
|
||||
PATH: `${fakeBinDirectory}${delimiter}${process.env.PATH ?? ""}`,
|
||||
SYSTEMCTL_CALL_LOG: systemctlCallLog,
|
||||
},
|
||||
homeDirectory,
|
||||
installerPath: join(deployDirectory, "install.sh"),
|
||||
npmCallLog,
|
||||
repositoryRoot,
|
||||
systemctlCallLog,
|
||||
vaultDirectory,
|
||||
};
|
||||
}
|
||||
|
||||
function runInstaller(fixture: InstallerFixture) {
|
||||
return spawnSync("bash", [fixture.installerPath, "--vault", fixture.vaultDirectory], {
|
||||
encoding: "utf8",
|
||||
env: fixture.environment,
|
||||
});
|
||||
}
|
||||
|
||||
afterEach(async () => {
|
||||
await Promise.all(
|
||||
temporaryDirectories.splice(0).map((directory) => rm(directory, { recursive: true, force: true }))
|
||||
);
|
||||
});
|
||||
|
||||
describe.skipIf(process.platform === "win32")("CLI systemd installer", () => {
|
||||
it("installs a runnable CLI independently of the source repository", async () => {
|
||||
const fixture = await createInstallerFixture(true);
|
||||
|
||||
const installation = runInstaller(fixture);
|
||||
|
||||
expect(installation.error).toBeUndefined();
|
||||
expect(installation.status, installation.stderr).toBe(0);
|
||||
expect(installation.stdout).toContain("[Done] livesync-cli service installed and started.");
|
||||
|
||||
const installedCommand = join(fixture.homeDirectory, ".local", "bin", "livesync-cli");
|
||||
const installedPayload = join(fixture.homeDirectory, ".local", "lib", "livesync-cli", "dist", "index.cjs");
|
||||
const installedUnit = join(fixture.homeDirectory, ".config", "systemd", "user", "livesync-cli.service");
|
||||
expect(await readFile(installedPayload, "utf8")).toContain('require("./chunk.cjs")');
|
||||
expect(await readFile(installedUnit, "utf8")).toContain("Type=exec");
|
||||
|
||||
await rm(fixture.repositoryRoot, { recursive: true });
|
||||
|
||||
const command = spawnSync(installedCommand, [], { encoding: "utf8", env: fixture.environment });
|
||||
expect(command.error).toBeUndefined();
|
||||
expect(command.status, command.stderr).toBe(0);
|
||||
expect(command.stdout).toBe("chunk-ready:dependency-ready\n");
|
||||
|
||||
const npmCalls = await readFile(fixture.npmCallLog, "utf8");
|
||||
expect(npmCalls).toContain(`${fixture.repositoryRoot}|install --silent`);
|
||||
expect(npmCalls).toContain(`${fixture.cliDirectory}|run build`);
|
||||
expect(npmCalls).toMatch(/install .*--omit=dev|install --omit=dev/);
|
||||
|
||||
const systemctlCalls = await readFile(fixture.systemctlCallLog, "utf8");
|
||||
expect(systemctlCalls).toContain("--user enable --now livesync-cli");
|
||||
expect(systemctlCalls).toContain("--user is-active --quiet livesync-cli");
|
||||
});
|
||||
|
||||
it("does not report success when the service fails to remain active", async () => {
|
||||
const fixture = await createInstallerFixture(false);
|
||||
|
||||
const installation = runInstaller(fixture);
|
||||
const combinedOutput = `${installation.stdout}\n${installation.stderr}`;
|
||||
|
||||
expect(installation.error).toBeUndefined();
|
||||
expect(installation.status).not.toBe(0);
|
||||
expect(combinedOutput).toContain("service did not remain active after startup");
|
||||
expect(combinedOutput).not.toContain("[Done]");
|
||||
});
|
||||
});
|
||||
@@ -4,7 +4,7 @@ After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
Type=exec
|
||||
ExecStart=LIVESYNC_BIN LIVESYNC_VAULT_PATH
|
||||
Restart=on-failure
|
||||
RestartSec=10
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "self-hosted-livesync-cli",
|
||||
"private": true,
|
||||
"version": "1.0.23-cli",
|
||||
"version": "1.0.24-cli",
|
||||
"main": "dist/index.cjs",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
@@ -12,7 +12,7 @@
|
||||
"buildRun": "npm run build && npm run cli --",
|
||||
"build:docker": "docker build -f Dockerfile -t livesync-cli ../../..",
|
||||
"check": "tsc -p tsconfig.json",
|
||||
"test:unit": "cd ../../.. && npx vitest run --config vitest.config.unit.ts src/apps/cli/main.unit.spec.ts src/apps/cli/settingsPersistence.unit.spec.ts src/apps/cli/commands/utils.unit.spec.ts src/apps/cli/commands/runCommand.unit.spec.ts src/apps/cli/commands/p2p.unit.spec.ts",
|
||||
"test:unit": "cd ../../.. && npx vitest run --config vitest.config.unit.ts src/apps/cli/main.unit.spec.ts src/apps/cli/settingsPersistence.unit.spec.ts src/apps/cli/commands/utils.unit.spec.ts src/apps/cli/commands/runCommand.unit.spec.ts src/apps/cli/commands/p2p.unit.spec.ts src/apps/cli/deploy/install.unit.spec.ts",
|
||||
"test:e2e:two-vaults": "bash test/test-e2e-two-vaults-with-docker-linux.sh",
|
||||
"test:e2e:two-vaults:common": "bash test/test-e2e-two-vaults-common.sh",
|
||||
"test:e2e:two-vaults:matrix": "bash test/test-e2e-two-vaults-matrix.sh",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "livesync-webapp",
|
||||
"private": true,
|
||||
"version": "1.0.23-webapp",
|
||||
"version": "1.0.24-webapp",
|
||||
"type": "module",
|
||||
"description": "Browser-based Self-hosted LiveSync using FileSystem API",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "webpeer",
|
||||
"private": true,
|
||||
"version": "1.0.23-webpeer",
|
||||
"version": "1.0.24-webpeer",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -7,8 +7,6 @@
|
||||
* remove it from this map in the same change.
|
||||
*/
|
||||
export const liveSyncProvisionalEnglishMessages = {
|
||||
"This first setup has several short steps because it confirms encryption, the connection method, and which device provides the initial data. Once it is complete, additional devices can reuse a Setup URI.":
|
||||
"This first setup has several short steps because it confirms encryption, the connection method, and which device provides the initial data. Once it is complete, additional devices can reuse a Setup URI.",
|
||||
"Setup Complete: Preparing to Fetch from Another Device": "Setup Complete: Preparing to Fetch from Another Device",
|
||||
"The P2P connection has been configured successfully. The initial synchronisation data must now be fetched from an online source device.":
|
||||
"The P2P connection has been configured successfully. The initial synchronisation data must now be fetched from an online source device.",
|
||||
@@ -59,10 +57,6 @@ export const liveSyncProvisionalEnglishMessages = {
|
||||
"Follow whenever this device connects": "Follow whenever this device connects",
|
||||
"Include in the P2P synchronisation command": "Include in the P2P synchronisation command",
|
||||
"More actions for ${DEVICE}": "More actions for ${DEVICE}",
|
||||
"Create or connect to database and continue": "Create or connect to database and continue",
|
||||
"Connect to existing database and continue": "Connect to existing database and continue",
|
||||
"Test connection and save": "Test connection and save",
|
||||
"Save without connecting": "Save without connecting",
|
||||
"Use this device's settings": "Use this device's settings",
|
||||
Retry: "Retry",
|
||||
"No Synchronisation Settings Found": "No Synchronisation Settings Found",
|
||||
@@ -75,14 +69,6 @@ export const liveSyncProvisionalEnglishMessages = {
|
||||
"Could not read the remote's synchronisation settings. Retry, or continue the overwrite with this device's settings. A working connection is still required.",
|
||||
"Skips checking and applying synchronisation settings from the remote.":
|
||||
"Skips checking and applying synchronisation settings from the remote.",
|
||||
"Enter a complete HTTP or HTTPS URL.": "Enter a complete HTTP or HTTPS URL.",
|
||||
"CouchDB validates the database name when you connect. The name must not be empty.":
|
||||
"CouchDB validates the database name when you connect. The name must not be empty.",
|
||||
"Saving without a successful connection test keeps this profile, but automatic synchronisation may fail until the connection is corrected.":
|
||||
"Saving without a successful connection test keeps this profile, but automatic synchronisation may fail until the connection is corrected.",
|
||||
"This optional check uses Obsidian's internal request API and sends the credentials above to the CouchDB server. Use it only with a server you trust; administrator access may be required.":
|
||||
"This optional check uses Obsidian's internal request API and sends the credentials above to the CouchDB server. Use it only with a server you trust; administrator access may be required.",
|
||||
"Check server requirements": "Check server requirements",
|
||||
"Change CouchDB server setting": "Change CouchDB server setting",
|
||||
"Change CouchDB server setting '${SETTING}' to '${VALUE}'?":
|
||||
"Change CouchDB server setting '${SETTING}' to '${VALUE}'?",
|
||||
|
||||
@@ -148,16 +148,9 @@ export const allMessages: Readonly<Record<string, Readonly<Record<string, string
|
||||
zh: "(正则表达式)如果已设置,则所有匹配此模式的本地和远端文件变更都会被跳过。",
|
||||
"zh-tw": "(正則表示式)若已設定,所有符合此模式的本機與遠端檔案變更都會被略過。",
|
||||
},
|
||||
"(Select this if you are already using synchronisation on another computer or smartphone.) This option is suitable if you are new to LiveSync and want to set it up from scratch.":
|
||||
"(Select this if you already have another synchronising device.) This option adds this device to the same synchronisation.":
|
||||
{
|
||||
def: "(Select this if you are already using synchronisation on another computer or smartphone.) This option is suitable if you are new to LiveSync and want to set it up from scratch.",
|
||||
es: "(Seleccione esto si ya utiliza la sincronización en otro ordenador o teléfono). Esta opción es adecuada si desea añadir este dispositivo a una configuración de LiveSync existente。",
|
||||
ja: "(別の PC やスマートフォンですでに同期を利用している場合に選択してください。)この端末を既存の LiveSync 構成に追加する場合に適しています。",
|
||||
ko: "(다른 컴퓨터나 스마트폰에서 이미 동기화를 사용 중이라면 선택하세요.) 이 기기를 기존 LiveSync 구성에 추가하려는 경우에 적합합니다.",
|
||||
ru: "(Выберите этот вариант, если вы уже используете синхронизацию на другом компьютере или смартфоне.) Он подходит, если вы хотите добавить это устройство к уже существующей конфигурации LiveSync。",
|
||||
zh: "(如果你已经在另一台电脑或手机上使用同步,请选择此项。)此选项适合将当前设备加入现有 LiveSync 配置的用户。",
|
||||
"zh-tw":
|
||||
"(如果你已經在另一台電腦或手機上使用同步,請選擇此項。)此選項適合將目前裝置加入既有 LiveSync 設定的使用者。",
|
||||
def: "(Select this if you already have another synchronising device.) This option adds this device to the same synchronisation.",
|
||||
},
|
||||
"(Select this if you are configuring this device as the first synchronisation device.) This option is suitable if you are new to LiveSync and want to set it up from scratch.":
|
||||
{
|
||||
@@ -740,6 +733,10 @@ export const allMessages: Readonly<Record<string, Readonly<Record<string, string
|
||||
zh: "检查尚未转换为路径混淆 ID 的文档,并在需要时将其转换。",
|
||||
"zh-tw": "檢查尚未轉換為路徑混淆 ID 的文件,並在需要時進行轉換。",
|
||||
},
|
||||
"Check server requirements": {
|
||||
def: "Check server requirements",
|
||||
es: "Comprobar los requisitos del servidor",
|
||||
},
|
||||
"Checking connection... Please wait.": {
|
||||
def: "Checking connection... Please wait.",
|
||||
es: "Comprobando la conexión... Espera un momento.",
|
||||
@@ -993,6 +990,10 @@ export const allMessages: Readonly<Record<string, Readonly<Record<string, string
|
||||
ko: "연결",
|
||||
"zh-tw": "連線",
|
||||
},
|
||||
"Connect to existing database and continue": {
|
||||
def: "Connect to existing database and continue",
|
||||
es: "Conectar a la base de datos existente y continuar",
|
||||
},
|
||||
"Connected to Signaling Server (as Peer ID: ${peerId})": {
|
||||
def: "Connected to Signaling Server (as Peer ID: ${peerId})",
|
||||
es: "Conectado al servidor de señalización (como ID de par: ${peerId})",
|
||||
@@ -1094,6 +1095,14 @@ export const allMessages: Readonly<Record<string, Readonly<Record<string, string
|
||||
zh: "CouchDB 连接调优",
|
||||
"zh-tw": "CouchDB 連線調校",
|
||||
},
|
||||
"CouchDB validates the database name when you connect. The name must not be empty.": {
|
||||
def: "CouchDB validates the database name when you connect. The name must not be empty.",
|
||||
es: "CouchDB valida el nombre de la base de datos al conectar. El nombre no puede estar vacío.",
|
||||
},
|
||||
"Create or connect to database and continue": {
|
||||
def: "Create or connect to database and continue",
|
||||
es: "Crear o conectar a la base de datos y continuar",
|
||||
},
|
||||
"Create P2P remote": {
|
||||
def: "Create P2P remote",
|
||||
es: "Crear remoto P2P",
|
||||
@@ -1433,7 +1442,7 @@ export const allMessages: Readonly<Record<string, Readonly<Record<string, string
|
||||
},
|
||||
"dialog.yourLanguageAvailable": {
|
||||
def: "Self-hosted LiveSync had translations for your language, so the %{Display language} setting was enabled.\n\nNote: Not all messages are translated. We are waiting for your contributions!\nNote 2: If you create an Issue, **please revert to Default** and then take screenshots, messages and logs. This can be done in the setting dialogue.\nMay you find it easy to use!",
|
||||
es: "Self-hosted LiveSync tenía traducciones para tu idioma, así que se ha activado el ajuste %{Display language}.\n\nNota: no todos los mensajes están traducidos. ¡Esperamos tus contribuciones!\nNota 2: si abres una incidencia, **vuelve antes a Predeterminado** y luego haz las capturas de pantalla y recoge los mensajes y registros. Puedes hacerlo desde el diálogo de ajustes.\n¡Que lo disfrutes!",
|
||||
es: "Self-hosted LiveSync tenía traducciones para tu idioma, así que se ha activado el ajuste Idioma de visualización.\n\nNota: no todos los mensajes están traducidos. ¡Esperamos tus contribuciones!\nNota 2: si abres una incidencia, **vuelve antes a Predeterminado** y luego haz las capturas de pantalla y recoge los mensajes y registros. Puedes hacerlo desde el diálogo de ajustes.\n¡Que lo disfrutes!",
|
||||
fr: "Self-hosted LiveSync dispose d'une traduction pour votre langue, le paramètre %{Display language} a donc été activé.\n\nNote : Tous les messages ne sont pas traduits. Nous attendons vos contributions !\nNote 2 : Si vous créez un ticket, **veuillez revenir à Par défaut** puis prendre des captures d'écran, messages et journaux. Cela peut être fait dans la boîte de dialogue des paramètres.\nBonne utilisation !",
|
||||
he: "ל-Self-hosted LiveSync יש תרגום לשפתך, ולכן הגדרת %{Display language} הופעלה.\n\nהערה: לא כל ההודעות מתורגמות. אנחנו ממתינים לתרומותיך!\nהערה 2: אם אתה פותח Issue, **אנא חזור ל-%{lang-def}** ואז צלם צילומי מסך, הודעות ויומנים. ניתן לעשות זאת בדיאלוג ההגדרות.\nנקווה שתמצא/י את הפלאגין נוח לשימוש!",
|
||||
ja: "Self-hosted LiveSync に設定されている言語の翻訳がありましたので、インターフェースの表示言語が適用されました。\n\n注意: 全てのメッセージは翻訳されていません。あなたの貢献をお待ちしています!\nGithubにIssueを作成する際には、 インターフェースの表示言語 を一旦 Default に戻してから、スクショやメッセージ、ログを収集してください。これは設定から変更できます。\n\n便利に使用できれば幸いです。",
|
||||
@@ -2035,6 +2044,10 @@ export const allMessages: Readonly<Record<string, Readonly<Record<string, string
|
||||
zh: "增大块大小",
|
||||
"zh-tw": "擴大 chunk 大小",
|
||||
},
|
||||
"Enter a complete HTTP or HTTPS URL.": {
|
||||
def: "Enter a complete HTTP or HTTPS URL.",
|
||||
es: "Introduce una URL HTTP o HTTPS completa.",
|
||||
},
|
||||
"Enter a folder prefix (optional)": {
|
||||
def: "Enter a folder prefix (optional)",
|
||||
es: "Introduce un prefijo de carpeta (opcional)",
|
||||
@@ -2530,6 +2543,10 @@ export const allMessages: Readonly<Record<string, Readonly<Record<string, string
|
||||
ko: "해당 없는 항목 숨기기",
|
||||
"zh-tw": "隱藏不適用的項目",
|
||||
},
|
||||
"Hide password": {
|
||||
def: "Hide password",
|
||||
es: "Ocultar contraseña",
|
||||
},
|
||||
"Higher (${local} > ${remote})": {
|
||||
def: "Higher (${local} > ${remote})",
|
||||
es: "Superior (${local} > ${remote})",
|
||||
@@ -5369,7 +5386,7 @@ export const allMessages: Readonly<Record<string, Readonly<Record<string, string
|
||||
},
|
||||
"obsidianLiveSyncSettingTab.logConfiguredLiveSync": {
|
||||
def: "Configured synchronization mode: LiveSync",
|
||||
es: "Modo de sincronización configurado: Sincronización en Vivo",
|
||||
es: "Modo de sincronización configurado: Sincronización en vivo",
|
||||
fr: "Mode de synchronisation configuré : LiveSync",
|
||||
he: "מצב סנכרון שהוגדר: LiveSync",
|
||||
ja: "設定された同期モード: LiveSync",
|
||||
@@ -5981,7 +5998,7 @@ export const allMessages: Readonly<Record<string, Readonly<Record<string, string
|
||||
},
|
||||
"obsidianLiveSyncSettingTab.nameTestDatabaseConnection": {
|
||||
def: "Test Database Connection",
|
||||
es: "Probar Conexión de Base de Datos",
|
||||
es: "Probar conexión de base de datos",
|
||||
fr: "Tester la connexion à la base de données",
|
||||
he: "בדוק חיבור למסד נתונים",
|
||||
ja: "データベース接続テスト",
|
||||
@@ -5992,7 +6009,7 @@ export const allMessages: Readonly<Record<string, Readonly<Record<string, string
|
||||
},
|
||||
"obsidianLiveSyncSettingTab.nameValidateDatabaseConfig": {
|
||||
def: "Validate Database Configuration",
|
||||
es: "Validar Configuración de la Base de Datos",
|
||||
es: "Validar configuración de la base de datos",
|
||||
fr: "Valider la configuration de la base de données",
|
||||
he: "אמת תצורת מסד נתונים",
|
||||
ja: "データベース設定を検証",
|
||||
@@ -6300,7 +6317,7 @@ export const allMessages: Readonly<Record<string, Readonly<Record<string, string
|
||||
},
|
||||
"obsidianLiveSyncSettingTab.panelGeneralSettings": {
|
||||
def: "General Settings",
|
||||
es: "Configuraciones Generales",
|
||||
es: "Configuraciones generales",
|
||||
fr: "Paramètres généraux",
|
||||
he: "הגדרות כלליות",
|
||||
ja: "一般設定",
|
||||
@@ -6311,7 +6328,7 @@ export const allMessages: Readonly<Record<string, Readonly<Record<string, string
|
||||
},
|
||||
"obsidianLiveSyncSettingTab.panelPrivacyEncryption": {
|
||||
def: "Privacy & Encryption",
|
||||
es: "Privacidad y Cifrado",
|
||||
es: "Privacidad y cifrado",
|
||||
fr: "Confidentialité et chiffrement",
|
||||
he: "פרטיות והצפנה",
|
||||
ja: "プライバシーと暗号化",
|
||||
@@ -6640,7 +6657,7 @@ export const allMessages: Readonly<Record<string, Readonly<Record<string, string
|
||||
},
|
||||
"obsidianLiveSyncSettingTab.titleSyncSettings": {
|
||||
def: "Sync Settings",
|
||||
es: "Configuraciones de Sincronización",
|
||||
es: "Configuraciones de sincronización",
|
||||
fr: "Paramètres de synchronisation",
|
||||
he: "הגדרות סנכרון",
|
||||
ja: "同期設定",
|
||||
@@ -8384,6 +8401,10 @@ export const allMessages: Readonly<Record<string, Readonly<Record<string, string
|
||||
zh: "将设置保存到一个 Markdown 文件中。当新设置到达时,您将收到通知。您可以根据平台设置不同的文件 ",
|
||||
"zh-tw": "將設定儲存到 Markdown 檔案中。有新設定送達時會通知你,可依平台設定不同的檔案。",
|
||||
},
|
||||
"Save without connecting": {
|
||||
def: "Save without connecting",
|
||||
es: "Guardar sin conectar",
|
||||
},
|
||||
"Saving will be performed forcefully after this number of seconds.": {
|
||||
def: "Saving will be performed forcefully after this number of seconds.",
|
||||
es: "Guardado forzado tras esta cantidad de segundos",
|
||||
@@ -8395,6 +8416,11 @@ export const allMessages: Readonly<Record<string, Readonly<Record<string, string
|
||||
zh: "在此秒数后将强制执行保存 ",
|
||||
"zh-tw": "經過這個秒數後,會強制執行儲存。",
|
||||
},
|
||||
"Saving without a successful connection test keeps this profile, but automatic synchronisation may fail until the connection is corrected.":
|
||||
{
|
||||
def: "Saving without a successful connection test keeps this profile, but automatic synchronisation may fail until the connection is corrected.",
|
||||
es: "Guardar sin una prueba de conexión correcta conserva este perfil, pero la sincronización automática puede fallar hasta que se corrija la conexión.",
|
||||
},
|
||||
"Scan a QR Code (Recommended for mobile)": {
|
||||
def: "Scan a QR Code (Recommended for mobile)",
|
||||
es: "Escanear un código QR (recomendado para móviles)",
|
||||
@@ -9414,6 +9440,10 @@ export const allMessages: Readonly<Record<string, Readonly<Record<string, string
|
||||
zh: "仅显示通知",
|
||||
"zh-tw": "僅顯示通知",
|
||||
},
|
||||
"Show password": {
|
||||
def: "Show password",
|
||||
es: "Mostrar contraseña",
|
||||
},
|
||||
"Show status as icons only": {
|
||||
def: "Show status as icons only",
|
||||
es: "Mostrar estado solo con íconos",
|
||||
@@ -9773,6 +9803,10 @@ export const allMessages: Readonly<Record<string, Readonly<Record<string, string
|
||||
zh: "目标模式",
|
||||
"zh-tw": "目標模式",
|
||||
},
|
||||
"Test connection and save": {
|
||||
def: "Test connection and save",
|
||||
es: "Probar la conexión y guardar",
|
||||
},
|
||||
"Test Settings and Continue": {
|
||||
def: "Test Settings and Continue",
|
||||
es: "Probar los ajustes y continuar",
|
||||
@@ -9998,6 +10032,11 @@ export const allMessages: Readonly<Record<string, Readonly<Record<string, string
|
||||
"zh-tw":
|
||||
"此功能可在裝置之間直接同步,無需伺服器;但同步時兩台裝置必須同時在線,且部分功能可能受限。網際網路連線僅用於訊號交換(偵測對端),不用於資料傳輸。",
|
||||
},
|
||||
"This first setup has several short steps because it confirms encryption, the connection method, and which device provides the initial data. Once it is complete, additional devices can reuse a Setup URI.":
|
||||
{
|
||||
def: "This first setup has several short steps because it confirms encryption, the connection method, and which device provides the initial data. Once it is complete, additional devices can reuse a Setup URI.",
|
||||
es: "Esta primera configuración consta de varios pasos breves, ya que confirma el cifrado, el método de conexión y qué dispositivo aporta los datos iniciales. Una vez completada, los demás dispositivos podrán reutilizar un Setup URI.",
|
||||
},
|
||||
"This is an advanced option for users who do not have a URI or who wish to configure detailed settings.": {
|
||||
def: "This is an advanced option for users who do not have a URI or who wish to configure detailed settings.",
|
||||
es: "Esta es una opción avanzada para usuarios que no disponen de un URI o que desean configurar parámetros detallados。",
|
||||
@@ -10024,6 +10063,11 @@ export const allMessages: Readonly<Record<string, Readonly<Record<string, string
|
||||
zh: "这是最符合当前设计的同步方式,所有功能均可用。你需要事先部署好 CouchDB 实例。",
|
||||
"zh-tw": "這是最符合目前設計的同步方式,所有功能皆可使用。你需要事先部署好 CouchDB 實例。",
|
||||
},
|
||||
"This optional check uses Obsidian's internal request API and sends the credentials above to the CouchDB server. Use it only with a server you trust; administrator access may be required.":
|
||||
{
|
||||
def: "This optional check uses Obsidian's internal request API and sends the credentials above to the CouchDB server. Use it only with a server you trust; administrator access may be required.",
|
||||
es: "Esta comprobación opcional usa la API interna de solicitudes de Obsidian y envía las credenciales anteriores al servidor CouchDB. Utilízala solo con un servidor de confianza; puede requerir acceso de administrador.",
|
||||
},
|
||||
"This passphrase will not be copied to another device. It will be set to `Default` until you configure it again.": {
|
||||
def: "This passphrase will not be copied to another device. It will be set to `Default` until you configure it again.",
|
||||
es: "Esta frase no se copia a otros dispositivos. Usará `Default` hasta reconfigurar",
|
||||
@@ -12684,6 +12728,20 @@ export const allMessages: Readonly<Record<string, Readonly<Record<string, string
|
||||
"zh-tw":
|
||||
"只有在特殊情況下才應執行此操作,例如伺服器資料已完全損毀、其他所有裝置上的變更都已不再需要,或資料庫大小相對於 Vault 大小已變得異常龐大時。",
|
||||
},
|
||||
"you wanted(Thank you)!": {
|
||||
def: "you wanted(Thank you)!",
|
||||
es: "tu solicitud (¡gracias!)",
|
||||
},
|
||||
"(Select this if another device is already using LiveSync.) This option adds this device to that existing synchronisation setup.":
|
||||
{
|
||||
es: "(Seleccione esto si ya utiliza la sincronización en otro ordenador o teléfono). Esta opción es adecuada si desea añadir este dispositivo a una configuración de LiveSync existente。",
|
||||
ja: "(別の PC やスマートフォンですでに同期を利用している場合に選択してください。)この端末を既存の LiveSync 構成に追加する場合に適しています。",
|
||||
ko: "(다른 컴퓨터나 스마트폰에서 이미 동기화를 사용 중이라면 선택하세요.) 이 기기를 기존 LiveSync 구성에 추가하려는 경우에 적합합니다.",
|
||||
ru: "(Выберите этот вариант, если вы уже используете синхронизацию на другом компьютере или смартфоне.) Он подходит, если вы хотите добавить это устройство к уже существующей конфигурации LiveSync。",
|
||||
zh: "(如果你已经在另一台电脑或手机上使用同步,请选择此项。)此选项适合将当前设备加入现有 LiveSync 配置的用户。",
|
||||
"zh-tw":
|
||||
"(如果你已經在另一台電腦或手機上使用同步,請選擇此項。)此選項適合將目前裝置加入既有 LiveSync 設定的使用者。",
|
||||
},
|
||||
"Compute revisions for chunks (Previous behaviour)": {
|
||||
es: "Calcular revisiones para chunks (comportamiento anterior)",
|
||||
},
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"(Active)": "(Aktiv)",
|
||||
"(RegExp) Empty to sync all files. Set filter as a regular expression to limit synchronising files.": "(RegExp) Leer lassen, um alle Dateien zu synchronisieren. Legen Sie einen Filter als regulären Ausdruck fest, um die zu synchronisierenden Dateien einzuschränken.",
|
||||
"(RegExp) If this is set, any changes to local and remote files that match this will be skipped.": "(RegExp) Wenn dies gesetzt ist, werden alle Änderungen an lokalen und Remote-Dateien übersprungen, die diesem Muster entsprechen.",
|
||||
"(Select this if you are already using synchronisation on another computer or smartphone.) This option is suitable if you are new to LiveSync and want to set it up from scratch.": "(Wählen Sie dies, wenn Sie die Synchronisation bereits auf einem anderen Computer oder Smartphone verwenden.) Diese Option ist geeignet, wenn Sie dieses Gerät zu einer bestehenden LiveSync-Einrichtung hinzufügen möchten.",
|
||||
"(Select this if another device is already using LiveSync.) This option adds this device to that existing synchronisation setup.": "(Wählen Sie dies, wenn Sie die Synchronisation bereits auf einem anderen Computer oder Smartphone verwenden.) Diese Option ist geeignet, wenn Sie dieses Gerät zu einer bestehenden LiveSync-Einrichtung hinzufügen möchten.",
|
||||
"(Select this if you are configuring this device as the first synchronisation device.) This option is suitable if you are new to LiveSync and want to set it up from scratch.": "(Wählen Sie dies, wenn Sie dieses Gerät als erstes Synchronisationsgerät einrichten.) Diese Option ist geeignet, wenn Sie LiveSync neu verwenden und von Grund auf einrichten möchten.",
|
||||
"> [!INFO]- The connected devices have been detected as follows:\n${devices}": "> [!INFO]- Die folgenden verbundenen Geräte wurden erkannt:\n${devices}",
|
||||
"A Setup URI is a single string of text containing your server address and authentication details. Using a URI, if one was generated by your server installation script, provides a simple and secure configuration.": "Eine Setup-URI ist eine einzelne Zeichenfolge, die Ihre Serveradresse und Authentifizierungsdaten enthält. Wenn Ihre Serverinstallation eine URI erzeugt hat, bietet deren Verwendung eine einfache und sichere Konfiguration。",
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
"(Obsolete) Use an old adapter for compatibility": "(Obsolete) Use an old adapter for compatibility",
|
||||
"(RegExp) Empty to sync all files. Set filter as a regular expression to limit synchronising files.": "(RegExp) Empty to sync all files. Set filter as a regular expression to limit synchronising files.",
|
||||
"(RegExp) If this is set, any changes to local and remote files that match this will be skipped.": "(RegExp) If this is set, any changes to local and remote files that match this will be skipped.",
|
||||
"(Select this if you are already using synchronisation on another computer or smartphone.) This option is suitable if you are new to LiveSync and want to set it up from scratch.": "(Select this if you are already using synchronisation on another computer or smartphone.) This option is suitable if you are new to LiveSync and want to set it up from scratch.",
|
||||
"(Select this if you already have another synchronising device.) This option adds this device to the same synchronisation.": "(Select this if you already have another synchronising device.) This option adds this device to the same synchronisation.",
|
||||
"(Select this if you are configuring this device as the first synchronisation device.) This option is suitable if you are new to LiveSync and want to set it up from scratch.": "(Select this if you are configuring this device as the first synchronisation device.) This option is suitable if you are new to LiveSync and want to set it up from scratch.",
|
||||
"↑: Overwrite Remote": "↑: Overwrite Remote",
|
||||
"↓: Overwrite Local": "↓: Overwrite Local",
|
||||
@@ -90,6 +90,7 @@
|
||||
"Check": "Check",
|
||||
"Check and convert non-path-obfuscated files": "Check and convert non-path-obfuscated files",
|
||||
"Check for documents that have not been converted to path-obfuscated IDs and convert them if necessary.": "Check for documents that have not been converted to path-obfuscated IDs and convert them if necessary.",
|
||||
"Check server requirements": "Check server requirements",
|
||||
"Checking connection... Please wait.": "Checking connection... Please wait.",
|
||||
"Chunks": "Chunks",
|
||||
"Close": "Close",
|
||||
@@ -121,6 +122,7 @@
|
||||
"Configure Remote": "Configure Remote",
|
||||
"Configure the same server information as your other devices again, manually, very advanced users only.": "Configure the same server information as your other devices again, manually, very advanced users only.",
|
||||
"Connect": "Connect",
|
||||
"Connect to existing database and continue": "Connect to existing database and continue",
|
||||
"Connected to Signaling Server (as Peer ID: ${peerId})": "Connected to Signaling Server (as Peer ID: ${peerId})",
|
||||
"Connected:": "Connected:",
|
||||
"Connection Method": "Connection Method",
|
||||
@@ -134,6 +136,8 @@
|
||||
"Copy Report to clipboard": "Copy Report to clipboard",
|
||||
"CouchDB Configuration": "CouchDB Configuration",
|
||||
"CouchDB Connection Tweak": "CouchDB Connection Tweak",
|
||||
"CouchDB validates the database name when you connect. The name must not be empty.": "CouchDB validates the database name when you connect. The name must not be empty.",
|
||||
"Create or connect to database and continue": "Create or connect to database and continue",
|
||||
"Create P2P remote": "Create P2P remote",
|
||||
"Cross-platform": "Cross-platform",
|
||||
"Current adapter: {adapter}": "Current adapter: {adapter}",
|
||||
@@ -236,6 +240,7 @@
|
||||
"End-to-End Encryption": "End-to-End Encryption",
|
||||
"Endpoint URL": "Endpoint URL",
|
||||
"Enhance chunk size": "Enhance chunk size",
|
||||
"Enter a complete HTTP or HTTPS URL.": "Enter a complete HTTP or HTTPS URL.",
|
||||
"Enter a folder prefix (optional)": "Enter a folder prefix (optional)",
|
||||
"Enter Server Information": "Enter Server Information",
|
||||
"Enter Setup URI": "Enter Setup URI",
|
||||
@@ -303,6 +308,7 @@
|
||||
"Hidden Files": "Hidden Files",
|
||||
"Hide completely": "Hide completely",
|
||||
"Hide not applicable items": "Hide not applicable items",
|
||||
"Hide password": "Hide password",
|
||||
"Higher (${local} > ${remote})": "Higher (${local} > ${remote})",
|
||||
"Highlight diff": "Highlight diff",
|
||||
"How to display network errors when the sync server is unreachable.": "How to display network errors when the sync server is unreachable.",
|
||||
@@ -910,7 +916,9 @@
|
||||
"Same or local only": "Same or local only",
|
||||
"Save and Apply": "Save and Apply",
|
||||
"Save settings to a markdown file. You will be notified when new settings arrive. You can set different files by the platform.": "Save settings to a markdown file. You will be notified when new settings arrive. You can set different files by the platform.",
|
||||
"Save without connecting": "Save without connecting",
|
||||
"Saving will be performed forcefully after this number of seconds.": "Saving will be performed forcefully after this number of seconds.",
|
||||
"Saving without a successful connection test keeps this profile, but automatic synchronisation may fail until the connection is corrected.": "Saving without a successful connection test keeps this profile, but automatic synchronisation may fail until the connection is corrected.",
|
||||
"Scan a QR Code (Recommended for mobile)": "Scan a QR Code (Recommended for mobile)",
|
||||
"Scan changes": "Scan changes",
|
||||
"Scan changes on customization sync": "Scan changes on customization sync",
|
||||
@@ -1019,6 +1027,7 @@
|
||||
"Show history": "Show history",
|
||||
"Show icon only": "Show icon only",
|
||||
"Show only notifications": "Show only notifications",
|
||||
"Show password": "Show password",
|
||||
"Show status as icons only": "Show status as icons only",
|
||||
"Show status icon instead of file warnings banner": "Show status icon instead of file warnings banner",
|
||||
"Show status inside the editor": "Show status inside the editor",
|
||||
@@ -1060,6 +1069,7 @@
|
||||
"Syncing": "Syncing",
|
||||
"Syncing...": "Syncing...",
|
||||
"Target patterns": "Target patterns",
|
||||
"Test connection and save": "Test connection and save",
|
||||
"Test Settings and Continue": "Test Settings and Continue",
|
||||
"Testing only - Resolve file conflicts by syncing newer copies of the file, this can overwrite modified files. Be Warned.": "Testing only - Resolve file conflicts by syncing newer copies of the file, this can overwrite modified files. Be Warned.",
|
||||
"The connection test cannot add a signalling relay while P2P is active. Use the active relay settings, or disconnect P2P before testing.": "The connection test cannot add a signalling relay while P2P is active. Use the active relay settings, or disconnect P2P before testing.",
|
||||
@@ -1089,9 +1099,11 @@
|
||||
"This device": "This device",
|
||||
"This device name": "This device name",
|
||||
"This feature enables direct synchronisation between devices. No server is required, but both devices must be online at the same time for synchronisation to occur, and some features may be limited. Internet connection is only required to signalling (detecting peers) and not for data transfer.": "This feature enables direct synchronisation between devices. No server is required, but both devices must be online at the same time for synchronisation to occur, and some features may be limited. Internet connection is only required to signalling (detecting peers) and not for data transfer.",
|
||||
"This first setup has several short steps because it confirms encryption, the connection method, and which device provides the initial data. Once it is complete, additional devices can reuse a Setup URI.": "This first setup has several short steps because it confirms encryption, the connection method, and which device provides the initial data. Once it is complete, additional devices can reuse a Setup URI.",
|
||||
"This is an advanced option for users who do not have a URI or who wish to configure detailed settings.": "This is an advanced option for users who do not have a URI or who wish to configure detailed settings.",
|
||||
"This is an extremely powerful operation. We strongly recommend that you copy your Vault folder to a safe location.": "This is an extremely powerful operation. We strongly recommend that you copy your Vault folder to a safe location.",
|
||||
"This is the most suitable synchronisation method for the design. All functions are available. You must have set up a CouchDB instance.": "This is the most suitable synchronisation method for the design. All functions are available. You must have set up a CouchDB instance.",
|
||||
"This optional check uses Obsidian's internal request API and sends the credentials above to the CouchDB server. Use it only with a server you trust; administrator access may be required.": "This optional check uses Obsidian's internal request API and sends the credentials above to the CouchDB server. Use it only with a server you trust; administrator access may be required.",
|
||||
"This passphrase will not be copied to another device. It will be set to `Default` until you configure it again.": "This passphrase will not be copied to another device. It will be set to `Default` until you configure it again.",
|
||||
"This password is used to encrypt the connection. Use something long enough.": "This password is used to encrypt the connection. Use something long enough.",
|
||||
"This procedure will first delete all existing synchronisation data from the server. Following this, the server data will be completely rebuilt, using the current state of your Vault on this device (including its local database) as": "This procedure will first delete all existing synchronisation data from the server. Following this, the server data will be completely rebuilt, using the current state of your Vault on this device (including its local database) as",
|
||||
@@ -1462,5 +1474,6 @@
|
||||
"You are adding this device to an existing synchronisation setup.": "You are adding this device to an existing synchronisation setup.",
|
||||
"You can configure in the Obsidian Plugin Settings.": "You can configure in the Obsidian Plugin Settings.",
|
||||
"You should create a new synchronisation destination and rebuild your data there.": "You should create a new synchronisation destination and rebuild your data there.",
|
||||
"You should perform this operation only in exceptional circumstances, such as when the server data is completely corrupted, when changes on all other devices are no longer needed, or when the database size has become unusually large in comparison to the Vault size.": "You should perform this operation only in exceptional circumstances, such as when the server data is completely corrupted, when changes on all other devices are no longer needed, or when the database size has become unusually large in comparison to the Vault size."
|
||||
"You should perform this operation only in exceptional circumstances, such as when the server data is completely corrupted, when changes on all other devices are no longer needed, or when the database size has become unusually large in comparison to the Vault size.": "You should perform this operation only in exceptional circumstances, such as when the server data is completely corrupted, when changes on all other devices are no longer needed, or when the database size has become unusually large in comparison to the Vault size.",
|
||||
"you wanted(Thank you)!": "you wanted(Thank you)!"
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
"(Obsolete) Use an old adapter for compatibility": "(Obsoleto) Usar adaptador antiguo",
|
||||
"(RegExp) Empty to sync all files. Set filter as a regular expression to limit synchronising files.": "(RegExp) Déjelo vacío para sincronizar todos los archivos. Defina un filtro como expresión regular para limitar los archivos que se sincronizan.",
|
||||
"(RegExp) If this is set, any changes to local and remote files that match this will be skipped.": "(RegExp) Si se establece, se omitirá cualquier cambio en archivos locales y remotos que coincida con este patrón.",
|
||||
"(Select this if you are already using synchronisation on another computer or smartphone.) This option is suitable if you are new to LiveSync and want to set it up from scratch.": "(Seleccione esto si ya utiliza la sincronización en otro ordenador o teléfono). Esta opción es adecuada si desea añadir este dispositivo a una configuración de LiveSync existente。",
|
||||
"(Select this if another device is already using LiveSync.) This option adds this device to that existing synchronisation setup.": "(Seleccione esto si ya utiliza la sincronización en otro ordenador o teléfono). Esta opción es adecuada si desea añadir este dispositivo a una configuración de LiveSync existente。",
|
||||
"(Select this if you are configuring this device as the first synchronisation device.) This option is suitable if you are new to LiveSync and want to set it up from scratch.": "(Seleccione esto si está configurando este dispositivo como el primer dispositivo de sincronización). Esta opción es adecuada si es nuevo en LiveSync y desea configurarlo desde cero。",
|
||||
"↑: Overwrite Remote": "↑: Sobrescribir remoto",
|
||||
"↓: Overwrite Local": "↓: Sobrescribir local",
|
||||
@@ -90,6 +90,7 @@
|
||||
"Check": "Comprobar",
|
||||
"Check and convert non-path-obfuscated files": "Comprobar y convertir archivos sin ofuscación de ruta",
|
||||
"Check for documents that have not been converted to path-obfuscated IDs and convert them if necessary.": "Comprueba los documentos que aún no se hayan convertido a identificadores con ruta ofuscada y conviértelos si es necesario.",
|
||||
"Check server requirements": "Comprobar los requisitos del servidor",
|
||||
"Checking connection... Please wait.": "Comprobando la conexión... Espera un momento.",
|
||||
"Chunks": "Fragmentos (chunks)",
|
||||
"Close": "Cerrar",
|
||||
@@ -122,6 +123,7 @@
|
||||
"Configure Remote": "Configurar remoto",
|
||||
"Configure the same server information as your other devices again, manually, very advanced users only.": "Configure manualmente la misma información del servidor que en sus otros dispositivos. Solo para usuarios muy avanzados。",
|
||||
"Connect": "Conectar",
|
||||
"Connect to existing database and continue": "Conectar a la base de datos existente y continuar",
|
||||
"Connected to Signaling Server (as Peer ID: ${peerId})": "Conectado al servidor de señalización (como ID de par: ${peerId})",
|
||||
"Connected:": "Conectadas:",
|
||||
"Connection Method": "Método de conexión",
|
||||
@@ -135,6 +137,8 @@
|
||||
"Copy Report to clipboard": "Copiar el informe al portapapeles",
|
||||
"CouchDB Configuration": "Configuración de CouchDB",
|
||||
"CouchDB Connection Tweak": "Ajustes de conexión de CouchDB",
|
||||
"CouchDB validates the database name when you connect. The name must not be empty.": "CouchDB valida el nombre de la base de datos al conectar. El nombre no puede estar vacío.",
|
||||
"Create or connect to database and continue": "Crear o conectar a la base de datos y continuar",
|
||||
"Create P2P remote": "Crear remoto P2P",
|
||||
"Cross-platform": "Multiplataforma",
|
||||
"Current adapter: {adapter}": "Adaptador actual: {adapter}",
|
||||
@@ -177,7 +181,7 @@
|
||||
"Device Setup Method": "Método de configuración del dispositivo",
|
||||
"Devices:": "Dispositivos:",
|
||||
"Diagnostic RTCPeerConnection is enabled": "El RTCPeerConnection de diagnóstico está habilitado",
|
||||
"dialog.yourLanguageAvailable": "Self-hosted LiveSync tenía traducciones para tu idioma, así que se ha activado el ajuste %{Display language}.\n\nNota: no todos los mensajes están traducidos. ¡Esperamos tus contribuciones!\nNota 2: si abres una incidencia, **vuelve antes a %{lang-def}** y luego haz las capturas de pantalla y recoge los mensajes y registros. Puedes hacerlo desde el diálogo de ajustes.\n¡Que lo disfrutes!",
|
||||
"dialog.yourLanguageAvailable": "Self-hosted LiveSync tenía traducciones para tu idioma, así que se ha activado el ajuste %{Display Language}.\n\nNota: no todos los mensajes están traducidos. ¡Esperamos tus contribuciones!\nNota 2: si abres una incidencia, **vuelve antes a %{lang-def}** y luego haz las capturas de pantalla y recoge los mensajes y registros. Puedes hacerlo desde el diálogo de ajustes.\n¡Que lo disfrutes!",
|
||||
"dialog.yourLanguageAvailable.btnRevertToDefault": "Mantener %{lang-def}",
|
||||
"dialog.yourLanguageAvailable.Title": " ¡Hay traducción disponible!",
|
||||
"Diff": "Diferencias",
|
||||
@@ -237,6 +241,7 @@
|
||||
"End-to-End Encryption": "Cifrado de extremo a extremo",
|
||||
"Endpoint URL": "URL del endpoint",
|
||||
"Enhance chunk size": "Mejorar tamaño de chunks",
|
||||
"Enter a complete HTTP or HTTPS URL.": "Introduce una URL HTTP o HTTPS completa.",
|
||||
"Enter a folder prefix (optional)": "Introduce un prefijo de carpeta (opcional)",
|
||||
"Enter Server Information": "Introducir información del servidor",
|
||||
"Enter Setup URI": "Introducir el Setup URI",
|
||||
@@ -304,6 +309,7 @@
|
||||
"Hidden Files": "Archivos ocultos",
|
||||
"Hide completely": "Ocultar por completo",
|
||||
"Hide not applicable items": "Ocultar elementos no aplicables",
|
||||
"Hide password": "Ocultar contraseña",
|
||||
"Higher (${local} > ${remote})": "Superior (${local} > ${remote})",
|
||||
"Highlight diff": "Resaltar las diferencias",
|
||||
"How to display network errors when the sync server is unreachable.": "Cómo mostrar los errores de red cuando el servidor de sincronización no está disponible.",
|
||||
@@ -593,7 +599,7 @@
|
||||
"obsidianLiveSyncSettingTab.logCheckingDbConfig": "Verificando la configuración de la base de datos",
|
||||
"obsidianLiveSyncSettingTab.logCheckPassphraseFailed": "ERROR: Error al comprobar la frase de contraseña con el servidor remoto:\n${db}.",
|
||||
"obsidianLiveSyncSettingTab.logConfiguredDisabled": "Modo de sincronización configurado: DESACTIVADO",
|
||||
"obsidianLiveSyncSettingTab.logConfiguredLiveSync": "Modo de sincronización configurado: Sincronización en Vivo",
|
||||
"obsidianLiveSyncSettingTab.logConfiguredLiveSync": "Modo de sincronización configurado: Sincronización en vivo",
|
||||
"obsidianLiveSyncSettingTab.logConfiguredPeriodic": "Modo de sincronización configurado: Periódico",
|
||||
"obsidianLiveSyncSettingTab.logCouchDbConfigFail": "Configuración de CouchDB: ${title} falló",
|
||||
"obsidianLiveSyncSettingTab.logCouchDbConfigSet": "Configuración de CouchDB: ${title} -> Establecer ${key} en ${value}",
|
||||
@@ -648,8 +654,8 @@
|
||||
"obsidianLiveSyncSettingTab.nameHiddenFileSynchronization": "Sincronización de archivos ocultos",
|
||||
"obsidianLiveSyncSettingTab.nameManualSetup": "Configuración manual",
|
||||
"obsidianLiveSyncSettingTab.nameTestConnection": "Probar conexión",
|
||||
"obsidianLiveSyncSettingTab.nameTestDatabaseConnection": "Probar Conexión de Base de Datos",
|
||||
"obsidianLiveSyncSettingTab.nameValidateDatabaseConfig": "Validar Configuración de la Base de Datos",
|
||||
"obsidianLiveSyncSettingTab.nameTestDatabaseConnection": "Probar conexión de base de datos",
|
||||
"obsidianLiveSyncSettingTab.nameValidateDatabaseConfig": "Validar configuración de la base de datos",
|
||||
"obsidianLiveSyncSettingTab.okAdminPrivileges": "✔ Tienes privilegios de administrador.",
|
||||
"obsidianLiveSyncSettingTab.okCorsCredentials": "✔ cors.credentials está correcto.",
|
||||
"obsidianLiveSyncSettingTab.okCorsCredentialsForOrigin": "CORS credenciales OK",
|
||||
@@ -677,8 +683,8 @@
|
||||
"obsidianLiveSyncSettingTab.optionRebuildBoth": "Reconstructuir ambos desde este dispositivo",
|
||||
"obsidianLiveSyncSettingTab.optionSaveOnlySettings": "(Peligro) Guardar solo configuración",
|
||||
"obsidianLiveSyncSettingTab.panelChangeLog": "Registro de cambios",
|
||||
"obsidianLiveSyncSettingTab.panelGeneralSettings": "Configuraciones Generales",
|
||||
"obsidianLiveSyncSettingTab.panelPrivacyEncryption": "Privacidad y Cifrado",
|
||||
"obsidianLiveSyncSettingTab.panelGeneralSettings": "Configuraciones generales",
|
||||
"obsidianLiveSyncSettingTab.panelPrivacyEncryption": "Privacidad y cifrado",
|
||||
"obsidianLiveSyncSettingTab.panelRemoteConfiguration": "Configuración remota",
|
||||
"obsidianLiveSyncSettingTab.panelSetup": "Configuración",
|
||||
"obsidianLiveSyncSettingTab.serverVersion": "Información del servidor: ${info}",
|
||||
@@ -706,7 +712,7 @@
|
||||
"obsidianLiveSyncSettingTab.titleSetupOtherDevices": "Para configurar otros dispositivos",
|
||||
"obsidianLiveSyncSettingTab.titleSynchronizationMethod": "Método de sincronización",
|
||||
"obsidianLiveSyncSettingTab.titleSynchronizationPreset": "Preestablecimiento de sincronización",
|
||||
"obsidianLiveSyncSettingTab.titleSyncSettings": "Configuraciones de Sincronización",
|
||||
"obsidianLiveSyncSettingTab.titleSyncSettings": "Configuraciones de sincronización",
|
||||
"obsidianLiveSyncSettingTab.titleSyncSettingsViaMarkdown": "Configuración de sincronización a través de Markdown",
|
||||
"obsidianLiveSyncSettingTab.titleUpdateThinning": "Actualización de adelgazamiento",
|
||||
"obsidianLiveSyncSettingTab.warnCorsOriginUnmatched": "⚠ El origen de CORS no coincide: {from}->{to}",
|
||||
@@ -903,7 +909,9 @@
|
||||
"Same or local only": "Igual o solo local",
|
||||
"Save and Apply": "Guardar y aplicar",
|
||||
"Save settings to a markdown file. You will be notified when new settings arrive. You can set different files by the platform.": "Guardar configuración en archivo markdown. Se notificarán nuevos ajustes. Puede definir diferentes archivos por plataforma",
|
||||
"Save without connecting": "Guardar sin conectar",
|
||||
"Saving will be performed forcefully after this number of seconds.": "Guardado forzado tras esta cantidad de segundos",
|
||||
"Saving without a successful connection test keeps this profile, but automatic synchronisation may fail until the connection is corrected.": "Guardar sin una prueba de conexión correcta conserva este perfil, pero la sincronización automática puede fallar hasta que se corrija la conexión.",
|
||||
"Scan a QR Code (Recommended for mobile)": "Escanear un código QR (recomendado para móviles)",
|
||||
"Scan changes": "Buscar cambios",
|
||||
"Scan changes on customization sync": "Escanear cambios en sincronización de personalización",
|
||||
@@ -1053,6 +1061,7 @@
|
||||
"Show history": "Mostrar el historial",
|
||||
"Show icon only": "Mostrar solo el icono",
|
||||
"Show only notifications": "Mostrar solo notificaciones",
|
||||
"Show password": "Mostrar contraseña",
|
||||
"Show status as icons only": "Mostrar estado solo con íconos",
|
||||
"Show status icon instead of file warnings banner": "Mostrar icono de estado en lugar del banner de advertencia de archivos",
|
||||
"Show status inside the editor": "Mostrar estado dentro del editor",
|
||||
@@ -1094,6 +1103,7 @@
|
||||
"Syncing": "Sincronización",
|
||||
"Syncing...": "Sincronizando...",
|
||||
"Target patterns": "Patrones objetivo",
|
||||
"Test connection and save": "Probar la conexión y guardar",
|
||||
"Test Settings and Continue": "Probar los ajustes y continuar",
|
||||
"Testing only - Resolve file conflicts by syncing newer copies of the file, this can overwrite modified files. Be Warned.": "Solo pruebas - Resolver conflictos sincronizando copias nuevas (puede sobrescribir modificaciones)",
|
||||
"The connection to the server has been configured successfully. As the next step,": "La conexión con el servidor se ha configurado correctamente. Como paso siguiente,",
|
||||
@@ -1122,9 +1132,11 @@
|
||||
"This device": "Este dispositivo",
|
||||
"This device name": "Nombre de este dispositivo",
|
||||
"This feature enables direct synchronisation between devices. No server is required, but both devices must be online at the same time for synchronisation to occur, and some features may be limited. Internet connection is only required to signalling (detecting peers) and not for data transfer.": "Esta función permite la sincronización directa entre dispositivos. No requiere servidor, pero ambos dispositivos deben estar en línea al mismo tiempo para que la sincronización se produzca, y algunas funciones pueden ser limitadas. La conexión a Internet solo se necesita para la señalización (detección de pares), no para la transferencia de datos。",
|
||||
"This first setup has several short steps because it confirms encryption, the connection method, and which device provides the initial data. Once it is complete, additional devices can reuse a Setup URI.": "Esta primera configuración consta de varios pasos breves, ya que confirma el cifrado, el método de conexión y qué dispositivo aporta los datos iniciales. Una vez completada, los demás dispositivos podrán reutilizar un Setup URI.",
|
||||
"This is an advanced option for users who do not have a URI or who wish to configure detailed settings.": "Esta es una opción avanzada para usuarios que no disponen de un URI o que desean configurar parámetros detallados。",
|
||||
"This is an extremely powerful operation. We strongly recommend that you copy your Vault folder to a safe location.": "Esta es una operación extremadamente potente. Te recomendamos encarecidamente copiar la carpeta de tu Vault a un lugar seguro.",
|
||||
"This is the most suitable synchronisation method for the design. All functions are available. You must have set up a CouchDB instance.": "Este es el método de sincronización más adecuado para el diseño. Todas las funciones están disponibles. Debe tener configurada una instancia de CouchDB。",
|
||||
"This optional check uses Obsidian's internal request API and sends the credentials above to the CouchDB server. Use it only with a server you trust; administrator access may be required.": "Esta comprobación opcional usa la API interna de solicitudes de Obsidian y envía las credenciales anteriores al servidor CouchDB. Utilízala solo con un servidor de confianza; puede requerir acceso de administrador.",
|
||||
"This passphrase will not be copied to another device. It will be set to `Default` until you configure it again.": "Esta frase no se copia a otros dispositivos. Usará `Default` hasta reconfigurar",
|
||||
"This password is used to encrypt the connection. Use something long enough.": "Esta contraseña se usa para cifrar la conexión. Usa algo suficientemente largo.",
|
||||
"This procedure will first delete all existing synchronisation data from the server. Following this, the server data will be completely rebuilt, using the current state of your Vault on this device (including its local database) as": "Este procedimiento eliminará primero todos los datos de sincronización existentes en el servidor. A continuación, los datos del servidor se reconstruirán por completo usando el estado actual del Vault de este dispositivo (incluida su base de datos local) como",
|
||||
@@ -1473,5 +1485,6 @@
|
||||
"You are adding this device to an existing synchronisation setup.": "Está añadiendo este dispositivo a una configuración de sincronización existente。",
|
||||
"You can configure in the Obsidian Plugin Settings.": "Puedes configurarlo en los ajustes del complemento de Obsidian.",
|
||||
"You should create a new synchronisation destination and rebuild your data there.": "Deberías crear un nuevo destino de sincronización y reconstruir allí tus datos.",
|
||||
"You should perform this operation only in exceptional circumstances, such as when the server data is completely corrupted, when changes on all other devices are no longer needed, or when the database size has become unusually large in comparison to the Vault size.": "Solo deberías realizar esta operación en circunstancias excepcionales: cuando los datos del servidor estén completamente corruptos, cuando ya no necesites los cambios de los demás dispositivos o cuando el tamaño de la base de datos sea inusualmente grande respecto al del Vault."
|
||||
"You should perform this operation only in exceptional circumstances, such as when the server data is completely corrupted, when changes on all other devices are no longer needed, or when the database size has become unusually large in comparison to the Vault size.": "Solo deberías realizar esta operación en circunstancias excepcionales: cuando los datos del servidor estén completamente corruptos, cuando ya no necesites los cambios de los demás dispositivos o cuando el tamaño de la base de datos sea inusualmente grande respecto al del Vault.",
|
||||
"you wanted(Thank you)!": "tu solicitud (¡gracias!)"
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"(Obsolete) Use an old adapter for compatibility": "(廃止済み)古いアダプターを互換性のために利用",
|
||||
"(RegExp) Empty to sync all files. Set filter as a regular expression to limit synchronising files.": "(正規表現)空欄で全ファイルを同期します。正規表現を指定すると、同期対象のファイルを絞り込めます。",
|
||||
"(RegExp) If this is set, any changes to local and remote files that match this will be skipped.": "(正規表現)設定すると、これに一致するローカル/リモートファイルの変更はすべてスキップされます。",
|
||||
"(Select this if you are already using synchronisation on another computer or smartphone.) This option is suitable if you are new to LiveSync and want to set it up from scratch.": "(別の PC やスマートフォンですでに同期を利用している場合に選択してください。)この端末を既存の LiveSync 構成に追加する場合に適しています。",
|
||||
"(Select this if another device is already using LiveSync.) This option adds this device to that existing synchronisation setup.": "(別の PC やスマートフォンですでに同期を利用している場合に選択してください。)この端末を既存の LiveSync 構成に追加する場合に適しています。",
|
||||
"(Select this if you are configuring this device as the first synchronisation device.) This option is suitable if you are new to LiveSync and want to set it up from scratch.": "(この端末を最初の同期端末として設定する場合に選択してください。)LiveSync を初めて利用し、最初から設定したい場合に適しています。",
|
||||
"> [!INFO]- The connected devices have been detected as follows:\n${devices}": "> [!INFO]- 次の接続済みデバイスが検出されました:\n${devices}",
|
||||
"A Setup URI is a single string of text containing your server address and authentication details. Using a URI, if one was generated by your server installation script, provides a simple and secure configuration.": "Setup URI は、サーバーアドレスと認証情報を含む 1 本の文字列です。サーバーのインストールスクリプトで生成された URI がある場合は、それを使うと簡単かつ安全に設定できます。",
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
"(Obsolete) Use an old adapter for compatibility": "(사용 중단) 호환성을 위해 이전 어댑터 사용",
|
||||
"(RegExp) Empty to sync all files. Set filter as a regular expression to limit synchronising files.": "(정규식) 비워 두면 모든 파일을 동기화합니다. 정규식을 지정하면 동기화할 파일을 제한할 수 있습니다.",
|
||||
"(RegExp) If this is set, any changes to local and remote files that match this will be skipped.": "(정규식) 설정하면 이 패턴과 일치하는 로컬 및 원격 파일 변경은 모두 건너뜁니다.",
|
||||
"(Select this if you are already using synchronisation on another computer or smartphone.) This option is suitable if you are new to LiveSync and want to set it up from scratch.": "(다른 컴퓨터나 스마트폰에서 이미 동기화를 사용 중이라면 선택하세요.) 이 기기를 기존 LiveSync 구성에 추가하려는 경우에 적합합니다.",
|
||||
"(Select this if another device is already using LiveSync.) This option adds this device to that existing synchronisation setup.": "(다른 컴퓨터나 스마트폰에서 이미 동기화를 사용 중이라면 선택하세요.) 이 기기를 기존 LiveSync 구성에 추가하려는 경우에 적합합니다.",
|
||||
"(Select this if you are configuring this device as the first synchronisation device.) This option is suitable if you are new to LiveSync and want to set it up from scratch.": "(이 기기를 첫 번째 동기화 기기로 설정한다면 선택하세요.) LiveSync를 처음 사용하며 처음부터 설정하려는 경우에 적합합니다.",
|
||||
"↑: Overwrite Remote": "↑: 원격 덮어쓰기",
|
||||
"↓: Overwrite Local": "↓: 로컬 덮어쓰기",
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
"(Obsolete) Use an old adapter for compatibility": "(Устарело) Использовать старый адаптер для совместимости",
|
||||
"(RegExp) Empty to sync all files. Set filter as a regular expression to limit synchronising files.": "(RegExp) Оставьте пустым, чтобы синхронизировать все файлы. Укажите регулярное выражение, чтобы ограничить синхронизируемые файлы.",
|
||||
"(RegExp) If this is set, any changes to local and remote files that match this will be skipped.": "(RegExp) Если задано, любые изменения локальных и удалённых файлов, соответствующих этому шаблону, будут пропускаться.",
|
||||
"(Select this if you are already using synchronisation on another computer or smartphone.) This option is suitable if you are new to LiveSync and want to set it up from scratch.": "(Выберите этот вариант, если вы уже используете синхронизацию на другом компьютере или смартфоне.) Он подходит, если вы хотите добавить это устройство к уже существующей конфигурации LiveSync。",
|
||||
"(Select this if another device is already using LiveSync.) This option adds this device to that existing synchronisation setup.": "(Выберите этот вариант, если вы уже используете синхронизацию на другом компьютере или смартфоне.) Он подходит, если вы хотите добавить это устройство к уже существующей конфигурации LiveSync。",
|
||||
"(Select this if you are configuring this device as the first synchronisation device.) This option is suitable if you are new to LiveSync and want to set it up from scratch.": "(Выберите этот вариант, если настраиваете это устройство как первое устройство синхронизации.) Он подходит, если вы впервые используете LiveSync и хотите настроить всё с нуля。",
|
||||
"> [!INFO]- The connected devices have been detected as follows:\n${devices}": "> [!INFO]- Обнаружены следующие подключённые устройства:\n${devices}",
|
||||
"A Setup URI is a single string of text containing your server address and authentication details. Using a URI, if one was generated by your server installation script, provides a simple and secure configuration.": "Setup URI — это одна строка текста, содержащая адрес сервера и данные аутентификации. Если URI был создан скриптом установки сервера, его использование обеспечивает простую и безопасную настройку。",
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
"(Obsolete) Use an old adapter for compatibility": "(已淘汰)使用舊版轉接器以維持相容性",
|
||||
"(RegExp) Empty to sync all files. Set filter as a regular expression to limit synchronising files.": "(正則表示式)留空即同步所有檔案。設定正則表示式可限制要同步的檔案。",
|
||||
"(RegExp) If this is set, any changes to local and remote files that match this will be skipped.": "(正則表示式)若已設定,所有符合此模式的本機與遠端檔案變更都會被略過。",
|
||||
"(Select this if you are already using synchronisation on another computer or smartphone.) This option is suitable if you are new to LiveSync and want to set it up from scratch.": "(如果你已經在另一台電腦或手機上使用同步,請選擇此項。)此選項適合將目前裝置加入既有 LiveSync 設定的使用者。",
|
||||
"(Select this if another device is already using LiveSync.) This option adds this device to that existing synchronisation setup.": "(如果你已經在另一台電腦或手機上使用同步,請選擇此項。)此選項適合將目前裝置加入既有 LiveSync 設定的使用者。",
|
||||
"(Select this if you are configuring this device as the first synchronisation device.) This option is suitable if you are new to LiveSync and want to set it up from scratch.": "(如果你正在將此裝置設定為第一台同步裝置,請選擇此項。)此選項適合初次使用 LiveSync,並希望從頭開始設定的使用者。",
|
||||
"↑: Overwrite Remote": "↑:覆寫遠端",
|
||||
"↓: Overwrite Local": "↓:覆寫本機",
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"(Obsolete) Use an old adapter for compatibility": "(已弃用)为兼容性使用旧适配器",
|
||||
"(RegExp) Empty to sync all files. Set filter as a regular expression to limit synchronising files.": "(正则表达式)留空表示同步所有文件。可设置正则表达式来限制需要同步的文件。",
|
||||
"(RegExp) If this is set, any changes to local and remote files that match this will be skipped.": "(正则表达式)如果已设置,则所有匹配此模式的本地和远端文件变更都会被跳过。",
|
||||
"(Select this if you are already using synchronisation on another computer or smartphone.) This option is suitable if you are new to LiveSync and want to set it up from scratch.": "(如果你已经在另一台电脑或手机上使用同步,请选择此项。)此选项适合将当前设备加入现有 LiveSync 配置的用户。",
|
||||
"(Select this if another device is already using LiveSync.) This option adds this device to that existing synchronisation setup.": "(如果你已经在另一台电脑或手机上使用同步,请选择此项。)此选项适合将当前设备加入现有 LiveSync 配置的用户。",
|
||||
"(Select this if you are configuring this device as the first synchronisation device.) This option is suitable if you are new to LiveSync and want to set it up from scratch.": "(如果你正在将此设备配置为第一台同步设备,请选择此项。)此选项适合初次使用 LiveSync,并希望从头开始配置的用户。",
|
||||
"> [!INFO]- The connected devices have been detected as follows:\n${devices}": "> [!INFO]- 已检测到以下已连接设备:\n${devices}",
|
||||
"A Setup URI is a single string of text containing your server address and authentication details. Using a URI, if one was generated by your server installation script, provides a simple and secure configuration.": "Setup URI 是一段包含服务器地址与认证信息的文本。如果服务器安装脚本已经生成了 URI,使用它可以更简单且更安全地完成配置。",
|
||||
|
||||
@@ -283,7 +283,7 @@ xxhash64 (Fastest): xxhash64 (am schnellsten)
|
||||
"I am setting this up for the first time": "Ich richte dies zum ersten Mal ein"
|
||||
"(Select this if you are configuring this device as the first synchronisation device.) This option is suitable if you are new to LiveSync and want to set it up from scratch.": "(Wählen Sie dies, wenn Sie dieses Gerät als erstes Synchronisationsgerät einrichten.) Diese Option ist geeignet, wenn Sie LiveSync neu verwenden und von Grund auf einrichten möchten."
|
||||
"I am adding a device to an existing synchronisation setup": "Ich füge ein Gerät zu einer bestehenden Synchronisationseinrichtung hinzu"
|
||||
"(Select this if you are already using synchronisation on another computer or smartphone.) This option is suitable if you are new to LiveSync and want to set it up from scratch.": "(Wählen Sie dies, wenn Sie die Synchronisation bereits auf einem anderen Computer oder Smartphone verwenden.) Diese Option ist geeignet, wenn Sie dieses Gerät zu einer bestehenden LiveSync-Einrichtung hinzufügen möchten."
|
||||
"(Select this if another device is already using LiveSync.) This option adds this device to that existing synchronisation setup.": "(Wählen Sie dies, wenn Sie die Synchronisation bereits auf einem anderen Computer oder Smartphone verwenden.) Diese Option ist geeignet, wenn Sie dieses Gerät zu einer bestehenden LiveSync-Einrichtung hinzufügen möchten."
|
||||
"Yes, I want to set up a new synchronisation": "Ja, ich möchte eine neue Synchronisation einrichten"
|
||||
"Yes, I want to add this device to my existing synchronisation": "Ja, ich möchte dieses Gerät zu meiner bestehenden Synchronisation hinzufügen"
|
||||
"No, please take me back": "Nein, bitte zurück"
|
||||
|
||||
@@ -129,6 +129,7 @@ Check and convert non-path-obfuscated files: Check and convert non-path-obfuscat
|
||||
Check for documents that have not been converted to path-obfuscated IDs and convert them if necessary.:
|
||||
Check for documents that have not been converted to path-obfuscated IDs and
|
||||
convert them if necessary.
|
||||
Check server requirements: Check server requirements
|
||||
Checking connection... Please wait.: Checking connection... Please wait.
|
||||
Chunks: Chunks
|
||||
Close: Close
|
||||
@@ -158,6 +159,7 @@ Configure And Change Remote: Configure And Change Remote
|
||||
Configure E2EE: Configure E2EE
|
||||
Configure Remote: Configure Remote
|
||||
Connect: Connect
|
||||
Connect to existing database and continue: Connect to existing database and continue
|
||||
"Connected to Signaling Server (as Peer ID: ${peerId})": "Connected to Signaling Server (as Peer ID: ${peerId})"
|
||||
"Connected:": "Connected:"
|
||||
Connection Settings: Connection Settings
|
||||
@@ -167,7 +169,11 @@ Copy: Copy
|
||||
Copy Report to clipboard: Copy Report to clipboard
|
||||
CouchDB Configuration: CouchDB Configuration
|
||||
CouchDB Connection Tweak: CouchDB Connection Tweak
|
||||
CouchDB validates the database name when you connect. The name must not be empty.:
|
||||
CouchDB validates the database name when you connect. The name must not be
|
||||
empty.
|
||||
Create P2P remote: Create P2P remote
|
||||
Create or connect to database and continue: Create or connect to database and continue
|
||||
Cross-platform: Cross-platform
|
||||
"Current adapter: {adapter}": "Current adapter: {adapter}"
|
||||
Custom Headers: Custom Headers
|
||||
@@ -340,6 +346,7 @@ Encryption phassphrase. If changed, you should overwrite the server's database w
|
||||
End-to-End Encryption: End-to-End Encryption
|
||||
Endpoint URL: Endpoint URL
|
||||
Enhance chunk size: Enhance chunk size
|
||||
Enter a complete HTTP or HTTPS URL.: Enter a complete HTTP or HTTPS URL.
|
||||
Enter a folder prefix (optional): Enter a folder prefix (optional)
|
||||
Enter Setup URI: Enter Setup URI
|
||||
Enter TURN credential: Enter TURN credential
|
||||
@@ -402,6 +409,7 @@ Hidden file synchronization have been temporarily disabled. Please enable them a
|
||||
Hidden Files: Hidden Files
|
||||
Hide completely: Hide completely
|
||||
Hide not applicable items: Hide not applicable items
|
||||
Hide password: Hide password
|
||||
Higher (${local} > ${remote}): Higher (${local} > ${remote})
|
||||
Highlight diff: Highlight diff
|
||||
How to display network errors when the sync server is unreachable.: How to display network errors when the sync server is unreachable.
|
||||
@@ -1488,7 +1496,11 @@ Save and Apply: Save and Apply
|
||||
Save settings to a markdown file. You will be notified when new settings arrive. You can set different files by the platform.:
|
||||
Save settings to a markdown file. You will be notified when new settings
|
||||
arrive. You can set different files by the platform.
|
||||
Save without connecting: Save without connecting
|
||||
Saving will be performed forcefully after this number of seconds.: Saving will be performed forcefully after this number of seconds.
|
||||
Saving without a successful connection test keeps this profile, but automatic synchronisation may fail until the connection is corrected.:
|
||||
Saving without a successful connection test keeps this profile, but
|
||||
automatic synchronisation may fail until the connection is corrected.
|
||||
Scan changes: Scan changes
|
||||
Scan changes on customization sync: Scan changes on customization sync
|
||||
Scan customization automatically: Scan customization automatically
|
||||
@@ -1750,6 +1762,7 @@ Sync: Sync
|
||||
Sync once: Sync once
|
||||
Syncing...: Syncing...
|
||||
Test Settings and Continue: Test Settings and Continue
|
||||
Test connection and save: Test connection and save
|
||||
The connection to the server has been configured successfully. As the next step,:
|
||||
The connection to the server has been configured successfully. As the next
|
||||
step,
|
||||
@@ -1800,6 +1813,7 @@ Show full banner: Show full banner
|
||||
Show history: Show history
|
||||
Show icon only: Show icon only
|
||||
Show only notifications: Show only notifications
|
||||
Show password: Show password
|
||||
Show status as icons only: Show status as icons only
|
||||
Show status icon instead of file warnings banner: Show status icon instead of file warnings banner
|
||||
Show status inside the editor: Show status inside the editor
|
||||
@@ -1876,9 +1890,17 @@ This can isolate your connections between devices. Use the same Room ID for the
|
||||
the same devices.
|
||||
This device: This device
|
||||
This device name: This device name
|
||||
This first setup has several short steps because it confirms encryption, the connection method, and which device provides the initial data. Once it is complete, additional devices can reuse a Setup URI.:
|
||||
This first setup has several short steps because it confirms encryption,
|
||||
the connection method, and which device provides the initial data. Once it
|
||||
is complete, additional devices can reuse a Setup URI.
|
||||
This is an extremely powerful operation. We strongly recommend that you copy your Vault folder to a safe location.:
|
||||
This is an extremely powerful operation. We strongly recommend that you copy
|
||||
your Vault folder to a safe location.
|
||||
This optional check uses Obsidian's internal request API and sends the credentials above to the CouchDB server. Use it only with a server you trust; administrator access may be required.:
|
||||
This optional check uses Obsidian's internal request API and sends the
|
||||
credentials above to the CouchDB server. Use it only with a server you
|
||||
trust; administrator access may be required.
|
||||
This passphrase will not be copied to another device. It will be set to `Default` until you configure it again.:
|
||||
This passphrase will not be copied to another device. It will be set to
|
||||
`Default` until you configure it again.
|
||||
@@ -2071,7 +2093,7 @@ xxhash64 (Fastest): xxhash64 (Fastest)
|
||||
"I am setting this up for the first time": "I am setting this up for the first time"
|
||||
"(Select this if you are configuring this device as the first synchronisation device.) This option is suitable if you are new to LiveSync and want to set it up from scratch.": "(Select this if you are configuring this device as the first synchronisation device.) This option is suitable if you are new to LiveSync and want to set it up from scratch."
|
||||
"I am adding a device to an existing synchronisation setup": "I am adding a device to an existing synchronisation setup"
|
||||
"(Select this if you are already using synchronisation on another computer or smartphone.) This option is suitable if you are new to LiveSync and want to set it up from scratch.": "(Select this if you are already using synchronisation on another computer or smartphone.) This option is suitable if you are new to LiveSync and want to set it up from scratch."
|
||||
"(Select this if you already have another synchronising device.) This option adds this device to the same synchronisation.": "(Select this if you already have another synchronising device.) This option adds this device to the same synchronisation."
|
||||
"Yes, I want to set up a new synchronisation": "Yes, I want to set up a new synchronisation"
|
||||
"Yes, I want to add this device to my existing synchronisation": "Yes, I want to add this device to my existing synchronisation"
|
||||
"No, please take me back": "No, please take me back"
|
||||
@@ -2419,6 +2441,7 @@ Ui:
|
||||
Title: Choose a synchronisation remote
|
||||
|
||||
You can configure in the Obsidian Plugin Settings.: You can configure in the Obsidian Plugin Settings.
|
||||
"you wanted(Thank you)!": "you wanted(Thank you)!"
|
||||
|
||||
You should create a new synchronisation destination and rebuild your data there.:
|
||||
You should create a new synchronisation destination and rebuild your data
|
||||
|
||||
@@ -132,6 +132,7 @@ Check and convert non-path-obfuscated files: Comprobar y convertir archivos sin
|
||||
Check for documents that have not been converted to path-obfuscated IDs and convert them if necessary.:
|
||||
Comprueba los documentos que aún no se hayan convertido a identificadores con
|
||||
ruta ofuscada y conviértelos si es necesario.
|
||||
Check server requirements: Comprobar los requisitos del servidor
|
||||
Checking connection... Please wait.: Comprobando la conexión... Espera un momento.
|
||||
Chunks: Fragmentos (chunks)
|
||||
Close: Cerrar
|
||||
@@ -166,6 +167,7 @@ Configure And Change Remote: Configurar y cambiar remoto
|
||||
Configure E2EE: Configurar E2EE
|
||||
Configure Remote: Configurar remoto
|
||||
Connect: Conectar
|
||||
Connect to existing database and continue: Conectar a la base de datos existente y continuar
|
||||
"Connected to Signaling Server (as Peer ID: ${peerId})": "Conectado al servidor de señalización (como ID de par: ${peerId})"
|
||||
"Connected:": "Conectadas:"
|
||||
Connection Settings: Ajustes de conexión
|
||||
@@ -176,6 +178,10 @@ Copy Report to clipboard: Copiar el informe al portapapeles
|
||||
CouchDB Configuration: Configuración de CouchDB
|
||||
CouchDB Connection Tweak: Ajustes de conexión de CouchDB
|
||||
Create P2P remote: Crear remoto P2P
|
||||
CouchDB validates the database name when you connect. The name must not be empty.:
|
||||
CouchDB valida el nombre de la base de datos al conectar. El nombre no puede
|
||||
estar vacío.
|
||||
Create or connect to database and continue: Crear o conectar a la base de datos y continuar
|
||||
Cross-platform: Multiplataforma
|
||||
"Current adapter: {adapter}": "Adaptador actual: {adapter}"
|
||||
Custom Headers: Encabezados personalizados
|
||||
@@ -224,7 +230,7 @@ dialog:
|
||||
yourLanguageAvailable:
|
||||
_value: >-
|
||||
Self-hosted LiveSync tenía traducciones para tu idioma, así que se ha
|
||||
activado el ajuste %{Display language}.
|
||||
activado el ajuste %{Display Language}.
|
||||
|
||||
|
||||
Nota: no todos los mensajes están traducidos. ¡Esperamos tus
|
||||
@@ -345,6 +351,7 @@ Encryption phassphrase. If changed, you should overwrite the server's database w
|
||||
End-to-End Encryption: Cifrado de extremo a extremo
|
||||
Endpoint URL: URL del endpoint
|
||||
Enhance chunk size: Mejorar tamaño de chunks
|
||||
Enter a complete HTTP or HTTPS URL.: Introduce una URL HTTP o HTTPS completa.
|
||||
Enter a folder prefix (optional): Introduce un prefijo de carpeta (opcional)
|
||||
Enter Setup URI: Introducir el Setup URI
|
||||
Enter TURN credential: Introduce la credencial de TURN
|
||||
@@ -425,6 +432,7 @@ Hidden file synchronization have been temporarily disabled. Please enable them a
|
||||
Hidden Files: Archivos ocultos
|
||||
Hide completely: Ocultar por completo
|
||||
Hide not applicable items: Ocultar elementos no aplicables
|
||||
Hide password: Ocultar contraseña
|
||||
Higher (${local} > ${remote}): Superior (${local} > ${remote})
|
||||
Highlight diff: Resaltar las diferencias
|
||||
How to display network errors when the sync server is unreachable.:
|
||||
@@ -1518,7 +1526,7 @@ obsidianLiveSyncSettingTab:
|
||||
ERROR: Error al comprobar la frase de contraseña con el servidor remoto:
|
||||
${db}.
|
||||
logConfiguredDisabled: "Modo de sincronización configurado: DESACTIVADO"
|
||||
logConfiguredLiveSync: "Modo de sincronización configurado: Sincronización en Vivo"
|
||||
logConfiguredLiveSync: "Modo de sincronización configurado: Sincronización en vivo"
|
||||
logConfiguredPeriodic: "Modo de sincronización configurado: Periódico"
|
||||
logCouchDbConfigFail: "Configuración de CouchDB: ${title} falló"
|
||||
logCouchDbConfigSet: "Configuración de CouchDB: ${title} -> Establecer ${key} en ${value}"
|
||||
@@ -1656,8 +1664,8 @@ obsidianLiveSyncSettingTab:
|
||||
nameHiddenFileSynchronization: Sincronización de archivos ocultos
|
||||
nameManualSetup: Configuración manual
|
||||
nameTestConnection: Probar conexión
|
||||
nameTestDatabaseConnection: Probar Conexión de Base de Datos
|
||||
nameValidateDatabaseConfig: Validar Configuración de la Base de Datos
|
||||
nameTestDatabaseConnection: Probar conexión de base de datos
|
||||
nameValidateDatabaseConfig: Validar configuración de la base de datos
|
||||
okAdminPrivileges: ✔ Tienes privilegios de administrador.
|
||||
okCorsCredentials: ✔ cors.credentials está correcto.
|
||||
okCorsCredentialsForOrigin: CORS credenciales OK
|
||||
@@ -1684,8 +1692,8 @@ obsidianLiveSyncSettingTab:
|
||||
optionRebuildBoth: Reconstructuir ambos desde este dispositivo
|
||||
optionSaveOnlySettings: (Peligro) Guardar solo configuración
|
||||
panelChangeLog: Registro de cambios
|
||||
panelGeneralSettings: Configuraciones Generales
|
||||
panelPrivacyEncryption: Privacidad y Cifrado
|
||||
panelGeneralSettings: Configuraciones generales
|
||||
panelPrivacyEncryption: Privacidad y cifrado
|
||||
panelRemoteConfiguration: Configuración remota
|
||||
panelSetup: Configuración
|
||||
titleAppearance: Apariencia
|
||||
@@ -1711,7 +1719,7 @@ obsidianLiveSyncSettingTab:
|
||||
titleSetupOtherDevices: Para configurar otros dispositivos
|
||||
titleSynchronizationMethod: Método de sincronización
|
||||
titleSynchronizationPreset: Preestablecimiento de sincronización
|
||||
titleSyncSettings: Configuraciones de Sincronización
|
||||
titleSyncSettings: Configuraciones de sincronización
|
||||
titleSyncSettingsViaMarkdown: Configuración de sincronización a través de Markdown
|
||||
titleUpdateThinning: Actualización de adelgazamiento
|
||||
warnCorsOriginUnmatched: "⚠ El origen de CORS no coincide: {from}->{to}"
|
||||
@@ -1804,7 +1812,11 @@ Restore or reconstruct local database from remote.: Restaura o reconstruye la ba
|
||||
Save settings to a markdown file. You will be notified when new settings arrive. You can set different files by the platform.:
|
||||
Guardar configuración en archivo markdown. Se notificarán nuevos ajustes.
|
||||
Puede definir diferentes archivos por plataforma
|
||||
Save without connecting: Guardar sin conectar
|
||||
Saving will be performed forcefully after this number of seconds.: Guardado forzado tras esta cantidad de segundos
|
||||
Saving without a successful connection test keeps this profile, but automatic synchronisation may fail until the connection is corrected.:
|
||||
Guardar sin una prueba de conexión correcta conserva este perfil, pero la
|
||||
sincronización automática puede fallar hasta que se corrija la conexión.
|
||||
Scan changes on customization sync: Escanear cambios en sincronización de personalización
|
||||
Scan customization automatically: Escanear personalización automáticamente
|
||||
Scan customization before replicating.: Escanear personalización antes de replicar
|
||||
@@ -1910,6 +1922,7 @@ Show full banner: Mostrar banner completo
|
||||
Show history: Mostrar el historial
|
||||
Show icon only: Mostrar solo el icono
|
||||
Show only notifications: Mostrar solo notificaciones
|
||||
Show password: Mostrar contraseña
|
||||
Show status as icons only: Mostrar estado solo con íconos
|
||||
Show status icon instead of file warnings banner: Mostrar icono de estado en lugar del banner de advertencia de archivos
|
||||
Show status inside the editor: Mostrar estado dentro del editor
|
||||
@@ -1960,6 +1973,7 @@ Syncing:
|
||||
"": Sincronizando...
|
||||
Target patterns: Patrones objetivo
|
||||
Test Settings and Continue: Probar los ajustes y continuar
|
||||
Test connection and save: Probar la conexión y guardar
|
||||
Testing only - Resolve file conflicts by syncing newer copies of the file, this can overwrite modified files. Be Warned.:
|
||||
Solo pruebas - Resolver conflictos sincronizando copias nuevas (puede
|
||||
sobrescribir modificaciones)
|
||||
@@ -2022,9 +2036,17 @@ This can isolate your connections between devices. Use the same Room ID for the
|
||||
para los mismos dispositivos.
|
||||
This device: Este dispositivo
|
||||
This device name: Nombre de este dispositivo
|
||||
This first setup has several short steps because it confirms encryption, the connection method, and which device provides the initial data. Once it is complete, additional devices can reuse a Setup URI.:
|
||||
Esta primera configuración consta de varios pasos breves, ya que confirma el
|
||||
cifrado, el método de conexión y qué dispositivo aporta los datos iniciales.
|
||||
Una vez completada, los demás dispositivos podrán reutilizar un Setup URI.
|
||||
This is an extremely powerful operation. We strongly recommend that you copy your Vault folder to a safe location.:
|
||||
Esta es una operación extremadamente potente. Te recomendamos encarecidamente
|
||||
copiar la carpeta de tu Vault a un lugar seguro.
|
||||
This optional check uses Obsidian's internal request API and sends the credentials above to the CouchDB server. Use it only with a server you trust; administrator access may be required.:
|
||||
Esta comprobación opcional usa la API interna de solicitudes de Obsidian y
|
||||
envía las credenciales anteriores al servidor CouchDB. Utilízala solo con un
|
||||
servidor de confianza; puede requerir acceso de administrador.
|
||||
This passphrase will not be copied to another device. It will be set to `Default` until you configure it again.: Esta frase no se copia a otros dispositivos. Usará `Default` hasta reconfigurar
|
||||
This password is used to encrypt the connection. Use something long enough.: Esta contraseña se usa para cifrar la conexión. Usa algo suficientemente largo.
|
||||
This procedure will first delete all existing synchronisation data from the server. Following this, the server data will be completely rebuilt, using the current state of your Vault on this device (including its local database) as:
|
||||
@@ -2610,7 +2632,7 @@ xxhash64 (Fastest): xxhash64 (el más rápido)
|
||||
"I am adding a device to an existing synchronisation setup":
|
||||
"Estoy agregando un dispositivo a una configuración de sincronización
|
||||
existente"
|
||||
"(Select this if you are already using synchronisation on another computer or smartphone.) This option is suitable if you are new to LiveSync and want to set it up from scratch.":
|
||||
"(Select this if another device is already using LiveSync.) This option adds this device to that existing synchronisation setup.":
|
||||
"(Seleccione esto si ya utiliza la sincronización en otro ordenador o
|
||||
teléfono). Esta opción es adecuada si desea añadir este dispositivo a una
|
||||
configuración de LiveSync existente。"
|
||||
@@ -2666,6 +2688,7 @@ xxhash64 (Fastest): xxhash64 (el más rápido)
|
||||
limitadas. La conexión a Internet solo se necesita para la señalización
|
||||
(detección de pares), no para la transferencia de datos。"
|
||||
You can configure in the Obsidian Plugin Settings.: Puedes configurarlo en los ajustes del complemento de Obsidian.
|
||||
"you wanted(Thank you)!": "tu solicitud (¡gracias!)"
|
||||
You should create a new synchronisation destination and rebuild your data there.: Deberías crear un nuevo destino de sincronización y reconstruir allí tus datos.
|
||||
You should perform this operation only in exceptional circumstances, such as when the server data is completely corrupted, when changes on all other devices are no longer needed, or when the database size has become unusually large in comparison to the Vault size.:
|
||||
"Solo deberías realizar esta operación en circunstancias excepcionales: cuando
|
||||
|
||||
@@ -1186,7 +1186,7 @@ The minimum interval for automatic synchronisation on event.: イベント発生
|
||||
"I am setting this up for the first time": "はじめて設定します"
|
||||
"(Select this if you are configuring this device as the first synchronisation device.) This option is suitable if you are new to LiveSync and want to set it up from scratch.": "(この端末を最初の同期端末として設定する場合に選択してください。)LiveSync を初めて利用し、最初から設定したい場合に適しています。"
|
||||
"I am adding a device to an existing synchronisation setup": "既存の同期構成に端末を追加します"
|
||||
"(Select this if you are already using synchronisation on another computer or smartphone.) This option is suitable if you are new to LiveSync and want to set it up from scratch.": "(別の PC やスマートフォンですでに同期を利用している場合に選択してください。)この端末を既存の LiveSync 構成に追加する場合に適しています。"
|
||||
"(Select this if another device is already using LiveSync.) This option adds this device to that existing synchronisation setup.": "(別の PC やスマートフォンですでに同期を利用している場合に選択してください。)この端末を既存の LiveSync 構成に追加する場合に適しています。"
|
||||
"Yes, I want to set up a new synchronisation": "はい、新しい同期を設定します"
|
||||
"Yes, I want to add this device to my existing synchronisation": "はい、この端末を既存の同期に追加します"
|
||||
"No, please take me back": "いいえ、前に戻ります"
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
(Obsolete) Use an old adapter for compatibility: (사용 중단) 호환성을 위해 이전 어댑터 사용
|
||||
(RegExp) Empty to sync all files. Set filter as a regular expression to limit synchronising files.: (정규식) 비워 두면 모든 파일을 동기화합니다. 정규식을 지정하면 동기화할 파일을 제한할 수 있습니다.
|
||||
(RegExp) If this is set, any changes to local and remote files that match this will be skipped.: (정규식) 설정하면 이 패턴과 일치하는 로컬 및 원격 파일 변경은 모두 건너뜁니다.
|
||||
(Select this if you are already using synchronisation on another computer or smartphone.) This option is suitable if you are new to LiveSync and want to set it up from scratch.: (다른 컴퓨터나 스마트폰에서 이미 동기화를 사용 중이라면 선택하세요.) 이 기기를 기존 LiveSync 구성에 추가하려는 경우에 적합합니다.
|
||||
(Select this if another device is already using LiveSync.) This option adds this device to that existing synchronisation setup.: (다른 컴퓨터나 스마트폰에서 이미 동기화를 사용 중이라면 선택하세요.) 이 기기를 기존 LiveSync 구성에 추가하려는 경우에 적합합니다.
|
||||
(Select this if you are configuring this device as the first synchronisation device.) This option is suitable if you are new to LiveSync and want to set it up from scratch.: (이 기기를 첫 번째 동기화 기기로 설정한다면 선택하세요.) LiveSync를 처음 사용하며 처음부터 설정하려는 경우에 적합합니다.
|
||||
"↑: Overwrite Remote": "↑: 원격 덮어쓰기"
|
||||
"↓: Overwrite Local": "↓: 로컬 덮어쓰기"
|
||||
|
||||
@@ -1058,7 +1058,7 @@ xxhash64 (Fastest): xxhash64 (самый быстрый)
|
||||
"I am setting this up for the first time": "Я настраиваю это впервые"
|
||||
"(Select this if you are configuring this device as the first synchronisation device.) This option is suitable if you are new to LiveSync and want to set it up from scratch.": "(Выберите этот вариант, если настраиваете это устройство как первое устройство синхронизации.) Он подходит, если вы впервые используете LiveSync и хотите настроить всё с нуля。"
|
||||
"I am adding a device to an existing synchronisation setup": "Я добавляю устройство к существующей настройке синхронизации"
|
||||
"(Select this if you are already using synchronisation on another computer or smartphone.) This option is suitable if you are new to LiveSync and want to set it up from scratch.": "(Выберите этот вариант, если вы уже используете синхронизацию на другом компьютере или смартфоне.) Он подходит, если вы хотите добавить это устройство к уже существующей конфигурации LiveSync。"
|
||||
"(Select this if another device is already using LiveSync.) This option adds this device to that existing synchronisation setup.": "(Выберите этот вариант, если вы уже используете синхронизацию на другом компьютере или смартфоне.) Он подходит, если вы хотите добавить это устройство к уже существующей конфигурации LiveSync。"
|
||||
"Yes, I want to set up a new synchronisation": "Да, я хочу настроить новую синхронизацию"
|
||||
"Yes, I want to add this device to my existing synchronisation": "Да, я хочу добавить это устройство к существующей синхронизации"
|
||||
"No, please take me back": "Нет, верните меня назад"
|
||||
|
||||
@@ -1667,7 +1667,7 @@ xxhash64 (Fastest): xxhash64(最快)
|
||||
"I am setting this up for the first time": "我是第一次進行設定"
|
||||
"(Select this if you are configuring this device as the first synchronisation device.) This option is suitable if you are new to LiveSync and want to set it up from scratch.": "(如果你正在將此裝置設定為第一台同步裝置,請選擇此項。)此選項適合初次使用 LiveSync,並希望從頭開始設定的使用者。"
|
||||
"I am adding a device to an existing synchronisation setup": "我要將裝置加入既有同步設定"
|
||||
"(Select this if you are already using synchronisation on another computer or smartphone.) This option is suitable if you are new to LiveSync and want to set it up from scratch.": "(如果你已經在另一台電腦或手機上使用同步,請選擇此項。)此選項適合將目前裝置加入既有 LiveSync 設定的使用者。"
|
||||
"(Select this if another device is already using LiveSync.) This option adds this device to that existing synchronisation setup.": "(如果你已經在另一台電腦或手機上使用同步,請選擇此項。)此選項適合將目前裝置加入既有 LiveSync 設定的使用者。"
|
||||
"Yes, I want to set up a new synchronisation": "是的,我要設定新的同步"
|
||||
"Yes, I want to add this device to my existing synchronisation": "是的,我要把這台裝置加入既有同步"
|
||||
"No, please take me back": "不,返回上一步"
|
||||
|
||||
@@ -1568,7 +1568,7 @@ xxhash64 (Fastest): xxhash64(最快)
|
||||
"I am setting this up for the first time": "我是第一次进行设置"
|
||||
"(Select this if you are configuring this device as the first synchronisation device.) This option is suitable if you are new to LiveSync and want to set it up from scratch.": "(如果你正在将此设备配置为第一台同步设备,请选择此项。)此选项适合初次使用 LiveSync,并希望从头开始配置的用户。"
|
||||
"I am adding a device to an existing synchronisation setup": "我要将设备加入现有同步配置"
|
||||
"(Select this if you are already using synchronisation on another computer or smartphone.) This option is suitable if you are new to LiveSync and want to set it up from scratch.": "(如果你已经在另一台电脑或手机上使用同步,请选择此项。)此选项适合将当前设备加入现有 LiveSync 配置的用户。"
|
||||
"(Select this if another device is already using LiveSync.) This option adds this device to that existing synchronisation setup.": "(如果你已经在另一台电脑或手机上使用同步,请选择此项。)此选项适合将当前设备加入现有 LiveSync 配置的用户。"
|
||||
"Yes, I want to set up a new synchronisation": "是的,我要配置新的同步"
|
||||
"Yes, I want to add this device to my existing synchronisation": "是的,我要把这台设备加入现有同步"
|
||||
"No, please take me back": "不,返回上一步"
|
||||
|
||||
@@ -74,7 +74,7 @@ export function paneHatch(this: ObsidianLiveSyncSettingTab, paneEl: HTMLElement,
|
||||
.setDisabled(false)
|
||||
.onClick(() => {
|
||||
this.closeSetting();
|
||||
eventHub.emitEvent(EVENT_REQUEST_RUN_DOCTOR, "you wanted(Thank you)!");
|
||||
eventHub.emitEvent(EVENT_REQUEST_RUN_DOCTOR, $msg("you wanted(Thank you)!"));
|
||||
})
|
||||
);
|
||||
new Setting(paneEl)
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
bind:value={userType}
|
||||
>
|
||||
{translateMessage(
|
||||
"(Select this if you are already using synchronisation on another computer or smartphone.) This option is suitable if you are new to LiveSync and want to set it up from scratch."
|
||||
"(Select this if you already have another synchronising device.) This option adds this device to the same synchronisation."
|
||||
)}
|
||||
</Option>
|
||||
</Options>
|
||||
|
||||
@@ -48,80 +48,94 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<DialogHeader title={translateMessage("End-to-End Encryption")} />
|
||||
<Guidance>{translateMessage("Please configure your end-to-end encryption settings.")}</Guidance>
|
||||
<InputRow label={translateMessage("End-to-End Encryption")}>
|
||||
<input type="checkbox" bind:checked={encryptionSettings.encrypt} />
|
||||
<Password
|
||||
name="e2ee-passphrase"
|
||||
placeholder={translateMessage("Enter your passphrase")}
|
||||
bind:value={encryptionSettings.passphrase}
|
||||
disabled={!encryptionSettings.encrypt}
|
||||
required={encryptionSettings.encrypt}
|
||||
/>
|
||||
</InputRow>
|
||||
<InfoNote title={translateMessage("Strongly Recommended")}>
|
||||
{translateMessage(
|
||||
"Enabling end-to-end encryption ensures that your data is encrypted on your device before being sent to the remote server. This means that even if someone gains access to the server, they won't be able to read your data without the passphrase. Make sure to remember your passphrase, as it will be required to decrypt your data on other devices."
|
||||
)}
|
||||
<br />
|
||||
{translateMessage(
|
||||
"Also, please note that if you are using Peer-to-Peer synchronization, this configuration will be used when you switch to other methods and connect to a remote server in the future."
|
||||
)}
|
||||
</InfoNote>
|
||||
<InfoNote warning>
|
||||
{translateMessage("This setting must be the same even when connecting to multiple synchronisation destinations.")}
|
||||
</InfoNote>
|
||||
<InputRow label={translateMessage("Obfuscate Properties")}>
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={encryptionSettings.usePathObfuscation}
|
||||
disabled={!encryptionSettings.encrypt}
|
||||
/>
|
||||
</InputRow>
|
||||
|
||||
<InfoNote>
|
||||
{translateMessage(
|
||||
"Obfuscating properties (e.g., path of file, size, creation and modification dates) adds an additional layer of security by making it harder to identify the structure and names of your files and folders on the remote server. This helps protect your privacy and makes it more difficult for unauthorized users to infer information about your data."
|
||||
)}
|
||||
</InfoNote>
|
||||
|
||||
<ExtraItems title={translateMessage("Advanced")}>
|
||||
<InputRow label={translateMessage("Encryption Algorithm")}>
|
||||
<select bind:value={encryptionSettings.E2EEAlgorithm} disabled={!encryptionSettings.encrypt}>
|
||||
{#each Object.values(E2EEAlgorithms) as alg}
|
||||
<option value={alg}>{E2EEAlgorithmNames[alg] ?? alg}</option>
|
||||
{/each}
|
||||
</select>
|
||||
<div class="sls-e2ee-dialog">
|
||||
<DialogHeader title={translateMessage("End-to-End Encryption")} />
|
||||
<Guidance>{translateMessage("Please configure your end-to-end encryption settings.")}</Guidance>
|
||||
<InputRow label={translateMessage("End-to-End Encryption")}>
|
||||
<input type="checkbox" bind:checked={encryptionSettings.encrypt} />
|
||||
</InputRow>
|
||||
<InfoNote>
|
||||
<InfoNote title={translateMessage("Strongly Recommended")}>
|
||||
{translateMessage(
|
||||
"In most cases, you should stick with the default algorithm (${algorithm}), This setting is only required if you have an existing Vault encrypted in a different format.",
|
||||
{ algorithm: E2EEAlgorithmNames[DEFAULT_SETTINGS.E2EEAlgorithm] }
|
||||
"Enabling end-to-end encryption ensures that your data is encrypted on your device before being sent to the remote server. This means that even if someone gains access to the server, they won't be able to read your data without the passphrase. Make sure to remember your passphrase, as it will be required to decrypt your data on other devices."
|
||||
)}
|
||||
<br />
|
||||
{translateMessage(
|
||||
"Also, please note that if you are using Peer-to-Peer synchronization, this configuration will be used when you switch to other methods and connect to a remote server in the future."
|
||||
)}
|
||||
</InfoNote>
|
||||
{#if encryptionSettings.encrypt}
|
||||
<InputRow label={translateMessage("Passphrase")}>
|
||||
<Password
|
||||
name="e2ee-passphrase"
|
||||
placeholder={translateMessage("Enter your passphrase")}
|
||||
bind:value={encryptionSettings.passphrase}
|
||||
required
|
||||
/>
|
||||
</InputRow>
|
||||
<InfoNote warning>
|
||||
{translateMessage(
|
||||
"This setting must be the same even when connecting to multiple synchronisation destinations."
|
||||
)}
|
||||
</InfoNote>
|
||||
<InputRow label={translateMessage("Obfuscate Properties")}>
|
||||
<input type="checkbox" bind:checked={encryptionSettings.usePathObfuscation} />
|
||||
</InputRow>
|
||||
<InfoNote>
|
||||
{translateMessage(
|
||||
"Obfuscating properties (e.g., path of file, size, creation and modification dates) adds an additional layer of security by making it harder to identify the structure and names of your files and folders on the remote server. This helps protect your privacy and makes it more difficult for unauthorized users to infer information about your data."
|
||||
)}
|
||||
</InfoNote>
|
||||
{/if}
|
||||
|
||||
<ExtraItems title={translateMessage("Advanced")}>
|
||||
<InputRow label={translateMessage("Encryption Algorithm")}>
|
||||
<select bind:value={encryptionSettings.E2EEAlgorithm} disabled={!encryptionSettings.encrypt}>
|
||||
{#each Object.values(E2EEAlgorithms) as alg}
|
||||
<option value={alg}>{E2EEAlgorithmNames[alg] ?? alg}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</InputRow>
|
||||
<InfoNote>
|
||||
{translateMessage(
|
||||
"In most cases, you should stick with the default algorithm (${algorithm}), This setting is only required if you have an existing Vault encrypted in a different format.",
|
||||
{ algorithm: E2EEAlgorithmNames[DEFAULT_SETTINGS.E2EEAlgorithm] }
|
||||
)}
|
||||
</InfoNote>
|
||||
<InfoNote warning>
|
||||
{translateMessage(
|
||||
"Changing the encryption algorithm will prevent access to any data previously encrypted with a different algorithm. Ensure that all your devices are configured to use the same algorithm to maintain access to your data."
|
||||
)}
|
||||
</InfoNote>
|
||||
</ExtraItems>
|
||||
|
||||
<InfoNote warning>
|
||||
{translateMessage(
|
||||
"Changing the encryption algorithm will prevent access to any data previously encrypted with a different algorithm. Ensure that all your devices are configured to use the same algorithm to maintain access to your data."
|
||||
)}
|
||||
<p>
|
||||
{translateMessage(
|
||||
"Please be aware that the End-to-End Encryption passphrase is not validated until the synchronisation process actually commences. This is a security measure designed to protect your data."
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
{translateMessage(
|
||||
"Therefore, we ask that you exercise extreme caution when configuring server information manually. If an incorrect passphrase is entered, the data on the server will become corrupted."
|
||||
)} <br /><br />
|
||||
{translateMessage("Please understand that this is intended behaviour.")}
|
||||
</p>
|
||||
</InfoNote>
|
||||
</ExtraItems>
|
||||
|
||||
<InfoNote warning>
|
||||
<p>
|
||||
{translateMessage(
|
||||
"Please be aware that the End-to-End Encryption passphrase is not validated until the synchronisation process actually commences. This is a security measure designed to protect your data."
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
{translateMessage(
|
||||
"Therefore, we ask that you exercise extreme caution when configuring server information manually. If an incorrect passphrase is entered, the data on the server will become corrupted."
|
||||
)} <br /><br />
|
||||
{translateMessage("Please understand that this is intended behaviour.")}
|
||||
</p>
|
||||
</InfoNote>
|
||||
<UserDecisions>
|
||||
<Decision title={translateMessage("Proceed")} important disabled={!e2eeValid} commit={() => commit()} />
|
||||
<Decision title={translateMessage("Cancel")} commit={() => setResult(TYPE_CANCELLED)} />
|
||||
</UserDecisions>
|
||||
</div>
|
||||
|
||||
<UserDecisions>
|
||||
<Decision title={translateMessage("Proceed")} important disabled={!e2eeValid} commit={() => commit()} />
|
||||
<Decision title={translateMessage("Cancel")} commit={() => setResult(TYPE_CANCELLED)} />
|
||||
</UserDecisions>
|
||||
<style>
|
||||
.sls-e2ee-dialog {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5em;
|
||||
}
|
||||
:global(.dialog-host .sls-e2ee-dialog label > span) {
|
||||
width: auto;
|
||||
min-width: 8em;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
let showPassword = $state(false);
|
||||
const type = $derived.by(() => (showPassword ? "text" : "password"));
|
||||
const translatedPlaceholder = $derived.by(() => translate(placeholder));
|
||||
const toggleLabel = $derived.by(() => translate(showPassword ? "Hide password" : "Show password"));
|
||||
</script>
|
||||
|
||||
<input
|
||||
@@ -31,4 +32,36 @@
|
||||
autocorrect="off"
|
||||
autocapitalize="off"
|
||||
/>
|
||||
<input type="checkbox" bind:checked={showPassword} />
|
||||
<button
|
||||
type="button"
|
||||
class="sls-password-toggle"
|
||||
aria-label={toggleLabel}
|
||||
title={toggleLabel}
|
||||
aria-pressed={showPassword}
|
||||
{disabled}
|
||||
onclick={() => (showPassword = !showPassword)}
|
||||
>
|
||||
👁️
|
||||
</button>
|
||||
|
||||
<style>
|
||||
.sls-password-toggle {
|
||||
display: inline-flex;
|
||||
flex: 0 0 auto;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 4px;
|
||||
margin-left: 4px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
}
|
||||
.sls-password-toggle:hover {
|
||||
background: var(--background-modifier-hover);
|
||||
}
|
||||
:global(body.is-mobile) .sls-password-toggle {
|
||||
min-width: 44px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
Your {title || "data"} has been copied to the clipboard.
|
||||
</InfoNote>
|
||||
<UserDecisions>
|
||||
<Decision title="OK" important={true} {commit} />
|
||||
<Decision title={translateMessage("Ok")} important={true} {commit} />
|
||||
</UserDecisions>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -291,6 +291,15 @@ export async function adjustSettingToRemote(
|
||||
return true;
|
||||
}
|
||||
|
||||
if (operation === "rebuild") {
|
||||
// An overwrite makes this device authoritative for both the Vault contents and the
|
||||
// shared synchronisation settings. The remote lookup above remains a connection
|
||||
// preflight, but settings from the database which is about to be replaced must not
|
||||
// overwrite intentional local changes such as enabling E2EE.
|
||||
log("Rebuild will use this device's synchronisation settings.", LOG_LEVEL_NOTICE);
|
||||
return true;
|
||||
}
|
||||
|
||||
const remoteTweaks = remoteResult.values;
|
||||
const necessary = extractObject(TweakValuesShouldMatchedTemplate, remoteTweaks);
|
||||
// Check if any necessary tweak value is different from current config.
|
||||
|
||||
@@ -1149,6 +1149,33 @@ describe("Red Flag Feature", () => {
|
||||
});
|
||||
|
||||
describe("Remote configuration adjustment", () => {
|
||||
it("keeps this device's E2EE settings when preparing to overwrite the remote", async () => {
|
||||
const host = createHostMock();
|
||||
Object.assign(host.mocks.setting.settings, TweakValuesShouldMatchedTemplate, {
|
||||
encrypt: true,
|
||||
passphrase: "local-encryption-passphrase",
|
||||
});
|
||||
host.mocks.tweakValue.fetchRemotePreferred.mockResolvedValueOnce(
|
||||
availableRemoteTweaks({
|
||||
...TweakValuesShouldMatchedTemplate,
|
||||
encrypt: false,
|
||||
})
|
||||
);
|
||||
|
||||
const result = await adjustSettingToRemote(
|
||||
host as any,
|
||||
createLoggerMock(),
|
||||
host.mocks.setting.currentSettings(),
|
||||
"rebuild"
|
||||
);
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(host.mocks.tweakValue.fetchRemotePreferred).toHaveBeenCalledOnce();
|
||||
expect(host.mocks.setting.currentSettings().encrypt).toBe(true);
|
||||
expect(host.mocks.setting.currentSettings().passphrase).toBe("local-encryption-passphrase");
|
||||
expect(host.mocks.setting.applyExternalSettings).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should skip remote configuration fetch when preventFetchingConfig is true", async () => {
|
||||
const host = createHostMock();
|
||||
const config = { preventFetchingConfig: true } as any;
|
||||
@@ -1855,8 +1882,15 @@ describe("Red Flag Feature", () => {
|
||||
it("should handle rebuildAll flag with flagHandlerToEventHandler", async () => {
|
||||
const host = createHostMock();
|
||||
const log = createLoggerMock();
|
||||
Object.assign(host.mocks.setting.settings, TweakValuesShouldMatchedTemplate, {
|
||||
encrypt: true,
|
||||
passphrase: "local-encryption-passphrase",
|
||||
});
|
||||
host.mocks.tweakValue.fetchRemotePreferred.mockResolvedValueOnce(
|
||||
availableRemoteTweaks({ customChunkSize: 1 })
|
||||
availableRemoteTweaks({
|
||||
...TweakValuesShouldMatchedTemplate,
|
||||
encrypt: false,
|
||||
})
|
||||
);
|
||||
|
||||
host.mocks.storageAccess.files.add(FlagFilesOriginal.REBUILD_ALL);
|
||||
@@ -1868,6 +1902,8 @@ describe("Red Flag Feature", () => {
|
||||
await Promise.resolve(eventHandler());
|
||||
await new Promise((resolve) => setTimeout(resolve, 10));
|
||||
expect(host.mocks.rebuilder.$rebuildEverything).toHaveBeenCalled();
|
||||
expect(host.mocks.setting.currentSettings().encrypt).toBe(true);
|
||||
expect(host.mocks.setting.applyExternalSettings).not.toHaveBeenCalled();
|
||||
|
||||
expect(host.mocks.ui.dialogManager.openWithExplicitCancel).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -345,6 +345,12 @@ body {
|
||||
}
|
||||
|
||||
.sls-onboarding-invitation-action {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
/* Touch devices need a larger tap target; on desktop this would just pad the
|
||||
link with blank space and make it look like a misplaced button. */
|
||||
body.is-mobile .sls-onboarding-invitation-action {
|
||||
display: inline-flex;
|
||||
min-width: 44px;
|
||||
min-height: 44px;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { Locator, Page } from "playwright";
|
||||
import { $msg } from "../../../src/common/translation.ts";
|
||||
import { evalObsidianJson } from "./cli.ts";
|
||||
import { captureObsidianDialogue, captureObsidianElement, withObsidianPage } from "./ui.ts";
|
||||
|
||||
@@ -166,7 +167,7 @@ export async function generateSetupURIFromDevice(
|
||||
);
|
||||
await withObsidianPage(port, async (page) => {
|
||||
const result = modalByTitle(page, resultTitle);
|
||||
await result.getByRole("button", { name: "OK", exact: true }).click({ timeout: uiTimeoutMs });
|
||||
await result.getByRole("button", { name: $msg("Ok"), exact: true }).click({ timeout: uiTimeoutMs });
|
||||
await result.waitFor({ state: "hidden", timeout: uiTimeoutMs });
|
||||
});
|
||||
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import { randomBytes } from "node:crypto";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { DEVICE_ID_PREFERRED, MILESTONE_DOCID } from "@vrtmrz/livesync-commonlib/compat/common/types";
|
||||
import { evalObsidianJson } from "../runner/cli.ts";
|
||||
import {
|
||||
assertCouchDbReachable,
|
||||
deleteCouchDbDatabase,
|
||||
fetchCouchDbDocument,
|
||||
loadCouchDbConfig,
|
||||
makeUniqueDatabaseName,
|
||||
putCouchDbDocument,
|
||||
waitForCouchDbDocs,
|
||||
type CouchDbConfig,
|
||||
} from "../runner/couchdb.ts";
|
||||
@@ -28,7 +31,8 @@ import {
|
||||
continueWithoutRemoteSettings,
|
||||
type SetupArtifact,
|
||||
} from "../runner/setupUri.ts";
|
||||
import { captureObsidianPage, withObsidianPage } from "../runner/ui.ts";
|
||||
import { captureObsidianPage, openLiveSyncSettings, withObsidianPage } from "../runner/ui.ts";
|
||||
import { dismissConfigDoctorIfShown } from "../runner/upgradeWorkflow.ts";
|
||||
import { createTemporaryVault, type TemporaryVault } from "../runner/vault.ts";
|
||||
|
||||
process.env.E2E_OBSIDIAN_CLI_TIMEOUT_MS ??= "90000";
|
||||
@@ -44,6 +48,10 @@ const captures = {
|
||||
scenario: "couchdb-manual-setup-workflow",
|
||||
guide: "couchdb-manual",
|
||||
} as const;
|
||||
const e2eeRebuildCaptures = {
|
||||
scenario: "couchdb-manual-setup-workflow",
|
||||
guide: "couchdb-manual-e2ee-rebuild",
|
||||
} as const;
|
||||
|
||||
type RunnerContext = {
|
||||
binary: string;
|
||||
@@ -111,25 +119,59 @@ async function enterManualCouchDBSettings(port: number, couchDb: CouchDbConfig,
|
||||
await withObsidianPage(port, async (page) => {
|
||||
const method = modalByTitle(page, "Connection Method");
|
||||
await selectRadioOption(method, "Configure a remote manually");
|
||||
await method
|
||||
.getByRole("button", { name: "Proceed with manual configuration" })
|
||||
.click({ timeout: uiTimeoutMs });
|
||||
await method.getByRole("button", { name: "Proceed with manual configuration" }).click({ timeout: uiTimeoutMs });
|
||||
|
||||
const encryption = modalByTitle(page, "End-to-End Encryption");
|
||||
await encryption.waitFor({ state: "visible", timeout: uiTimeoutMs });
|
||||
assertEqual(
|
||||
await encryption.locator('input[name="e2ee-passphrase"]').count(),
|
||||
0,
|
||||
"The passphrase field was present before end-to-end encryption was enabled."
|
||||
);
|
||||
assertEqual(
|
||||
await encryption.locator("label.row").filter({ hasText: "Obfuscate Properties" }).count(),
|
||||
0,
|
||||
"The Obfuscate Properties row was present before end-to-end encryption was enabled."
|
||||
);
|
||||
await encryption
|
||||
.locator("label.row")
|
||||
.filter({ hasText: "End-to-End Encryption" })
|
||||
.locator('input[type="checkbox"]')
|
||||
.first()
|
||||
.check({ timeout: uiTimeoutMs });
|
||||
const passphraseInput = encryption.locator('input[name="e2ee-passphrase"]');
|
||||
await passphraseInput.waitFor({ state: "visible", timeout: uiTimeoutMs });
|
||||
await encryption
|
||||
.locator("label.row")
|
||||
.filter({ hasText: "Obfuscate Properties" })
|
||||
.locator('input[type="checkbox"]')
|
||||
.first()
|
||||
.check({ timeout: uiTimeoutMs });
|
||||
await encryption.locator('input[name="e2ee-passphrase"]').fill(randomBytes(24).toString("base64url"));
|
||||
const passphraseValue = randomBytes(24).toString("base64url");
|
||||
await passphraseInput.fill(passphraseValue);
|
||||
const passwordToggle = encryption.locator("button.sls-password-toggle");
|
||||
await passwordToggle.click({ timeout: uiTimeoutMs });
|
||||
assertEqual(
|
||||
await passphraseInput.getAttribute("type"),
|
||||
"text",
|
||||
"Toggling visibility did not reveal the passphrase."
|
||||
);
|
||||
assertEqual(
|
||||
await passphraseInput.inputValue(),
|
||||
passphraseValue,
|
||||
"Toggling visibility changed the passphrase value."
|
||||
);
|
||||
await passwordToggle.click({ timeout: uiTimeoutMs });
|
||||
assertEqual(
|
||||
await passphraseInput.getAttribute("type"),
|
||||
"password",
|
||||
"Toggling visibility again did not re-mask the passphrase."
|
||||
);
|
||||
assertEqual(
|
||||
await passphraseInput.inputValue(),
|
||||
passphraseValue,
|
||||
"Re-masking the passphrase changed its value."
|
||||
);
|
||||
});
|
||||
screenshots.push(await captureGuideDialogue(port, "guide-couchdb-manual-encryption.png", "End-to-End Encryption"));
|
||||
await withObsidianPage(port, async (page) => {
|
||||
@@ -246,6 +288,112 @@ async function waitForRemoteEntry(context: RunnerContext, entry: { id: string; c
|
||||
});
|
||||
}
|
||||
|
||||
async function assertPersistedE2EE(vault: TemporaryVault): Promise<void> {
|
||||
const persisted = JSON.parse(
|
||||
await readFile(join(vault.path, ".obsidian", "plugins", "obsidian-livesync", "data.json"), "utf8")
|
||||
) as {
|
||||
encrypt?: unknown;
|
||||
encryptedPassphrase?: unknown;
|
||||
passphrase?: unknown;
|
||||
};
|
||||
assertEqual(persisted.encrypt, true, "Manual CouchDB setup did not persist E2EE as enabled.");
|
||||
assertEqual(persisted.passphrase, "", "Manual CouchDB setup persisted the E2EE passphrase in plain text.");
|
||||
if (typeof persisted.encryptedPassphrase !== "string" || persisted.encryptedPassphrase.length === 0) {
|
||||
throw new Error("Manual CouchDB setup did not persist an encrypted E2EE passphrase.");
|
||||
}
|
||||
}
|
||||
|
||||
async function setRemotePreferredE2EEDisabled(context: RunnerContext): Promise<void> {
|
||||
const milestone = await fetchCouchDbDocument(context.couchDb, context.dbName, MILESTONE_DOCID);
|
||||
const tweakValues = milestone.tweak_values;
|
||||
if (typeof tweakValues !== "object" || tweakValues === null || Array.isArray(tweakValues)) {
|
||||
throw new Error("The existing CouchDB milestone did not contain synchronisation settings.");
|
||||
}
|
||||
const preferred = (tweakValues as Record<string, unknown>)[DEVICE_ID_PREFERRED];
|
||||
if (typeof preferred !== "object" || preferred === null || Array.isArray(preferred)) {
|
||||
throw new Error("The existing CouchDB milestone did not contain preferred synchronisation settings.");
|
||||
}
|
||||
await putCouchDbDocument(context.couchDb, context.dbName, {
|
||||
...milestone,
|
||||
tweak_values: {
|
||||
...tweakValues,
|
||||
[DEVICE_ID_PREFERRED]: {
|
||||
...(preferred as Record<string, unknown>),
|
||||
encrypt: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function assertRemotePreferredE2EE(context: RunnerContext, expected: boolean): Promise<void> {
|
||||
const milestone = await fetchCouchDbDocument(context.couchDb, context.dbName, MILESTONE_DOCID);
|
||||
const tweakValues = milestone.tweak_values;
|
||||
const preferred =
|
||||
typeof tweakValues === "object" && tweakValues !== null && !Array.isArray(tweakValues)
|
||||
? (tweakValues as Record<string, unknown>)[DEVICE_ID_PREFERRED]
|
||||
: undefined;
|
||||
const encrypt =
|
||||
typeof preferred === "object" && preferred !== null && !Array.isArray(preferred)
|
||||
? (preferred as Record<string, unknown>).encrypt
|
||||
: undefined;
|
||||
assertEqual(encrypt, expected, `The remote preferred E2EE setting was not ${expected ? "enabled" : "disabled"}.`);
|
||||
}
|
||||
|
||||
async function scheduleRemoteOverwrite(port: number): Promise<void> {
|
||||
await dismissConfigDoctorIfShown(port);
|
||||
await withObsidianPage(port, async (page) => {
|
||||
const settingsNavigator = await openLiveSyncSettings(page, uiTimeoutMs);
|
||||
const maintenance = await settingsNavigator.openPage("Maintenance");
|
||||
const overwrite = maintenance
|
||||
.locator(".setting-item")
|
||||
.filter({ hasText: "Overwrite Server Data with This Device's Files" });
|
||||
await overwrite
|
||||
.getByRole("button", { name: "Schedule and Restart", exact: true })
|
||||
.click({ timeout: uiTimeoutMs });
|
||||
});
|
||||
}
|
||||
|
||||
async function assertRemoteEntryEncrypted(
|
||||
context: RunnerContext,
|
||||
entry: { id: string; path: string; children: string[] },
|
||||
plaintextPath: string,
|
||||
plaintext: string
|
||||
): Promise<void> {
|
||||
const remoteMetadata = await fetchCouchDbDocument(context.couchDb, context.dbName, entry.id);
|
||||
const serialisedMetadata = JSON.stringify(remoteMetadata);
|
||||
if (
|
||||
!remoteMetadata._id.startsWith("f:") ||
|
||||
typeof remoteMetadata.path !== "string" ||
|
||||
!remoteMetadata.path.startsWith("/\\:") ||
|
||||
remoteMetadata.path === entry.path ||
|
||||
serialisedMetadata.includes(plaintextPath) ||
|
||||
!Array.isArray(remoteMetadata.children) ||
|
||||
remoteMetadata.children.length !== 0 ||
|
||||
remoteMetadata.mtime !== 0 ||
|
||||
remoteMetadata.ctime !== 0 ||
|
||||
remoteMetadata.size !== 0
|
||||
) {
|
||||
throw new Error("The directly fetched CouchDB Metadata document did not protect its properties.");
|
||||
}
|
||||
|
||||
const childId = entry.children[0];
|
||||
if (!childId) {
|
||||
throw new Error("The local E2EE test entry did not reference a Chunk document.");
|
||||
}
|
||||
if (!childId.startsWith("h:+")) {
|
||||
throw new Error(`The E2EE test entry used an unencrypted Chunk identifier: ${childId}`);
|
||||
}
|
||||
const remoteChunk = await fetchCouchDbDocument(context.couchDb, context.dbName, childId);
|
||||
assertEqual(remoteChunk.e_, true, "The directly fetched CouchDB Chunk was not marked as encrypted.");
|
||||
if (
|
||||
typeof remoteChunk.data !== "string" ||
|
||||
remoteChunk.data === plaintext ||
|
||||
remoteChunk.data.includes(plaintext)
|
||||
) {
|
||||
throw new Error("The directly fetched CouchDB Chunk contained readable Vault content.");
|
||||
}
|
||||
}
|
||||
|
||||
async function main(): Promise<void> {
|
||||
const binary = requireObsidianBinary();
|
||||
const cli = discoverObsidianCli();
|
||||
@@ -288,11 +436,37 @@ async function main(): Promise<void> {
|
||||
1,
|
||||
"Manual CouchDB setup did not persist exactly one remote profile."
|
||||
);
|
||||
await assertPersistedE2EE(vaultA);
|
||||
|
||||
await writeNoteViaObsidian(context.cliBinary, session.cliEnv, notePath, noteContent);
|
||||
const entry = await waitForLocalDatabaseEntry(context.cliBinary, session.cliEnv, notePath);
|
||||
await pushLocalChanges(context.cliBinary, session.cliEnv);
|
||||
await waitForRemoteEntry(context, entry);
|
||||
} catch (error) {
|
||||
await captureFailure(session, "first-device");
|
||||
throw error;
|
||||
} finally {
|
||||
await stopTrackedSession(context, session);
|
||||
}
|
||||
|
||||
await setRemotePreferredE2EEDisabled(context);
|
||||
await assertRemotePreferredE2EE(context, false);
|
||||
|
||||
session = await startUnconfiguredSession(context, vaultA);
|
||||
try {
|
||||
await scheduleRemoteOverwrite(session.remoteDebuggingPort);
|
||||
screenshots.push(await confirmRebuild(session.remoteDebuggingPort, e2eeRebuildCaptures));
|
||||
screenshots.push(
|
||||
await acknowledgeDisabledOptionalFeatures(session.remoteDebuggingPort, e2eeRebuildCaptures)
|
||||
);
|
||||
await finishInitialisation(session.remoteDebuggingPort, context.cliBinary, session.cliEnv);
|
||||
await resumeCompatibilityReviewIfShown(session.remoteDebuggingPort);
|
||||
await assertPersistedE2EE(vaultA);
|
||||
|
||||
const rebuiltEntry = await waitForLocalDatabaseEntry(context.cliBinary, session.cliEnv, notePath);
|
||||
await waitForRemoteEntry(context, rebuiltEntry);
|
||||
await assertRemoteEntryEncrypted(context, rebuiltEntry, notePath, noteContent);
|
||||
await assertRemotePreferredE2EE(context, true);
|
||||
|
||||
const generated = await generateSetupURIFromDevice(
|
||||
session.remoteDebuggingPort,
|
||||
@@ -302,7 +476,7 @@ async function main(): Promise<void> {
|
||||
secondDeviceArtifact = generated.artifact;
|
||||
screenshots.push(...generated.screenshots);
|
||||
} catch (error) {
|
||||
await captureFailure(session, "first-device");
|
||||
await captureFailure(session, "e2ee-rebuild");
|
||||
throw error;
|
||||
} finally {
|
||||
await stopTrackedSession(context, session);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { $msg } from "../../../src/common/translation.ts";
|
||||
import { discoverObsidianCli, requireObsidianBinary } from "../runner/environment.ts";
|
||||
import { createE2eCouchDbPluginData, waitForLiveSyncCoreReady } from "../runner/liveSyncWorkflow.ts";
|
||||
import { assertMobileDialogueLayout, assertMobileNoticeLayout, setObsidianMobileTestMode } from "../runner/mobileUi.ts";
|
||||
@@ -909,7 +910,7 @@ async function verifyLogAndReportSurfaces(): Promise<{ log: string; report: stri
|
||||
if (!report.includes("# ---- Debug Info Dump ----")) {
|
||||
throw new Error("The full-report dialogue did not contain the generated debug report.");
|
||||
}
|
||||
await modal.getByRole("button", { name: "OK", exact: true }).waitFor({
|
||||
await modal.getByRole("button", { name: $msg("Ok"), exact: true }).waitFor({
|
||||
state: "visible",
|
||||
timeout: uiTimeoutMs,
|
||||
});
|
||||
@@ -920,7 +921,7 @@ async function verifyLogAndReportSurfaces(): Promise<{ log: string; report: stri
|
||||
const modal = page.locator(".modal-container").filter({
|
||||
hasText: "Your Debug info is ready to be copied",
|
||||
});
|
||||
await modal.getByRole("button", { name: "OK", exact: true }).click({ timeout: uiTimeoutMs });
|
||||
await modal.getByRole("button", { name: $msg("Ok"), exact: true }).click({ timeout: uiTimeoutMs });
|
||||
await modal.waitFor({ state: "hidden", timeout: uiTimeoutMs });
|
||||
});
|
||||
|
||||
|
||||
@@ -131,7 +131,57 @@ async function captureAndSelectMobileInvitation(): Promise<string> {
|
||||
return screenshot;
|
||||
}
|
||||
|
||||
async function captureAndCloseIntro(filename: string, mobile: boolean): Promise<string> {
|
||||
async function captureMobilePasswordToggle(): Promise<string> {
|
||||
const port = obsidianRemoteDebuggingPort();
|
||||
await withObsidianPage(port, async (page) => {
|
||||
const intro = onboardingDialogue(page);
|
||||
await intro
|
||||
.locator("label")
|
||||
.filter({ hasText: "I am setting this up for the first time" })
|
||||
.locator('input[type="radio"]')
|
||||
.first()
|
||||
.check({ timeout: uiTimeoutMs });
|
||||
await intro
|
||||
.getByRole("button", { name: "Yes, I want to set up a new synchronisation" })
|
||||
.click({ timeout: uiTimeoutMs });
|
||||
|
||||
const method = page.locator(".modal-container").filter({ hasText: "Connection Method" });
|
||||
await method.waitFor({ state: "visible", timeout: uiTimeoutMs });
|
||||
await method
|
||||
.locator("label")
|
||||
.filter({ hasText: "Configure a remote manually" })
|
||||
.locator('input[type="radio"]')
|
||||
.first()
|
||||
.check({ timeout: uiTimeoutMs });
|
||||
await method.getByRole("button", { name: "Proceed with manual configuration" }).click({ timeout: uiTimeoutMs });
|
||||
|
||||
const encryption = page.locator(".modal-container").filter({ hasText: "End-to-End Encryption" });
|
||||
await encryption.waitFor({ state: "visible", timeout: uiTimeoutMs });
|
||||
await encryption
|
||||
.locator("label.row")
|
||||
.filter({ hasText: "End-to-End Encryption" })
|
||||
.locator('input[type="checkbox"]')
|
||||
.first()
|
||||
.check({ timeout: uiTimeoutMs });
|
||||
});
|
||||
const screenshot = await captureObsidianDialogue(port, "onboarding-e2ee-mobile.png", async (page) => {
|
||||
const encryption = page.locator(".modal-container").filter({ hasText: "End-to-End Encryption" });
|
||||
const passwordToggle = encryption.locator("button.sls-password-toggle");
|
||||
await passwordToggle.waitFor({ state: "visible", timeout: uiTimeoutMs });
|
||||
await passwordToggle.evaluate((element) => element.scrollIntoView({ block: "center" }));
|
||||
await assertLocatorHasMinimumTouchTarget(page, passwordToggle, {
|
||||
label: "mobile password visibility button",
|
||||
});
|
||||
});
|
||||
await withObsidianPage(port, async (page) => {
|
||||
const encryption = page.locator(".modal-container").filter({ hasText: "End-to-End Encryption" });
|
||||
await encryption.getByRole("button", { name: "Cancel", exact: true }).click({ timeout: uiTimeoutMs });
|
||||
await encryption.waitFor({ state: "hidden", timeout: uiTimeoutMs });
|
||||
});
|
||||
return screenshot;
|
||||
}
|
||||
|
||||
async function captureIntro(filename: string, mobile: boolean, closeAfterCapture = true): Promise<string> {
|
||||
const port = obsidianRemoteDebuggingPort();
|
||||
const screenshot = await captureObsidianDialogue(port, filename, async (page) => {
|
||||
const container = onboardingDialogue(page);
|
||||
@@ -145,11 +195,13 @@ async function captureAndCloseIntro(filename: string, mobile: boolean): Promise<
|
||||
.waitFor({ state: "visible", timeout: uiTimeoutMs });
|
||||
if (mobile) await assertMobileDialogueLayout(page, container, "mobile onboarding introduction");
|
||||
});
|
||||
await withObsidianPage(port, async (page) => {
|
||||
const container = onboardingDialogue(page);
|
||||
await container.getByRole("button", { name: "No, please take me back" }).click({ timeout: uiTimeoutMs });
|
||||
await container.waitFor({ state: "hidden", timeout: uiTimeoutMs });
|
||||
});
|
||||
if (closeAfterCapture) {
|
||||
await withObsidianPage(port, async (page) => {
|
||||
const container = onboardingDialogue(page);
|
||||
await container.getByRole("button", { name: "No, please take me back" }).click({ timeout: uiTimeoutMs });
|
||||
await container.waitFor({ state: "hidden", timeout: uiTimeoutMs });
|
||||
});
|
||||
}
|
||||
return screenshot;
|
||||
}
|
||||
|
||||
@@ -231,10 +283,11 @@ async function main(): Promise<void> {
|
||||
|
||||
const desktopInvitation = await captureDesktopInvitation();
|
||||
const mobileInvitation = await captureAndSelectMobileInvitation();
|
||||
const mobileIntro = await captureAndCloseIntro("onboarding-intro-mobile.png", true);
|
||||
const mobileIntro = await captureIntro("onboarding-intro-mobile.png", true, false);
|
||||
const mobileEncryption = await captureMobilePasswordToggle();
|
||||
await setObsidianMobileTestMode(obsidianRemoteDebuggingPort(), false, uiTimeoutMs);
|
||||
await openOnboardingFromSettings();
|
||||
const settingsIntro = await captureAndCloseIntro("onboarding-intro-settings-desktop.png", false);
|
||||
const settingsIntro = await captureIntro("onboarding-intro-settings-desktop.png", false);
|
||||
await dismissVisibleNotices();
|
||||
await closeSettings();
|
||||
|
||||
@@ -243,6 +296,7 @@ async function main(): Promise<void> {
|
||||
desktopInvitation,
|
||||
mobileInvitation,
|
||||
mobileIntro,
|
||||
mobileEncryption,
|
||||
settingsIntro,
|
||||
].join(", ")}`
|
||||
);
|
||||
|
||||
+27
@@ -12,6 +12,33 @@ Earlier releases remain available in the 1.0 release history, the 1.0 preview hi
|
||||
|
||||
## Unreleased
|
||||
|
||||
## 1.0.24
|
||||
|
||||
3rd September, 2026
|
||||
|
||||
### Interface and translation
|
||||
|
||||
#### Fixed
|
||||
|
||||
- The Setup Wizard now correctly explains that the existing-device path adds this device to an existing synchronisation (PR #1118). Thank you to @nikhilmaddirala for the contribution!
|
||||
- Spanish translations now resolve the **Display language** placeholder, cover previously untranslated Setup Wizard and CouchDB text, translate user-facing Config Doctor values and confirmation controls, and use Spanish sentence case (PR #1129). Thank you to @zeedif for the contribution!
|
||||
|
||||
#### Improved
|
||||
|
||||
- The Setup Wizard now shows the passphrase and **Obfuscate Properties** controls only after E2EE is enabled, provides a password-visibility button, allows longer translated labels to wrap, and keeps the invitation link compact on desktop while preserving its mobile touch target (PR #1130). Thank you to @zeedif for the contribution!
|
||||
|
||||
### Synchronisation and storage
|
||||
|
||||
#### Fixed
|
||||
|
||||
- **Overwrite Server Data with This Device's Files** now keeps this device's synchronisation settings instead of reapplying settings from the remote database which is about to be replaced. Enabling E2EE before a rebuild therefore remains enabled and uploads encrypted data. (#1146)
|
||||
|
||||
### Command-line tool
|
||||
|
||||
#### Fixed
|
||||
|
||||
- The systemd installer now finds the repository root correctly, installs every generated bundle chunk and required production dependency, checks the installed command before activation, and reports success only when the service remains active.
|
||||
|
||||
## 1.0.23
|
||||
|
||||
2nd September, 2026
|
||||
|
||||
+2
-1
@@ -35,5 +35,6 @@
|
||||
"1.0.20": "1.7.2",
|
||||
"1.0.21": "1.7.2",
|
||||
"1.0.22": "1.7.2",
|
||||
"1.0.23": "1.7.2"
|
||||
"1.0.23": "1.7.2",
|
||||
"1.0.24": "1.7.2"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user