diff --git a/.gitignore b/.gitignore index a6bced9..c443c8e 100644 --- a/.gitignore +++ b/.gitignore @@ -30,4 +30,5 @@ cov_profile/** coverage src/apps/cli/dist/* _testdata/** -utils/bench/splitResults.csv \ No newline at end of file +utils/bench/splitResults.csv +.eslintcache \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..a14a132 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,65 @@ +# AI Coding Assistant Instructions (AGENTS.md) + +When working on this repository (writing code, comments, documentation, or commits), you MUST follow these guidelines to maintain consistency. + +## Required Reference Files + +Before making changes to documentation, user-facing text, or settings: +1. Read [docs/terms.md](file:///Users/vorotamoroz/dev/js/obsidian-livesync/docs/terms.md) for terminology, vocabulary conventions, and technical definitions. +2. Read [docs/settings.md](file:///Users/vorotamoroz/dev/js/obsidian-livesync/docs/settings.md) (and [docs/settings_ja.md](file:///Users/vorotamoroz/dev/js/obsidian-livesync/docs/settings_ja.md)) for UI settings and setting key mappings. +3. Read [docs/troubleshooting.md](file:///Users/vorotamoroz/dev/js/obsidian-livesync/docs/troubleshooting.md) for troubleshooting guidelines and common recovery steps (such as flag files and SCRAM state). +4. Read [devs.md](file:///Users/vorotamoroz/dev/js/obsidian-livesync/devs.md) for development workflows, module architecture, and testing infrastructure. + +--- + +## Documentation and User-Facing Text Rules + +Always adhere to the following stylistic and spelling rules: + +1. **British English Spelling**: + - Write all documentation and user-facing messages in British English. If in doubt, the BBC News Styleguide may be useful as a reference. + - **Traditional Spelling (Trad-spelling)**: Use `-ise` and `-isation` suffixes instead of `-ize` and `-ization` (for example: 'initialisation', 'synchronisation', and 'organisation'). + - **Oxford Comma**: Use the serial (Oxford) comma to separate items in lists of three or more (for example: 'settings, snippets, and themes' instead of 'settings, snippets and themes'). + - **Logical Punctuation**: Place punctuation marks (such as commas and full stops) outside quotation marks unless they are part of the quoted text itself (for example: write 'dialogue', not 'dialogue,'). + +2. **No Contractions**: + - Do not use contractions in general text or documentation (for example: write "do not" instead of "don't", "cannot" instead of "can't", and "is not" instead of "isn't"). + +3. **Quotation Style**: + - Prefer single quotation marks (`'`) over double quotation marks (`"`) in general documentation text, unless the context requires double quotes (for example, inside JSON code blocks). + +4. **Specific Terminology and Spelling**: + - Use **'dialogue'** in documentation, user-facing messages, and general text. Use **'dialog'** only inside source code (e.g. class names, methods). + - Use the hyphenated form **'plug-in'** in user-facing text. Use **'plugin'** only in codebase files, configuration settings, or technical contexts. + +--- + +## Technical & Architecture Rules + +1. **Database Structure**: + - Remember that Self-hosted LiveSync splits files into **Metadata** (file properties, size, paths) and **Chunks** (actual content). Do not store raw content in the metadata document directly. +2. **Setup and Recovery**: + - **Fast Setup (Simple Fetch)** is the preferred flow for initial replication on secondary devices. It utilises stream-based replication for high speed and delays local file reflection to suppress temporary synchronisation warnings. + - **Flag files** (such as `redflag.md`, `redflag2.md`, and `redflag3.md`) at the root of the vault control the boot-up sequence and trigger automated fetch/rebuild tasks. +3. **Subrepositories**: + - The directory [src/lib](file:///Users/vorotamoroz/dev/js/obsidian-livesync/src/lib) is a subrepository (Git submodule) pointing to the shared library `livesync-commonlib`. Do not make modifications inside this directory without careful consideration, as changes affect the shared library. +4. **Application Directories**: + - The directory [src/apps](file:///Users/vorotamoroz/dev/js/obsidian-livesync/src/apps) contains independent application modules: + - `cli`: A Command Line Interface application. Tests specifically for the CLI (both unit and End-to-End tests) are located and executed within [src/apps/cli](file:///Users/vorotamoroz/dev/js/obsidian-livesync/src/apps/cli) using its local `package.json` scripts. + - `webapp`: A Web-based application. + - `webpeer`: A Web-based peer utility. + +--- + +## Development & Verification Commands + +Before submitting code, you should run verification scripts locally to ensure correct syntax and function. + +1. **Lint and Type Checking**: + - Run `npm run check` to perform code verification. This runs type-checking (`tsc-check`), ESLint (`lint`), and Svelte checks (`svelte-check`). +2. **Unit Tests**: + - Run `npm run test:unit` to execute fast local unit tests. + - Run `npm run test` or `npm run test:full` for full testing suites (including dockerised services). +3. **Build**: + - Run `npm run build` to compile the production bundle (`main.js`). + - Run `npm run dev` for the development watch/build task. diff --git a/README.md b/README.md index ed2540e..3f1ecdc 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Self-hosted LiveSync is a community-developed synchronisation plug-in available on all Obsidian-compatible platforms. It leverages robust server solutions such as CouchDB or object storage systems (e.g., MinIO, S3, R2, etc.) to ensure reliable data synchronisation. -Additionally, it supports peer-to-peer synchronisation using WebRTC, enabling you to synchronise your notes directly between devices without relying on a server. Documentations is available for [Peer-to-Peer Synchronisation](./docs/p2p_sync_updates_2026.md). +Additionally, it supports peer-to-peer synchronisation using WebRTC, enabling you to synchronise your notes directly between devices without relying on a server. Documentation is available for [Peer-to-Peer Synchronisation](./docs/p2p_sync_updates_2026.md).  @@ -25,17 +25,17 @@ Additionally, it supports peer-to-peer synchronisation using WebRTC, enabling yo - Instead of keeping your device online as a stable peer, you can use two pseudo-peers: - [livesync-serverpeer](https://github.com/vrtmrz/livesync-serverpeer): A pseudo-client running on the server for receiving and sending data between devices. - [webpeer](https://github.com/vrtmrz/obsidian-livesync/tree/main/src/apps/webpeer): A pseudo-client for receiving and sending data between devices. - - A pre-built instance is available at [fancy-syncing.vrtmrz.net/webpeer](https://fancy-syncing.vrtmrz.net/webpeer/) (hosted on the vrtmrz blog site). This is also peer-to-peer. Feel free to use it. + - A pre-built instance is available at [fancy-syncing.vrtmrz.net/webpeer](https://fancy-syncing.vrtmrz.net/webpeer/) (hosted on the vrtmrz's blog site). This is also peer-to-peer. Feel free to use it. - For more information, refer to the [English explanatory article](https://fancy-syncing.vrtmrz.net/blog/0034-p2p-sync-en.html) or the [Japanese explanatory article](https://fancy-syncing.vrtmrz.net/blog/0034-p2p-sync). This plug-in may be particularly useful for researchers, engineers, and developers who need to keep their notes fully self-hosted for security reasons. It is also suitable for anyone seeking the peace of mind that comes with knowing their notes remain entirely private. >[!IMPORTANT] > - Before installing or upgrading this plug-in, please back up your vault. -> - Do not enable this plug-in alongside another synchronisation solution at the same time (including iCloud and Obsidian Sync). +> - Do not enable this plug-in alongside another synchronisation solution (including iCloud and Obsidian Sync). > - For backups, we also provide a plug-in called [Differential ZIP Backup](https://github.com/vrtmrz/diffzip). -## How to use +## How to Use ### 3-minute setup - CouchDB on fly.io @@ -43,54 +43,55 @@ This plug-in may be particularly useful for researchers, engineers, and develope [](https://www.youtube.com/watch?v=7sa_I1832Xc) -1. [Setup CouchDB on fly.io](docs/setup_flyio.md) +1. [Set up CouchDB on fly.io](docs/setup_flyio.md) 2. Configure plug-in in [Quick Setup](docs/quick_setup.md) -### Manually Setup +### Manual Setup -1. Setup the server - 1. [Setup CouchDB on fly.io](docs/setup_flyio.md) - 2. [Setup your CouchDB](docs/setup_own_server.md) +1. Set up the server + 1. [Set up CouchDB on fly.io](docs/setup_flyio.md) + 2. [Set up your CouchDB](docs/setup_own_server.md) 2. Configure plug-in in [Quick Setup](docs/quick_setup.md) > [!TIP] -> Fly.io is no longer free. Fortunately, despite some issues, we can still use IBM Cloudant. Refer to [Setup IBM Cloudant](docs/setup_cloudant.md). -> And also, we can use peer-to-peer synchronisation without a server. Or very cheap Object Storage -- Cloudflare R2 can be used for free. -> HOWEVER, most importantly, we can use the server that we trust. Therefore, please set up your own server. -> CouchDB can be run on a Raspberry Pi. (But please be careful about the security of your server). +> Fly.io is no longer free. Fortunately, we can still use IBM Cloudant despite some limitations. Refer to [Set up IBM Cloudant](docs/setup_cloudant.md). +> We can also use peer-to-peer synchronisation without a server. Alternatively, cheap object storage like Cloudflare R2 can be used for free. +> However, most importantly, we can use a server that we trust. Therefore, please set up your own server. +> CouchDB can also be run on a Raspberry Pi (please be mindful of your server's security). -## Information in StatusBar +## Information in the Status Bar -Synchronization status is shown in the status bar with the following icons. +Synchronisation status is shown in the status bar with the following icons. - Activity Indicator - ð² Network request - Status - â¹ïž Stopped - ð€ LiveSync enabled. Waiting for changes - - â¡ïž Synchronization in progress + - â¡ïž Synchronisation in progress - â An error occurred -- Statistical indicator +- Statistical Indicators - â Uploaded chunks and metadata - â Downloaded chunks and metadata -- Progress indicator +- Progress Indicators - ð¥ Unprocessed transferred items - ð Working database operation - ðŸ Working write storage processes - â³ Working read storage processes - ð« Pending read storage processes - ð¬ Batched read storage processes - - âïž Working or pending storage processes of hidden files + - âïž Working or pending storage processes for hidden files - ð§© Waiting chunks - - ð Working Customisation items (Configuration, snippets, and plug-ins) + - ð Working customisation items (configuration, snippets, and plug-ins) -To prevent file and database corruption, please wait to stop Obsidian until all progress indicators have disappeared as possible (The plugin will also try to resume, though). Especially in case of if you have deleted or renamed files. +To prevent file and database corruption, please avoid closing Obsidian until all progress indicators have disappeared as much as possible (although the plug-in will attempt to resume if interrupted). This is especially important if you have deleted or renamed files. ## Tips and Troubleshooting -If you are having problems getting the plugin working see: [Tips and Troubleshooting](docs/troubleshooting.md). +- If you want a faster and simpler initial replication when setting up subsequent devices, see the [Fast Setup Guide](docs/tips/fast-setup.md). +- If you are having problems getting the plug-in working, see [Tips and Troubleshooting](docs/troubleshooting.md). ## Acknowledgements -The project has been in continual progress and harmony thanks to: +The project has been in continual progress and harmony thanks to the following: - Many [Contributors](https://github.com/vrtmrz/obsidian-livesync/graphs/contributors). - Many [GitHub Sponsors](https://github.com/sponsors/vrtmrz#sponsors). - JetBrains Community Programs / Support for Open-Source Projects. @@ -98,7 +99,7 @@ The project has been in continual progress and harmony thanks to: May those who have contributed be honoured and remembered for their kindness and generosity. ## Development Guide -Please refer to [Development Guide](devs.md) for development setup, testing infrastructure, code conventions, and more. +Please refer to the [Development Guide](devs.md) for development setup, testing infrastructure, code conventions, and more. ## License diff --git a/README_ja.md b/README_ja.md index 3ec21b7..47e3f5a 100644 --- a/README_ja.md +++ b/README_ja.md @@ -78,7 +78,8 @@ NDAãé¡äŒŒã®å¥çŽã矩åãå«çãå®ãå¿ èŠã®ãããç ç©¶è ã ## Tips and Troubleshooting -äœãããŸã£ããã[Tips and Troubleshooting](docs/troubleshooting.md)ããåç §ãã ããã +- 2å°ç®ä»¥éã®ã»ããã¢ããæã«ãåæåæãããè¿ éãã€ç°¡åã«è¡ãã«ã¯ã[ãã¡ã¹ãã»ããã¢ããã¬ã€ã](docs/tips/fast-setup_ja.md)ããåç §ãã ããã +- äœãããŸã£ããã[Tips and Troubleshooting](docs/troubleshooting.md)ããåç §ãã ããã ## License diff --git a/devs.md b/devs.md index d9d9ef1..3363c22 100644 --- a/devs.md +++ b/devs.md @@ -3,6 +3,76 @@ Self-hosted LiveSync is an Obsidian plugin for synchronising vaults across devices using CouchDB, MinIO/S3, or peer-to-peer WebRTC. The codebase uses a modular architecture with TypeScript, Svelte, and PouchDB. +## Build & Development Workflow + +### Environment Setup + +#### First-time Setup + +This repository uses submodules by convention. Therefore, you must use the `--recursive` flag when cloning it. +```bash +git clone --recursive https://github.com/vrtmrz/obsidian-livesync +npm ci +npm run build +``` + +Note: if you already cloned without submodules, run: `git submodule update --init --recursive` + +#### Branch switching +When switching branches, please make sure to update submodules as well, since they may be updated in the new branch. +```bash +git checkout --recurse-submodules 0.25.70-patch1 # tag or branch name +npm ci +npm run build +``` + +### Commands + +```bash +npm run test:unit # Run unit tests with vitest (or `npm run test:unit:coverage` for coverage) +npm run check # TypeScript and svelte type checking +npm run dev # Development build with auto-rebuild (uses .env for test vault paths) +npm run build # Production build +npm run buildDev # Development build (one-time) +npm run bakei18n # Pre-build step: compile i18n resources (YAML â JSON â TS) +npm run test:unit # Run unit tests only (no Docker services required) +npm test # Run Harness based vitest tests (requires Docker services), not recommended, unstable. +``` + +### Tips + +We can use CLI's E2E test command instead of `npm test`. + +### Auto-copy to test vaults + +To facilitate development and testing, the build process can automatically copy the built plugin to specified test vault + +- Create `.env` file with `PATHS_TEST_INSTALL` pointing to test vault plug-in directories (`:` separated on Unix, `;` on Windows) +- Development builds auto-copy to these paths on build whilst `npm run dev` is running (watch mode) + +### Testing Infrastructure + +- ~~**Deno Tests**: Unit tests for platform-independent code (e.g., `HashManager.test.ts`)~~ + - This is now obsolete, migrated to vitest. +- **Vitest** (`vitest.config.ts`): E2E test by Browser-based-harness using Playwright, unit tests. + - Unit tests should be `*.unit.spec.ts` and placed alongside the implementation file (e.g., `ChunkFetcher.unit.spec.ts`). + +- **Docker Services**: Tests require CouchDB, MinIO (S3), and P2P services: + ```bash + npm run test:docker-all:start # Start all test services + npm run test:full # Run tests with coverage + npm run test:docker-all:stop # Stop services + ``` + If some services are not needed, start only required ones (e.g., `test:docker-couchdb:start`) + Note that if services are already running, starting script will fail. Please stop them first. + +- **Test Structure**: + - `test/suite/` - Integration tests for sync operations + - `test/unit/` - Unit tests (via vitest, as harness is browser-based) + - `test/harness/` - Mock implementations (e.g., `obsidian-mock.ts`) + + + ## Architecture ### Module System @@ -17,7 +87,7 @@ The plugin uses a dynamic module system to reduce coupling and improve maintaina - `coreObsidian/` - Obsidian-specific core (e.g., `ModuleFileAccessObsidian`) - `essential/` - Required modules (e.g., `ModuleMigration`, `ModuleKeyValueDB`) - `features/` - Optional features (e.g., `ModuleLog`, `ModuleObsidianSettings`) - - `extras/` - Development/testing tools (e.g., `ModuleDev`, `ModuleIntegratedTest`) + - `extras/` - Development/testing tools (e.g., `ModuleDev`, ~~`ModuleIntegratedTest`~~) - **Services**: Core services (e.g., `database`, `replicator`, `storageAccess`) are registered in `ServiceHub` and accessed by modules. They provide an extension point for add new behaviour without modifying existing code. - For example, checks before the replication can be added to the `replication.onBeforeReplicate` handler, and the handlers can be return `false` to prevent replication-starting. `vault.isTargetFile` also can be used to prevent processing specific files. - **ServiceModule**: A new type of module that directly depends on services. @@ -47,48 +117,6 @@ Hence, the new feature should be implemented as follows: - **Development code**: Use `.dev.ts` suffix (replaced with `.prod.ts` in production) - **Path aliases**: `@/*` maps to `src/*`, `@lib/*` maps to `src/lib/src/*` -## Build & Development Workflow - -### Commands - -```bash -npm run test:unit # Run unit tests with vitest (or `npm run test:unit:coverage` for coverage) -npm run check # TypeScript and svelte type checking -npm run dev # Development build with auto-rebuild (uses .env for test vault paths) -npm run build # Production build -npm run buildDev # Development build (one-time) -npm run bakei18n # Pre-build step: compile i18n resources (YAML â JSON â TS) -npm test # Run vitest tests (requires Docker services) -``` - -### Environment Setup - -- Clone with submodules: `git clone --recurse-submodules ` -- If you already cloned without them, run: `git submodule update --init --recursive` -- The shared common library is provided by the `src/lib` submodule, and builds will fail if it is missing -- Create `.env` file with `PATHS_TEST_INSTALL` pointing to test vault plug-in directories (`:` separated on Unix, `;` on Windows) -- Development builds auto-copy to these paths on build - -### Testing Infrastructure - -- ~~**Deno Tests**: Unit tests for platform-independent code (e.g., `HashManager.test.ts`)~~ - - This is now obsolete, migrated to vitest. -- **Vitest** (`vitest.config.ts`): E2E test by Browser-based-harness using Playwright, unit tests. - - Unit tests should be `*.unit.spec.ts` and placed alongside the implementation file (e.g., `ChunkFetcher.unit.spec.ts`). - -- **Docker Services**: Tests require CouchDB, MinIO (S3), and P2P services: - ```bash - npm run test:docker-all:start # Start all test services - npm run test:full # Run tests with coverage - npm run test:docker-all:stop # Stop services - ``` - If some services are not needed, start only required ones (e.g., `test:docker-couchdb:start`) - Note that if services are already running, starting script will fail. Please stop them first. -- **Test Structure**: - - `test/suite/` - Integration tests for sync operations - - `test/unit/` - Unit tests (via vitest, as harness is browser-based) - - `test/harness/` - Mock implementations (e.g., `obsidian-mock.ts`) - ## Code Conventions ### Internationalisation (i18n) @@ -156,17 +184,17 @@ export class ModuleExample extends AbstractObsidianModule { ## Beta Policy -- Beta versions are denoted by appending `+patchedN` to the base version number. +- Beta versions are denoted by appending `-patchedN` to the base version number. - `The base version` mostly corresponds to the stable release version. - - e.g., v0.25.41+patched1 is equivalent to v0.25.42-beta1. + - e.g., v0.25.41-patched1 is equivalent to v0.25.42-beta1. - This notation is due to SemVer incompatibility of Obsidian's plugin system. - - Hence, this release is `0.25.41+patched1`. + - Hence, this release is `0.25.41-patched1`. - Each beta version may include larger changes, but bug fixes will often not be included. - I think that in most cases, bug fixes will cause the stable releases. - They will not be released per branch or backported; they will simply be released. - Bug fixes for previous versions will be applied to the latest beta version. - This means, if xx.yy.02+patched1 exists and there is a defect in xx.yy.01, a fix is applied to xx.yy.02+patched1 and yields xx.yy.02+patched2. - If the fix is required immediately, it is released as xx.yy.02 (with xx.yy.01+patched1). + This means, if xx.yy.02-patched1 exists and there is a defect in xx.yy.01, a fix is applied to xx.yy.02-patched1 and yields xx.yy.02-patched2. + If the fix is required immediately, it is released as xx.yy.02 (with xx.yy.01-patched1). - This procedure remains unchanged from the current one. - At the very least, I am using the latest beta. - However, I will not be using a beta continuously for a week after it has been released. It is probably closer to an RC in nature. diff --git a/docs/p2p_sync_updates_2026.md b/docs/p2p_sync_updates_2026.md index b6f3903..9507360 100644 --- a/docs/p2p_sync_updates_2026.md +++ b/docs/p2p_sync_updates_2026.md @@ -53,7 +53,7 @@ This command synchronises with every peer whose **SYNC** toggle is enabled in th *Tip: Pair this command with a hotkey for a quick, keyboard-driven sync workflow.* ## 6. Technical Improvements in 2026 -- **Decoupled Architecture:** The UI is now strictly separated from the core logic, making the plugin more stable across different platforms (Mobile, Desktop, and Web). +- **Decoupled Architecture:** The UI is now strictly separated from the core logic, making the plug-in more stable across different platforms (Mobile, Desktop, and Web). - **Svelte 5 UI:** The interface has been rebuilt for better responsiveness and clearer status indicators. - **Security:** All data remains end-to-end encrypted. Even the signalling relay never sees your actual notes. diff --git a/docs/quick_setup.md b/docs/quick_setup.md index c6e1aef..f56f575 100644 --- a/docs/quick_setup.md +++ b/docs/quick_setup.md @@ -2,7 +2,7 @@ [Japanese docs](./quick_setup_ja.md) - [Chinese docs](./quick_setup_cn.md). -The plugin has so many configuration options to deal with different circumstances. However, only a few settings are required in the normal cases. Therefore, `The Setup wizard` has been implemented to simplify the setup. +The plug-in has so many configuration options to deal with different circumstances. However, only a few settings are required in the normal cases. Therefore, `The Setup wizard` has been implemented to simplify the setup.  @@ -10,7 +10,7 @@ There are three methods to set up Self-hosted LiveSync. 1. [Using setup URIs](#1-using-setup-uris) *(Recommended)* 2. [Minimal setup](#2-minimal-setup) -3. [Full manually setup the and Enable on this dialogue](#3-manually-setup) +3. [Fully manual setup and enabling on this dialogue](#3-manually-setup) ## At the first device @@ -24,7 +24,7 @@ There are three methods to set up Self-hosted LiveSync. In this procedure, [this video](https://youtu.be/7sa_I1832Xc?t=146) may help us. -1. Click `Use` button (Or launch `Use the copied setup URI` from Command palette). +1. Click the `Use` button (or launch the `Use the copied setup URI (Formerly Open setup URI)` command from the command palette). 2. Paste the Setup URI into the dialogue 3. Type the passphrase of the Setup URI 4. Answer `yes` for `Importing LiveSync's conf, OK?`. @@ -107,23 +107,27 @@ Note: If you are going to use Object Storage, you cannot select `LiveSync`. Select any synchronisation methods we want to use and `Apply`. If database initialisation is required, it will be performed at this time. When `All done!` is displayed, we are ready to synchronise. -The dialogue of `Copy settings as a new setup URI` will be open automatically. Please input a passphrase to encrypt the new `Setup URI`. (This passphrase is to encrypt the setup URI, not the vault). +The dialogue of `Copy current settings as a new setup URI` will open automatically. Please input a passphrase to encrypt the new `Setup URI`. (This passphrase is to encrypt the setup URI, not the vault).  The Setup URI will be copied to the clipboard, please make a note(Not in Obsidian) of this. >[!TIP] -We can copy this in any time by `Copy current settings as a new setup URI`. +We can copy this at any time by running the "Copy settings as a new setup URI" command from the command palette (or clicking the "Copy the current settings to a Setup URI" button in the settings UI). ### 3. Manually setup It is strongly recommended to perform a "minimal set-up" first and set up the other contents after making sure has been synchronised. However, if you have some specific reasons to configure it manually, please click the `Enable` button of `Enable LiveSync on this device as the set-up was completed manually`. -And, please copy the setup URI by `Copy current settings as a new setup URI` and make a note(Not in Obsidian) of this. +And, please copy the setup URI by running the "Copy settings as a new setup URI" command (or using the "Copy the current settings to a Setup URI" button) and make a note(Not in Obsidian) of this. ## At the subsequent device After installing Self-hosted LiveSync on the first device, we should have a setup URI. **The first choice is to use it**. Please share it with the device you want to setup. It is completely same as [Using setup URIs on the first device](#1-using-setup-uris). Please refer it. + +> [!TIP] +> **Fast Setup (Simple Fetch)** +> In recent versions, when you import a Setup URI or trigger a Fetch All, the plug-in boots in scheduled fetch mode and runs a simplified **Fast Setup** process. This allows you to choose your sync strategy with a single dialogue and performs initial synchronisation in one step. Refer to the [Fast Setup Guide](./tips/fast-setup.md) for more details. diff --git a/docs/quick_setup_ja.md b/docs/quick_setup_ja.md index 8af0779..06c2644 100644 --- a/docs/quick_setup_ja.md +++ b/docs/quick_setup_ja.md @@ -1,6 +1,6 @@ # Quick setup ãã®ãã©ã°ã€ã³ã«ã¯ããããããªç¶æ³ã«å¯Ÿå¿ããããã®éåžžã«å€ãã®èšå®ãªãã·ã§ã³ããããŸããããããå®éã«äœ¿çšããèšå®é ç®ã¯ããã»ã©å€ãã¯ãããŸãããããã§ãåæèšå®ãç°¡ç¥åããããã«ããã»ããã¢ãããŠã£ã¶ãŒãããå®è£ ããŠããŸãã -â»ãªããæ¬¡ã®ããã€ã¹ããã¯ã`Copy setup URI`ãš`Open setup URI`ã䜿ã£ãŠã»ããã¢ããããŠãã ããã +â»ãªããæ¬¡ã®ããã€ã¹ããã¯ã`çŸåšã®èšå®ãã»ããã¢ããURIã«ã³ããŒ`ãš`ã»ããã¢ããURIã§æ¥ç¶`ã䜿ã£ãŠã»ããã¢ããããŠãã ããã ## Wizardã®äœ¿ãæ¹ @@ -71,7 +71,8 @@ Fixãã¿ã³ããªããªãããã¹ãŠãã§ãã¯ããŒã¯ã«ãªãã°å®äº  Presetsãããããããã®åææ¹æ³ãéžã³`Apply`ãè¡ããšãå¿ èŠã«å¿ããŠããŒã«ã«ã»ãªã¢ãŒãã®ããŒã¿ããŒã¹ãåæåã»æ§ç¯ããŸãã -All done! ãšè¡šç€ºãããã°å®äºã§ããèªåçã«ã`Copy setup URI`ãéãã`Setup URI`ãæå·åãããã¹ãã¬ãŒãºãèãããŸãã +ãAll done!ãïŒæ¥æ¬èªç°å¢ã§ã¯ãå®äºïŒãïŒãšè¡šç€ºãããã°å®äºã§ããèªåçã«ããçŸåšã®èšå®ãã»ããã¢ããURIã«ã³ããŒãã®ãã€ã¢ãã°ãéããSetup URIãæå·åããããã®ãã¹ãã¬ãŒãºãæ±ããããŸãïŒãã®ãã¹ãã¬ãŒãºã¯Setup URIãæå·åããããã®ãã®ã§ãVaultèªäœã®æå·åããŒã§ã¯ãããŸããïŒã +ãã¹ãã¬ãŒãºãå ¥åãããšãã¯ãªããããŒãã«Setup URIãä¿åãããŸãã®ã§ãããã2å°ç®ä»¥éã®ããã€ã¹ã«äœããã®æ¹æ³ã§è»¢éããŠãã ããã  @@ -79,10 +80,14 @@ All done! ãšè¡šç€ºãããã°å®äºã§ããèªåçã«ã`Copy setup URI`ã ã¯ãªããããŒãã«Setup URIãä¿åãããŸãã®ã§ãããã2å°ç®ä»¥éã®ããã€ã¹ã«äœããã®æ¹æ³ã§è»¢éããŠãã ããã # 2å°ç®ä»¥éã®èšå®æ¹æ³ -2å°ç®ã®ç«¯æ«ã«Self-hosted LiveSyncãã€ã³ã¹ããŒã«ããããšãã³ãã³ããã¬ãããã`Open setup URI`ãéžæãã転éããsetup URIãå ¥åããŸãããã®åŸããã¹ãã¬ãŒãºãå ¥åãããšã»ããã¢ããçšã®ãŠã£ã¶ãŒããéããŸãã +2å°ç®ã®ç«¯æ«ã«Self-hosted LiveSyncãã€ã³ã¹ããŒã«ããããšãã³ãã³ããã¬ãããã`Use the copied setup URI (Formerly Open setup URI)`ãéžæãã転éããsetup URIãå ¥åããŸãããã®åŸããã¹ãã¬ãŒãºãå ¥åãããšã»ããã¢ããçšã®ãŠã£ã¶ãŒããéããŸãã äžèšã®ããã«çããŠãã ããã - `Importing LiveSync's conf, OK?` ã« `Yes` - `How would you like to set it up?` ã« `Set it up as secondary or subsequent device` -ããã§èšå®ãåæ ãããã¬ããªã±ãŒã·ã§ã³ãéå§ãããŸãã \ No newline at end of file +ããã§èšå®ãåæ ãããã¬ããªã±ãŒã·ã§ã³ãéå§ãããŸãã + +> [!TIP] +> **ãã¡ã¹ãã»ããã¢ãã (Fast Setup)** +> è¿å¹Žã®ããŒãžã§ã³ã§ã¯ãã»ããã¢ããURIã®èªã¿èŸŒã¿ãããŒã¿ã®å šååŸïŒFetch AllïŒãå®è¡ããéãããç°¡åã«åææŠç¥ãéžæããŠå³åº§ã«åæåæãå®äºã§ãã **ãã¡ã¹ãã»ããã¢ãã (Simple Fetch)** ãããŒãå©çšã§ããŸãã詳现㯠[ãã¡ã¹ãã»ããã¢ããã¬ã€ã](./tips/fast-setup_ja.md) ããåç §ãã ããã \ No newline at end of file diff --git a/docs/settings.md b/docs/settings.md index 29fbcd6..e0f2ca5 100644 --- a/docs/settings.md +++ b/docs/settings.md @@ -12,7 +12,7 @@ There are many settings in Self-hosted LiveSync. This document describes each se | ð°ïž | [3. Remote Configuration](#3-remote-configuration) | | ð | [4. Sync Settings](#4-sync-settings) | | ðŠ | [5. Selector (Advanced)](#5-selector-advanced) | -| ð | [6. Customization sync (Advanced)](#6-customization-sync-advanced) | +| ð | [6. Customisation sync (Advanced)](#6-customisation-sync-advanced) | | ð§° | [7. Hatch](#7-hatch) | | ð§ | [8. Advanced (Advanced)](#8-advanced-advanced) | | ðª | [9. Power users (Power User)](#9-power-users-power-user) | @@ -68,7 +68,7 @@ Following panes will be shown when you enable this setting. | Icon | Description | | :--: | ------------------------------------------------------------------ | | ðŠ | [5. Selector (Advanced)](#5-selector-advanced) | -| ð | [6. Customization sync (Advanced)](#6-customization-sync-advanced) | +| ð | [6. Customisation sync (Advanced)](#6-customisation-sync-advanced) | | ð§ | [8. Advanced (Advanced)](#8-advanced-advanced) | #### Enable poweruser features @@ -120,6 +120,18 @@ Setting key: showStatusOnStatusbar We can show the status of synchronisation on the status bar. (Default: On) +#### Show status icon instead of file warnings banner + +Setting key: hideFileWarningNotice + +If enabled, the â icon will be shown inside the status instead of the file warnings banner. No details will be shown. + +#### Network warning style + +Setting key: networkWarningStyle + +How to display network errors when the sync server is unreachable. + ### 2. Logging #### Show only notifications @@ -138,11 +150,19 @@ Show verbose log. Please enable when you report the logs ### 1. Remote Server +Self-hosted LiveSync supports multiple remote connection profiles under **Remote Server** -> **Remote Databases**. This allows you to save and switch between multiple databases or bucket configurations in a single vault. + +- **â Add new connection**: Create a new connection profile by launching the setup dialogue. +- **ð¥ Import connection**: Paste a connection string (e.g., `sls+https://...`, `sls+s3://...`, `sls+p2p://...`) to import a remote configuration profile. +- **ð§ Configure**: Open the setup dialogue to edit settings for the selected connection profile. +- **â Activate**: Select and activate this profile as the current active remote. +- **ðïž Delete**: Remove this connection profile from the list. + #### Remote Type Setting key: remoteType -Remote server type +The active remote server type. This is automatically projected to the legacy configuration when you activate a connection profile. ### 2. Notification @@ -172,6 +192,14 @@ Setting key: usePathObfuscation In default, the path of the file is not obfuscated to improve the performance. If you enable this, the path of the file will be obfuscated. This is useful when you want to hide the path of the file. +#### Encryption Algorithm + +Setting key: E2EEAlgorithm + +The encryption algorithm version used for end-to-end encryption. +- `v2` (V2: AES-256-GCM With HKDF): Recommended and default version. +- `forceV1` or `""` (V1: Legacy): Older legacy encryption. Only use this if you have an existing vault encrypted in the legacy format. + #### Use dynamic iteration count (Experimental) Setting key: useDynamicIterationCount @@ -192,30 +220,62 @@ Fetch necessary settings from already configured remote server. ### 5. Minio,S3,R2 +These settings are configured within the S3/MinIO/R2 Setup dialogue when adding (`â`) or editing (`ð§`) an Object Storage connection profile. + #### Endpoint URL Setting key: endpoint +The URL of the remote storage endpoint. +Note: Only Secure (HTTPS) connections can be used on Obsidian Mobile. + #### Access Key Setting key: accessKey +The Access Key ID used for authentication. + #### Secret Key Setting key: secretKey +The Secret Access Key used for authentication. + #### Region Setting key: region +The storage region (e.g., `us-east-1`, or `auto` for Cloudflare R2). + #### Bucket Name Setting key: bucket +The name of the bucket to store synchronised files. + #### Use Custom HTTP Handler Setting key: useCustomRequestHandler -Enable this if your Object Storage doesn't support CORS + +This option is labeled **Use internal API** in the setup dialogue. Enable this if your Object Storage does not support CORS. It uses Obsidian's internal API to communicate with the S3 server, which is not compliant with web standards but can bypass CORS restrictions. Note that this might break in future Obsidian versions. + +#### File prefix on the bucket + +Setting key: bucketPrefix + +This option is labeled **Folder Prefix** in the setup dialogue. Effectively a directory. Should end with `/`. e.g., `vault-name/`. Leave blank to store data at the root of the bucket. + +#### Enable forcePathStyle + +Setting key: forcePathStyle + +This option is labeled **Use Path-Style Access** in the setup dialogue. If enabled, the forcePathStyle option will be used for bucket operations. + +#### Custom Headers + +Setting key: bucketCustomHeaders + +Custom HTTP headers to include in every request sent to the Object Storage bucket. Specify them in the format `Header-Name: Value`, with each header on a new line. #### Test Connection @@ -223,24 +283,82 @@ Enable this if your Object Storage doesn't support CORS ### 6. CouchDB +These settings are configured within the CouchDB Setup dialogue when adding (`â`) or editing (`ð§`) a CouchDB connection profile. + #### Server URI Setting key: couchDB_URI +The URI of the CouchDB server. +Note: Only Secure (HTTPS) connections can be used on Obsidian Mobile. The URI must not end with a trailing slash. + #### Username Setting key: couchDB_USER -username + +The username used to authenticate with CouchDB. #### Password Setting key: couchDB_PASSWORD -password + +The password used to authenticate with CouchDB. #### Database Name Setting key: couchDB_DBNAME +The name of the database. +Note: The database name cannot contain capital letters, spaces, or special characters other than `_$()+/-`, and cannot start with an underscore (`_`). + +#### Use Request API to avoid inevitable CORS problem + +Setting key: useRequestAPI + +This option is labeled **Use Internal API** in the setup dialogue. If enabled, Obsidian's internal request API will be used to bypass CORS restrictions. This is a workaround that may not be compliant with web standards and is less secure. Note that this might break in future Obsidian versions. + +#### Custom Headers + +Setting key: couchDB_CustomHeaders + +Custom HTTP headers to include in every request sent to the CouchDB server. Specify them in the format `Header-Name: Value`, with each header on a new line. + +#### Use JWT Authentication + +Setting key: useJWT + +Enable JSON Web Token (JWT) authentication for CouchDB. This is an experimental feature and has not been thoroughly verified. + +#### JWT Algorithm + +Setting key: jwtAlgorithm + +The algorithm used to sign the JWT. Supported algorithms: `HS256`, `HS512`, `ES256`, `ES512`. + +#### JWT Expiration Duration (minutes) + +Setting key: jwtExpDuration + +Token expiration duration in minutes. Set to 0 to disable expiration. + +#### JWT Key + +Setting key: jwtKey + +The secret key (for HS256/HS512) or the PKCS#8 PEM-formatted private key (for ES256/ES512) used to sign the JWT. + +#### JWT Key ID (kid) + +Setting key: jwtKid + +The Key ID (`kid`) header parameter included in the JWT. + +#### JWT Subject (sub) + +Setting key: jwtSub + +The subject (`sub`) claim of the JWT, which should match your CouchDB username. + #### Test Database Connection Open database connection. If the remote database is not found and you have permission to create a database, the database will be created. @@ -251,26 +369,100 @@ Checks and fixes any potential issues with the database config. #### Apply Settings +### 7. Peer-to-Peer (P2P) Synchronisation + +#### Enable P2P Synchronisation + +Setting key: P2P_Enabled + +Enable direct peer-to-peer synchronisation via WebRTC. + +#### Relay URL + +Setting key: P2P_relays + +The WebSocket relay server URL(s) used for coordinating P2P connections via WebRTC. Multiple URLs can be separated by commas. + +#### Group ID + +Setting key: P2P_roomID + +The room ID or Group ID used to identify your group of synchronising devices. All devices you wish to synchronise must use the same Group ID. You can enter any custom string or generate a random Group ID. + +#### Passphrase + +Setting key: P2P_passphrase + +The password or passphrase used to authenticate and encrypt P2P communication. All devices must use the same passphrase. + +#### Device Peer ID + +Setting key: P2P_DevicePeerName + +The peer name or identifier of this device in the P2P network. This should be unique within your group of devices. + +#### Automatically start P2P connection on launch + +Setting key: P2P_AutoStart + +This option is labeled **Auto Start P2P Connection** in the setup dialogue. If enabled, the P2P connection will start automatically when the plug-in launches. + +#### Automatically broadcast changes to connected peers + +Setting key: P2P_AutoBroadcast + +This option is labeled **Auto Broadcast Changes** in the setup dialogue. If enabled, changes will be automatically broadcasted to connected peers, requesting them to fetch the changes. + +#### TURN Server URLs (comma-separated) + +Setting key: P2P_turnServers + +A comma-separated list of TURN/STUN server URLs. Used to relay P2P connections when direct WebRTC connection fails due to strict NAT or firewalls. In most cases, these can be left blank. + +#### TURN Username + +Setting key: P2P_turnUsername + +The username for authentication with the TURN server. + +#### TURN Credential + +Setting key: P2P_turnCredential + +The password or credential for authentication with the TURN server. + ## 4. Sync Settings -### 1. Synchronization Preset +### 1. Synchronisation Preset #### Presets Setting key: preset Apply preset configuration -### 2. Synchronization Method +### 2. Synchronisation Method #### Sync Mode Setting key: syncMode +The trigger mechanism for synchronisation. +- **LiveSync** (`LIVESYNC`): Real-time, continuous, bidirectional synchronisation. + Note: This requires a CouchDB or WebRTC P2P remote server. It is not supported for S3-compatible Object Storage. +- **Periodic Sync** (`PERIODIC`): Synchronisation is performed at regular intervals specified by the **Periodic Sync interval** setting. +- **On Events** (`ONEVENTS`): Synchronisation is triggered by specific events (such as save, file open, or startup) configured via the toggles below. + #### Periodic Sync interval Setting key: periodicReplicationInterval Interval (sec) +#### Minimum interval for syncing + +Setting key: syncMinimumInterval + +The minimum interval for automatic synchronisation on event. + #### Sync on Save Setting key: syncOnSave @@ -323,7 +515,7 @@ Move remotely deleted files to the trash, instead of deleting. #### Keep empty folder Setting key: doNotDeleteFolder -Should we keep folders that don't have any files inside? +Should we keep folders that do not have any files inside? ### 5. Conflict resolution (Advanced) @@ -360,7 +552,7 @@ Setting key: notifyAllSettingSyncFile ### 7. Hidden Files (Advanced) -#### Hidden file synchronization +#### Hidden file synchronisation #### Enable Hidden files sync @@ -373,6 +565,12 @@ Setting key: syncInternalFilesBeforeReplication Setting key: syncInternalFilesInterval Seconds, 0 to disable +#### Suppress notification of hidden files change + +Setting key: suppressNotifyHiddenFilesChange + +If enabled, the notification of hidden files change will be suppressed. + ## 5. Selector (Advanced) ### 1. Normal Files @@ -406,42 +604,42 @@ Comma separated `.gitignore, .dockerignore` #### Add default patterns -## 6. Customization sync (Advanced) +## 6. Customisation sync (Advanced) -### 1. Customization Sync +### 1. Customisation Sync #### Device name Setting key: deviceAndVaultName -Unique name between all synchronized devices. To edit this setting, please disable customization sync once. +Unique name between all synchronised devices. To edit this setting, please disable customisation sync once. -#### Per-file-saved customization sync +#### Per-file-saved customisation sync Setting key: usePluginSyncV2 -If enabled per-filed efficient customization sync will be used. We need a small migration when enabling this. And all devices should be updated to v0.23.18. Once we enabled this, we lost a compatibility with old versions. +If enabled, per-file efficient customisation sync will be used. We need a small migration when enabling this. And all devices should be updated to v0.23.18. Once we enable this, we lose compatibility with old versions. -#### Enable customization sync +#### Enable customisation sync Setting key: usePluginSync -#### Scan customization automatically +#### Scan customisation automatically Setting key: autoSweepPlugins -Scan customization before replicating. +Scan customisation before replicating. -#### Scan customization periodically +#### Scan customisation periodically Setting key: autoSweepPluginsPeriodic -Scan customization every 1 minute. +Scan customisation every 1 minute. -#### Notify customized +#### Notify customised Setting key: notifyPluginOrSettingUpdated -Notify when other device has newly customized. +Notify when another device has newly customised. #### Open -Open the dialog +Open the dialogue ## 7. Hatch @@ -456,14 +654,18 @@ Warning! This will have a serious impact on performance. And the logs will not b ### 2. Scram Switches +Emergency controls to suspend synchronisation processes in order to prevent database corruption. If a critical mismatch or sync error occurs, the plug-in may automatically enter a Scram state and suspend operations. + #### Suspend file watching Setting key: suspendFileWatching -Stop watching for file changes. + +Stop watching for local file changes. #### Suspend database reflecting Setting key: suspendParseReplicationResult + Stop reflecting database changes to storage files. ### 3. Recovery and Repair @@ -486,7 +688,7 @@ Compare the content of files between on local database and storage. If not match #### Back to non-configured -#### Delete all customization sync data +#### Delete all customisation sync data ## 8. Advanced (Advanced) @@ -507,6 +709,12 @@ Setting key: hashCacheMaxAmount Setting key: customChunkSize +#### Chunk Splitter + +Setting key: chunkSplitterVersion + +Select the chunk splitter version; V3 is the most efficient. If you experience issues, please choose Default or Legacy. + #### Use splitting-limit-capped chunk splitter Setting key: enableChunkSplitterV2 @@ -532,6 +740,12 @@ Setting key: concurrencyOfReadChunksOnline Setting key: minimumIntervalOfReadChunksOnline +#### Maximum size of chunks to send in one request + +Setting key: sendChunksBulkMaxSize + +Limit the maximum size of chunks to send in a single bulk request (MB). + ## 9. Power users (Power User) ### 1. Remote Database Tweak @@ -639,7 +853,7 @@ If this enabled, All files are handled as case-Sensitive (Previous behaviour). ### 4. Compatibility (Internal API Usage) -#### Scan changes on customization sync +#### Scan changes on customisation sync Setting key: watchInternalFileChanges Do not use internal API @@ -664,7 +878,13 @@ Setting key: doNotSuspendOnFetching #### Keep empty folder Setting key: doNotDeleteFolder -Should we keep folders that don't have any files inside? +Should we keep folders that do not have any files inside? + +#### Process files even if seems to be corrupted + +Setting key: processSizeMismatchedFiles + +Enable this setting to process files with size mismatches, which can sometimes be created by certain external APIs or integrations. ### 7. Edge case addressing (Processing) @@ -684,17 +904,25 @@ If enabled, the file under 1kb will be processed in the UI thread. Setting key: disableCheckingConfigMismatch +### 9. Remediation + +#### Maximum file modification time for reflected file events + +Setting key: maxMTimeForReflectEvents + +Files with modification times greater than this value (in seconds since the Unix epoch) will not have their events reflected. Set to 0 to disable this limit. + ## 11. Maintenance ### 1. Scram! #### Lock Server -Lock the remote server to prevent synchronization with other devices. +Lock the remote server to prevent synchronisation with other devices. #### Emergency restart -Disables all synchronization and restart. +Disables all synchronisation and restart. ### 2. Syncing @@ -712,17 +940,13 @@ Initialise journal sent history. On the next sync, every item except this device ### 3. Rebuilding Operations (Local) -#### Fetch from remote +#### Reset Synchronisation on This Device Restore or reconstruct local database from remote. -#### Fetch rebuilt DB (Save local documents before) - -Restore or reconstruct local database from remote database but use local chunks. - ### 4. Total Overhaul -#### Rebuild everything +#### Overwrite Server Data with This Device's Files Rebuild local and remote database with local files. @@ -752,7 +976,7 @@ Delete all data on the remote server. #### Run database cleanup -Attempt to shrink the database by deleting unused chunks. This may not work consistently. Use the 'Rebuild everything' under Total Overhaul. +Attempt to shrink the database by deleting unused chunks. This may not work consistently. Use the 'Overwrite Server Data with This Device's Files' under Reset Synchronisation information. ### 7. Reset diff --git a/docs/settings_ja.md b/docs/settings_ja.md index d7b9cf3..8d75dd7 100644 --- a/docs/settings_ja.md +++ b/docs/settings_ja.md @@ -3,23 +3,133 @@ # ãã®ãã©ã°ã€ã³ã®èšå®é ç® ## Remote Database Configurations -åæå ã®ããŒã¿ããŒã¹èšå®ãè¡ããŸããäœããã®åæãæå¹ã«ãªã£ãŠããå Žåã¯ç·šéã§ããªããããåæãè§£é€ããŠããè¡ã£ãŠãã ããã +åæå ã®ããŒã¿ããŒã¹èšå®ïŒRemote ServerïŒãè¡ããŸãã -### URI -CouchDBã®URIãå ¥åããŸããCloudantã®å Žåã¯ãExternal Endpoint(preferred)ãã«ãªããŸãã -**ã¹ã©ãã·ã¥ã§çµãã£ãŠã¯ãããŸããã** -ãã¡ãã«ããŒã¿ããŒã¹åãå«ããŠãããŸããŸããã +çŸåšã®ããŒãžã§ã³ã§ã¯ãè€æ°ã®ãªã¢ãŒãæ¥ç¶èšå®ïŒæ¥ç¶ãããã¡ã€ã«ïŒãç»é²ã»ç®¡çããåãæ¿ããŠäœ¿çšããããšãå¯èœã§ãïŒãRemote Databasesããªã¹ãïŒã -### Username -ãŠãŒã¶ãŒåãå ¥åããŸãããã®ãŠãŒã¶ãŒã¯ç®¡çè æš©éãããããšãæãŸããã§ãã +- **â æ°èŠæ¥ç¶ã远å (Add new connection)**: æ°ããæ¥ç¶èšå®ãäœæããåã»ããã¢ãããã€ã¢ãã°ãèµ·åããŸãã +- **ð¥ æ¥ç¶ãã€ã³ããŒã (Import connection)**: æ¥ç¶æååïŒ`sls+https://...`ã`sls+s3://...`ã`sls+p2p://...`ãªã©ïŒã貌ãä»ããŠã€ã³ããŒãããŸãã +- **ð§ èšå® (Configure)**: ã»ããã¢ãããã€ã¢ãã°ãéããéžæããæ¥ç¶ãããã¡ã€ã«ã®èšå®ãç·šéããŸãã +- **â æå¹å (Activate)**: éžæãããããã¡ã€ã«ãã¢ã¯ãã£ããªåæå ãšããŠæå¹åããŸãã +- **ðïž åé€ (Delete)**: æ¥ç¶ãããã¡ã€ã«ãäžèЧããåé€ããŸãã -### Password -ãã¹ã¯ãŒããå ¥åããŸãã +ãããã®æ¥ç¶ãããã¡ã€ã«ã远å ã»ç·šéããéãéžæããããŒã¿ããŒã¹ã®çš®é¡ïŒCouchDBãS3äºæãªããžã§ã¯ãã¹ãã¬ãŒãžãP2Pãªã©ïŒã«å¿ããã»ããã¢ãããã€ã¢ãã°ãéããŸãã -### Database Name -åæããããŒã¿ããŒã¹åãå ¥åããŸãã -â ïžååšããªãå Žåã¯ããã¹ããæ¥ç¶ãè¡ã£ãéãèªåçã«äœæãããŸã[^1]ã -[^1]:æš©éããªãå Žåã¯èªåäœæã«ã¯å€±æããŸãã +äœããã®åæãæå¹ã«ãªã£ãŠããå Žåã¯ç·šéã§ããªããããåæãè§£é€ããŠããè¡ã£ãŠãã ããã + +### CouchDB ã®èšå® +CouchDBã®åèšå®é ç®ã¯ãæ¥ç¶ãããã¡ã€ã«ã远å (â) ãŸãã¯èšå® (ð§) ããéã«éã **CouchDB ã»ããã¢ãããã€ã¢ãã°** å ã§èšå®ããŸãã + +#### URI +èšå®ããŒ: couchDB_URI + +CouchDBã®æ¥ç¶å URIã§ãããã€ã¢ãã°å ã§ã¯ **URL** ãšè¡šèšãããŸããCloudantã®å Žåã¯ãExternal Endpoint (preferred)ãã«ãªããŸãã +泚æ: Obsidian Mobileã§ã¯ã»ãã¥ã¢æ¥ç¶ (HTTPS) ã®ã¿ã䜿çšå¯èœã§ãããŸããæ«å°Ÿã«ã¹ã©ãã·ã¥ïŒ`/`ïŒãä»ããŠã¯ãããŸããã + +#### Username +èšå®ããŒ: couchDB_USER + +CouchDBã®ãã°ã€ã³ãŠãŒã¶ãŒåã§ãããã€ã¢ãã°å ã§ã¯ **Username** ãšè¡šèšãããŸãããã®ãŠãŒã¶ãŒã«ã¯ç®¡çè æš©éãããããšãæãŸããã§ãã + +#### Password +èšå®ããŒ: couchDB_PASSWORD + +CouchDBã®ãã°ã€ã³ãã¹ã¯ãŒãã§ãããã€ã¢ãã°å ã§ã¯ **Password** ãšè¡šèšãããŸãã + +#### Database Name +èšå®ããŒ: couchDB_DBNAME + +åæå ã®ããŒã¿ããŒã¹åã§ãããã€ã¢ãã°å ã§ã¯ **Database Name** ãšè¡šèšãããŸãã +泚æ: ããŒã¿ããŒã¹åã«ã¯å€§æåãã¹ããŒã¹ãããã³äžéšã®ç¹æ®æåïŒ`_$()+/-` 以å€ïŒã¯äœ¿çšã§ããŸããããŸããã¢ã³ããŒã¹ã³ã¢ïŒ`_`ïŒããå§ããããšã¯ã§ããŸãããååšããªãå Žåã¯ãæ¥ç¶ãã¹ãæãŸãã¯èšå®é©çšæã«èªåäœæãããŸãïŒäœææš©éãå¿ èŠã§ãïŒã + +#### CORSåé¿ã®ããã«Request APIã䜿çšãã +èšå®ããŒ: useRequestAPI + +ãã®é ç®ã¯ã»ããã¢ãããã€ã¢ãã°å ã§ã¯ **Use Internal API** ãšè¡šèšãããŸããæå¹ãªå Žåãäžå¯é¿ãªCORSåé¡ãåé¿ããããã«Obsidianã®å éšRequest APIã䜿çšããŸããããã¯Webæšæºã«æºæ ããŠããªãåé¿çã§ããããã¹ãŠã®ç°å¢ã§ã®åäœãä¿èšŒãããã®ã§ã¯ãããŸãããå®å šæ§ãäœäžããå¯èœæ§ãããç¹ã«ã泚æãã ãããå°æ¥ã®Obsidianã®ã¢ããããŒãã«ãã£ãŠåäœããªããªãå¯èœæ§ããããŸãã + +#### ã«ã¹ã¿ã ããã㌠+èšå®ããŒ: couchDB_CustomHeaders + +CouchDBãµãŒããŒã«éä¿¡ãããã¹ãŠã®ãªã¯ãšã¹ãã«å«ããã«ã¹ã¿ã HTTPããããŒãèšå®ããŸãããã€ã¢ãã°å ã§ã¯ **Custom Headers** ãšè¡šèšãããŸãã`ããããŒå: å€` ã®åœ¢åŒã§ã1è¡ã«1ã€ãã€å ¥åããŠãã ããã + +#### JWTèªèšŒã®äœ¿çš (å®éšçæ©èœ) +èšå®ããŒ: useJWT + +CouchDBã§ã®JSON Web Token (JWT) èªèšŒãæå¹ã«ããŸãããã€ã¢ãã°å ã§ã¯ **Use JWT Authentication** ãšè¡šèšãããŸããååã«æ€èšŒãããŠããªãå®éšçæ©èœã§ããããããæ³šæãã ããã + +#### JWTã¢ã«ãŽãªãºã +èšå®ããŒ: jwtAlgorithm + +JWTã®çœ²åã«äœ¿çšããã¢ã«ãŽãªãºã ãéžæããŸãããã€ã¢ãã°å ã§ã¯ **JWT Algorithm** ãšè¡šèšãããŸãã察å¿ã¢ã«ãŽãªãºã : `HS256`, `HS512`, `ES256`, `ES512` + +#### JWTæå¹æé (å) +èšå®ããŒ: jwtExpDuration + +ããŒã¯ã³ã®æå¹æéãååäœã§æå®ããŸãããã€ã¢ãã°å ã§ã¯ **JWT Expiration Duration (minutes)** ãšè¡šèšãããŸãã`0` ãæå®ãããšæå¹æéã¯ç¡å¹ã«ãªããŸãã + +#### JWTã㌠+èšå®ããŒ: jwtKey + +JWTã®çœ²åã«äœ¿çšããç§å¯éµãŸãã¯ãã©ã€ããŒãããŒãæå®ããŸãããã€ã¢ãã°å ã§ã¯ **JWT Key** ãšè¡šèšãããŸãã`HS256/HS512` ã®å Žåã¯å ±ééµãã`ES256/ES512` ã®å Žå㯠pkcs8 PEM圢åŒã®ç§å¯éµãå ¥åããŠãã ããã + +#### JWTããŒID (kid) +èšå®ããŒ: jwtKid + +JWTããããŒã«å«ããããŒIDãæå®ããŸãããã€ã¢ãã°å ã§ã¯ **JWT Key ID (kid)** ãšè¡šèšãããŸãã + +#### JWTãµããžã§ã¯ã (sub) +èšå®ããŒ: jwtSub + +JWTã®ãµããžã§ã¯ã (CouchDBãŠãŒã¶ãŒå) ãæå®ããŸãããã€ã¢ãã°å ã§ã¯ **JWT Subject (sub)** ãšè¡šèšãããŸãã + +### Object Storage (Minio, S3, R2) ã®èšå® +Object Storageã®åèšå®é ç®ã¯ãæ¥ç¶ãããã¡ã€ã«ã远å (â) ãŸãã¯èšå® (ð§) ããéã«éã **S3/MinIO/R2 ã»ããã¢ãããã€ã¢ãã°** å ã§èšå®ããŸãã + +#### ãšã³ããã€ã³ãURL +èšå®ããŒ: endpoint + +S3äºæã¹ãã¬ãŒãžã®ãšã³ããã€ã³ãURLã§ãããã€ã¢ãã°å ã§ã¯ **Endpoint URL** ãšè¡šèšãããŸãã +泚æ: Obsidian Mobileã§ã¯ã»ãã¥ã¢æ¥ç¶ (HTTPS) ã®ã¿ã䜿çšå¯èœã§ãã + +#### ã¢ã¯ã»ã¹ã㌠ID +èšå®ããŒ: accessKey + +èªèšŒã«äœ¿çšããã¢ã¯ã»ã¹ããŒIDã§ãããã€ã¢ãã°å ã§ã¯ **Access Key ID** ãšè¡šèšãããŸãã + +#### ã·ãŒã¯ã¬ããã¢ã¯ã»ã¹ã㌠+èšå®ããŒ: secretKey + +èªèšŒã«äœ¿çšããã·ãŒã¯ã¬ããã¢ã¯ã»ã¹ããŒã§ãããã€ã¢ãã°å ã§ã¯ **Secret Access Key** ãšè¡šèšãããŸãã + +#### ãªãŒãžã§ã³ +èšå®ããŒ: region + +ã¹ãã¬ãŒãžã®ãªãŒãžã§ã³ãæå®ããŸãïŒäŸ: `us-east-1`ãCloudflare R2ã®å Žå㯠`auto`ïŒããã€ã¢ãã°å ã§ã¯ **Region** ãšè¡šèšãããŸãã + +#### ãã±ããå +èšå®ããŒ: bucket + +åæããŒã¿ãä¿åãããã±ããåã§ãããã€ã¢ãã°å ã§ã¯ **Bucket Name** ãšè¡šèšãããŸãã + +#### ã«ã¹ã¿ã HTTPãã³ãã©ãŒã䜿çšãã +èšå®ããŒ: useCustomRequestHandler + +ãã®é ç®ã¯ã»ããã¢ãããã€ã¢ãã°å ã§ã¯ **Use internal API** ãšè¡šèšãããŸãããªããžã§ã¯ãã¹ãã¬ãŒãžãCORSããµããŒãããŠããªãå Žåã«æå¹ã«ããŸããObsidianã®å éšAPIã䜿çšããŠS3ãµãŒããŒãšéä¿¡ããããšã§CORSå¶çŽãåé¿ããŸããWebæšæºã«ã¯æºæ ããŠããªããããå°æ¥ã®Obsidianã®ã¢ããããŒãã«ãã£ãŠåäœããªããªãå¯èœæ§ããããŸãã + +#### ãã±ããå ã®ãã¡ã€ã«ãã¬ãã£ãã¯ã¹ +èšå®ããŒ: bucketPrefix + +ãã®é ç®ã¯ã»ããã¢ãããã€ã¢ãã°å ã§ã¯ **Folder Prefix** ãšè¡šèšãããŸããå®è³ªçãªãã£ã¬ã¯ããªæå®ã§ããæ«å°Ÿã¯ `/` ã§ããå¿ èŠããããŸãïŒäŸïŒ`vault-name/`ïŒããã±ããã®ã«ãŒãã«ä¿åããå Žåã¯ç©ºæ¬ã®ãŸãŸã«ããŠãã ããã + +#### forcePathStyleãæå¹ã«ãã +èšå®ããŒ: forcePathStyle + +ãã®é ç®ã¯ã»ããã¢ãããã€ã¢ãã°å ã§ã¯ **Use Path-Style Access** ãšè¡šèšãããŸããæå¹ãªå Žåããã±ããæäœã§forcePathStyleãªãã·ã§ã³ã䜿çšããŸãã + +#### ã«ã¹ã¿ã ããã㌠+èšå®ããŒ: bucketCustomHeaders + +ãªããžã§ã¯ãã¹ãã¬ãŒãžãã±ããã«éä¿¡ãããã¹ãŠã®ãªã¯ãšã¹ãã«å«ããã«ã¹ã¿ã HTTPããããŒãèšå®ããŸãããã€ã¢ãã°å ã§ã¯ **Custom Headers** ãšè¡šèšãããŸãã`ããããŒå: å€` ã®åœ¢åŒã§ã1è¡ã«1ã€ãã€å ¥åããŠãã ããã @@ -30,6 +140,18 @@ CouchDBã®URIãå ¥åããŸããCloudantã®å Žåã¯ãExternal Endpoint(prefe ### Passphrase æå·åãè¡ãéã«äœ¿çšãããã¹ãã¬ãŒãºã§ããå åã«é·ããã®ã䜿çšããŠãã ããã +### ãã¹ã®é£èªå +èšå®ããŒ: usePathObfuscation + +ãã€ã¢ãã°å ã§ã¯ **Obfuscate Properties** ãšè¡šèšãããŸããæå¹ãªå Žåããªã¢ãŒããµãŒããŒäžã§ã®ãã¡ã€ã«ãã¹ããã©ã«ãåãé£èªåïŒæå·åïŒããŸããããã«ãããã©ã€ãã·ãŒãåäžããŸãããããã©ãŒãã³ã¹ããããã«äœäžããå¯èœæ§ããããŸãã + +### æå·åã¢ã«ãŽãªãºã +èšå®ããŒ: E2EEAlgorithm + +ãã€ã¢ãã°å ã§ã¯ **Encryption Algorithm** ãšè¡šèšãããŸãããšã³ãããŒãšã³ãæå·åã«äœ¿çšããæå·åã¢ã«ãŽãªãºã ã®ããŒãžã§ã³ãéžæããŸãã +- `v2` (V2: AES-256-GCM With HKDF): æšå¥šãããããã©ã«ãã®ããŒãžã§ã³ã§ãã +- `forceV1` ãŸã㯠`""` (V1: Legacy): ã¬ã¬ã·ãŒãªæå·åããŒãžã§ã³ã§ããå€ãããŒãžã§ã³ã§æå·åãããæ¢åã®ä¿ç®¡åº«ïŒVaultïŒãåæããå Žåã«ã®ã¿äœ¿çšããŠãã ããã + ### Apply End to End æå·åãè¡ãã«åœãã£ãŠãç°ãªããã¹ãã¬ãŒãºã§æå·åãããåäžã®å 容ãå ¥æãããããšã¯é¿ããã¹ãã§ãããŸããSelf-hosted LiveSyncã¯ã³ã³ãã³ãã®crc32ãéè€åé¿ã«äœ¿çšããŠããããããã®ç¹ã§ãæ»æãæå¹ã«ãªã£ãŠããŸããŸãã @@ -53,12 +175,66 @@ End to End æå·åãè¡ãã«åœãã£ãŠãç°ãªããã¹ãã¬ãŒãºã§æ ã©ã¡ãã®ãªãã¬ãŒã·ã§ã³ããå®è¡ãããšãã¹ãŠã®åæèšå®ãç¡å¹åãããŸãã + + ### Test Database connection äžèšã®èšå®ã§ããŒã¿ããŒã¹ã«æ¥ç¶ã§ããã確èªããŸãã ### Check database configuration ããããçŽæ¥CouchDBã®èšå®ã確èªã»å€æŽã§ããŸãã +### Peer-to-Peer (P2P) åæã®èšå® + +#### P2Påæãæå¹ã«ãã +èšå®ããŒ: P2P_Enabled + +WebRTCãä»ããããã€ã¹éã§ã®çŽæ¥çãªP2Påæãæå¹ã«ããŸãããã€ã¢ãã°å ã§ã¯ **Enabled** ãšè¡šèšãããŸãã + +#### ãªã¬ãŒãµãŒããŒã®URL +èšå®ããŒ: P2P_relays + +WebRTCã«ããP2Pæ¥ç¶ã仲ä»ã»èª¿æŽããããã®WebSocketãªã¬ãŒãµãŒããŒã®URLãæå®ããŸãããã€ã¢ãã°å ã§ã¯ **Relay URL** ãšè¡šèšãããŸããè€æ°ã®URLãæå®ããå Žåã¯ã«ã³ãã§åºåããŸãããã€ã¢ãã°å ã®ãã¿ã³ãã¯ãªãã¯ãããšãããã©ã«ãã®ãªã¬ãŒãµãŒããŒãèšå®ã§ããŸãã + +#### ã°ã«ãŒãID +èšå®ããŒ: P2P_roomID + +åæããããã€ã¹çŸ€ãèå¥ããããã®ã«ãŒã IDãŸãã¯ã°ã«ãŒãIDãæå®ããŸãããã€ã¢ãã°å ã§ã¯ **Group ID** ãšè¡šèšãããŸããåæãããããã¹ãŠã®ããã€ã¹ã§åãã°ã«ãŒãIDãæå®ããå¿ èŠããããŸããä»»æã®ã«ã¹ã¿ã æååãå ¥åããããã©ã³ãã çæãã¿ã³ã§çæã§ããŸãã + +#### ãã¹ãã¬ãŒãº +èšå®ããŒ: P2P_passphrase + +P2Péä¿¡ã®èªèšŒããã³æå·åã«äœ¿çšãããã¹ã¯ãŒãïŒãã¹ãã¬ãŒãºïŒãæå®ããŸãããã€ã¢ãã°å ã§ã¯ **Passphrase** ãšè¡šèšãããŸããåæãããã¹ãŠã®ããã€ã¹ã§åããã¹ãã¬ãŒãºãæå®ããå¿ èŠããããŸãã + +#### ããã€ã¹å +èšå®ããŒ: P2P_DevicePeerName + +P2Pãããã¯ãŒã¯äžã§ãã®ããã€ã¹ãèå¥ããããã®ååãæå®ããŸãããã€ã¢ãã°å ã§ã¯ **Device Peer ID** ãšè¡šèšãããŸããã°ã«ãŒãå ã®ããã€ã¹éã§éè€ããªãäžæã®å€ãèšå®ããŠãã ããã + +#### èµ·åæã®P2Pèªåæ¥ç¶éå§ +èšå®ããŒ: P2P_AutoStart + +æå¹ãªå Žåããã©ã°ã€ã³ã®èµ·åæã«èªåçã«P2Pæ¥ç¶ãéå§ããŸãããã€ã¢ãã°å ã§ã¯ **Auto Start P2P Connection** ãšè¡šèšãããŸãã + +#### æ¥ç¶æžã¿ãã¢ãžã®å€æŽã®èªåãããŒããã£ã¹ã +èšå®ããŒ: P2P_AutoBroadcast + +æå¹ãªå ŽåãããŒã«ã«ã§ã®å€æŽãæ¥ç¶æžã¿ã®ãã¢ã«èªåçã«ãããŒããã£ã¹ããããŸãããã€ã¢ãã°å ã§ã¯ **Auto Broadcast Changes** ãšè¡šèšãããŸããéç¥ããããã¢ã¯å€æŽã®ååŸãéå§ããŸãã + +#### TURNãµãŒããŒã®URL (ã«ã³ãåºåã) +èšå®ããŒ: P2P_turnServers + +ãã€ã¢ãã°å ã§ã¯ **TURN Server URLs (comma-separated)** ãšè¡šèšãããŸããå³ããNATããã¡ã€ã¢ãŠã©ãŒã«ãããç°å¢ã§ãWebRTCã®çŽæ¥æ¥ç¶ã確ç«ã§ããªãå Žåã«P2Pæ¥ç¶ãäžç¶ããããã®TURN/STUNãµãŒããŒã®URLãã«ã³ãåºåãã§æå®ããŸããéåžžã¯ç©ºæ¬ã®ãŸãŸã§åé¡ãããŸããã + +#### TURNãŠãŒã¶ãŒå +èšå®ããŒ: P2P_turnUsername + +TURNãµãŒããŒã§ã®èªèšŒã«äœ¿çšãããŠãŒã¶ãŒåãèšå®ããŸãããã€ã¢ãã°å ã§ã¯ **TURN Username** ãšè¡šèšãããŸãã + +#### TURNãã¹ã¯ãŒã +èšå®ããŒ: P2P_turnCredential + +TURNãµãŒããŒã§ã®èªèšŒã«äœ¿çšãããã¹ã¯ãŒãïŒã¯ã¬ãã³ã·ã£ã«ïŒãèšå®ããŸãããã€ã¢ãã°å ã§ã¯ **TURN Credential** ãšè¡šèšãããŸãã + ## Local Database Configurations 端æ«å ã«äœæãããããŒã¿ããŒã¹ã®èšå®ã§ãã @@ -71,7 +247,8 @@ End to End æå·åãè¡ãã«åœãã£ãŠãç°ãªããã¹ãã¬ãŒãºã§æ ãã®ãªãã·ã§ã³ã¯LiveSyncãšåæã«ã¯äœ¿çšã§ããŸããã ### minimum chunk size ãš LongLine threshold -ãã£ã³ã¯ã®åå²ã«ã€ããŠã®èšå®ã§ãã +ãã£ã³ã¯ã®åå²ã«ã€ããŠã®èšå®ã§ããâ»çŸåšãããã®é ç®ã¯UIããçŽæ¥èšå®ããããšã¯ã§ããŸããïŒããã©ã«ãå€ã§èªååŠçãããŸãïŒã + Self-hosted LiveSyncã¯äžã€ã®ãã£ã³ã¯ã®ãµã€ãºãæäœminimum chunk sizeæå確ä¿ããäžã§ãã§ããã ãå¹ççã«åæã§ãããããããŒããåå²ããŠãã£ã³ã¯ãäœæããŸãã ããã¯ãåæãè¡ãéã«ãäžå®ã®æåæ°ã§åå²ããå Žåãå é ã®æ¹ãç·šéãããšããã®åŸã®åå²äœçœ®ããã¹ãŠãããçµæãšããŠã»ãŒãŸãããšã®ãã¡ã€ã«ã®ãã¡ã€ã«éåä¿¡ãè¡ãããšã«ãªã£ãŠããåé¡ãé¿ããããã«å®è£ ãããŸããã å ·äœçã«ã¯ãå é ããé ã«çŽè¿ã®äžèšã®ç®æãæ€çŽ¢ããäžçªé·ãåãããã®ãäžã€ã®ãã£ã³ã¯ãšããŸãã @@ -88,6 +265,11 @@ Self-hosted LiveSyncã¯äžã€ã®ãã£ã³ã¯ã®ãµã€ãºãæäœminimum chunk s æ¹è¡æåãš#ãé€ãããã¹ãŠâã«çœ®æããŠããã¢ã«ãŽãªãºã ã¯æå¹ã«åããŸãã ããã©ã«ãã¯20æåãšã250æåã§ãã +### ãã£ã³ã¯ã¹ããªãã¿ãŒ +èšå®ããŒ: chunkSplitterVersion + +ãã£ã³ã¯åå²ã¢ã«ãŽãªãºã ãéžæããŸããV3ãæãå¹ççã§ããåé¡ãçºçããå Žåã¯DefaultãŸãã¯Legacyã«èšå®ããŠãã ããã + ## General Settings äžè¬çãªèšå®ã§ãã @@ -97,18 +279,35 @@ Self-hosted LiveSyncã¯äžã€ã®ãã£ã³ã¯ã®ãµã€ãºãæäœminimum chunk s ### Vervose log 詳现ãªãã°ããã°ã«åºåããŸãã +### ãã¡ã€ã«èŠåãããŒã®ä»£ããã«ã¹ããŒã¿ã¹ã¢ã€ã³ã³ã衚瀺 +èšå®ããŒ: hideFileWarningNotice + +æå¹ãªå Žåããã¡ã€ã«èŠåãããŒã®ä»£ããã«ã¹ããŒã¿ã¹è¡šç€ºå ã« â ã¢ã€ã³ã³ã衚瀺ãããŸãïŒè©³çްæ å ±ã¯é衚瀺ã«ãªããŸãïŒã + +### ãããã¯ãŒã¯èŠåã®ã¹ã¿ã€ã« +èšå®ããŒ: networkWarningStyle + +åæãµãŒããŒã«æ¥ç¶ã§ããªãå Žåã®ãããã¯ãŒã¯ãšã©ãŒã®è¡šç€ºæ¹æ³ã + ## Sync setting åæã«é¢ããèšå®ã§ãã -### LiveSync -LiveSyncãè¡ããŸãã -ä»ã®åææ¹æ³ã§ã¯ãåæã®é åºããããŒãžã§ã³ç¢ºèªãè¡ããããã¯ãè¡ãããŠããªãã確èªããåŸããªã¢ãŒãã®å€æŽãåä¿¡ããåŸãããã€ã¹ã®å€æŽãéä¿¡ããããšããæåã«ãªããŸãã +### åæã¢ãŒã (Sync Mode) +èšå®ããŒ: syncMode -### Periodic Sync -宿çã«åæãè¡ããŸãã +åæåŠçãå®è¡ããããªã¬ãŒãšãªãæ¡ä»¶ãèšå®ããŸãã +- **LiveSync** (`LIVESYNC`): ãªã¢ã«ã¿ã€ã ãã€ç¶ç¶çãªåæ¹ååæãè¡ããŸãã + 泚æ: ãã®ã¢ãŒãã«ã¯ CouchDB ãŸã㯠WebRTC P2P ãªã¢ãŒããµãŒããŒãå¿ èŠã§ããS3äºæãªããžã§ã¯ãã¹ãã¬ãŒãžã§ã¯ãµããŒããããŠããŸããã +- **Periodic Sync** (`PERIODIC`): **Periodic Sync Interval** ã§æå®ããäžå®ã®ééããšã«åæåŠçãå®è¡ããŸãã +- **On Events** (`ONEVENTS`): ãã¡ã€ã«ã®ä¿åããã¡ã€ã«ãéããèµ·åæãªã©ãç¹å®ã®ã€ãã³ããçºçããéã«åæãããªã¬ãŒããŸãïŒè©³çްã¯äžéšã®èšå®ã¹ã€ããã§å¶åŸ¡ããŸãïŒã ### Periodic Sync Interval -宿çã«åæãè¡ãå Žåã®ééã§ãã +宿çã«åæãè¡ãå Žåã®ééïŒç§åäœïŒã§ãã + +### åæã®æå°éé +èšå®ããŒ: syncMinimumInterval + +ã€ãã³ãæã®èªååæã®æå°ééïŒããªç§ïŒã ### Sync on Save ãã¡ã€ã«ãä¿åããããšãã«åæãè¡ããŸãã @@ -146,6 +345,11 @@ Self-hosted LiveSyncã¯éåžžããã©ã«ãå ã®ãã¡ã€ã«ããã¹ãŠåé€ - Scan hidden files periodicaly. ãã®ãªãã·ã§ã³ãæå¹ã«ãããšãnç§ããã«é ããã¡ã€ã«ãã¹ãã£ã³ããŸãã +#### é衚瀺ãã¡ã€ã«ã®å€æŽéç¥ãæå¶ +èšå®ããŒ: suppressNotifyHiddenFilesChange + +æå¹ãªå Žåãé衚瀺ãã¡ã€ã«ã®å€æŽã«é¢ããéç¥ãæå¶ããŸãã + é ããã¡ã€ã«ã¯èœåçã«æ€åºãããªããããã¹ãã£ã³ãå¿ èŠã§ãã ã¹ãã£ã³ã§ã¯ããã¡ã€ã«ãšå ±ã«ãã¡ã€ã«ã®å€æŽæå»ãä¿åããŸãããããã¡ã€ã«ãæ¶ãããå Žåã¯ããã®äºå®ãä¿åããŸãããã®ãã¡ã€ã«ãèšé²ãããšã³ããªãŒãã¬ããªã±ãŒã·ã§ã³ãããéãã¹ãã¬ãŒãžãããæ°ããå Žåã¯ã¹ãã¬ãŒãžã«åæ ãããŸãã @@ -176,6 +380,45 @@ Self-hosted LiveSyncã¯PouchDBã䜿çšãããªã¢ãŒããš[ãã®ãããã³ ### Batch limit äžåºŠã«åŠçããBatchã®æ°ã§ããããã©ã«ãã¯40ã§ãã +### 1åã®ãªã¯ãšã¹ãã§éä¿¡ãããã£ã³ã¯ã®æå€§ãµã€ãº +èšå®ããŒ: sendChunksBulkMaxSize + +ã¡ã¬ãã€ãïŒMBïŒåäœã§æå®ããŸãã + +## Customisation Sync (ã«ã¹ã¿ãã€ãºåæ) +ãã©ã°ã€ã³ããããããŒãããŒããã¹ãããããªã©ã®Obsidianã®ã«ã¹ã¿ãã€ãºèšå®ãåæããæ©èœã§ãïŒä»¥å㯠**Plugin Sync** ãšåŒã°ããŠããŸããïŒã + +### ããã€ã¹å (Device name) +èšå®ããŒ: deviceAndVaultName + +åæãããã¹ãŠã®ããã€ã¹éã§äžæãšãªãããã€ã¹åã§ãããã®èšå®ãç·šéããã«ã¯ãäžåºŠã«ã¹ã¿ãã€ãºåæãç¡å¹ã«ããå¿ èŠããããŸãã + +### ãã¡ã€ã«ä¿åããšã®ã«ã¹ã¿ãã€ãºåæ (Per-file-saved customisation sync) +èšå®ããŒ: usePluginSyncV2 + +æå¹ãªå Žåããã¡ã€ã«ããšã®å¹ççãªã«ã¹ã¿ãã€ãºåæã䜿çšãããŸããæå¹ã«ããéã«ã¯ç°¡åãªç§»è¡äœæ¥ãå¿ èŠã§ããããã¹ãŠã®ããã€ã¹ã v0.23.18 以éã«ã¢ããããŒãããå¿ èŠããããŸãããã®æ©èœãæå¹ã«ãããšãå€ãããŒãžã§ã³ãšã®äºææ§ã倱ãããŸãã + +### ã«ã¹ã¿ãã€ãºåæãæå¹ã«ãã (Enable customisation sync) +èšå®ããŒ: usePluginSync + +ããŒããã¹ããããããããããŒããã©ã°ã€ã³èšå®ãªã©ã®åæãæå¹ã«ããŸãã +泚æ: å®å šäžã®çç±ããããã®æ©èœã䜿çšããã«ã¯ãšã³ãããŒãšã³ãæå·åïŒEnd-to-End EncryptionïŒãæå¹ã«ãªã£ãŠããå¿ èŠããããŸãã + +### ã«ã¹ã¿ãã€ãºã®èªåã¹ãã£ã³ (Scan customisation automatically) +èšå®ããŒ: autoSweepPlugins + +ã¬ããªã±ãŒã·ã§ã³ïŒåæåŠçïŒãå®è¡ããåã«ãã«ã¹ã¿ãã€ãºèšå®ã®å€æŽãã¹ãã£ã³ããŸãã + +### 宿çãªã«ã¹ã¿ãã€ãºã®ã¹ãã£ã³ (Scan customisation periodically) +èšå®ããŒ: autoSweepPluginsPeriodic + +1åããšã«ã«ã¹ã¿ãã€ãºèšå®ã®å€æŽã宿çã«ã¹ãã£ã³ããŸãã + +### ã«ã¹ã¿ãã€ãºæŽæ°ã®éç¥ (Notify customised) +èšå®ããŒ: notifyPluginOrSettingUpdated + +ä»ã®ããã€ã¹ã§æ°ããã«ã¹ã¿ãã€ãºèšå®ãæŽæ°ããããšãã«éç¥ã衚瀺ããŸãã + ## Miscellaneous ãã®ä»ã®èšå®ã§ã ### Show status inside editor @@ -195,8 +438,8 @@ Self-hosted LiveSyncã¯PouchDBã䜿çšãããªã¢ãŒããš[ãã®ãããã³  ããŒã¿ããŒã¹ãããã¯ãããŠããŠã端æ«ãã解決æžã¿ããšããŒã¯ãããŠããªãå ŽåãèŠåã衚瀺ãããŸãã ä»ã®ããã€ã¹ã§ãEnd to Endæå·åãæå¹ã«ããããDrop Historyãè¡ã£ãçãä»ã®ç«¯æ«ããã®ãŸãŸåæãè¡ã£ãŠã¯ããªãç¶æ ã«é¥ã£ãå Žå衚瀺ãããŸãã -æå·åãæå¹åããå Žåã¯ããã¹ãã¬ãŒãºãèšå®ããŠApply and recieveãDrop Historyãè¡ã£ãå Žåã¯ãDrop and recieveãè¡ããšèªåçã«è§£é€ãããŸãã -æåã§ãã®ããã¯ãè§£é€ããå Žåã¯ãmark this device as resolvedããã¯ãªãã¯ããŠãã ããã +æå·åãæå¹åããå Žåã¯ããã¹ãã¬ãŒãºãèšå®ããŠããã®ããã€ã¹ã®åæç¶æ ããªã»ãããããŸãã¯ããã®ããã€ã¹ã®ãã¡ã€ã«ã§ãµãŒããŒããŒã¿ãäžæžãããè¡ããšèªåçã«è§£é€ãããŸãã +æåã§ãã®ããã¯ãè§£é€ããå Žåã¯ãI've made a backup, mark this device 'resolved'ããã¯ãªãã¯ããŠãã ããã - ãã¿ãŒã³ïŒ  @@ -207,18 +450,52 @@ Self-hosted LiveSyncã¯PouchDBã䜿çšãããªã¢ãŒããš[ãã®ãããã³ ### Verify and repair all files Vaultå ã®ãã¡ã€ã«ãå šãŠèªã¿èŸŒã¿çŽããããå·®åããã£ãããããŒã¿ããŒã¹ããæ£åžžã«èªã¿èŸŒããªãã£ããã®ã«é¢ããŠãããŒã¿ããŒã¹ã«åæ ããŸãã -- Drop and send -ããã€ã¹ãšãªã¢ãŒãã®ããŒã¿ããŒã¹ãç Žæ£ããããã¯ããŠããããã€ã¹ã®ãã¡ã€ã«ã§ããŒã¿ããŒã¹ãæ§ç¯åŸããªã¢ãŒãã«äžæžãããŸãã -- Drop and receive -ããã€ã¹ã®ããŒã¿ããŒã¹ãç Žæ£ããåŸããªã¢ãŒããããæäœããŠããããã€ã¹ã«é¢ããŠããã¯ãè§£é€ããããŒã¿ãåä¿¡ããŠåæ§ç¯ããŸãã +- ãã®ããã€ã¹ã®åæç¶æ ããªã»ãã (Reset Synchronisation on This Device) +ããŒã«ã«ã®ããŒã¿ããŒã¹ãç Žæ£ãããªã¢ãŒãã®ããŒã¿ããåæ§ç¯ããŸãã +- ãã®ããã€ã¹ã®ãã¡ã€ã«ã§ãµãŒããŒããŒã¿ãäžæžã (Overwrite Server Data with This Device's Files) +ããŒã«ã«ããã³ãªã¢ãŒãã®ããŒã¿ããŒã¹ããã®ããã€ã¹äžã®ãã¡ã€ã«ã§åæ§ç¯ïŒäžæžãïŒããŸãã ### Lock remote database ãªã¢ãŒãã®ããŒã¿ããŒã¹ãããã¯ããä»ã®ç«¯æ«ã§åæãè¡ãããšããŠããšã©ãŒãšãšãã«åæããã£ã³ã»ã«ãããããã«èšå®ããŸããããã¯ãããŒã¿ããŒã¹ã®åæ§ç¯ãè¡ã£ãå Žåãèªåçã«èšå®ããããã®ãšåããã®ã§ãã äžãäžåæã«äžå ·åãçºçããŠããŠã䜿çšããŠããããã€ã¹ã®ããŒã¿ïŒãµãŒããŒã®ããŒã¿ãä¿è·ããå Žåãªã©ã«ãç·æ¥é¿é£çã«äœ¿çšããŠãã ããã -### Suspend file watching -ãã¡ã€ã«ã®æŽæ°ã®ç£èŠãæ¢ããŸãã +### Scram ã¹ã€ãã (Scram Switches) +ããŒã¿ããŒã¹ã®ç ŽæãäºæããªãããŒã¿åªå€±ãé²ãããã«ãåæåŠçãç·æ¥åæ¢ããããã®ã¹ã€ããã§ããé倧ãªèšå®äžäžèŽãåæãšã©ãŒãçºçããå Žåããã©ã°ã€ã³ã¯èªåçã« Scram ç¶æ ã«ç§»è¡ããåæåäœãäžæåæ¢ããããšããããŸãã + +#### ãã¡ã€ã«ã®æŽæ°ç£èŠãäžæåæ¢ (Suspend file watching) +èšå®ããŒ: suspendFileWatching + +ããŒã«ã«ãã¡ã€ã«å€æŽã®ç£èŠãšæ€ç¥ã忢ããŸãã + +#### ããŒã¿ããŒã¹åæ ãäžæåæ¢ (Suspend database reflecting) +èšå®ããŒ: suspendParseReplicationResult + +ããŒã¿ããŒã¹ã§ã®å€æŽãã¹ãã¬ãŒãžãã¡ã€ã«ïŒVaultå ã®ãã¡ã€ã«ïŒãžæžãæ»ãåŠçã忢ããŸãã + +### äºææ§ïŒã¡ã¿ããŒã¿ïŒ(Compatibility (Metadata)) + +#### å逿žã¿ãã¡ã€ã«ã®ã¡ã¿ããŒã¿ãä¿æããªã (Do not keep metadata of deleted files.) +èšå®ããŒ: deleteMetadataOfDeletedFiles + +ãã¡ã€ã«ãåé€ããéã«ããã®ãã¡ã€ã«ã®åæå±¥æŽã¡ã¿ããŒã¿ãå³åº§ã«ããŒã¿ããŒã¹ããåé€ããä¿æããªãããã«ããŸãã + +#### å逿žã¿ããŒã¿ã®ã¡ã¿ããŒã¿ãã¯ãªãŒã³ããããã (Delete old metadata of deleted files on start-up) +èšå®ããŒ: automaticallyDeleteMetadataOfDeletedFiles + +ãã¡ã€ã«ãåé€ããéã®ã¡ã¿ããŒã¿ãä¿æããæéïŒæ¥æ°ïŒãèšå®ããŸããæå®ããæ¥æ°ãçµéããå€ãå逿žã¿ãã¡ã€ã«ã®ã¡ã¿ããŒã¿ã¯ããã©ã°ã€ã³èµ·åæã«ããŒã¿ããŒã¹ããèªåçã«åé€ïŒã¯ãªãŒã³ãããïŒãããŸãã`0` ãæå®ãããšèªååé€ã¯ç¡å¹ã«ãªããŸãã + +### ç ŽæããŠããå¯èœæ§ããããã¡ã€ã«ãåŠçãã +èšå®ããŒ: processSizeMismatchedFiles + +ãµã€ãºäžäžèŽã®ãããã¡ã€ã«ãåŠçããŸããç¹å®ã®APIãå€éšé£æºã«ãã£ãŠäœæããããã¡ã€ã«ãåæããéã«åœ¹ç«ã¡ãŸãã + +### Remediation + +#### ã€ãã³ãåæ æã®æå€§ãã¡ã€ã«æŽæ°æ¥æ +èšå®ããŒ: maxMTimeForReflectEvents + +ãã®å€ïŒUnixãšããã¯ããã®ç§æ°ïŒããæ°ããæŽæ°æ¥æãæã€ãã¡ã€ã«ã«ã€ããŠã¯ãã€ãã³ãã®åæ ãç¡èŠããŸãã0ãæå®ãããšå¶éãç¡å¹ã«ãªããŸãã ### Corrupted data  diff --git a/docs/setup_cloudant.md b/docs/setup_cloudant.md index 83ca3ea..a979cb9 100644 --- a/docs/setup_cloudant.md +++ b/docs/setup_cloudant.md @@ -13,7 +13,7 @@ In these instructions, create IBM Cloudant Instance for trial. 1. You can choose "Lite plan" for free.  -1. Select Multitenant(it's the default) and the region as you like. +1. Select Multitenant (it is the default) and the region as you like.  1. Be sure to select "IAM and Legacy credentials" for "Authentication Method". @@ -28,20 +28,20 @@ In these instructions, create IBM Cloudant Instance for trial. 1. When all of the above steps have been done, open "Resource list" on the left pane. you can see the Cloudant instance in the "Service and software". Click it.  -1. In resource details, there's information to connect from Self-hosted LiveSync. - Copy the "External Endpoint(preferred)" address. (\*1). We use this address later, with the database name. +1. In resource details, there is information to connect from Self-hosted LiveSync. + Copy the "External Endpoint (preferred)" address. (\*1). We use this address later, with the database name.  ## Database setup 1. Hit the "Launch Dashboard" button, Cloudant dashboard will be shown. - Yes, it's almost CouchDB's fauxton. + Yes, it is almost CouchDB's fauxton.  1. First, you have to enable the CORS option. Hit the Account menu and open the "CORS" tab. Initially, "Origin Domains" is set to "Restrict to specific domains"., so set to "All domains(\*)" - _NOTE: of course We want to set "app://obsidian.md" but it's not acceptable on Cloudant._ + _NOTE: of course We want to set "app://obsidian.md" but it is not acceptable on Cloudant._  1. Next, Open the "Databases" tab and hit the "Create Database" button. @@ -55,10 +55,10 @@ In these instructions, create IBM Cloudant Instance for trial. ### Credentials Setup -1. Back into IBM Cloud, Open the "Service credentials". You'll get an empty list, hit the "New credential" button. +1. Back into IBM Cloud, Open the "Service credentials". You will get an empty list, hit the "New credential" button.  -1. The dialog to create a credential will be shown. +1. The dialogue to create a credential will be shown. type any name or leave it default, hit the "Add" button.  _NOTE: This "name" is not related to your username that uses in Self-hosted LiveSync._ @@ -68,14 +68,14 @@ In these instructions, create IBM Cloudant Instance for trial.  The username and password pair is inside this JSON. "username" and "password" are so. - follow the figure, it's + follow the figure, it is "apikey-v2-2unu15184f7o8emr90xlqgkm2ncwhbltml6tgnjl9sd5"(\*3) and "c2c11651d75497fa3d3c486e4c8bdf27"(\*4) ## Self-hosted LiveSync settings  -The Setting should be as below: +The settings should be as follows: | Items | Value | example | | ------------- | ----- | ----------------------------------------------------------------- | diff --git a/docs/tech_info.md b/docs/tech_info.md index 97fc2cc..3aa24b1 100644 --- a/docs/tech_info.md +++ b/docs/tech_info.md @@ -1,8 +1,8 @@ # Designed architecture -## How does this plugin synchronize. +## How does this plug-in synchronise. - + 1. When notes are created or modified, Obsidian raises some events. Self-hosted LiveSync catches these events and reflects changes into Local PouchDB. 2. PouchDB automatically or manually replicates changes to remote CouchDB. diff --git a/docs/tech_info_cn.md b/docs/tech_info_cn.md index c3dc1ea..c1d450c 100644 --- a/docs/tech_info_cn.md +++ b/docs/tech_info_cn.md @@ -2,7 +2,7 @@ ## è¿äžªæä»¶æ¯æä¹å®ç°åæ¥ç. - + 1. åœç¬è®°å建æä¿®æ¹æ¶ïŒObsidianäŒè§Šåäºä»¶ãSelf-hosted LiveSyncæè·è¿äºäºä»¶ïŒå¹¶å°åæŽåæ¥è³æ¬å°PouchDB 2. PouchDBéè¿èªåšææåšæ¹åŒå°åæŽåæ¥è³è¿çšCouchDB diff --git a/docs/tech_info_ja.md b/docs/tech_info_ja.md index f23c22d..faee6b6 100644 --- a/docs/tech_info_ja.md +++ b/docs/tech_info_ja.md @@ -2,7 +2,7 @@ ## åæ - + 1. ããŒããæŽæ°ãããéãObsidianãã€ãã³ããçºå ±ããŸããObsidian-LiveSyncã¯ããããã³ããªã³ã°ããŠãããŒã«ã«ã®PouchDBã«å€æŽãåæ ããŸãã 2. PouchDBã¯ããªã¢ãŒãã®CouchDBã«å·®åãã¬ããªã±ãŒã·ã§ã³ããŸãã diff --git a/docs/terms.md b/docs/terms.md index e30a311..ac34ef0 100644 --- a/docs/terms.md +++ b/docs/terms.md @@ -2,23 +2,102 @@ ## Spelling and Vocabulary conventions -1. Almost all of the english words are written in British English. For example, "organisation" instead of "organization", "synchronisation" instead of "synchronization", etc. This convention originated from the author's personal preference but is now maintained for consistency. +All guidelines and conventions listed below are disclosed and maintained solely for the sake of documentation `consistency`. -2. Idiomatic terms, such as used in HTML, CSS, and JavaScript, are usually be aligned with the language used in the technology. For example, "color" instead of "colour", "program" instead of "programme", etc. Especially, terms which are used for attributes, properties, and methods are notable. +1. Almost all of the English words are written in British English. This convention originated from the author's personal preference. + - **Traditional Spelling (Trad-spelling)**: We prefer traditional British English spellings. In particular, we use `-ise` and `-isation` suffixes rather than the Oxford spelling `-ize` and `-ization` (for example, 'initialisation', 'synchronisation', and 'organisation'). + - **Oxford Comma**: We use the serial (Oxford) comma to separate items in lists of three or more (for example, 'settings, snippets, and themes' instead of 'settings, snippets and themes'). + - **Logical Punctuation**: We place punctuation marks (such as commas and full stops) outside quotation marks, unless the punctuation mark is part of the quoted text itself. For example, we write 'dialogue', not 'dialogue,'. + - **BBC News Styleguide**: If in wonder, the BBC News Styleguide may be useful as a reference. + +2. Idiomatic terms, such as those used in HTML, CSS, and JavaScript, are usually aligned with the language used in the technology. For example, "color" instead of "colour", "program" instead of "programme", etc. Especially, terms which are used for attributes, properties, and methods are notable. 3. We use `dialogue` in documentation for consistency. While `dialog` may appear in source code, particularly in class names, method names, and attributes (following technical conventions in No. 2), we consistently use `dialogue` for user-facing messages and general documentation text. This approach balances No. 1 with No. 2. -4. Contractions are not used. For example, "do not" instead of "don't", "cannot" instead of "can't", etc. especially `'d`. +4. Contractions are not used. For example, "do not" instead of "don't", "cannot" instead of "can't", etc., especially `'d`. - We may encounter difficulties with tenses. 5. However, try using affirmative forms, `Discard` instead of `Do not keep`, `Continue` instead of `Do not stop`, etc. - Some languages, such as Japanese, have a different meaning for `yes` and `no` between affirmative and negative questions. -## Terminology +6. Single quotation marks (`'`) are preferred over double quotation marks (`"`) in general documentation text, unless the context requires double quotes (for example, inside JSON code blocks). -- Self-hosted LiveSync - - This plug-in name. `Self-hosted` is one word. +### Terminology + +- Boot-up sequence (boot-sequence) + - The initialisation process of the plug-in when Obsidian starts. It starts with the loading of the plug-in, setting up core services, loading saved settings, and opening the local database. Once the layout is ready, the plug-in checks for the presence of flag files, runs configuration diagnostics, connects to the remote database, and begins file watching. The sequence finishes once the plug-in is fully ready and operational. +- Broken files (Size mismatch) + - A state where a file's metadata and the actual content stored in its chunks do not match, causing file retrieval or synchronisation failures. These mismatches can be detected and resolved by running validation tools such as `Verify and repair all files` on the Hatch pane. +- Chunk / Chunks + - Divided units of data stored in the database or object storage to facilitate efficient synchronisation. +- Compaction + - A database maintenance procedure that discards old historical document revisions to shrink the remote database size. +- Custom HTTP Handler / Use Internal API (CORS Bypass Settings) + - Settings used to bypass CORS restrictions by routing requests through Obsidian's native request APIs. There are two distinct settings under the hood depending on the remote server type: + - **For S3-compatible Object Storage (useCustomRequestHandler)**: Labeled as **"Use Custom HTTP Handler"** in the standard settings tab, **"Use internal API"** in the Svelte-based Setup Wizard dialogue, and represented as `useProxy` in the Setup URI's query parameters due to an unfortunate misunderstanding during development. + - **For CouchDB (useRequestAPI)**: Labeled as **"Use Request API to avoid `inevitable` CORS problem"** in the standard settings tab, **"Use Internal API"** in the Svelte-based Setup Wizard dialogue, and represented as `useRequestAPI` in the Setup URI's query parameters. +- Customisation Sync + - The feature that synchronises settings, snippets, themes, and plug-ins. Write with an "s" in documentation (`Customisation`), though technical configurations and links may use `customization`. +- Database Adapter (IDB vs. IndexedDB) + - The local database storage interface used by PouchDB. The `IDB` adapter is recommended since the older `IndexedDB` adapter is obsolete and known to cause memory leaks in `LiveSync` mode. Users can switch between these adapters without a full database rebuild, although a local data migration and an Obsidian restart are required. +- Database Suffix (additionalSuffixOfDatabaseName) + - A unique suffix appended to the database name to allow synchronising multiple vaults with the same name on the same remote server. +- E2EE Algorithm + - The cryptographic algorithm version used for end-to-end encryption. All devices in the synchronisation group must be configured with a compatible version (such as `V2` or `V1`). +- Eden (Eden Chunks) + - A performance optimisation where newly created chunks are held within the document until they stabilise, before graduating to independent chunks. +- Fast Setup (Simple Fetch) + - A simplified, automated initial synchronisation flow triggered when setting up subsequent devices or recovering a database. It bypasses the detailed step-by-step setup wizard dialogues, prompting the user with high-level data processing decisions and completing the initial download and local file scan in one continuous process. +- Flag files (redflag.md, redflag2.md, redflag3.md) + - Special Markdown files (or directories) placed at the root of the vault to stop the boot-up sequence or trigger recovery tasks. For instance, `redflag.md` suspends all processes, while `redflag2.md` (`flag_rebuild.md`) triggers a full database rebuild and `redflag3.md` (`flag_fetch.md`) discards the local database to fetch it again from the remote. +- Garbage Collection (GC) + - The process of identifying and purging unreferenced chunks (unused data) from local and remote databases to reclaim storage space. +- Hatch (Hatch pane) + - A dedicated troubleshooting and maintenance section in the plug-in settings, typically hidden behind a warning-labeled collapsible panel to prevent accidental misconfiguration. It contains diagnostic utilities, database reset controls, status reports, and advanced edge-case patches. +- Hidden File Sync + - The feature that synchronises files located in hidden directories (like `.obsidian`). +- JWT Authentication + - An experimental authentication option for CouchDB allowing secure token-based authentication instead of standard credentials. It requires a configured private key/secret, algorithm, expiration duration, subject, and key ID. - LiveSync - - Very confusing term. - - As shorten-form of `Self-hosted LiveSync`. - - As a name of synchronisation mode. This should be changed to `Continuos`, in contrast to `Periodic`. + - A very confusing term. + - As a shortened form of `Self-hosted LiveSync`. + - As the name of a synchronisation mode. This should be changed to `Continuous`, in contrast to `Periodic`. +- livesync-serverpeer / webpeer + - Pseudo-clients that assist in WebRTC peer-to-peer communication. +- Metadata (File metadata) + - A database document that stores properties of a file, including its filename, path, size, modification time, conflict history, and references (hashes) of the chunks that comprise the file's content. In Self-hosted LiveSync, metadata is stored separately from the actual file content to enable efficient synchronisation and versioning. +- OneShot Sync + - A single, immediate bidirectional synchronisation (pull then push) triggered on demand or on specific events, as opposed to continuous (live) replication. +- Overwrite Server Data with This Device's Files + - A maintenance operation (formerly known as `Rebuild everything`) that discards the remote database and reconstructs it by uploading all current local files as a fresh database, overwriting any remote changes. +- Path Obfuscation + - A privacy option that encrypts file paths and folder names on the remote server. +- plug-in + - We use the hyphenated form `plug-in` in user-facing messages and general documentation, while `plugin` may appear in codebase files, configuration settings, or technical contexts. +- Relay Server (P2P relays) + - A WebSocket-based coordination server used to establish direct WebRTC peer-to-peer connections. The default relay is provided by the plug-in author. +- Remediation (maxMTimeForReflectEvents) + - A recovery setting that restricts the propagation of changes from the database to local storage, ignoring any file events (such as accidental mass deletions) that occurred after a specified date and time. +- Reset Synchronisation on This Device + - A maintenance operation (formerly known as `Fetch everything`) that discards the local database and reconstructs it by downloading all data from the remote server. +- Scram (Scram Switches) + - Emergency controls in the settings that allow users to suspend file watching or database writes to prevent corruption. +- Segmenter (Segmented-splitter) + - A chunking method that divides files on semantic boundaries (such as paragraphs or sections) rather than arbitrary byte boundaries. +- Self-hosted LiveSync + - The name of this plug-in. `Self-hosted` is one word. +- Setting Doctor (Config Doctor) + - A diagnostic utility that checks for mismatches or suboptimal configurations, presenting users with ideal values and recommendation reasons to easily resolve issues during migration, configuration import, or general troubleshooting. +- Setup URI + - An encrypted representation of the plug-in's settings containing server configuration, which allows users to clone their configuration across devices securely using a passphrase. +- Streaming replication (Stream-based replication) + - A data transfer method that downloads database documents as a continuous stream of events. It is significantly faster than traditional chunk-by-chunk HTTP requests and is used during Fast Setup to retrieve remote metadata quickly. +- Sync Mode + - The replication trigger mechanism. Users can select from `On Events` (synchronising on local file changes), `Periodic and Events` (synchronising at fixed intervals as well as on events), or `LiveSync` (continuous, real-time synchronisation). +- TURN Server (WebRTC P2P) + - A server type (Traversal Using Relays around NAT) used as a fallback to relay traffic when direct WebRTC peer-to-peer connection is blocked by strict NAT or firewalls. +- Update Thinning (Batch database update) + - An optimisation that groups multiple local file edits together over a short delay before committing them to the local database, reducing the number of database write operations. +- WebRTC P2P (Peer-to-Peer) + - A synchronisation method enabling direct communication between devices without a central server database. + diff --git a/docs/tips/fast-setup.md b/docs/tips/fast-setup.md new file mode 100644 index 0000000..3e33e35 --- /dev/null +++ b/docs/tips/fast-setup.md @@ -0,0 +1,65 @@ +# Fast Setup (Simple Fetch) + +Fast Setup is a streamlined, user-friendly data retrieval and initialisation flow designed to simplify setting up secondary devices or recovering databases. + +Instead of guiding the user through the detailed multi-step setup wizard dialogues, Fast Setup prompts the user with high-level sync decisions and automates database download and local storage scanning in one continuous process. + +--- + +## How It Works + +When you import a **Setup URI** on a secondary device, or when a **Fetch All** operation is triggered (such as by placing a `redflag3.md` / `flag_fetch.md` flag file at the root of the vault), the plug-in schedules remote data retrieval. + +On the next startup, the plug-in boots in scheduled fetch mode and opens a simplified dialogue: **"Data retrieval scheduled"**. + +--- + +## Technical Characteristics + +Fast Setup leverages several backend optimisations to make the retrieval fast, safe, and clean: + +1. **Stream-based Replication for Speed** + - It fetches all remote metadata via stream reception, which is significantly faster than traditional chunk-by-chunk retrieval. +2. **Delayed File Reflection to Prevent Corrupted Warnings** + - By suspending file reflection during the download phase, it prevents the plug-in from raising temporary or false "corrupted data synchronisation" or "size mismatch" warnings that can occur during the chunk download process. +3. **Time-Based Comparison is Generally Sufficient** + - Since the vault is entering a fresh synchronisation or recovery state, comparing files based on their modification timestamps (newer-wins) is highly reliable and sufficient to reconcile files without needing complex manual conflict resolution. + +--- + +## Step-by-Step Guide + +### Step 1: Choose Data Processing Method +You will be prompted to choose how the retrieved remote data will interact with your existing local files: + +1. **Compare time and take newer (newer-wins)** + - Compares the modified time of files and accepts the newer version. + - **Recommended if:** You have been using Self-hosted LiveSync and have made changes on multiple devices that you want to merge. +2. **Overwrite all with remote files (remote-wins)** + - Remote data is treated as the source of truth. + - **Recommended if:** You are setting up a brand new device with an empty or clean vault. + - *Warning: This will overwrite local files with remote files. Please ensure you have a backup of your local vault before proceeding.* +3. **Use the detailed flow (legacy)** + - Switches back to the detailed, traditional setup wizard dialogues. + - **Recommended if:** You want full control over the step-by-step database setup options. + +### Step 2: Configure Conflict & Deletion Rules +Depending on your choice in Step 1, you will configure how to handle mismatches: + +#### If you chose "Compare time and take newer": +- **Delete local files if they were deleted on remote** + - Keeps your local vault clean by removing files that have already been deleted on other devices. +- **Recreate remote files even if they were deleted on remote** + - Preserves local files and uploads them back to the remote database, even if they were deleted on other devices. + +#### If you chose "Overwrite all with remote files": +- **Delete local files if not on remote** + - Removes local-only files so that your local vault matches the remote database exactly. +- **Keep local files even if not on remote** + - Retains all existing local-only files, although this may result in duplicates that you will need to clean up manually after synchronisation. + +### Step 3: Automated Synchronisation +Once you confirm your choices: +1. The plug-in performs a fast download of the remote database (`fetchLocalDBFast`). +2. It automatically runs a full scan (`synchroniseAllFilesBetweenDBandStorage`) in the foreground to reflect database changes in your local vault files immediately. +3. The plug-in finalises the process and resumes normal operational status. diff --git a/docs/tips/fast-setup_ja.md b/docs/tips/fast-setup_ja.md new file mode 100644 index 0000000..56be7d2 --- /dev/null +++ b/docs/tips/fast-setup_ja.md @@ -0,0 +1,66 @@ +# ãã¡ã¹ãã»ããã¢ãã (Fast Setup / Simple Fetch) + +ãã¡ã¹ãã»ããã¢ããã¯ã2å°ç®ä»¥éã®ããã€ã¹ã®ã»ããã¢ãããããŒã¿ããŒã¹åæ§ç¯æã®ããŒã¿ååŸã»åæååŠçããçŽæçãã€è¿ éã«è¡ãããã®ç°¡ç¥åãããåæãããŒã§ãã + +åŸæ¥ã®ã»ããã¢ãããŠã£ã¶ãŒãã«ãããè€æ°ã®è©³çްãªã¹ããããèžãããšãªããåæã®åºæ¬æ¹éãéžæããã ãã§ãããŒã¿ããŒã¹ã®ããŠã³ããŒããšããŒã«ã«ãã¡ã€ã«ã®ã¹ãã£ã³ã»åæ ãäžé£ã®ããã»ã¹ãšããŠèªåçã«å®è¡ããŸãã + +--- + +## ä»çµã¿ + +2å°ç®ä»¥éã®ããã€ã¹ã§ **ã»ããã¢ããURI** ãã€ã³ããŒãããå Žåããæåã§ããŒã¿ã®åãã§ããïŒVaultã«ãŒãã« `redflag3.md` / `flag_fetch.md` ãé 眮ããçïŒãäºçŽãããå Žåããã©ã°ã€ã³ã¯ãªã¢ãŒãããŒã¿ããŒã¹ããã®ããŒã¿ååŸã¹ã±ãžã¥ãŒã«ãèšå®ããŸãã + +ãã®åŸã®èµ·åæããã©ã°ã€ã³ã¯ããŒã¿ãã§ããäºçŽã¢ãŒãã§ç«ã¡äžããã**ãData retrieval scheduledïŒããŒã¿ååŸã®ã¹ã±ãžã¥ãŒã«ïŒã** ãšããç°¡ç¥åããããã€ã¢ãã°ã衚瀺ããŸãã + +--- + +## æè¡çãªç¹åŸŽ + +ãã¡ã¹ãã»ããã¢ããã¯ãé«éãã€å®å šã§ã¯ãªãŒã³ãªåŠçãå®çŸããããã«ã以äžã®æé©åãè¡ã£ãŠããŸãã + +1. **é«éãªã¹ããªãŒã åä¿¡** + - å šããŒã¿ãååŸããŸãããã¹ããªãŒã åä¿¡ã«ããã¬ããªã±ãŒã·ã§ã³ã䜿çšãããããåŠçãéåžžã«é«éã§ãã +2. **ã¹ãã¬ãŒãžåæ ã®é å»¶ã«ããäžèŠãªèŠåã®æå¶** + - ããŒã¿ã®ããŠã³ããŒãäžã«ããŒã«ã«ãã¡ã€ã«ïŒã¹ãã¬ãŒãžïŒãžã®æžãåºãïŒåæ ïŒåŠçããããŠé å»¶ãããããšã§ãåæéäžã§çºçããã¡ãªãç ŽæããŒã¿åæããããµã€ãºäžäžèŽããªã©ã®äžæçãªãšã©ãŒèŠåãæã蟌ã¿ãŸãã + - ãã¹ãŠã®ããŒã¿ããŠã³ããŒããå®äºããåŸã«äžæ¬ããŠã¹ãã¬ãŒãžãžæžãåºããããäžå¿ èŠãªèŠåç»é¢ã§ãŠãŒã¶ãŒãæ··ä¹±ãããŸããã +3. **æå»ããŒã¹æ¯èŒã®åŠ¥åœæ§** + - åæã»ããã¢ããããªã«ããªãŒã®æ®µéïŒãã®ç¶æ ã«ç§»è¡ããçŽåŸïŒã«ãããŠã¯ãæŠããã¡ã€ã«æŽæ°æå»ïŒã¿ã€ã ã¹ã¿ã³ãïŒããŒã¹ã§ã®åçŽæ¯èŒãè¡ãããšã§ãååãã€åŠ¥åœãªåæçµæãåŸãããšãã§ããŸããããã«ããè€éãªç«¶åè§£æ±ºã®æéãçããŸãã + +--- + +## èšå®æé + +### ã¹ããã 1: ããŒã¿ã®åæ æ¹æ³ã®éžæ +ååŸãããªã¢ãŒãããŒã¿ããæ¢åã®ããŒã«ã«ãã¡ã€ã«ãšã©ã®ããã«çµ±åããããéžæããŸãã + +1. **Compare time and take newer (newer-wins)** + - ãã¡ã€ã«ã®æŽæ°æ¥æãæ¯èŒããããæ°ããæ¹ãæ¡çšããŸãã + - **æšå¥šãããã±ãŒã¹:** ãã§ã« Self-hosted LiveSync ã䜿çšããŠãããè€æ°ã®ããã€ã¹ã§ç·šéãã倿Žå 容ãã¿ã€ã ã¹ã¿ã³ãã«åºã¥ããŠçµ±åãããå Žåã +2. **Overwrite all with remote files (remote-wins)** + - ãªã¢ãŒãããŒã¿ããŒã¹ã®å å®¹ãæ£ïŒSource of TruthïŒãšããŠæ±ããŸãã + - **æšå¥šãããã±ãŒã¹:** ãŸã£ããæ°ããããã€ã¹ãã»ããã¢ããããå ŽåïŒç©ºã®Vaultãªã©ïŒã + - *èŠå: ããŒã«ã«ã«ãããã¹ãŠã®ãã¡ã€ã«ããªã¢ãŒãã®å 容ã§äžæžããããŸããéèŠãªããŒã¿ãããå Žåã¯ãäºåã«ããã¯ã¢ãããååŸããŠãã ããã* +3. **Use the detailed flow (legacy)** + - åŸæ¥ã®è©³çްãªã»ããã¢ãããŠã£ã¶ãŒããã€ã¢ãã°ã«æ»ããŸãã + - **æšå¥šãããã±ãŒã¹:** ããŒã¿ããŒã¹ã®æ§æãªãã·ã§ã³ãã¹ããããã€ã¹ãããã§çްããå¶åŸ¡ã»ç¢ºèªãããå Žåã + +### ã¹ããã 2: ç«¶åããã³åé€ã«ãŒã«ã®æ§æ +ã¹ããã 1 ã§ã®éžæå 容ã«å¿ããŠãããŒã«ã«ãšãªã¢ãŒãã®äžäžèŽãã©ãåŠçããããèšå®ããŸãã + +#### ãCompare time and take newerããéžæããå Žå: +- **Delete local files if they were deleted on remote** + - ä»ã®ããã€ã¹ã§å逿žã¿ã®ãã¡ã€ã«ãããŒã«ã«ãããåé€ããVaultãåæã»ã¯ãªãŒã³ãªç¶æ ã«ä¿ã¡ãŸãã +- **Recreate remote files even if they were deleted on remote** + - ä»ã®ããã€ã¹ã§åé€ããããã¡ã€ã«ã§ãã£ãŠããããŒã«ã«ãã¡ã€ã«ãç¶æãããªã¢ãŒãããŒã¿ããŒã¹ã«å床ã¢ããããŒãããŸãã + +#### ãOverwrite all with remote filesããéžæããå Žå: +- **Delete local files if not on remote** + - ãªã¢ãŒãã«ååšããªãããŒã«ã«å°çšãã¡ã€ã«ãåé€ããããŒã«ã«ã®Vaultããªã¢ãŒãããŒã¿ããŒã¹ãšå®å šã«äžèŽãããŸãã +- **Keep local files even if not on remote** + - ãªã¢ãŒãã«ååšããªãããŒã«ã«ãã¡ã€ã«ããã®ãŸãŸæ®ããŸãããã ããåæåŸã«éè€ãã¡ã€ã«ãçºçããå¯èœæ§ãããããããã®å Žåã¯æåã§ã¯ãªãŒã³ã¢ãããè¡ã£ãŠãã ããã + +### ã¹ããã 3: èªååæã®å®è¡ +éžæã確å®ãããšã以äžã®åŠçãé æ¬¡å®è¡ãããŸãã +1. ãªã¢ãŒãããŒã¿ããŒã¹ã®é«éããŠã³ããŒããå®è¡ããŸã (`fetchLocalDBFast`)ã +2. ããŒã«ã«ãã¡ã€ã«ãžã®å€æŽåæ ã®ããããã©ã¢ã°ã©ãŠã³ãã§ãã«ã¹ãã£ã³ (`synchroniseAllFilesBetweenDBandStorage`) ãèªåçã«å®è¡ããŸãã +3. åŠçãå®äºãããšããã©ã°ã€ã³ã¯éåžžã®åäœç¶æ ãžåŸ©åž°ããŸãã diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index f463e36..36d1168 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -224,7 +224,7 @@ There are many cases where this is really unclear. One possibility is that the c - If you know when the files were deleted, set the time a bit before that. - If not, bisecting may help us. 6. Delete `redflag.md`. -7. Perform `Reset synchronisation on This Device` on the `ðïž Maintenance` pane. +7. Perform `Reset Synchronisation on This Device` on the `ðïž Maintenance` pane. This mode is very fragile. Please be careful. @@ -236,16 +236,16 @@ not been stable. The new adapter has better performance and has a new feature like purging. Therefore, we should use new adapters and current default is so. However, when switching from an old adapter to a new adapter, some converting or -local database rebuilding is required, and it takes a few time. It was a long +local database rebuilding is required, and it takes some time. It was a long time ago now, but we once inconvenienced everyone in a hurry when we changed the format of our database. For these reasons, this toggle is automatically on if we have upgraded from vault which using an old adapter. -When you rebuild everything or fetch from the remote again, you will be asked to +When you overwrite server data with this device's files or reset synchronisation on this device again, you will be asked to switch this. Therefore, experienced users (especially those stable enough not to have to -rebuild the database) may have this toggle enabled in their Vault. Please +overwrite server data) may have this toggle enabled in their Vault. Please disable it when you have enough time. ### ZIP (or any extensions) files were not synchronised. Why? @@ -303,9 +303,9 @@ happened on other devices. This means that conflicts will happen in the past, after the time we have synchronised. Hence we cannot collect and delete the unused chunks even though if we are not currently referenced. -To shrink the database size, `Rebuild everything` only reliably and effectively. +To shrink the database size, `Overwrite Server Data with This Device's Files` is the only reliable and effective way. But do not worry, if we have synchronised well. We have the actual and real -files. Only it takes a bit of time and traffics. +files. Only it takes a bit of time and traffic. ### How to launch the DevTools @@ -373,47 +373,64 @@ without Obsidian. For example, if there is `redflag.md`, Self-hosted LiveSync suspends all database and storage processes. -### Flag Files +### Scram State and Flag Files (SCRAM Warning Loop) -The flag file is a simple Markdown file designed to prevent storage events and database events in self-hosted LiveSync. -Its very existence is significant; it may be left blank, or it may contain text; either is acceptable. +The plug-in uses a **Scram state** (emergency suspension of all synchronisation processes) to prevent database corruption when severe errors or conflicts are detected. This state is often triggered or persisted by **flag files** placed at the root of the vault. -This file is in Markdown format so that it can be placed in the Vault externally, even if Obsidian fails to launch. +If you encounter a warning saying **"Scram detected, all sync operations are suspended per SCRAM"** or get caught in an infinite loop where the warning persists even after clicking "Resume", it is likely due to a flag file in your vault. -There are some options to use `redflag.md`. +#### Flag Files -| Filename | Human-Friendly Name | Description | -| ------------- | ------------------- | ------------------------------------------------------------------------------------ | -| `redflag.md` | - | Suspends all processes. | -| `redflag2.md` | `flag_rebuild.md` | Suspends all processes, and rebuild both local and remote databases by local files. | -| `redflag3.md` | `flag_fetch.md` | Suspends all processes, discard the local database, and fetch from the remote again. | +A flag file is a simple Markdown file located at the root of your vault. Its very existence is significant; it may be left blank or contain any text. These files are used so they can be easily placed or deleted from outside Obsidian (e.g., when Obsidian fails to launch). -When fetching everything remotely or performing a rebuild, restarting Obsidian -is performed once for safety reasons. At that time, Self-hosted LiveSync uses -these files to determine whether the process should be carried out. (The use of -normal markdown files is a trick to externally force cancellation in the event -of faults in the rebuild or fetch function itself, especially on mobile -devices). This mechanism is also used for set-up. And just for information, -these files are also not subject to synchronisation. +| Filename | Human-Friendly Name | Description | +| ------------- | ------------------- | --------------------------------------------------------------------------------------- | +| `redflag.md` | - | Suspends all processes (activates Scram). | +| `redflag2.md` | `flag_rebuild.md` | Suspends all processes, and overwrites server data with this device's files. | +| `redflag3.md` | `flag_fetch.md` | Suspends all processes, discards the local database, and resets synchronisation on this device. | -However, occasionally the deletion of files may fail. This should generally work -normally after restarting Obsidian. (As far as I can observe). +When resetting synchronisation on this device or overwriting server data, restarting Obsidian is performed once for safety reasons. At that time, Self-hosted LiveSync uses these files to determine whether the process should be carried out. (This mechanism is especially useful on mobile devices to force cancellation if the database rebuilding fails). These files are not subject to synchronisation. + +#### How to Resolve the Scram Loop + +If you cannot disable Scram, please follow these steps: +1. Close Obsidian completely. +2. Open your system's file manager and check the root directory of your vault. +3. Locate and delete any flag files (such as `redflag.md`, `redflag2.md`, or `redflag3.md`). +4. Launch Obsidian. +5. Go to the settings dialogue -> **Hatch** -> **Scram Switches**, and manually toggle **Suspend file watching** and **Suspend database reflecting** to `false` (disabled) if they have not been reset automatically. + +> [!TIP] +> This is the reason why flag files are standard `.md` files: it allows you to manage them externally. On mobile devices, you can use system file manager applications (such as the native **Files** app on iOS/iPadOS or **Files by Google** on Android) to find and delete these files to resolve a lock, or conversely, create/place a new `redflag.md` file (or directory) at the root of your vault to force-suspend synchronisation and stop Obsidian's boot-up sequence if you need to fix a database issue. + +### JWT Authentication Errors + +#### DataError when configuring JWT authentication + +If you encounter a `DataError:` with no additional information in the logs when configuring JWT authentication, this usually indicates a private key formatting issue. + +Self-hosted LiveSync requires the private key (for ES256/ES512 algorithms) to be in the **PKCS#8 PEM** format. Standard SEC1 EC private keys (which begin with `-----BEGIN EC PRIVATE KEY-----`) will trigger this error. + +To resolve this, convert your private key to PKCS#8 format using the following `openssl` command: +```bash +openssl pkcs8 -topk8 -inform PEM -nocrypt -in private.key -out pkcs8.key +``` +Then paste the contents of `pkcs8.key` (which begins with `-----BEGIN PRIVATE KEY-----`) into the JWT Key field. ### Old tips -- Rarely, a file in the database could be corrupted. The plugin will not write +- Rarely, a file in the database could be corrupted. The plug-in will not write to local storage when a file looks corrupted. If a local version of the file is on your device, the corruption could be fixed by editing the local file and - synchronizing it. But if the file does not exist on any of your devices, then + synchronising it. But if the file does not exist on any of your devices, then it can not be rescued. In this case, you can delete these items from the - settings dialog. + settings dialogue. - To stop the boot-up sequence (eg. for fixing problems on databases), you can put a `redflag.md` file (or directory) at the root of your vault. Tip for iOS: a redflag directory can be created at the root of the vault using the File application. -- Also, with `redflag2.md` placed, we can automatically rebuild both the local - and the remote databases during the boot-up sequence. With `redflag3.md`, we - can discard only the local database and fetch from the remote again. +- Also, with `redflag2.md` placed, we can automatically overwrite server data with this device's files during the boot-up sequence. With `redflag3.md`, we + can discard only the local database and reset synchronisation on this device. - Q: The database is growing, how can I shrink it down? A: each of the docs is saved with their past 100 revisions for detecting and resolving conflicts. Picturing that one device has been offline for a while, and comes online @@ -425,7 +442,7 @@ normally after restarting Obsidian. (As far as I can observe). So, We have to make the database again like an enlarged git repo if you want to solve the root of the problem. - And more technical Information is in the [Technical Information](tech_info.md) -- If you want to synchronize files without obsidian, you can use +- If you want to synchronise files without obsidian, you can use [filesystem-livesync](https://github.com/vrtmrz/filesystem-livesync). - WebClipper is also available on Chrome Web Store:[obsidian-livesync-webclip](https://chrome.google.com/webstore/detail/obsidian-livesync-webclip/jfpaflmpckblieefkegjncjoceapakdf) diff --git a/eslint.config.mjs b/eslint.config.mjs index 35b0d25..65f81a3 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -3,81 +3,132 @@ import obsidianmd from "eslint-plugin-obsidianmd"; import globals from "globals"; import { defineConfig, globalIgnores } from "eslint/config"; import * as sveltePlugin from "eslint-plugin-svelte"; - +import svelteParser from "svelte-eslint-parser"; +const warnWhileDev = "off"; // Change to "warn" to enable warnings for rules that are currently disabled. export default defineConfig([ globalIgnores([ - "**/node_modules/*", - "**/jest.config.js", + // Build outputs and legacy files + "**/build", + "coverage", + "**/main.js", + "main_org.js", + "pouchdb-browser.js", + "version-bump.mjs", + "package.json", + "**/*.json", + "**/.eslintrc.js.bak", + // Files from linked dependencies (those files should not exist for most people). + "modules/octagonal-wheels/dist/**/*", + + // Sub-projects (Exclude from root linting as they have different environments) + "src/apps/**/*", + "utils/**/*", + + // Specific exclusions from common library (src/lib) "src/lib/coverage", "src/lib/browsertest", - "**/test.ts", - "**/tests.ts", - "**/**test.ts", - "**/**.test.ts", - "**/*.unit.spec.ts", - "**/esbuild.*.mjs", - "**/terser.*.mjs", - "**/node_modules", - "**/build", - "**/.eslintrc.js.bak", - "src/lib/src/patches/pouchdb-utils", - "**/esbuild.config.mjs", - "**/rollup.config.js", - "modules/octagonal-wheels/rollup.config.js", - "modules/octagonal-wheels/dist/**/*", "src/lib/test", "src/lib/_tools", + "src/lib/src/patches/pouchdb-utils", "src/lib/src/cli", - "**/main.js", - "src/apps/**/*", - ".prettierrc.*.mjs", - ".prettierrc.mjs", - "*.config.mjs", - "src/apps/**/*", "src/lib/src/services/implements/browser/**", "src/lib/src/services/implements/headless/**", "src/lib/src/API", + + // Config files and build scripts + "**/jest.config.js", + "**/rollup.config.js", + "**/esbuild.config.mjs", + "**/terser.*.mjs", + ".prettierrc.*.mjs", + ".prettierrc.mjs", + "*.config.mjs", + "vite.*", + "vitest.*", + // Testing files (Simplified patterns) + "test/**", + "**/*.test.ts", + "**/*.unit.spec.ts", + "**/test.ts", + "**/tests.ts", ]), ...sveltePlugin.configs["flat/base"], ...obsidianmd.configs.recommended, { files: ["**/*.ts"], + // ignores:["src/lib/**/*.ts"], // Exclude library files from root linting (they have different environments and rules). languageOptions: { - globals: { ...globals.browser }, + globals: { ...globals.browser, PouchDB: "readonly" }, parser: tsParser, parserOptions: { project: "./tsconfig.json", }, }, + linterOptions:{ + reportUnusedDisableDirectives: false, + }, rules: { + // -- Base rules (turned off in favour of TS specific versions or explicitly disabled). "no-unused-vars": "off", - "@typescript-eslint/no-unused-vars": ["error", { args: "none" }], "no-unused-labels": "off", - "@typescript-eslint/ban-ts-comment": "off", "no-prototype-builtins": "off", - "@typescript-eslint/no-empty-function": "off", - "require-await": "error", - "obsidianmd/rule-custom-message": "off", // Temporary - "obsidianmd/ui/sentence-case": "off", // Temporary - "@typescript-eslint/require-await": "warn", - "@typescript-eslint/no-misused-promises": "warn", - "@typescript-eslint/no-floating-promises": "warn", - "no-async-promise-executor": "warn", + "require-await": "off", + // -- TypeScript specific rules + // @typescript-eslint/no-unsafe-* rules and @typescript-eslint/no-explicit-any: + // This project contains a lot of library-sh code where the use of `any` is often necessary and justified. + // Rules is now set to 'off' for a while. "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-unsafe-argument": "off", + "@typescript-eslint/no-unsafe-call": "off", + "@typescript-eslint/no-unsafe-member-access": "off", + "@typescript-eslint/no-unsafe-return": "off", + "@typescript-eslint/no-unsafe-assignment": "off", + // -- Reasonable rules. + "@typescript-eslint/no-deprecated": warnWhileDev, + "@typescript-eslint/no-unused-vars": ["error", { args: "none" }], + "@typescript-eslint/ban-ts-comment": "off", + "@typescript-eslint/no-empty-function": "off", + "@typescript-eslint/require-await": "error", + "@typescript-eslint/no-misused-promises": "error", + "@typescript-eslint/no-floating-promises": "error", "@typescript-eslint/no-unnecessary-type-assertion": "error", + + // -- Obsidian rules + // obsidianmd/no-unsupported-api: usually this project checks for API support at runtime, so this rule is not critical but can be helpful to catch potential issues. + "obsidianmd/no-unsupported-api": warnWhileDev, + + // -- General rules + "no-async-promise-executor": warnWhileDev, "no-constant-condition": ["error", { checkLoops: false }], + // -- Disabled rules + // no-undef: This option breaks the global declarations for the library files and is not worth the effort to fix at this time. + "no-undef": "off", + + // -- Plugin specific overrides + "obsidianmd/rule-custom-message": "off", + "obsidianmd/ui/sentence-case": "off", + "obsidianmd/no-plugin-as-component": "off", + + // -- Temporary overrides for migration + "obsidianmd/no-static-styles-assignment": "off", }, }, { files: ["**/*.svelte"], languageOptions: { + parser: svelteParser, parserOptions: { parser: tsParser, + extraFileExtensions: [".svelte"], }, }, rules: { - "no-unused-vars": ["error", { argsIgnorePattern: "^_", varsIgnorePattern: "^_" }], - "obsidianmd/no-plugin-as-component": "off", // Temporary + // no-unused-vars: + // Svelte template's declarations have a lot of false positives and the rule is not worth the effort to fix at this time. + // it may improve in the future with some options as like ["error", { argsIgnorePattern: "^_", varsIgnorePattern: "^_" }],] + "no-unused-vars": "off", + "obsidianmd/no-plugin-as-component": "off", + "obsidianmd/ui/sentence-case": "off", }, }, ]); diff --git a/manifest.json b/manifest.json index 027fd64..fbc492e 100644 --- a/manifest.json +++ b/manifest.json @@ -1,10 +1,10 @@ { "id": "obsidian-livesync", "name": "Self-hosted LiveSync", - "version": "0.25.68", + "version": "0.25.73", "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", "authorUrl": "https://github.com/vrtmrz", "isDesktopOnly": false -} \ No newline at end of file +} diff --git a/package-lock.json b/package-lock.json index bd8f9bd..a23ad25 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "obsidian-livesync", - "version": "0.25.68", + "version": "0.25.73", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "obsidian-livesync", - "version": "0.25.68", + "version": "0.25.73", "license": "MIT", "dependencies": { "@aws-sdk/client-s3": "^3.808.0", @@ -15,6 +15,7 @@ "@smithy/middleware-apply-body-checksum": "^4.3.9", "@smithy/protocol-http": "^5.3.9", "@smithy/querystring-builder": "^4.2.9", + "@smithy/util-retry": "^4.4.5", "@trystero-p2p/nostr": "^0.24.0", "chokidar": "^4.0.0", "commander": "^14.0.3", @@ -25,7 +26,7 @@ "micromatch": "^4.0.0", "minimatch": "^10.2.2", "obsidian": "^1.12.3", - "octagonal-wheels": "^0.1.45", + "octagonal-wheels": "^0.1.46", "pouchdb-adapter-leveldb": "^9.0.0", "qrcode-generator": "^1.4.4", "werift": "^0.23.0", @@ -51,9 +52,9 @@ "@types/transform-pouch": "^1.0.6", "@typescript-eslint/eslint-plugin": "8.56.1", "@typescript-eslint/parser": "8.56.1", - "@vitest/browser": "^4.1.1", - "@vitest/browser-playwright": "^4.1.1", - "@vitest/coverage-v8": "^4.1.1", + "@vitest/browser": "^4.1.8", + "@vitest/browser-playwright": "^4.1.8", + "@vitest/coverage-v8": "^4.1.8", "dotenv-cli": "^11.0.0", "esbuild": "0.25.0", "esbuild-plugin-inline-worker": "^0.1.1", @@ -90,7 +91,7 @@ "typescript": "5.9.3", "vite": "^7.3.1", "vite-plugin-istanbul": "^8.0.0", - "vitest": "^4.1.1", + "vitest": "^4.1.8", "webdriverio": "^9.27.0", "yaml": "^2.8.2" } @@ -1851,9 +1852,9 @@ "license": "MIT" }, "node_modules/@eslint/config-array/node_modules/brace-expansion": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", - "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", "dev": true, "license": "MIT", "dependencies": { @@ -1932,9 +1933,9 @@ "license": "MIT" }, "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", - "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", "dev": true, "license": "MIT", "dependencies": { @@ -3546,13 +3547,13 @@ } }, "node_modules/@smithy/core": { - "version": "3.24.3", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.24.3.tgz", - "integrity": "sha512-Ep/7tPamGY8mgESE3LyLKtxJyy6U52WWAqr/3wial47Sj4u3PiIF73AOGI27UyLy9duTkhZbgzodOfLV4TduZg==", + "version": "3.24.6", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.24.6.tgz", + "integrity": "sha512-wBXDRup6UU97VKyaiRo8AssnfStPtG0oAAfpq/bC0a1YYau8pM86YB4kM6ccoVi1mS8l/UHbn9oDM+7uozr/ug==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/crc32": "5.2.0", - "@smithy/types": "^4.14.2", + "@smithy/types": "^4.14.3", "tslib": "^2.6.2" }, "engines": { @@ -3719,12 +3720,12 @@ } }, "node_modules/@smithy/is-array-buffer": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.3.3.tgz", - "integrity": "sha512-RRxYqjUa/n8dRVkbhyuiRarppLzt4H/AtMUEFmiHlDy8o4wrgqAdzxsk9naemzu6iX67ZV375fNmX7Q8dynGKw==", + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.3.6.tgz", + "integrity": "sha512-/cSYHP8jPffkhBClQzH9fAJujIh8dwMwg2swrVF4stXQsUWO5Oi2bwyaMUcBPIyulUI5IxaJFxd9C8UQX+YZsQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.24.3", + "@smithy/core": "^3.24.6", "tslib": "^2.6.2" }, "engines": { @@ -3988,9 +3989,9 @@ } }, "node_modules/@smithy/types": { - "version": "4.14.2", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.14.2.tgz", - "integrity": "sha512-P+otAxbV4CqBybp7EkcJCrig63yE2E7PuNVOmilVMRcx/O+QDzGULTrKsq4DV13gSfak9ObPrWaHl/9bL5YcWw==", + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.14.3.tgz", + "integrity": "sha512-YupL0ZWmFtJexUN2cHzkvvF/b9pKrtAIfT1o7/oY/Ppu8IYeZ+lDPM5vZdQJaSeA132dJCqojjGC9NhXeF71VQ==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -4052,12 +4053,12 @@ } }, "node_modules/@smithy/util-buffer-from": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.3.3.tgz", - "integrity": "sha512-5xlgilVaX96HdVlLZymKUa7vOTZtisOTxBJloM2J4PeRqyAWBeFIq0DnIxQISvwxT4rgJAvk7rHhB+GlCCKe8g==", + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.3.6.tgz", + "integrity": "sha512-sms/ty2CJwHOiGzEaAVWizTVK5KusXpAYqCUeXIa+hWtNKLwjimH4z11mc07d0Fe3DT3lmZJIZWOMcVQ/N4hBQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.24.3", + "@smithy/core": "^3.24.6", "tslib": "^2.6.2" }, "engines": { @@ -4149,13 +4150,12 @@ } }, "node_modules/@smithy/util-retry": { - "version": "4.2.12", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.12.tgz", - "integrity": "sha512-1zopLDUEOwumjcHdJ1mwBHddubYF8GMQvstVCLC54Y46rqoHwlIU+8ZzUeaBcD+WCJHyDGSeZ2ml9YSe9aqcoQ==", + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.4.5.tgz", + "integrity": "sha512-W9Ovy9i02yGqtLlpqZNQuXNxXc5OPfXujnembxN/FxyBtGjJd8vKY0PQYEJ8FNybTOcXG+ZxsSsX23HOb3zQzg==", "license": "Apache-2.0", "dependencies": { - "@smithy/service-error-classification": "^4.2.12", - "@smithy/types": "^4.13.1", + "@smithy/core": "^3.24.5", "tslib": "^2.6.2" }, "engines": { @@ -4194,12 +4194,12 @@ } }, "node_modules/@smithy/util-utf8": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.3.3.tgz", - "integrity": "sha512-c1QpRBn3aMsoqE64dd4Imgjy8Pynfw+eR7GkjElquxUFSnezwYVaOFm8JcYa+Bo/5ssbEyPKcT3+4bmrWYh6eQ==", + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.3.6.tgz", + "integrity": "sha512-tAa4sePYB7mlJzdYbdBqdv37KwFKWixmM/r3ihcI0HFOVjf+a5oGvtcLXcGm4S1bY4DFsLAIOHgjubtp+oRufw==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.24.3", + "@smithy/core": "^3.24.6", "tslib": "^2.6.2" }, "engines": { @@ -4994,47 +4994,46 @@ } }, "node_modules/@vitest/browser": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@vitest/browser/-/browser-4.1.1.tgz", - "integrity": "sha512-gjjrFC4+kPVK/fN9URDJWrssU5Gqh8Az8pKG/NSfQ2V+ky8b/y1BgBg0Ug13+hOGp5pzInonmGRPn7vOgSLgzA==", + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/browser/-/browser-4.1.8.tgz", + "integrity": "sha512-u21VzX07HzlJYpFgkxmjEXar/tG2UqWGgyGG/46SrrPc7rSdCTPw5vuowopO9CIqF8UCUQzDFdbVnNpw6N0BfQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@blazediff/core": "1.9.1", - "@vitest/mocker": "4.1.1", - "@vitest/utils": "4.1.1", + "@vitest/mocker": "4.1.8", + "@vitest/utils": "4.1.8", "magic-string": "^0.30.21", "pngjs": "^7.0.0", "sirv": "^3.0.2", - "tinyrainbow": "^3.0.3", + "tinyrainbow": "^3.1.0", "ws": "^8.19.0" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "vitest": "4.1.1" + "vitest": "4.1.8" } }, "node_modules/@vitest/browser-playwright": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@vitest/browser-playwright/-/browser-playwright-4.1.1.tgz", - "integrity": "sha512-dtVSBZZha2k/7P7EAXXrEAoxuIKl8Yv9f2Dk4GN/DGfmhf4DQvkvu+57okR2wq/gan1xppKjL/aBxK/kbYrbGw==", + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/browser-playwright/-/browser-playwright-4.1.8.tgz", + "integrity": "sha512-SR7FqgegaexEg73xvf3ArtygXegagMdXnL0EZMpxrWvvhQxvicD/E8p0ib0J91riPRtQUViyh67Xjw3NqvyhVg==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@vitest/browser": "4.1.1", - "@vitest/mocker": "4.1.1", - "tinyrainbow": "^3.0.3" + "@vitest/browser": "4.1.8", + "@vitest/mocker": "4.1.8", + "tinyrainbow": "^3.1.0" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { "playwright": "*", - "vitest": "4.1.1" + "vitest": "4.1.8" }, "peerDependenciesMeta": { "playwright": { @@ -5043,14 +5042,15 @@ } }, "node_modules/@vitest/coverage-v8": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.1.tgz", - "integrity": "sha512-nZ4RWwGCoGOQRMmU/Q9wlUY540RVRxJZ9lxFsFfy0QV7Zmo5VVBhB6Sl9Xa0KIp2iIs3zWfPlo9LcY1iqbpzCw==", + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.8.tgz", + "integrity": "sha512-lt3kovsyHwYe00wq4D1ti0Z974fWj4NLp6siqiyEufUpyFwK9Yhi7rBhac9JL5aA0zoMrJqc4vYPZRUnI7l7nw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@bcoe/v8-coverage": "^1.0.2", - "@vitest/utils": "4.1.1", + "@vitest/utils": "4.1.8", "ast-v8-to-istanbul": "^1.0.0", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", @@ -5058,14 +5058,14 @@ "magicast": "^0.5.2", "obug": "^2.1.1", "std-env": "^4.0.0-rc.1", - "tinyrainbow": "^3.0.3" + "tinyrainbow": "^3.1.0" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@vitest/browser": "4.1.1", - "vitest": "4.1.1" + "@vitest/browser": "4.1.8", + "vitest": "4.1.8" }, "peerDependenciesMeta": { "@vitest/browser": { @@ -5074,41 +5074,31 @@ } }, "node_modules/@vitest/expect": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.1.tgz", - "integrity": "sha512-xAV0fqBTk44Rn6SjJReEQkHP3RrqbJo6JQ4zZ7/uVOiJZRarBtblzrOfFIZeYUrukp2YD6snZG6IBqhOoHTm+A==", + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.8.tgz", + "integrity": "sha512-h3nDO677RDLEGlBxyQ5CW8RlMThSKSRLUePLOx09gNIWRL40edgA1GCZSZgf1W55MFAG6/Sw14KeaAnqv0NKdQ==", "dev": true, "license": "MIT", "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.1", - "@vitest/utils": "4.1.1", + "@vitest/spy": "4.1.8", + "@vitest/utils": "4.1.8", "chai": "^6.2.2", - "tinyrainbow": "^3.0.3" + "tinyrainbow": "^3.1.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, - "node_modules/@vitest/expect/node_modules/chai": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", - "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, "node_modules/@vitest/mocker": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.1.tgz", - "integrity": "sha512-h3BOylsfsCLPeceuCPAAJ+BvNwSENgJa4hXoXu4im0bs9Lyp4URc4JYK4pWLZ4pG/UQn7AT92K6IByi6rE6g3A==", + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.8.tgz", + "integrity": "sha512-LEiN/xe4OSIbKe9HQIp5OC24agGD9J5CnmMgsLohVVoOPWL9a2sBoR6VBx43jQZb7Kr1l4RCuyCJzcAa0+dojw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "4.1.1", + "@vitest/spy": "4.1.8", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, @@ -5129,26 +5119,26 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.1.tgz", - "integrity": "sha512-GM+TEQN5WhOygr1lp7skeVjdLPqqWMHsfzXrcHAqZJi/lIVh63H0kaRCY8MDhNWikx19zBUK8ceaLB7X5AH9NQ==", + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.8.tgz", + "integrity": "sha512-9GasEBxpZ1VYIpqHf/0+YGg121uSNwCKOJqIrTwWP/TB7DmFCiaBpNl3aPZzoLWfWkuqhbH8vJIVobZkvdo2cA==", "dev": true, "license": "MIT", "dependencies": { - "tinyrainbow": "^3.0.3" + "tinyrainbow": "^3.1.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/runner": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.1.tgz", - "integrity": "sha512-f7+FPy75vN91QGWsITueq0gedwUZy1fLtHOCMeQpjs8jTekAHeKP80zfDEnhrleviLHzVSDXIWuCIOFn3D3f8A==", + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.8.tgz", + "integrity": "sha512-EmVxeBAfMJvycdjd6Hm+RbFBbA9fKvo0Kx37hNpBYoYeavH3RNsBXWDooR1mgD52dCrxIIuP7UotpfiwOikvcg==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "4.1.1", + "@vitest/utils": "4.1.8", "pathe": "^2.0.3" }, "funding": { @@ -5156,14 +5146,14 @@ } }, "node_modules/@vitest/snapshot": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.1.tgz", - "integrity": "sha512-kMVSgcegWV2FibXEx9p9WIKgje58lcTbXgnJixfcg15iK8nzCXhmalL0ZLtTWLW9PH1+1NEDShiFFedB3tEgWg==", + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.8.tgz", + "integrity": "sha512-acfZboRmAIf05DEKcBQy33VXojFJjtUdLyo7oOmV9kebb2xdU01UknNiPuPZoJZQyO7DF0gZdTGTpeAzET9QPQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.1", - "@vitest/utils": "4.1.1", + "@vitest/pretty-format": "4.1.8", + "@vitest/utils": "4.1.8", "magic-string": "^0.30.21", "pathe": "^2.0.3" }, @@ -5172,9 +5162,9 @@ } }, "node_modules/@vitest/spy": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.1.tgz", - "integrity": "sha512-6Ti/KT5OVaiupdIZEuZN7l3CZcR0cxnxt70Z0//3CtwgObwA6jZhmVBA3yrXSVN3gmwjgd7oDNLlsXz526gpRA==", + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.8.tgz", + "integrity": "sha512-6EevtBp6OZOPF7bmz36HrGMeP3txgVSrgebWxHOafDXGkhIzfXK14f8KF6MuFfgXXUeHxmpD3BQxkV00/3s5mA==", "dev": true, "license": "MIT", "funding": { @@ -5182,15 +5172,15 @@ } }, "node_modules/@vitest/utils": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.1.tgz", - "integrity": "sha512-cNxAlaB3sHoCdL6pj6yyUXv9Gry1NHNg0kFTXdvSIZXLHsqKH7chiWOkwJ5s5+d/oMwcoG9T0bKU38JZWKusrQ==", + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.8.tgz", + "integrity": "sha512-uOJamYALNhfJ6iolExyQM40yIQwDqYnkKtQ5VCiSe17E33H0aQ/u+1GlRuz4LZBk6Mm3sg90G9hEbmEt37C1Zg==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.1", + "@vitest/pretty-format": "4.1.8", "convert-source-map": "^2.0.0", - "tinyrainbow": "^3.0.3" + "tinyrainbow": "^3.1.0" }, "funding": { "url": "https://opencollective.com/vitest" @@ -5223,9 +5213,9 @@ "license": "MIT" }, "node_modules/@wdio/config/node_modules/brace-expansion": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", - "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz", + "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", "dev": true, "license": "MIT", "dependencies": { @@ -5591,9 +5581,9 @@ "license": "MIT" }, "node_modules/archiver-utils/node_modules/brace-expansion": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", - "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz", + "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", "dev": true, "license": "MIT", "dependencies": { @@ -6400,6 +6390,16 @@ "node": ">=6" } }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -7882,9 +7882,9 @@ "license": "MIT" }, "node_modules/eslint-plugin-import/node_modules/brace-expansion": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", - "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", "dev": true, "license": "MIT", "dependencies": { @@ -7979,9 +7979,9 @@ "license": "MIT" }, "node_modules/eslint-plugin-json-schema-validator/node_modules/brace-expansion": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", - "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz", + "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", "dev": true, "license": "MIT", "dependencies": { @@ -8045,9 +8045,9 @@ "license": "MIT" }, "node_modules/eslint-plugin-n/node_modules/brace-expansion": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", - "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz", + "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", "dev": true, "license": "MIT", "dependencies": { @@ -8206,9 +8206,9 @@ "license": "MIT" }, "node_modules/eslint-plugin-react/node_modules/brace-expansion": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", - "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", "dev": true, "license": "MIT", "dependencies": { @@ -8408,9 +8408,9 @@ "license": "MIT" }, "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", - "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", "dev": true, "license": "MIT", "dependencies": { @@ -9244,9 +9244,9 @@ "license": "MIT" }, "node_modules/globby/node_modules/brace-expansion": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", - "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", "dev": true, "license": "MIT", "dependencies": { @@ -10257,10 +10257,20 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz", + "integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -10928,9 +10938,9 @@ "license": "MIT" }, "node_modules/lru-cache": { - "version": "11.4.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.4.0.tgz", - "integrity": "sha512-W+R+kFL4HgVxONq2bhXPi3bGpzGe/yEhVOp233qw9wCRtgncJ15P3bC+e4zZMu4Cq7d+WAJjXGW0uUkifhcatA==", + "version": "11.5.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.1.tgz", + "integrity": "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==", "dev": true, "license": "BlueOak-1.0.0", "engines": { @@ -11491,9 +11501,9 @@ "license": "MIT" }, "node_modules/octagonal-wheels": { - "version": "0.1.45", - "resolved": "https://registry.npmjs.org/octagonal-wheels/-/octagonal-wheels-0.1.45.tgz", - "integrity": "sha512-gXoCrwoUIXhmu57YN4BxAtBe+JaYNJNaXaZuVjqjopwYKpH5p2mn1om6KjA22rgGPiIJFXkse2U28FFXoT3/0Q==", + "version": "0.1.46", + "resolved": "https://registry.npmjs.org/octagonal-wheels/-/octagonal-wheels-0.1.46.tgz", + "integrity": "sha512-19eB7b/WNNrZ4Xghu93f+NVJsbRiaZaIIzU1rn5shxb6SzwVBoOVkNPJdCAsONl6C1MwjaGDrPUS8CBXvPHjPg==", "license": "MIT", "dependencies": { "idb": "^8.0.3" @@ -12650,9 +12660,9 @@ "license": "MIT" }, "node_modules/readdir-glob/node_modules/brace-expansion": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", - "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz", + "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", "dev": true, "license": "MIT", "dependencies": { @@ -13075,9 +13085,9 @@ "license": "MIT" }, "node_modules/semver": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", - "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", + "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", "dev": true, "license": "ISC", "bin": { @@ -15249,9 +15259,9 @@ } }, "node_modules/undici": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.25.0.tgz", - "integrity": "sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.27.0.tgz", + "integrity": "sha512-+t2Z/GwkZQDtu00813aP66ygViGtPHKhhoFZpQKpKrE+9jIgES+Zw+mFNaDWOVRKiuJjuqKHzD3B1sfGg8+ZOQ==", "dev": true, "license": "MIT", "engines": { @@ -16030,20 +16040,19 @@ } }, "node_modules/vitest": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.1.tgz", - "integrity": "sha512-yF+o4POL41rpAzj5KVILUxm1GCjKnELvaqmU9TLLUbMfDzuN0UpUR9uaDs+mCtjPe+uYPksXDRLQGGPvj1cTmA==", + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.8.tgz", + "integrity": "sha512-flY6ScbCIt9HThs+C5HS7jvGOB560DJtk/Z15IQROTA6zEy49Nh8T/dofWTQL+n3vswqn87sbJNiuqw1SDp5Ig==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@vitest/expect": "4.1.1", - "@vitest/mocker": "4.1.1", - "@vitest/pretty-format": "4.1.1", - "@vitest/runner": "4.1.1", - "@vitest/snapshot": "4.1.1", - "@vitest/spy": "4.1.1", - "@vitest/utils": "4.1.1", + "@vitest/expect": "4.1.8", + "@vitest/mocker": "4.1.8", + "@vitest/pretty-format": "4.1.8", + "@vitest/runner": "4.1.8", + "@vitest/snapshot": "4.1.8", + "@vitest/spy": "4.1.8", + "@vitest/utils": "4.1.8", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", @@ -16054,7 +16063,7 @@ "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", - "tinyrainbow": "^3.0.3", + "tinyrainbow": "^3.1.0", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", "why-is-node-running": "^2.3.0" }, @@ -16071,10 +16080,12 @@ "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.1.1", - "@vitest/browser-preview": "4.1.1", - "@vitest/browser-webdriverio": "4.1.1", - "@vitest/ui": "4.1.1", + "@vitest/browser-playwright": "4.1.8", + "@vitest/browser-preview": "4.1.8", + "@vitest/browser-webdriverio": "4.1.8", + "@vitest/coverage-istanbul": "4.1.8", + "@vitest/coverage-v8": "4.1.8", + "@vitest/ui": "4.1.8", "happy-dom": "*", "jsdom": "*", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" @@ -16098,6 +16109,12 @@ "@vitest/browser-webdriverio": { "optional": true }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, "@vitest/ui": { "optional": true }, @@ -16206,9 +16223,9 @@ } }, "node_modules/webdriver/node_modules/undici": { - "version": "6.25.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.25.0.tgz", - "integrity": "sha512-ZgpWDC5gmNiuY9CnLVXEH8rl50xhRCuLNA97fAUnKi8RRuV4E6KG31pDTsLVUKnohJE0I3XDrTeEydAXRw47xg==", + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.26.0.tgz", + "integrity": "sha512-4yqz8a3n5HmGTlsbADNtr/dJlhkh/55Rq798G6ibiULcXbDtaLpTl1pvdqcbFfeoj3iSi52lePFM7h9H21cw/A==", "dev": true, "license": "MIT", "engines": { diff --git a/package.json b/package.json index 057971c..8b178cf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsidian-livesync", - "version": "0.25.68", + "version": "0.25.73", "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", @@ -19,13 +19,13 @@ "buildVite": "npx dotenv-cli -e .env -- vite build --mode production", "buildViteOriginal": "npx dotenv-cli -e .env -- vite build --mode original", "buildDev": "node esbuild.config.mjs dev", - "lint": "eslint src", + "lint": "eslint --cache --concurrency auto src", "svelte-check": "svelte-check --tsconfig ./tsconfig.json", "tsc-check": "tsc --noEmit", "pretty": "npm run prettyNoWrite -- --write --log-level error", "prettyCheck": "npm run prettyNoWrite -- --check", "prettyNoWrite": "prettier --config ./.prettierrc.mjs \"**/*.js\" \"**/*.ts\" \"**/*.json\" ", - "check": "npm run lint && npm run svelte-check", + "check": "npm run tsc-check && npm run lint && npm run svelte-check", "unittest": "deno test -A --no-check --coverage=cov_profile --v8-flags=--expose-gc --trace-leaks ./src/", "test": "vitest run", "test:unit": "vitest run --config vitest.config.unit.ts", @@ -80,9 +80,9 @@ "@types/transform-pouch": "^1.0.6", "@typescript-eslint/eslint-plugin": "8.56.1", "@typescript-eslint/parser": "8.56.1", - "@vitest/browser": "^4.1.1", - "@vitest/browser-playwright": "^4.1.1", - "@vitest/coverage-v8": "^4.1.1", + "@vitest/browser": "^4.1.8", + "@vitest/browser-playwright": "^4.1.8", + "@vitest/coverage-v8": "^4.1.8", "dotenv-cli": "^11.0.0", "esbuild": "0.25.0", "esbuild-plugin-inline-worker": "^0.1.1", @@ -119,7 +119,7 @@ "typescript": "5.9.3", "vite": "^7.3.1", "vite-plugin-istanbul": "^8.0.0", - "vitest": "^4.1.1", + "vitest": "^4.1.8", "webdriverio": "^9.27.0", "yaml": "^2.8.2" }, @@ -130,17 +130,18 @@ "@smithy/middleware-apply-body-checksum": "^4.3.9", "@smithy/protocol-http": "^5.3.9", "@smithy/querystring-builder": "^4.2.9", + "@smithy/util-retry": "^4.4.5", "@trystero-p2p/nostr": "^0.24.0", "chokidar": "^4.0.0", "commander": "^14.0.3", - "obsidian": "^1.12.3", "diff-match-patch": "^1.0.5", "fflate": "^0.8.2", "idb": "^8.0.3", "markdown-it": "^14.1.1", "micromatch": "^4.0.0", "minimatch": "^10.2.2", - "octagonal-wheels": "^0.1.45", + "obsidian": "^1.12.3", + "octagonal-wheels": "^0.1.46", "pouchdb-adapter-leveldb": "^9.0.0", "qrcode-generator": "^1.4.4", "werift": "^0.23.0", diff --git a/src/apps/cli/managers/CLIStorageEventManagerAdapter.ts b/src/apps/cli/managers/CLIStorageEventManagerAdapter.ts index c2f11e1..f6bc9e0 100644 --- a/src/apps/cli/managers/CLIStorageEventManagerAdapter.ts +++ b/src/apps/cli/managers/CLIStorageEventManagerAdapter.ts @@ -105,7 +105,7 @@ class CLIWatchAdapter implements IStorageEventWatchAdapter { private _toNodeFile(filePath: string, stats: Stats | undefined): NodeFile { return { - path: path.relative(this.basePath, filePath) as FilePath, + path: path.relative(this.basePath, filePath).replace(/\\/g, "/") as FilePath, stat: { ctime: stats?.ctimeMs ?? Date.now(), mtime: stats?.mtimeMs ?? Date.now(), @@ -117,7 +117,7 @@ class CLIWatchAdapter implements IStorageEventWatchAdapter { private _toNodeFolder(dirPath: string): NodeFolder { return { - path: path.relative(this.basePath, dirPath) as FilePath, + path: path.relative(this.basePath, dirPath).replace(/\\/g, "/") as FilePath, isFolder: true, }; } diff --git a/src/apps/cli/vite.config.ts b/src/apps/cli/vite.config.ts index 11104cd..01992e1 100644 --- a/src/apps/cli/vite.config.ts +++ b/src/apps/cli/vite.config.ts @@ -2,6 +2,7 @@ import { defineConfig } from "vite"; import { svelte } from "@sveltejs/vite-plugin-svelte"; import path from "node:path"; import { readFileSync } from "node:fs"; +const resolve = (...args: string[]) => path.resolve(...args).replace(/\\/g, "/"); const packageJson = JSON.parse(readFileSync("../../../package.json", "utf-8")); const manifestJson = JSON.parse(readFileSync("../../../manifest.json", "utf-8")); // https://vite.dev/config/ @@ -63,17 +64,14 @@ export default defineConfig({ resolve: { alias: { "@lib/worker/bgWorker.ts": "../../lib/src/worker/bgWorker.mock.ts", - "@lib/pouchdb/pouchdb-browser.ts": path.resolve(__dirname, "lib/pouchdb-node.ts"), + "@lib/pouchdb/pouchdb-browser.ts": resolve(__dirname, "lib/pouchdb-node.ts"), // The CLI runs on Node.js; force AWS XML builder to its CJS Node entry // so Vite does not resolve the browser DOMParser-based XML parser. - "@aws-sdk/xml-builder": path.resolve( - __dirname, - "../../../node_modules/@aws-sdk/xml-builder/dist-cjs/index.js" - ), + "@aws-sdk/xml-builder": resolve(__dirname, "../../../node_modules/@aws-sdk/xml-builder/dist-cjs/index.js"), // Force fflate to the Node CJS entry; browser entry expects Web Worker globals. - fflate: path.resolve(__dirname, "../../../node_modules/fflate/lib/node.cjs"), - "@": path.resolve(__dirname, "../../"), - "@lib": path.resolve(__dirname, "../../lib/src"), + fflate: resolve(__dirname, "../../../node_modules/fflate/lib/node.cjs"), + "@": resolve(__dirname, "../../"), + "@lib": resolve(__dirname, "../../lib/src"), "../../src/worker/bgWorker.ts": "../../src/worker/bgWorker.mock.ts", }, }, @@ -85,7 +83,7 @@ export default defineConfig({ minify: false, rollupOptions: { input: { - index: path.resolve(__dirname, "entrypoint.ts"), + index: resolve(__dirname, "entrypoint.ts"), }, external: (id) => { if (defaultExternal.includes(id)) return true; @@ -101,7 +99,7 @@ export default defineConfig({ }, }, lib: { - entry: path.resolve(__dirname, "entrypoint.ts"), + entry: resolve(__dirname, "entrypoint.ts"), formats: ["cjs"], fileName: "index", }, diff --git a/src/common/utils.ts b/src/common/utils.ts index b008f2c..829d608 100644 --- a/src/common/utils.ts +++ b/src/common/utils.ts @@ -128,7 +128,7 @@ export const _requestToCouchDBFetch = async ( username: string, password: string, path?: string, - body?: string | any, + body?: any, method?: string ) => { const utf8str = String.fromCharCode.apply(null, [...writeString(`${username}:${password}`)]); @@ -146,7 +146,7 @@ export const _requestToCouchDBFetch = async ( contentType: "application/json", body: JSON.stringify(body), }; - return await fetch(uri, requestParam); + return await _fetch(uri, requestParam); }; export const _requestToCouchDB = async ( @@ -214,6 +214,7 @@ import { BASE_IS_NEW, EVEN, TARGET_IS_NEW } from "@lib/common/models/shared.cons export { BASE_IS_NEW, EVEN, TARGET_IS_NEW }; // Why 2000? : ZIP FILE Does not have enough resolution. import { compareMTime } from "@lib/common/utils.ts"; +import { _fetch } from "@/lib/src/common/coreEnvFunctions.ts"; export { compareMTime }; function getKey(file: AnyEntry | string | UXFileInfoStub) { const key = typeof file == "string" ? file : stripAllPrefixes(file.path); diff --git a/src/features/ConfigSync/CmdConfigSync.ts b/src/features/ConfigSync/CmdConfigSync.ts index c98142c..585ef5c 100644 --- a/src/features/ConfigSync/CmdConfigSync.ts +++ b/src/features/ConfigSync/CmdConfigSync.ts @@ -68,9 +68,10 @@ import { ConflictResolveModal } from "../../modules/features/InteractiveConflict import { Semaphore } from "octagonal-wheels/concurrency/semaphore"; import { EVENT_REQUEST_OPEN_PLUGIN_SYNC_DIALOG, eventHub } from "../../common/events.ts"; import { PluginDialogModal } from "./PluginDialogModal.ts"; -import { $msg } from "src/lib/src/common/i18n.ts"; +import { $msg } from "@/lib/src/common/i18n.ts"; import type { InjectableServiceHub } from "../../lib/src/services/InjectableServices.ts"; import type { LiveSyncCore } from "../../main.ts"; +import { LiveSyncError } from "@lib/common/LSError.ts"; const d = "\u200b"; const d2 = "\n"; @@ -564,7 +565,7 @@ export class ConfigSync extends LiveSyncCommands { ...data, documentPath: this.getPath(wx), files: xFiles, - } as PluginDataExDisplay; + } satisfies PluginDataExDisplay; } return false; } @@ -1069,10 +1070,10 @@ export class ConfigSync extends LiveSyncCommands { } const baseDir = this.configDir; try { - if (!data.documentPath) throw "InternalError: Document path not exist"; + if (!data.documentPath) throw new LiveSyncError("InternalError: Document path not exist"); const dx = await this.localDatabase.getDBEntry(data.documentPath); if (dx == false) { - throw "Not found on database"; + throw new LiveSyncError("Not found on database"); } const loadedData = deserialize(getDocDataAsArray(dx.data), {}) as PluginDataEx; for (const f of loadedData.files) { @@ -1317,7 +1318,7 @@ export class ConfigSync extends LiveSyncCommands { } const docXDoc = await this.localDatabase.getDBEntryFromMeta(old, false, false); if (docXDoc == false) { - throw "Could not load the document"; + throw new LiveSyncError("Could not load the document"); } const dataSrc = getDocData(docXDoc.data); const dataStart = dataSrc.indexOf(DUMMY_END); diff --git a/src/features/HiddenFileSync/CmdHiddenFileSync.ts b/src/features/HiddenFileSync/CmdHiddenFileSync.ts index ead080a..3c086d6 100644 --- a/src/features/HiddenFileSync/CmdHiddenFileSync.ts +++ b/src/features/HiddenFileSync/CmdHiddenFileSync.ts @@ -50,6 +50,7 @@ import { hiddenFilesEventCount, hiddenFilesProcessingCount } from "../../lib/src import { EVENT_SETTING_SAVED, eventHub } from "../../common/events.ts"; import { Semaphore } from "octagonal-wheels/concurrency/semaphore"; import type { LiveSyncCore } from "../../main.ts"; +import { tryGetFilePath } from "@lib/common/utils.doc.ts"; type SyncDirection = "push" | "pull" | "safe" | "pullForce" | "pushForce"; declare global { @@ -317,7 +318,7 @@ export class HiddenFileSync extends LiveSyncCommands { this._fileInfoLastProcessed.set(file, key); } - async updateLastProcessedAsActualFile(file: FilePath, stat?: UXStat | null | undefined) { + async updateLastProcessedAsActualFile(file: FilePath, stat?: UXStat | null) { if (!stat) stat = await this.core.storageAccess.statHidden(file); this._fileInfoLastProcessed.set(file, this.statToKey(stat)); } @@ -411,10 +412,7 @@ export class HiddenFileSync extends LiveSyncCommands { } } - async updateLastProcessedAsActualDatabase( - file: FilePath, - doc?: MetaEntry | LoadedEntry | null | undefined | false - ) { + async updateLastProcessedAsActualDatabase(file: FilePath, doc?: MetaEntry | LoadedEntry | null | false) { const dbPath = addPrefix(file, ICHeader); if (!doc) doc = await this.localDatabase.getDBEntryMeta(dbPath); if (!doc) return; @@ -1050,7 +1048,7 @@ Offline Changed files: ${processFiles.length}`; } notifyProgress(); } catch (ex) { - this._log(`Failed to process storage change file:${file}`, logLevel); + this._log(`Failed to process storage change file:${tryGetFilePath(file)}`, logLevel); this._log(ex, LOG_LEVEL_VERBOSE); } }); @@ -1162,7 +1160,7 @@ Offline Changed files: ${files.length}`; await this.trackDatabaseFileModification(path, "[Scanning]", true, onlyNew, file); notifyProgress(); } catch (ex) { - this._log(`Failed to process database changes:${file}`); + this._log(`Failed to process database changes:${tryGetFilePath(file)}`); this._log(ex, LOG_LEVEL_VERBOSE); } return; @@ -1500,7 +1498,7 @@ Offline Changed files: ${files.length}`; } async storeInternalFileToDatabase(file: InternalFileInfo | UXFileInfo, forceWrite = false) { - const storeFilePath = stripAllPrefixes(file.path as FilePath); + const storeFilePath = stripAllPrefixes(file.path); const storageFilePath = file.path; if (await this.services.vault.isIgnoredByIgnoreFile(storageFilePath)) { return undefined; diff --git a/src/features/LocalDatabaseMainte/CmdLocalDatabaseMainte.ts b/src/features/LocalDatabaseMainte/CmdLocalDatabaseMainte.ts index fb8651c..54eb7aa 100644 --- a/src/features/LocalDatabaseMainte/CmdLocalDatabaseMainte.ts +++ b/src/features/LocalDatabaseMainte/CmdLocalDatabaseMainte.ts @@ -16,9 +16,8 @@ import { serialized } from "octagonal-wheels/concurrency/lock_v2"; import { arrayToChunkedArray } from "octagonal-wheels/collection"; import { EVENT_ANALYSE_DB_USAGE, EVENT_REQUEST_PERFORM_GC_V3, eventHub } from "@/common/events"; import type { LiveSyncCouchDBReplicator } from "@/lib/src/replication/couchdb/LiveSyncReplicator"; -import { delay, parseHeaderValues } from "@/lib/src/common/utils"; -import { generateCredentialObject } from "@/lib/src/replication/httplib"; -import { _requestToCouchDB } from "@/common/utils"; +import { delay } from "@/lib/src/common/utils"; +// import { _requestToCouchDB } from "@/common/utils"; const DB_KEY_SEQ = "gc-seq"; const DB_KEY_CHUNK_SET = "chunk-set"; const DB_KEY_DOC_USAGE_MAP = "doc-usage-map"; @@ -391,7 +390,7 @@ Note: **Make sure to synchronise all devices before deletion.** .map((revInfo) => db.get(doc._id, { rev: revInfo.rev })) ).then((docs) => docs.filter((doc) => doc)); for (const oldDoc of oldDocs) { - await processDoc(oldDoc as EntryDoc, false); + await processDoc(oldDoc, false); } } } catch (ex) { @@ -533,7 +532,7 @@ Success: ${successCount}, Errored: ${errored}`; const docMap = new Map>(); const info = await db.info(); // Total number of revisions to process (approximate) - const maxSeq = new Number(info.update_seq); + const maxSeq = Number.parseInt(`${info.update_seq ?? 0}`, 10); let processed = 0; let read = 0; let errored = 0; @@ -560,7 +559,7 @@ Success: ${successCount}, Errored: ${errored}`; }); docMap.set(id, set); } else if (doc.type === EntryTypes.CHUNK) { - const id = doc._id as DocumentID; + const id = doc._id; if (chunkMap.has(id)) { return; } @@ -759,68 +758,68 @@ Success: ${successCount}, Errored: ${errored}`; } } - /** - * Compact the database by temporarily setting the revision limit to 1. - * @returns - */ - async compactDatabaseWithRevLimit() { - // Temporarily set revs_limit to 1, perform compaction, and restore the original revs_limit. - // Very dangerous operation, so now suppressed. - return false; - const replicator = this.core.replicator as LiveSyncCouchDBReplicator; - const remote = await replicator.connectRemoteCouchDBWithSetting(this.settings, false, false, true); - if (!remote) { - this._notice("Failed to connect to remote for compaction."); - return; - } - if (typeof remote == "string") { - this._notice(`Failed to connect to remote for compaction. ${remote}`); - return; - } - const customHeaders = parseHeaderValues(this.settings.couchDB_CustomHeaders); - const credential = generateCredentialObject(this.settings); - const request = async (path: string, method: string = "GET", body: any = undefined) => { - const req = await _requestToCouchDB( - this.settings.couchDB_URI.replace(/\/+$/, "") + - (this.settings.couchDB_DBNAME ? `/${this.settings.couchDB_DBNAME}` : ""), - credential, - window.origin, - path, - body, - method, - customHeaders - ); - return req; - }; - let revsLimit = ""; - const req = await request(`_revs_limit`, "GET"); - if (req.status == 200) { - revsLimit = req.text.trim(); - this._info(`Remote database _revs_limit: ${revsLimit}`); - } else { - this._notice(`Failed to get remote database _revs_limit. Status: ${req.status}`); - return; - } - const req2 = await request(`_revs_limit`, "PUT", 1); - if (req2.status == 200) { - this._info(`Set remote database _revs_limit to 1 for compaction.`); - } - try { - await this.compactDatabase(); - } finally { - // Restore revs_limit - if (revsLimit) { - const req3 = await request(`_revs_limit`, "PUT", parseInt(revsLimit)); - if (req3.status == 200) { - this._info(`Restored remote database _revs_limit to ${revsLimit}.`); - } else { - this._notice( - `Failed to restore remote database _revs_limit. Status: ${req3.status} / ${req3.text}` - ); - } - } - } - } + // /** + // * Compact the database by temporarily setting the revision limit to 1. + // * @returns + // */ + // async compactDatabaseWithRevLimit() { + // // Temporarily set revs_limit to 1, perform compaction, and restore the original revs_limit. + // // Very dangerous operation, so now suppressed. + // return Promise.resolve(false); + // const replicator = this.core.replicator as LiveSyncCouchDBReplicator; + // const remote = await replicator.connectRemoteCouchDBWithSetting(this.settings, false, false, true); + // if (!remote) { + // this._notice("Failed to connect to remote for compaction."); + // return; + // } + // if (typeof remote == "string") { + // this._notice(`Failed to connect to remote for compaction. ${remote}`); + // return; + // } + // const customHeaders = parseHeaderValues(this.settings.couchDB_CustomHeaders); + // const credential = generateCredentialObject(this.settings); + // const request = async (path: string, method: string = "GET", body: any = undefined) => { + // const req = await _requestToCouchDB( + // this.settings.couchDB_URI.replace(/\/+$/, "") + + // (this.settings.couchDB_DBNAME ? `/${this.settings.couchDB_DBNAME}` : ""), + // credential, + // window.origin, + // path, + // body, + // method, + // customHeaders + // ); + // return req; + // }; + // let revsLimit = ""; + // const req = await request(`_revs_limit`, "GET"); + // if (req.status == 200) { + // revsLimit = req.text.trim(); + // this._info(`Remote database _revs_limit: ${revsLimit}`); + // } else { + // this._notice(`Failed to get remote database _revs_limit. Status: ${req.status}`); + // return; + // } + // const req2 = await request(`_revs_limit`, "PUT", 1); + // if (req2.status == 200) { + // this._info(`Set remote database _revs_limit to 1 for compaction.`); + // } + // try { + // await this.compactDatabase(); + // } finally { + // // Restore revs_limit + // if (revsLimit) { + // const req3 = await request(`_revs_limit`, "PUT", parseInt(revsLimit)); + // if (req3.status == 200) { + // this._info(`Restored remote database _revs_limit to ${revsLimit}.`); + // } else { + // this._notice( + // `Failed to restore remote database _revs_limit. Status: ${req3.status} / ${req3.text}` + // ); + // } + // } + // } + // } async gcv3() { if (!this.isAvailable()) return; const replicator = this.core.replicator as LiveSyncCouchDBReplicator; @@ -929,7 +928,7 @@ This may indicate that some devices have not completed synchronisation, which co usedChunks.add(chunkId); } } else if (doc.type === EntryTypes.CHUNK) { - allChunks.set(doc._id as DocumentID, doc._rev); + allChunks.set(doc._id, doc._rev); } } this._notice( diff --git a/src/features/P2PSync/P2PReplicator/P2PServerStatusCard.svelte b/src/features/P2PSync/P2PReplicator/P2PServerStatusCard.svelte index 4d53651..0d8aad6 100644 --- a/src/features/P2PSync/P2PReplicator/P2PServerStatusCard.svelte +++ b/src/features/P2PSync/P2PReplicator/P2PServerStatusCard.svelte @@ -24,6 +24,7 @@ let serverInfo = $state(undefined); let replicatorStatus = $state(undefined); let roomSuffix = $state(extractP2PRoomSuffix(core?.services.setting.currentSettings()?.P2P_roomID ?? "")); + let useDiagRTC = $state(core?.services.setting.currentSettings()?.P2P_useDiagRTC ?? false); async function requestServerStatus() { await Promise.resolve(liveSyncReplicator.requestStatus()); @@ -48,6 +49,18 @@ } } + async function toggleDiagRTC() { + if (!core) { + return; + } + const next = !useDiagRTC; + await core.services.setting.updateSettings((settings) => { + settings.P2P_useDiagRTC = next; + return settings; + }, true); + useDiagRTC = next; + } + onMount(() => { const unsubscribe = eventHub.onEvent(EVENT_SERVER_STATUS, (status) => { serverInfo = status; @@ -58,6 +71,7 @@ }); const unsubscribeSettings = eventHub.onEvent(EVENT_SETTING_SAVED, (settings) => { roomSuffix = extractP2PRoomSuffix(settings?.P2P_roomID ?? ""); + useDiagRTC = settings?.P2P_useDiagRTC ?? false; }); fireAndForget(async () => { @@ -131,6 +145,48 @@ {/if} + + {#if core} + + + ðµïž Diag + + + {useDiagRTC ? 'On' : 'Off'} + + + {/if} + + {#if serverInfo} + + Stats + + + Incoming: + {serverInfo.diag.totalNewConnections} + + + Connected: + {serverInfo.diag.totalSuccessfulConnections} + + + Failed: + {serverInfo.diag.totalFailedConnections} + + + Closed: + {serverInfo.diag.totalClosedConnections} + + + + {/if} \ No newline at end of file diff --git a/src/lib b/src/lib index b9aaf3c..76d9167 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit b9aaf3c03a773cf54887da5826e52774239106b9 +Subproject commit 76d91674c235c1ccf991a14802c737e82e144ef1 diff --git a/src/main.ts b/src/main.ts index 6c5c3f3..3de86ce 100644 --- a/src/main.ts +++ b/src/main.ts @@ -12,8 +12,6 @@ import { ModuleObsidianEvents } from "./modules/essentialObsidian/ModuleObsidian import { ModuleObsidianSettingDialogue } from "./modules/features/ModuleObsidianSettingTab.ts"; import { ModuleObsidianDocumentHistory } from "./modules/features/ModuleObsidianDocumentHistory.ts"; import { ModuleObsidianGlobalHistory } from "./modules/features/ModuleGlobalHistory.ts"; -import { ModuleIntegratedTest } from "./modules/extras/ModuleIntegratedTest.ts"; -import { ModuleReplicateTest } from "./modules/extras/ModuleReplicateTest.ts"; import { LocalDatabaseMaintenance } from "./features/LocalDatabaseMainte/CmdLocalDatabaseMainte.ts"; import type { InjectableServiceHub } from "@lib/services/implements/injectable/InjectableServiceHub.ts"; import { ObsidianServiceHub } from "./modules/services/ObsidianServiceHub.ts"; @@ -156,8 +154,6 @@ export default class ObsidianLiveSyncPlugin extends Plugin { new ModuleInteractiveConflictResolver(this, core), new ModuleObsidianGlobalHistory(this, core), new ModuleDev(this, core), - new ModuleReplicateTest(this, core), - new ModuleIntegratedTest(this, core), new SetupManager(core), // this should be moved to core? new ModuleMigration(core), ]; diff --git a/src/modules/coreObsidian/UILib/dialogs.ts b/src/modules/coreObsidian/UILib/dialogs.ts index ce77bd9..d84bcab 100644 --- a/src/modules/coreObsidian/UILib/dialogs.ts +++ b/src/modules/coreObsidian/UILib/dialogs.ts @@ -1,9 +1,11 @@ import { ButtonComponent } from "@/deps.ts"; import { App, FuzzySuggestModal, MarkdownRenderer, Modal, Plugin, Setting } from "../../../deps.ts"; import { EVENT_PLUGIN_UNLOADED, eventHub } from "../../../common/events.ts"; +import { compatGlobal, type CompatIntervalHandle } from "@lib/common/coreEnvFunctions.ts"; class AutoClosableModal extends Modal { _closeByUnload() { + // eslint-disable-next-line @typescript-eslint/unbound-method eventHub.off(EVENT_PLUGIN_UNLOADED, this._closeByUnload); this.close(); } @@ -11,9 +13,11 @@ class AutoClosableModal extends Modal { constructor(app: App) { super(app); this._closeByUnload = this._closeByUnload.bind(this); + // eslint-disable-next-line @typescript-eslint/unbound-method eventHub.once(EVENT_PLUGIN_UNLOADED, this._closeByUnload); } override onClose() { + // eslint-disable-next-line @typescript-eslint/unbound-method eventHub.off(EVENT_PLUGIN_UNLOADED, this._closeByUnload); } } @@ -121,7 +125,7 @@ export class PopoverSelectString extends FuzzySuggestModal { this.callback = undefined; } override onClose(): void { - setTimeout(() => { + compatGlobal.setTimeout(() => { if (this.callback) { this.callback(""); this.callback = undefined; @@ -139,7 +143,7 @@ export class MessageBox extends AutoClosableModal { isManuallyClosed = false; defaultAction: string | undefined; timeout: number | undefined; - timer: ReturnType | undefined = undefined; + timer: CompatIntervalHandle | undefined = undefined; defaultButtonComponent: ButtonComponent | undefined; wideButton: boolean; @@ -165,12 +169,12 @@ export class MessageBox extends AutoClosableModal { this.timeout = timeout; this.wideButton = wideButton; if (this.timeout) { - this.timer = setInterval(() => { + this.timer = compatGlobal.setInterval(() => { if (this.timeout === undefined) return; this.timeout--; if (this.timeout < 0) { if (this.timer) { - clearInterval(this.timer); + compatGlobal.clearInterval(this.timer); this.defaultButtonComponent?.setButtonText(`${defaultAction}`); this.timer = undefined; } @@ -213,7 +217,7 @@ export class MessageBox extends AutoClosableModal { if (this.timer) { labelWrapper.empty(); labelWrapper.style.display = "none"; - clearInterval(this.timer); + compatGlobal.clearInterval(this.timer); this.timer = undefined; this.defaultButtonComponent?.setButtonText(`${this.defaultAction}`); } @@ -224,7 +228,7 @@ export class MessageBox extends AutoClosableModal { this.isManuallyClosed = true; this.result = button; if (this.timer) { - clearInterval(this.timer); + compatGlobal.clearInterval(this.timer); this.timer = undefined; } this.close(); @@ -247,7 +251,7 @@ export class MessageBox extends AutoClosableModal { const { contentEl } = this; contentEl.empty(); if (this.timer) { - clearInterval(this.timer); + compatGlobal.clearInterval(this.timer); this.timer = undefined; } if (this.isManuallyClosed) { diff --git a/src/modules/essentialObsidian/APILib/ObsHttpHandler.ts b/src/modules/essentialObsidian/APILib/ObsHttpHandler.ts index 5504918..ef5614c 100644 --- a/src/modules/essentialObsidian/APILib/ObsHttpHandler.ts +++ b/src/modules/essentialObsidian/APILib/ObsHttpHandler.ts @@ -27,7 +27,6 @@ export class ObsHttpHandler extends FetchHttpHandler { this.requestTimeoutInMs = options === undefined ? undefined : options.requestTimeout; this.reverseProxyNoSignUrl = reverseProxyNoSignUrl; } - // eslint-disable-next-line require-await override async handle( request: HttpRequest, { abortSignal }: HttpHandlerOptions = {} diff --git a/src/modules/essentialObsidian/ModuleObsidianEvents.ts b/src/modules/essentialObsidian/ModuleObsidianEvents.ts index 33f0485..638b960 100644 --- a/src/modules/essentialObsidian/ModuleObsidianEvents.ts +++ b/src/modules/essentialObsidian/ModuleObsidianEvents.ts @@ -13,6 +13,7 @@ import { hiddenFilesProcessingCount, } from "../../lib/src/mock_and_interop/stores.ts"; import type { LiveSyncCore } from "../../main.ts"; +import { compatGlobal } from "@lib/common/coreEnvFunctions.ts"; export class ModuleObsidianEvents extends AbstractObsidianModule { _everyOnloadStart(): Promise { @@ -79,11 +80,19 @@ export class ModuleObsidianEvents extends AbstractObsidianModule { this.watchWindowVisibility = this.watchWindowVisibility.bind(this); this.watchWorkspaceOpen = this.watchWorkspaceOpen.bind(this); this.watchOnline = this.watchOnline.bind(this); + // Already bound + // eslint-disable-next-line @typescript-eslint/unbound-method this.plugin.registerEvent(this.app.workspace.on("file-open", this.watchWorkspaceOpen)); - this.plugin.registerDomEvent(document, "visibilitychange", this.watchWindowVisibility); + // Already bound + // eslint-disable-next-line @typescript-eslint/unbound-method + this.plugin.registerDomEvent(activeDocument, "visibilitychange", this.watchWindowVisibility); this.plugin.registerDomEvent(window, "focus", () => this.setHasFocus(true)); this.plugin.registerDomEvent(window, "blur", () => this.setHasFocus(false)); + // Already bound + // eslint-disable-next-line @typescript-eslint/unbound-method this.plugin.registerDomEvent(window, "online", this.watchOnline); + // Already bound + // eslint-disable-next-line @typescript-eslint/unbound-method this.plugin.registerDomEvent(window, "offline", this.watchOnline); } @@ -222,9 +231,9 @@ export class ModuleObsidianEvents extends AbstractObsidianModule { ); }); this.plugin.registerInterval( - setInterval(() => { + compatGlobal.setInterval(() => { __tick.value++; - }, 1000) as unknown as number + }, 1000) ); let stableCheck = 3; diff --git a/src/modules/extras/ModuleDev.ts b/src/modules/extras/ModuleDev.ts index a69776a..0395c9e 100644 --- a/src/modules/extras/ModuleDev.ts +++ b/src/modules/extras/ModuleDev.ts @@ -8,7 +8,6 @@ import { TestPaneView, VIEW_TYPE_TEST } from "./devUtil/TestPaneView.ts"; import { writable } from "svelte/store"; import type { FilePathWithPrefix } from "../../lib/src/common/types.ts"; import type { LiveSyncCore } from "../../main.ts"; - export class ModuleDev extends AbstractObsidianModule { _everyOnloadStart(): Promise { __onMissingTranslation(() => {}); @@ -98,6 +97,7 @@ export class ModuleDev extends AbstractObsidianModule { }); return Promise.resolve(true); } + async _everyOnLayoutReady(): Promise { if (!this.settings.enableDebugTools) return Promise.resolve(true); // if (await this.core.storageAccess.isExistsIncludeHidden("_SHOWDIALOGAUTO.md")) { @@ -111,7 +111,7 @@ export class ModuleDev extends AbstractObsidianModule { const filename = "test-create-conflict.md"; const content = `# Test create conflict\n\n`; const w = await this.core.databaseFileAccess.store({ - name: filename as FilePathWithPrefix, + name: filename, path: filename as FilePathWithPrefix, body: new Blob([content], { type: "text/markdown" }), stat: { diff --git a/src/modules/extras/ModuleIntegratedTest.ts b/src/modules/extras/ModuleIntegratedTest.ts deleted file mode 100644 index df5cc3a..0000000 --- a/src/modules/extras/ModuleIntegratedTest.ts +++ /dev/null @@ -1,446 +0,0 @@ -import { delay } from "octagonal-wheels/promises"; -import { LOG_LEVEL_NOTICE, REMOTE_MINIO, type FilePathWithPrefix } from "src/lib/src/common/types"; -import { shareRunningResult } from "octagonal-wheels/concurrency/lock"; -import { AbstractObsidianModule } from "../AbstractObsidianModule"; - -export class ModuleIntegratedTest extends AbstractObsidianModule { - async waitFor(proc: () => Promise, timeout = 10000): Promise { - await delay(100); - const start = Date.now(); - while (!(await proc())) { - if (timeout > 0) { - if (Date.now() - start > timeout) { - this._log(`Timeout`); - return false; - } - } - await delay(500); - } - return true; - } - waitWithReplicating(proc: () => Promise, timeout = 10000): Promise { - return this.waitFor(async () => { - await this.tryReplicate(); - return await proc(); - }, timeout); - } - async storageContentIsEqual(file: string, content: string): Promise { - try { - const fileContent = await this.readStorageContent(file as FilePathWithPrefix); - if (fileContent === content) { - return true; - } else { - // this._log(`Content is not same \n Expected:${content}\n Actual:${fileContent}`, LOG_LEVEL_VERBOSE); - return false; - } - } catch (e) { - this._log(`Error: ${e}`); - return false; - } - } - async assert(proc: () => Promise): Promise { - if (!(await proc())) { - this._log(`Assertion failed`); - return false; - } - return true; - } - async __orDie(key: string, proc: () => Promise): Promise | never { - if (!(await this._test(key, proc))) { - throw new Error(`${key}`); - } - return true; - } - tryReplicate() { - if (!this.settings.liveSync) { - return shareRunningResult("replicate-test", async () => { - await this.services.replication.replicate(); - }); - } - } - async readStorageContent(file: FilePathWithPrefix): Promise { - if (!(await this.core.storageAccess.isExistsIncludeHidden(file))) { - return undefined; - } - return await this.core.storageAccess.readHiddenFileText(file); - } - async __proceed(no: number, title: string): Promise { - const stepFile = "_STEP.md" as FilePathWithPrefix; - const stepAckFile = "_STEP_ACK.md" as FilePathWithPrefix; - const stepContent = `Step ${no}`; - await this.services.conflict.resolveByNewest(stepFile); - await this.core.storageAccess.writeFileAuto(stepFile, stepContent); - await this.__orDie(`Wait for acknowledge ${no}`, async () => { - if ( - !(await this.waitWithReplicating(async () => { - return await this.storageContentIsEqual(stepAckFile, stepContent); - }, 20000)) - ) - return false; - return true; - }); - return true; - } - async __join(no: number, title: string): Promise { - const stepFile = "_STEP.md" as FilePathWithPrefix; - const stepAckFile = "_STEP_ACK.md" as FilePathWithPrefix; - // const otherStepFile = `_STEP_${isLeader ? "R" : "L"}.md` as FilePathWithPrefix; - const stepContent = `Step ${no}`; - - await this.__orDie(`Wait for step ${no} (${title})`, async () => { - if ( - !(await this.waitWithReplicating(async () => { - return await this.storageContentIsEqual(stepFile, stepContent); - }, 20000)) - ) - return false; - return true; - }); - await this.services.conflict.resolveByNewest(stepAckFile); - await this.core.storageAccess.writeFileAuto(stepAckFile, stepContent); - await this.tryReplicate(); - return true; - } - - async performStep({ - step, - title, - isGameChanger, - proc, - check, - }: { - step: number; - title: string; - isGameChanger: boolean; - proc: () => Promise; - check: () => Promise; - }): Promise { - if (isGameChanger) { - await this.__proceed(step, title); - try { - await proc(); - } catch (e) { - this._log(`Error: ${e}`); - return false; - } - return await this.__orDie(`Step ${step} - ${title}`, async () => await this.waitWithReplicating(check)); - } else { - return await this.__join(step, title); - } - } - // // see scenario.md - // async testLeader(testMain: (testFileName: FilePathWithPrefix) => Promise): Promise { - - // } - // async testReceiver(testMain: (testFileName: FilePathWithPrefix) => Promise): Promise { - - // } - async nonLiveTestRunner( - isLeader: boolean, - testMain: (testFileName: FilePathWithPrefix, isLeader: boolean) => Promise - ): Promise { - const storage = this.core.storageAccess; - // const database = this.core.databaseFileAccess; - // const _orDie = this._orDie.bind(this); - const testCommandFile = "IT.md" as FilePathWithPrefix; - const textCommandResponseFile = "ITx.md" as FilePathWithPrefix; - let testFileName: FilePathWithPrefix; - this.addTestResult( - "-------Starting ... ", - true, - `Test as ${isLeader ? "Leader" : "Receiver"} command file ${testCommandFile}` - ); - if (isLeader) { - await this.__proceed(0, "start"); - } - await this.tryReplicate(); - - await this.performStep({ - step: 0, - title: "Make sure that command File Not Exists", - isGameChanger: isLeader, - proc: async () => await storage.removeHidden(testCommandFile), - check: async () => !(await storage.isExistsIncludeHidden(testCommandFile)), - }); - await this.performStep({ - step: 1, - title: "Make sure that command File Not Exists On Receiver", - isGameChanger: !isLeader, - proc: async () => await storage.removeHidden(textCommandResponseFile), - check: async () => !(await storage.isExistsIncludeHidden(textCommandResponseFile)), - }); - - await this.performStep({ - step: 2, - title: "Decide the test file name", - isGameChanger: isLeader, - proc: async () => { - testFileName = (Date.now() + "-" + Math.ceil(Math.random() * 1000) + ".md") as FilePathWithPrefix; - const testCommandFile = "IT.md" as FilePathWithPrefix; - await storage.writeFileAuto(testCommandFile, testFileName); - }, - check: () => Promise.resolve(true), - }); - await this.performStep({ - step: 3, - title: "Wait for the command file to be arrived", - isGameChanger: !isLeader, - proc: async () => {}, - check: async () => await storage.isExistsIncludeHidden(testCommandFile), - }); - - await this.performStep({ - step: 4, - title: "Send the response file", - isGameChanger: !isLeader, - proc: async () => { - await storage.writeHiddenFileAuto(textCommandResponseFile, "!"); - }, - check: () => Promise.resolve(true), - }); - await this.performStep({ - step: 5, - title: "Wait for the response file to be arrived", - isGameChanger: isLeader, - proc: async () => {}, - check: async () => await storage.isExistsIncludeHidden(textCommandResponseFile), - }); - - await this.performStep({ - step: 6, - title: "Proceed to begin the test", - isGameChanger: isLeader, - proc: async () => {}, - check: () => Promise.resolve(true), - }); - await this.performStep({ - step: 6, - title: "Begin the test", - isGameChanger: !false, - proc: async () => {}, - check: () => { - return Promise.resolve(true); - }, - }); - // await this.step(0, isLeader, true); - try { - this.addTestResult("** Main------", true, ``); - if (isLeader) { - return await testMain(testFileName!, true); - } else { - const testFileName = await this.readStorageContent(testCommandFile); - this.addTestResult("testFileName", true, `Request client to use :${testFileName!}`); - return await testMain(testFileName! as FilePathWithPrefix, false); - } - } finally { - this.addTestResult("Teardown", true, `Deleting ${testFileName!}`); - await storage.removeHidden(testFileName!); - } - - return true; - // Make sure the - } - - async testBasic(filename: FilePathWithPrefix, isLeader: boolean): Promise { - const storage = this.core.storageAccess; - const database = this.core.databaseFileAccess; - - await this.addTestResult( - `---**Starting Basic Test**---`, - true, - `Test as ${isLeader ? "Leader" : "Receiver"} command file ${filename}` - ); - // if (isLeader) { - // await this._proceed(0); - // } - // await this.tryReplicate(); - - await this.performStep({ - step: 0, - title: "Make sure that file is not exist", - isGameChanger: !isLeader, - proc: async () => {}, - check: async () => !(await storage.isExists(filename)), - }); - - await this.performStep({ - step: 1, - title: "Write a file", - isGameChanger: isLeader, - proc: async () => await storage.writeFileAuto(filename, "Hello World"), - check: async () => await storage.isExists(filename), - }); - await this.performStep({ - step: 2, - title: "Make sure the file is arrived", - isGameChanger: !isLeader, - proc: async () => {}, - check: async () => await storage.isExists(filename), - }); - await this.performStep({ - step: 3, - title: "Update to Hello World 2", - isGameChanger: isLeader, - proc: async () => await storage.writeFileAuto(filename, "Hello World 2"), - check: async () => await this.storageContentIsEqual(filename, "Hello World 2"), - }); - await this.performStep({ - step: 4, - title: "Make sure the modified file is arrived", - isGameChanger: !isLeader, - proc: async () => {}, - check: async () => await this.storageContentIsEqual(filename, "Hello World 2"), - }); - await this.performStep({ - step: 5, - title: "Update to Hello World 3", - isGameChanger: !isLeader, - proc: async () => await storage.writeFileAuto(filename, "Hello World 3"), - check: async () => await this.storageContentIsEqual(filename, "Hello World 3"), - }); - await this.performStep({ - step: 6, - title: "Make sure the modified file is arrived", - isGameChanger: isLeader, - proc: async () => {}, - check: async () => await this.storageContentIsEqual(filename, "Hello World 3"), - }); - - const multiLineContent = `Line1:A -Line2:B -Line3:C -Line4:D`; - - await this.performStep({ - step: 7, - title: "Update to Multiline", - isGameChanger: isLeader, - proc: async () => await storage.writeFileAuto(filename, multiLineContent), - check: async () => await this.storageContentIsEqual(filename, multiLineContent), - }); - - await this.performStep({ - step: 8, - title: "Make sure the modified file is arrived", - isGameChanger: !isLeader, - proc: async () => {}, - check: async () => await this.storageContentIsEqual(filename, multiLineContent), - }); - - // While LiveSync, possibly cannot cause the conflict. - if (!this.settings.liveSync) { - // Step 9 Make Conflict But Resolvable - const multiLineContentL = `Line1:A -Line2:B -Line3:C! -Line4:D`; - const multiLineContentC = `Line1:A -Line2:bbbbb -Line3:C -Line4:D`; - - await this.performStep({ - step: 9, - title: "Progress to be conflicted", - isGameChanger: isLeader, - proc: async () => {}, - check: () => Promise.resolve(true), - }); - - await storage.writeFileAuto(filename, isLeader ? multiLineContentL : multiLineContentC); - - await this.performStep({ - step: 10, - title: "Update As Conflicted", - isGameChanger: !isLeader, - proc: async () => {}, - check: () => Promise.resolve(true), - }); - - await this.performStep({ - step: 10, - title: "Make sure Automatically resolved", - isGameChanger: isLeader, - proc: async () => {}, - check: async () => (await database.getConflictedRevs(filename)).length === 0, - }); - await this.performStep({ - step: 11, - title: "Make sure Automatically resolved", - isGameChanger: !isLeader, - proc: async () => {}, - check: async () => (await database.getConflictedRevs(filename)).length === 0, - }); - - const sensiblyMergedContent = `Line1:A -Line2:bbbbb -Line3:C! -Line4:D`; - - await this.performStep({ - step: 12, - title: "Make sure Sensibly Merged on Leader", - isGameChanger: isLeader, - proc: async () => {}, - check: async () => await this.storageContentIsEqual(filename, sensiblyMergedContent), - }); - await this.performStep({ - step: 13, - title: "Make sure Sensibly Merged on Receiver", - isGameChanger: !isLeader, - proc: async () => {}, - check: async () => await this.storageContentIsEqual(filename, sensiblyMergedContent), - }); - } - await this.performStep({ - step: 14, - title: "Delete File", - isGameChanger: isLeader, - proc: async () => { - await storage.removeHidden(filename); - }, - check: async () => !(await storage.isExists(filename)), - }); - - await this.performStep({ - step: 15, - title: "Make sure File is deleted", - isGameChanger: !isLeader, - proc: async () => {}, - check: async () => !(await storage.isExists(filename)), - }); - this._log(`The Basic Test has been completed`, LOG_LEVEL_NOTICE); - return true; - } - - async testBasicEvent(isLeader: boolean) { - this.settings.liveSync = false; - await this.saveSettings(); - await this._test("basic", async () => await this.nonLiveTestRunner(isLeader, (t, l) => this.testBasic(t, l))); - } - async testBasicLive(isLeader: boolean) { - this.settings.liveSync = true; - await this.saveSettings(); - await this._test("basic", async () => await this.nonLiveTestRunner(isLeader, (t, l) => this.testBasic(t, l))); - } - - async _everyModuleTestMultiDevice(): Promise { - if (!this.settings.enableDebugTools) return Promise.resolve(true); - const isLeader = this.core.services.vault.vaultName().indexOf("recv") === -1; - this.addTestResult("-------", true, `Test as ${isLeader ? "Leader" : "Receiver"}`); - try { - this._log(`Starting Test`); - await this.testBasicEvent(isLeader); - if (this.settings.remoteType == REMOTE_MINIO) await this.testBasicLive(isLeader); - } catch (e) { - this._log(e); - this._log(`Error: ${e}`); - return Promise.resolve(false); - } - - return Promise.resolve(true); - } - override onBindFunction(core: typeof this.core, services: typeof core.services): void { - services.test.testMultiDevice.addHandler(this._everyModuleTestMultiDevice.bind(this)); - } -} diff --git a/src/modules/extras/ModuleReplicateTest.ts b/src/modules/extras/ModuleReplicateTest.ts deleted file mode 100644 index 394042a..0000000 --- a/src/modules/extras/ModuleReplicateTest.ts +++ /dev/null @@ -1,590 +0,0 @@ -// I intend to discontinue maintenance of this class. It seems preferable to test it externally. -import { delay } from "octagonal-wheels/promises"; -import { AbstractObsidianModule } from "../AbstractObsidianModule.ts"; -import { LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger"; -import { eventHub } from "../../common/events"; -import { getWebCrypto } from "../../lib/src/mods.ts"; -import { uint8ArrayToHexString } from "octagonal-wheels/binary/hex"; -import { parseYaml, requestUrl, stringifyYaml } from "@/deps.ts"; -import type { FilePath } from "../../lib/src/common/types.ts"; -import { scheduleTask } from "octagonal-wheels/concurrency/task"; -import { getFileRegExp } from "../../lib/src/common/utils.ts"; -import type { LiveSyncCore } from "../../main.ts"; - -declare global { - interface LSEvents { - "debug-sync-status": string[]; - } -} - -export class ModuleReplicateTest extends AbstractObsidianModule { - testRootPath = "_test/"; - testInfoPath = "_testinfo/"; - - get isLeader() { - return ( - this.services.vault.getVaultName().indexOf("dev") >= 0 && - this.services.vault.vaultName().indexOf("recv") < 0 - ); - } - - get nameByKind() { - if (!this.isLeader) { - return "RECV"; - } else if (this.isLeader) { - return "LEADER"; - } - } - get pairName() { - if (this.isLeader) { - return "RECV"; - } else if (!this.isLeader) { - return "LEADER"; - } - } - - watchIsSynchronised = false; - - statusBarSyncStatus?: HTMLElement; - async readFileContent(file: string) { - try { - return await this.core.storageAccess.readHiddenFileText(file); - } catch { - return ""; - } - } - - async dumpList() { - if (this.settings.syncInternalFiles) { - this._log("Write file list (Include Hidden)"); - await this.__dumpFileListIncludeHidden("files.md"); - } else { - this._log("Write file list"); - await this.__dumpFileList("files.md"); - } - } - async _everyBeforeReplicate(showMessage: boolean): Promise { - if (!this.settings.enableDebugTools) return Promise.resolve(true); - await this.dumpList(); - return true; - } - private _everyOnloadAfterLoadSettings(): Promise { - if (!this.settings.enableDebugTools) return Promise.resolve(true); - this.addCommand({ - id: "dump-file-structure-normal", - name: `Dump Structure (Normal)`, - callback: () => { - void this.__dumpFileList("files.md").finally(() => { - void this.refreshSyncStatus(); - }); - }, - }); - this.addCommand({ - id: "dump-file-structure-ih", - name: "Dump Structure (Include Hidden)", - callback: () => { - const d = "files.md"; - void this.__dumpFileListIncludeHidden(d); - }, - }); - this.addCommand({ - id: "dump-file-structure-auto", - name: "Dump Structure", - callback: () => { - void this.dumpList(); - }, - }); - this.addCommand({ - id: "dump-file-test", - name: `Perform Test (Dev) ${this.isLeader ? "(Leader)" : "(Recv)"}`, - callback: () => { - void this.performTestManually(); - }, - }); - this.addCommand({ - id: "watch-sync-result", - name: `Watch sync result is matched between devices`, - callback: () => { - this.watchIsSynchronised = !this.watchIsSynchronised; - void this.refreshSyncStatus(); - }, - }); - this.app.vault.on("modify", async (file) => { - if (file.path.startsWith(this.testInfoPath)) { - await this.refreshSyncStatus(); - } else { - scheduleTask("dumpStatus", 125, async () => { - await this.dumpList(); - return true; - }); - } - }); - this.statusBarSyncStatus = this.plugin.addStatusBarItem(); - return Promise.resolve(true); - } - async getSyncStatusAsText() { - const fileMine = this.testInfoPath + this.nameByKind + "/" + "files.md"; - const filePair = this.testInfoPath + this.pairName + "/" + "files.md"; - const mine = parseYaml(await this.readFileContent(fileMine)); - const pair = parseYaml(await this.readFileContent(filePair)); - const result = [] as string[]; - if (mine.length != pair.length) { - result.push(`File count is different: ${mine.length} vs ${pair.length}`); - } - const filesAll = new Set([...mine.map((e: any) => e.path), ...pair.map((e: any) => e.path)]); - for (const file of filesAll) { - const mineFile = mine.find((e: any) => e.path == file); - const pairFile = pair.find((e: any) => e.path == file); - if (!mineFile || !pairFile) { - result.push(`File not found: ${file}`); - } else { - if (mineFile.size != pairFile.size) { - result.push(`Size is different: ${file} ${mineFile.size} vs ${pairFile.size}`); - } - if (mineFile.hash != pairFile.hash) { - result.push(`Hash is different: ${file} ${mineFile.hash} vs ${pairFile.hash}`); - } - } - } - eventHub.emitEvent("debug-sync-status", result); - return result.join("\n"); - } - - async refreshSyncStatus() { - if (this.watchIsSynchronised) { - // Normal Files - const syncStatus = await this.getSyncStatusAsText(); - if (syncStatus) { - this.statusBarSyncStatus!.setText(`Sync Status: Having Error`); - this._log(`Sync Status: Having Error\n${syncStatus}`, LOG_LEVEL_INFO); - } else { - this.statusBarSyncStatus!.setText(`Sync Status: Synchronised`); - } - } else { - this.statusBarSyncStatus!.setText(""); - } - } - - async __dumpFileList(outFile?: string) { - if (!this.core || !this.core.storageAccess) { - this._log("No storage access", LOG_LEVEL_INFO); - return; - } - const files = await this.core.storageAccess.getFiles(); - const out = [] as any[]; - const webcrypto = await getWebCrypto(); - for (const file of files) { - if (!(await this.services.vault.isTargetFile(file.path))) { - continue; - } - if (file.path.startsWith(this.testInfoPath)) continue; - const stat = await this.core.storageAccess.stat(file.path); - if (stat) { - const hashSrc = await this.core.storageAccess.readHiddenFileBinary(file.path); - const hash = await webcrypto.subtle.digest("SHA-1", hashSrc); - const hashStr = uint8ArrayToHexString(new Uint8Array(hash)); - const item = { - path: file.path, - name: file.name, - size: stat.size, - mtime: stat.mtime, - hash: hashStr, - }; - // const fileLine = `-${file.path}:${stat.size}:${stat.mtime}:${hashStr}`; - out.push(item); - } - } - out.sort((a, b) => a.path.localeCompare(b.path)); - if (outFile) { - outFile = this.testInfoPath + this.nameByKind + "/" + outFile; - await this.core.storageAccess.ensureDir(outFile); - await this.core.storageAccess.writeHiddenFileAuto(outFile, stringifyYaml(out)); - } else { - // console.dir(out); - } - this._log(`Dumped ${out.length} files`, LOG_LEVEL_INFO); - } - - async __dumpFileListIncludeHidden(outFile?: string) { - const ignorePatterns = getFileRegExp(this.core.settings, "syncInternalFilesIgnorePatterns"); - const targetPatterns = getFileRegExp(this.core.settings, "syncInternalFilesTargetPatterns"); - const out = [] as any[]; - const files = await this.core.storageAccess.getFilesIncludeHidden("", targetPatterns, ignorePatterns); - // console.dir(files); - const webcrypto = await getWebCrypto(); - for (const file of files) { - // if (!await this.core.$$isTargetFile(file)) { - // continue; - // } - if (file.startsWith(this.testInfoPath)) continue; - const stat = await this.core.storageAccess.statHidden(file); - if (stat) { - const hashSrc = await this.core.storageAccess.readHiddenFileBinary(file); - const hash = await webcrypto.subtle.digest("SHA-1", hashSrc); - const hashStr = uint8ArrayToHexString(new Uint8Array(hash)); - const item = { - path: file, - name: file.split("/").pop(), - size: stat.size, - mtime: stat.mtime, - hash: hashStr, - }; - // const fileLine = `-${file.path}:${stat.size}:${stat.mtime}:${hashStr}`; - out.push(item); - } - } - out.sort((a, b) => a.path.localeCompare(b.path)); - if (outFile) { - outFile = this.testInfoPath + this.nameByKind + "/" + outFile; - await this.core.storageAccess.ensureDir(outFile); - await this.core.storageAccess.writeHiddenFileAuto(outFile, stringifyYaml(out)); - } else { - // console.dir(out); - } - this._log(`Dumped ${out.length} files`, LOG_LEVEL_NOTICE); - } - - async collectTestFiles() { - const remoteTopDir = "https://raw.githubusercontent.com/vrtmrz/obsidian-livesync/refs/heads/main/"; - const files = [ - "README.md", - "docs/adding_translations.md", - "docs/design_docs_of_journalsync.md", - "docs/design_docs_of_keep_newborn_chunks.md", - "docs/design_docs_of_prefixed_hidden_file_sync.md", - "docs/design_docs_of_sharing_tweak_value.md", - "docs/quick_setup_cn.md", - "docs/quick_setup_ja.md", - "docs/quick_setup.md", - "docs/settings_ja.md", - "docs/settings.md", - "docs/setup_cloudant_ja.md", - "docs/setup_cloudant.md", - "docs/setup_flyio.md", - "docs/setup_own_server_cn.md", - "docs/setup_own_server_ja.md", - "docs/setup_own_server.md", - "docs/tech_info_ja.md", - "docs/tech_info.md", - "docs/terms.md", - "docs/troubleshooting.md", - "images/1.png", - "images/2.png", - "images/corrupted_data.png", - "images/hatch.png", - "images/lock_pattern1.png", - "images/lock_pattern2.png", - "images/quick_setup_1.png", - "images/quick_setup_2.png", - "images/quick_setup_3.png", - "images/quick_setup_3b.png", - "images/quick_setup_4.png", - "images/quick_setup_5.png", - "images/quick_setup_6.png", - "images/quick_setup_7.png", - "images/quick_setup_8.png", - "images/quick_setup_9_1.png", - "images/quick_setup_9_2.png", - "images/quick_setup_10.png", - "images/remote_db_setting.png", - "images/write_logs_into_the_file.png", - ]; - for (const file of files) { - const remote = remoteTopDir + file; - const local = this.testRootPath + file; - try { - const f = (await requestUrl(remote)).arrayBuffer; - await this.core.storageAccess.ensureDir(local); - await this.core.storageAccess.writeHiddenFileAuto(local, f); - } catch (ex) { - this._log(`Could not fetch ${remote}`, LOG_LEVEL_VERBOSE); - this._log(ex, LOG_LEVEL_VERBOSE); - } - } - - await this.dumpList(); - } - - async waitFor(proc: () => Promise, timeout = 10000): Promise { - await delay(100); - const start = Date.now(); - while (!(await proc())) { - if (timeout > 0) { - if (Date.now() - start > timeout) { - this._log(`Timeout`); - return false; - } - } - await delay(500); - } - return true; - } - - async testConflictedManually1() { - await this.services.replication.replicate(); - - const commonFile = `Resolve! -*****, the amazing chocolatier!!`; - - if (this.isLeader) { - await this.core.storageAccess.writeHiddenFileAuto(this.testRootPath + "wonka.md", commonFile); - } - - await this.services.replication.replicate(); - await this.services.replication.replicate(); - if ( - (await this.core.confirm.askYesNoDialog("Ready to begin the test conflict Manually 1?", { - timeout: 30, - defaultOption: "Yes", - })) == "no" - ) { - return; - } - - const fileA = `Resolve to KEEP THIS -Willy Wonka, Willy Wonka, the amazing chocolatier!!`; - - const fileB = `Resolve to DISCARD THIS -Charlie Bucket, Charlie Bucket, the amazing chocolatier!!`; - - if (this.isLeader) { - await this.core.storageAccess.writeHiddenFileAuto(this.testRootPath + "wonka.md", fileA); - } else { - await this.core.storageAccess.writeHiddenFileAuto(this.testRootPath + "wonka.md", fileB); - } - - if ( - (await this.core.confirm.askYesNoDialog("Ready to check the result of Manually 1?", { - timeout: 30, - defaultOption: "Yes", - })) == "no" - ) { - return; - } - await this.services.replication.replicate(); - await this.services.replication.replicate(); - - if ( - !(await this.waitFor(async () => { - await this.services.replication.replicate(); - return ( - (await this.__assertStorageContent( - (this.testRootPath + "wonka.md") as FilePath, - fileA, - false, - true - )) == true - ); - }, 30000)) - ) { - return await this.__assertStorageContent((this.testRootPath + "wonka.md") as FilePath, fileA, false, true); - } - return true; - // We have to check the result - } - - async testConflictedManually2() { - await this.services.replication.replicate(); - - const commonFile = `Resolve To concatenate -ABCDEFG`; - - if (this.isLeader) { - await this.core.storageAccess.writeHiddenFileAuto(this.testRootPath + "concat.md", commonFile); - } - - await this.services.replication.replicate(); - await this.services.replication.replicate(); - if ( - (await this.core.confirm.askYesNoDialog("Ready to begin the test conflict Manually 2?", { - timeout: 30, - defaultOption: "Yes", - })) == "no" - ) { - return; - } - - const fileA = `Resolve to Concatenate -ABCDEFGHIJKLMNOPQRSTYZ`; - - const fileB = `Resolve to Concatenate -AJKLMNOPQRSTUVWXYZ`; - - const concatenated = `Resolve to Concatenate -ABCDEFGHIJKLMNOPQRSTUVWXYZ`; - if (this.isLeader) { - await this.core.storageAccess.writeHiddenFileAuto(this.testRootPath + "concat.md", fileA); - } else { - await this.core.storageAccess.writeHiddenFileAuto(this.testRootPath + "concat.md", fileB); - } - if ( - (await this.core.confirm.askYesNoDialog("Ready to test conflict Manually 2?", { - timeout: 30, - defaultOption: "Yes", - })) == "no" - ) { - return; - } - await this.services.replication.replicate(); - await this.services.replication.replicate(); - - if ( - !(await this.waitFor(async () => { - await this.services.replication.replicate(); - return ( - (await this.__assertStorageContent( - (this.testRootPath + "concat.md") as FilePath, - concatenated, - false, - true - )) == true - ); - }, 30000)) - ) { - return await this.__assertStorageContent( - (this.testRootPath + "concat.md") as FilePath, - concatenated, - false, - true - ); - } - return true; - } - - async testConflictAutomatic() { - if (this.isLeader) { - const baseDoc = `Tasks! -- [ ] Task 1 -- [ ] Task 2 -- [ ] Task 3 -- [ ] Task 4 -`; - await this.core.storageAccess.writeHiddenFileAuto(this.testRootPath + "task.md", baseDoc); - } - await delay(100); - await this.services.replication.replicate(); - await this.services.replication.replicate(); - - if ( - (await this.core.confirm.askYesNoDialog("Ready to test conflict?", { - timeout: 30, - defaultOption: "Yes", - })) == "no" - ) { - return; - } - const mod1Doc = `Tasks! -- [ ] Task 1 -- [v] Task 2 -- [ ] Task 3 -- [ ] Task 4 -`; - - const mod2Doc = `Tasks! -- [ ] Task 1 -- [ ] Task 2 -- [v] Task 3 -- [ ] Task 4 -`; - if (this.isLeader) { - await this.core.storageAccess.writeHiddenFileAuto(this.testRootPath + "task.md", mod1Doc); - } else { - await this.core.storageAccess.writeHiddenFileAuto(this.testRootPath + "task.md", mod2Doc); - } - - await this.services.replication.replicate(); - await this.services.replication.replicate(); - await delay(1000); - if ( - (await this.core.confirm.askYesNoDialog("Ready to check result?", { timeout: 30, defaultOption: "Yes" })) == - "no" - ) { - return; - } - await this.services.replication.replicate(); - await this.services.replication.replicate(); - const mergedDoc = `Tasks! -- [ ] Task 1 -- [v] Task 2 -- [v] Task 3 -- [ ] Task 4 -`; - return this.__assertStorageContent((this.testRootPath + "task.md") as FilePath, mergedDoc, false, true); - } - - // No longer tested - async checkConflictResolution() { - this._log("Before testing conflicted files, resolve all once", LOG_LEVEL_NOTICE); - await this.services.conflict.resolveAllConflictedFilesByNewerOnes(); - await this.services.conflict.resolveAllConflictedFilesByNewerOnes(); - await this.services.replication.replicate(); - await delay(1000); - if (!(await this.testConflictAutomatic())) { - this._log("Conflict resolution (Auto) failed", LOG_LEVEL_NOTICE); - return false; - } - if (!(await this.testConflictedManually1())) { - this._log("Conflict resolution (Manual1) failed", LOG_LEVEL_NOTICE); - return false; - } - if (!(await this.testConflictedManually2())) { - this._log("Conflict resolution (Manual2) failed", LOG_LEVEL_NOTICE); - return false; - } - return true; - } - - async __assertStorageContent( - fileName: FilePath, - content: string, - inverted = false, - showResult = false - ): Promise { - try { - const fileContent = await this.core.storageAccess.readHiddenFileText(fileName); - let result = fileContent === content; - if (inverted) { - result = !result; - } - if (result) { - return true; - } else { - if (showResult) { - this._log(`Content is not same \n Expected:${content}\n Actual:${fileContent}`, LOG_LEVEL_VERBOSE); - } - return `Content is not same \n Expected:${content}\n Actual:${fileContent}`; - } - } catch (e) { - this._log(`Cannot assert storage content: ${e}`); - return false; - } - } - async performTestManually() { - if (!this.settings.enableDebugTools) return Promise.resolve(true); - await this.checkConflictResolution(); - // await this.collectTestFiles(); - } - - // testResults = writable<[boolean, string, string][]>([]); - // testResults: string[] = []; - - // $$addTestResult(name: string, key: string, result: boolean, summary?: string, message?: string): void { - // const logLine = `${name}: ${key} ${summary ?? ""}`; - // this.testResults.update((results) => { - // results.push([result, logLine, message ?? ""]); - // return results; - // }); - // } - private async _everyModuleTestMultiDevice(): Promise { - if (!this.settings.enableDebugTools) return Promise.resolve(true); - // this.core.$$addTestResult("DevModule", "Test", true); - // return Promise.resolve(true); - await this._test("Conflict resolution", async () => await this.checkConflictResolution()); - return this.testDone(); - } - override onBindFunction(core: LiveSyncCore, services: typeof core.services): void { - services.appLifecycle.onSettingLoaded.addHandler(this._everyOnloadAfterLoadSettings.bind(this)); - services.replication.onBeforeReplicate.addHandler(this._everyBeforeReplicate.bind(this)); - services.test.testMultiDevice.addHandler(this._everyModuleTestMultiDevice.bind(this)); - } -} diff --git a/src/modules/extras/devUtil/TestPane.svelte b/src/modules/extras/devUtil/TestPane.svelte index 6ea0eea..2d8f378 100644 --- a/src/modules/extras/devUtil/TestPane.svelte +++ b/src/modules/extras/devUtil/TestPane.svelte @@ -1,17 +1,15 @@ - - - - - - {msg("logPane.wrap", {}, lang)} - - - - {msg("logPane.autoScroll", {}, lang)} - - - - {msg("logPane.pause", {}, lang)} - + + + + + + {msg("logPane.wrap", {}, lang)} + + + + {msg("logPane.autoScroll", {}, lang)} + + + + {msg("logPane.pause", {}, lang)} + closeDialogue()}>Close - - - - {#each messages as line} - {line} - {/each} - + + + + {#each messages as line} + {line} + {/each} + diff --git a/src/modules/features/Log/LogPaneView.ts b/src/modules/features/Log/LogPaneView.ts index 5af45f6..c032705 100644 --- a/src/modules/features/Log/LogPaneView.ts +++ b/src/modules/features/Log/LogPaneView.ts @@ -2,7 +2,7 @@ import { WorkspaceLeaf } from "@/deps.ts"; import LogPaneComponent from "./LogPane.svelte"; import type ObsidianLiveSyncPlugin from "../../../main.ts"; import { SvelteItemView } from "../../../common/SvelteItemView.ts"; -import { $msg } from "src/lib/src/common/i18n.ts"; +import { $msg } from "@lib/common/i18n.ts"; import { mount } from "svelte"; export const VIEW_TYPE_LOG = "log-log"; //Log view diff --git a/src/modules/features/ModuleInteractiveConflictResolver.ts b/src/modules/features/ModuleInteractiveConflictResolver.ts index 0c04092..0ef7c12 100644 --- a/src/modules/features/ModuleInteractiveConflictResolver.ts +++ b/src/modules/features/ModuleInteractiveConflictResolver.ts @@ -88,7 +88,7 @@ export class ModuleInteractiveConflictResolver extends AbstractObsidianModule { return false; } } else { - this._log(`Merge: Something went wrong: ${filename}, (${toDelete})`, LOG_LEVEL_NOTICE); + this._log(`Merge: Something went wrong: ${filename}, (${toDelete as string})`, LOG_LEVEL_NOTICE); return false; } // In here, some merge has been processed. @@ -163,7 +163,7 @@ export class ModuleInteractiveConflictResolver extends AbstractObsidianModule { this._log(`There are no conflicting files`, LOG_LEVEL_VERBOSE); } } catch (e) { - this._log(`Error while scanning conflicted files: ${e}`, LOG_LEVEL_NOTICE); + this._log(`Error while scanning conflicted files...`, LOG_LEVEL_NOTICE); this._log(e, LOG_LEVEL_VERBOSE); return false; } diff --git a/src/modules/features/ModuleLog.ts b/src/modules/features/ModuleLog.ts index 1eb7945..062549a 100644 --- a/src/modules/features/ModuleLog.ts +++ b/src/modules/features/ModuleLog.ts @@ -29,7 +29,7 @@ import { addIcon, debounce, normalizePath, Notice, stringifyYaml, type Workspace import { LOG_LEVEL_NOTICE, setGlobalLogFunction } from "octagonal-wheels/common/logger"; import { LogPaneView, VIEW_TYPE_LOG } from "./Log/LogPaneView.ts"; import { serialized } from "octagonal-wheels/concurrency/lock"; -import { $msg } from "src/lib/src/common/i18n.ts"; +import { $msg } from "@lib/common/i18n.ts"; import { P2PLogCollector } from "@/lib/src/replication/trystero/P2PLogCollector.ts"; import type { LiveSyncCore } from "../../main.ts"; import { LiveSyncError } from "@lib/common/LSError.ts"; @@ -262,7 +262,7 @@ export class ModuleLog extends AbstractObsidianModule { this.statusDiv.remove(); // this.statusDiv.pa(); const container = mdv.view.containerEl; - container.insertBefore(this.statusDiv, container.lastChild); + container.appendChild(this.statusDiv); } } @@ -466,12 +466,14 @@ ${stringifyYaml(info)} this.observeForLogs(); - this.statusDiv = this.app.workspace.containerEl.createDiv({ cls: "livesync-status" }); - this.statusLine = this.statusDiv.createDiv({ cls: "livesync-status-statusline" }); - this.messageArea = this.statusDiv.createDiv({ cls: "livesync-status-messagearea" }); - this.logMessage = this.statusDiv.createDiv({ cls: "livesync-status-logmessage" }); - this.logHistory = this.statusDiv.createDiv({ cls: "livesync-status-loghistory" }); - this.statusDiv.style.display = this.settings?.showStatusOnEditor ? "" : "none"; + if (this.settings.showStatusOnEditor) { + this.statusDiv = this.app.workspace.containerEl.createDiv({ cls: "livesync-status" }); + this.statusLine = this.statusDiv.createDiv({ cls: "livesync-status-statusline" }); + this.messageArea = this.statusDiv.createDiv({ cls: "livesync-status-messagearea" }); + this.logMessage = this.statusDiv.createDiv({ cls: "livesync-status-logmessage" }); + this.logHistory = this.statusDiv.createDiv({ cls: "livesync-status-loghistory" }); + this.statusDiv.style.display = this.settings?.showStatusOnEditor ? "" : "none"; + } eventHub.onEvent(EVENT_LAYOUT_READY, () => this.adjustStatusDivPosition()); if (this.settings?.showStatusOnStatusbar) { this.statusBar = this.services.API.addStatusBarItem(); @@ -516,7 +518,12 @@ ${stringifyYaml(info)} let errorInfo = ""; if (message instanceof Error) { if (message instanceof LiveSyncError) { - errorInfo = `${message.cause?.name}:${message.cause?.message}\n[StackTrace]: ${message.stack}\n[CausedBy]: ${message.cause?.stack}`; + if (message.cause && message.cause instanceof Error) { + const causedError = message.cause; + errorInfo = `${causedError?.name}:${causedError?.message}\n[StackTrace]: ${message.stack}\n[CausedBy]: ${causedError?.stack}`; + } else { + errorInfo = `${message.name}:${message.message}\n[StackTrace]: ${message.stack}`; + } } else { const thisStack = new Error().stack; errorInfo = `${message.name}:${message.message}\n[StackTrace]: ${message.stack}\n[LogCallStack]: ${thisStack}`; diff --git a/src/modules/features/SettingDialogue/LiveSyncSetting.ts b/src/modules/features/SettingDialogue/LiveSyncSetting.ts index 0a7f90e..894916e 100644 --- a/src/modules/features/SettingDialogue/LiveSyncSetting.ts +++ b/src/modules/features/SettingDialogue/LiveSyncSetting.ts @@ -8,12 +8,7 @@ import { type ValueComponent, } from "@/deps.ts"; import { unique } from "octagonal-wheels/collection"; -import { - LEVEL_ADVANCED, - LEVEL_POWER_USER, - statusDisplay, - type ConfigurationItem, -} from "../../../lib/src/common/types.ts"; +import { LEVEL_ADVANCED, LEVEL_POWER_USER, statusDisplay, type ConfigurationItem } from "@lib/common/types.ts"; import { createStub, type ObsidianLiveSyncSettingTab } from "./ObsidianLiveSyncSettingTab.ts"; import { type AllSettingItemKey, @@ -23,7 +18,7 @@ import { type AllNumericItemKey, type AllBooleanItemKey, } from "./settingConstants.ts"; -import { $msg } from "src/lib/src/common/i18n.ts"; +import { $msg } from "@lib/common/i18n.ts"; import { findAttrFromParent, wrapMemo, type AutoWireOption, type OnUpdateResult } from "./SettingPane.ts"; export class LiveSyncSetting extends Setting { diff --git a/src/modules/features/SettingDialogue/ObsidianLiveSyncSettingTab.ts b/src/modules/features/SettingDialogue/ObsidianLiveSyncSettingTab.ts index 9469397..c06dcfe 100644 --- a/src/modules/features/SettingDialogue/ObsidianLiveSyncSettingTab.ts +++ b/src/modules/features/SettingDialogue/ObsidianLiveSyncSettingTab.ts @@ -62,6 +62,7 @@ import { paneAdvanced } from "./PaneAdvanced.ts"; import { panePowerUsers } from "./PanePowerUsers.ts"; import { panePatches } from "./PanePatches.ts"; import { paneMaintenance } from "./PaneMaintenance.ts"; +import { compatGlobal } from "@lib/common/coreEnvFunctions.ts"; // For creating a document const toc = new Set(); @@ -141,7 +142,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab { async saveLocalSetting(key: keyof typeof OnDialogSettingsDefault) { if (key == "configPassphrase") { - localStorage.setItem("ls-setting-passphrase", this.editingSettings?.[key] ?? ""); + compatGlobal.localStorage.setItem("ls-setting-passphrase", this.editingSettings?.[key] ?? ""); return await Promise.resolve(); } if (key == "deviceAndVaultName") { @@ -180,7 +181,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab { // if (runOnSaved) { const handlers = this.onSavedHandlers .filter((e) => appliedKeys.indexOf(e.key) !== -1) - .map((e) => e.handler(this.editingSettings[e.key as AllSettingItemKey])); + .map((e) => Promise.resolve(e.handler(this.editingSettings[e.key as AllSettingItemKey]))); await Promise.all(handlers); // } keys.forEach((e) => this.refreshSetting(e)); @@ -214,7 +215,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab { reloadAllLocalSettings() { const ret = { ...OnDialogSettingsDefault }; - ret.configPassphrase = localStorage.getItem("ls-setting-passphrase") || ""; + ret.configPassphrase = compatGlobal.localStorage.getItem("ls-setting-passphrase") || ""; ret.preset = ""; ret.deviceAndVaultName = this.services.setting.getDeviceAndVaultName(); return ret; @@ -349,7 +350,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab { createEl( el: HTMLElement, tag: T, - o?: string | DomElementInfo | undefined, + o?: string | DomElementInfo, callback?: (el: HTMLElementTagNameMap[T]) => void, func?: OnUpdateFunc ) { @@ -361,7 +362,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab { addEl( el: HTMLElement, tag: T, - o?: string | DomElementInfo | undefined, + o?: string | DomElementInfo, callback?: (el: HTMLElementTagNameMap[T]) => void, func?: OnUpdateFunc ) { @@ -647,7 +648,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab { this.editingSettings.passphrase = ""; } await this.saveAllDirtySettings(); - await this.applyAllSettings(); + await Promise.resolve(this.applyAllSettings()); if (result == OPTION_FETCH) { await this.core.storageAccess.writeFileAuto(FLAGMD_REDFLAG3_HR, ""); this.services.appLifecycle.scheduleRestart(); @@ -738,6 +739,8 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab { ); } setLevelClass(el, level); + // TODO: Refactor to use Obsidian's recommended way to create heading. + // eslint-disable-next-line obsidianmd/settings-tab/no-manual-html-headings el.createEl("h3", { text: title, cls: "sls-setting-pane-title" }); if (this.menuEl) { this.menuEl.createEl( diff --git a/src/modules/features/SettingDialogue/PaneHatch.ts b/src/modules/features/SettingDialogue/PaneHatch.ts index d9ca27c..18c878f 100644 --- a/src/modules/features/SettingDialogue/PaneHatch.ts +++ b/src/modules/features/SettingDialogue/PaneHatch.ts @@ -1,29 +1,16 @@ -import { stringifyYaml } from "../../../deps.ts"; import { - type ObsidianLiveSyncSettings, type FilePathWithPrefix, type DocumentID, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE, type LoadedEntry, - REMOTE_COUCHDB, - REMOTE_MINIO, type MetaEntry, type FilePath, - DEFAULT_SETTINGS, -} from "../../../lib/src/common/types.ts"; -import { - createBlob, - getFileRegExp, - isDocContentSame, - parseHeaderValues, - readAsBlob, -} from "../../../lib/src/common/utils.ts"; -import { Logger } from "../../../lib/src/common/logger.ts"; -import { isCloudantURI } from "../../../lib/src/pouchdb/utils_couchdb.ts"; -import { requestToCouchDBWithCredentials } from "../../../common/utils.ts"; -import { addPrefix, shouldBeIgnored, stripAllPrefixes } from "../../../lib/src/string_and_binary/path.ts"; -import { $msg } from "../../../lib/src/common/i18n.ts"; +} from "@lib/common/types.ts"; +import { createBlob, getFileRegExp, isDocContentSame, readAsBlob } from "@lib/common/utils.ts"; +import { Logger } from "@lib/common/logger.ts"; +import { addPrefix, shouldBeIgnored, stripAllPrefixes } from "@lib/string_and_binary/path.ts"; +import { $msg } from "@lib/common/i18n.ts"; import { Semaphore } from "octagonal-wheels/concurrency/semaphore"; import { LiveSyncSetting as Setting } from "./LiveSyncSetting.ts"; import { @@ -32,14 +19,13 @@ import { EVENT_REQUEST_RUN_DOCTOR, EVENT_REQUEST_RUN_FIX_INCOMPLETE, eventHub, -} from "../../../common/events.ts"; -import { ICHeader, ICXHeader, PSCHeader } from "../../../common/types.ts"; -import { HiddenFileSync } from "../../../features/HiddenFileSync/CmdHiddenFileSync.ts"; -import { EVENT_REQUEST_SHOW_HISTORY } from "../../../common/obsidianEvents.ts"; -import { generateCredentialObject } from "../../../lib/src/replication/httplib.ts"; +} from "@/common/events.ts"; +import { ICHeader, ICXHeader, PSCHeader } from "@/common/types.ts"; +import { HiddenFileSync } from "@/features/HiddenFileSync/CmdHiddenFileSync.ts"; +import { EVENT_REQUEST_SHOW_HISTORY } from "@/common/obsidianEvents.ts"; import type { ObsidianLiveSyncSettingTab } from "./ObsidianLiveSyncSettingTab.ts"; import type { PageFunctions } from "./SettingPane.ts"; -import { generateReport } from "@/common/reportTool.ts"; +import { isNotFoundError } from "@lib/common/utils.doc.ts"; export function paneHatch(this: ObsidianLiveSyncSettingTab, paneEl: HTMLElement, { addPanel }: PageFunctions): void { // const hatchWarn = this.createEl(paneEl, "div", { text: `To stop the boot up sequence for fixing problems on databases, you can put redflag.md on top of your vault (Rebooting obsidian is required).` }); // hatchWarn.addClass("op-warn-info"); @@ -160,14 +146,14 @@ export function paneHatch(this: ObsidianLiveSyncSettingTab, paneEl: HTMLElement, } if (!(await addOn.storeInternalFileToDatabase(file, true))) { Logger( - `Failed to store the file to the database (Hidden file): ${file}`, + `Failed to store the file to the database (Hidden file): ${file.path}`, LOG_LEVEL_NOTICE ); return; } } } else { - if (!(await this.core.fileHandler.storeFileToDB(file as FilePath, true))) { + if (!(await this.core.fileHandler.storeFileToDB(file, true))) { Logger( `Failed to store the file to the database: ${file}`, LOG_LEVEL_NOTICE @@ -406,8 +392,8 @@ export function paneHatch(this: ObsidianLiveSyncSettingTab, paneEl: HTMLElement, Logger(`Converting ${docName} Failed!`, LOG_LEVEL_NOTICE); Logger(ret, LOG_LEVEL_VERBOSE); } - } catch (ex: any) { - if (ex?.status == 404) { + } catch (ex: unknown) { + if (isNotFoundError(ex)) { // We can perform this safely if ((await this.core.localDatabase.putRaw(newDoc)).ok) { Logger(`${docName} has been converted`, LOG_LEVEL_NOTICE); diff --git a/src/modules/features/SettingDialogue/PanePatches.ts b/src/modules/features/SettingDialogue/PanePatches.ts index 0631bbd..839f7b2 100644 --- a/src/modules/features/SettingDialogue/PanePatches.ts +++ b/src/modules/features/SettingDialogue/PanePatches.ts @@ -150,7 +150,7 @@ export function panePatches(this: ObsidianLiveSyncSettingTab, paneEl: HTMLElemen xxhash64: "xxhash64 (Fastest)", "mixed-purejs": "PureJS fallback (Fast, W/O WebAssembly)", sha1: "Older fallback (Slow, W/O WebAssembly)", - } as Record, + } satisfies Record, }); this.addOnSaved("hashAlg", async () => { await this.core.localDatabase._prepareHashFunctions(); @@ -188,7 +188,7 @@ export function panePatches(this: ObsidianLiveSyncSettingTab, paneEl: HTMLElemen } this.requestUpdate(); }; - text.inputEl.before((dateEl = document.createElement("span"))); + text.inputEl.before((dateEl = activeDocument.createElement("span"))); text.inputEl.type = "datetime-local"; if (this.editingSettings.maxMTimeForReflectEvents > 0) { const date = new Date(this.editingSettings.maxMTimeForReflectEvents); diff --git a/src/modules/features/SettingDialogue/PaneRemoteConfig.ts b/src/modules/features/SettingDialogue/PaneRemoteConfig.ts index e609e39..8fb94e9 100644 --- a/src/modules/features/SettingDialogue/PaneRemoteConfig.ts +++ b/src/modules/features/SettingDialogue/PaneRemoteConfig.ts @@ -5,6 +5,7 @@ import { DEFAULT_SETTINGS, LOG_LEVEL_NOTICE, type ObsidianLiveSyncSettings, + LOG_LEVEL_VERBOSE, } from "../../../lib/src/common/types.ts"; import { Menu } from "@/deps.ts"; import { $msg } from "../../../lib/src/common/i18n.ts"; @@ -288,7 +289,8 @@ export function paneRemoteConfig( try { parsed = ConnectionStringParser.parse(trimmedURI); } catch (ex) { - this.services.API.addLog(`Failed to import remote configuration: ${ex}`, LOG_LEVEL_NOTICE); + this.services.API.addLog(`Failed to import remote configuration!`, LOG_LEVEL_NOTICE); + this.services.API.addLog(ex, LOG_LEVEL_VERBOSE); return; } @@ -343,9 +345,10 @@ export function paneRemoteConfig( parsed = ConnectionStringParser.parse(config.uri); } catch (ex) { this.services.API.addLog( - `Failed to parse remote configuration '${config.id}' for editing: ${ex}`, + `Failed to parse remote configuration '${config.id}' for editing!`, LOG_LEVEL_NOTICE ); + this.services.API.addLog(ex, LOG_LEVEL_VERBOSE); return; } const workSettings = createBaseRemoteSettings(); @@ -452,9 +455,10 @@ export function paneRemoteConfig( parsed = ConnectionStringParser.parse(config.uri); } catch (ex) { this.services.API.addLog( - `Failed to parse remote configuration '${config.id}': ${ex}`, + `Failed to parse remote configuration '${config.id}' for fetching settings!`, LOG_LEVEL_NOTICE ); + this.services.API.addLog(ex, LOG_LEVEL_VERBOSE); return; } const workSettings = createBaseRemoteSettings(); diff --git a/src/modules/features/SettingDialogue/settingUtils.ts b/src/modules/features/SettingDialogue/settingUtils.ts index b965298..847ebc0 100644 --- a/src/modules/features/SettingDialogue/settingUtils.ts +++ b/src/modules/features/SettingDialogue/settingUtils.ts @@ -75,7 +75,7 @@ export function getSummaryFromPartialSettings(setting: Partial { - const newSetting = await this.dialogManager.openWithExplicitCancel(UseSetupURI, setupURI); + const newSetting = await this.dialogManager.openWithExplicitCancel( + UseSetupURI, + setupURI + ); if (newSetting === "cancelled") { this._log("Setup URI dialog cancelled.", LOG_LEVEL_NOTICE); return false; @@ -140,7 +160,10 @@ export class SetupManager extends AbstractModule { ): Promise { const originalSetting = JSON.parse(JSON.stringify(currentSetting)) as ObsidianLiveSyncSettings; const baseSetting = JSON.parse(JSON.stringify(originalSetting)) as ObsidianLiveSyncSettings; - const couchConf = await this.dialogManager.openWithExplicitCancel(SetupRemoteCouchDB, originalSetting); + const couchConf = await this.dialogManager.openWithExplicitCancel< + SetupRemoteCouchDBResultType, + CouchDBConnection + >(SetupRemoteCouchDB, originalSetting); if (couchConf === "cancelled") { this._log("Manual configuration cancelled.", LOG_LEVEL_NOTICE); return await this.onOnboard(userMode); @@ -164,7 +187,10 @@ export class SetupManager extends AbstractModule { currentSetting: ObsidianLiveSyncSettings, activate = true ): Promise { - const bucketConf = await this.dialogManager.openWithExplicitCancel(SetupRemoteBucket, currentSetting); + const bucketConf = await this.dialogManager.openWithExplicitCancel< + SetupRemoteBucketResultType, + BucketSyncSetting + >(SetupRemoteBucket, currentSetting); if (bucketConf === "cancelled") { this._log("Manual configuration cancelled.", LOG_LEVEL_NOTICE); return await this.onOnboard(userMode); @@ -188,14 +214,33 @@ export class SetupManager extends AbstractModule { currentSetting: ObsidianLiveSyncSettings, activate = true ): Promise { - const p2pConf = await this.dialogManager.openWithExplicitCancel(SetupRemoteP2P, currentSetting); + const p2pConf = await this.dialogManager.openWithExplicitCancel( + SetupRemoteP2P, + currentSetting + ); if (p2pConf === "cancelled") { this._log("Manual configuration cancelled.", LOG_LEVEL_NOTICE); return await this.onOnboard(userMode); } const newSetting = { ...currentSetting, ...p2pConf } as ObsidianLiveSyncSettings; + // Apply remoteConfigurations + if (newSetting.P2P_ActiveRemoteConfigurationId) { + const id = newSetting.P2P_ActiveRemoteConfigurationId; + const merged = { + ...newSetting, + ...p2pConf, + } as ObsidianLiveSyncSettings; + const uri = ConnectionStringParser.serialize({ type: "p2p", settings: merged }); + newSetting.remoteConfigurations[id] = { + ...newSetting.remoteConfigurations[id], + uri, + isEncrypted: false, + }; + newSetting.P2P_ActiveRemoteConfigurationId = id; + } if (activate) { newSetting.remoteType = REMOTE_P2P; + newSetting.activeConfigurationId = newSetting.P2P_ActiveRemoteConfigurationId; } return await this.onConfirmApplySettingsFromWizard(newSetting, userMode, activate); } @@ -207,10 +252,13 @@ export class SetupManager extends AbstractModule { * @returns */ async onlyE2EEConfiguration(userMode: UserMode, currentSetting: ObsidianLiveSyncSettings): Promise { - const e2eeConf = await this.dialogManager.openWithExplicitCancel(SetupRemoteE2EE, currentSetting); + const e2eeConf = await this.dialogManager.openWithExplicitCancel( + SetupRemoteE2EE, + currentSetting + ); if (e2eeConf === "cancelled") { this._log("E2EE configuration cancelled.", LOG_LEVEL_NOTICE); - return await false; + return false; } const newSetting = { ...currentSetting, @@ -226,7 +274,10 @@ export class SetupManager extends AbstractModule { * @returns */ async onConfigureManually(originalSetting: ObsidianLiveSyncSettings, userMode: UserMode): Promise { - const e2eeConf = await this.dialogManager.openWithExplicitCancel(SetupRemoteE2EE, originalSetting); + const e2eeConf = await this.dialogManager.openWithExplicitCancel( + SetupRemoteE2EE, + originalSetting + ); if (e2eeConf === "cancelled") { this._log("Manual configuration cancelled.", LOG_LEVEL_NOTICE); return await this.onOnboard(userMode); @@ -245,7 +296,7 @@ export class SetupManager extends AbstractModule { * @returns */ async onSelectServer(currentSetting: ObsidianLiveSyncSettings, userMode: UserMode): Promise { - const method = await this.dialogManager.openWithExplicitCancel(SetupRemote); + const method = await this.dialogManager.openWithExplicitCancel(SetupRemote); if (method === "couchdb") { return await this.onCouchDBManualSetup(userMode, currentSetting, true); } else if (method === "bucket") { @@ -285,9 +336,9 @@ export class SetupManager extends AbstractModule { this._log("No changes in settings detected. Skipping applying settings from wizard.", LOG_LEVEL_NOTICE); return true; } - const patch = generatePatchObj(this.settings, newConf); - console.log(`Changes:`); - console.dir(patch); + // const patch = generatePatchObj(this.settings, newConf); + // console.log(`Changes:`); + // console.dir(patch); if (!activate) { extra(); await this.applySetting(newConf, UserMode.ExistingUser); @@ -304,7 +355,8 @@ export class SetupManager extends AbstractModule { this._log("Settings from wizard applied.", LOG_LEVEL_NOTICE); return true; } else { - const userModeResult = await this.dialogManager.openWithExplicitCancel(OutroAskUserMode); + const userModeResult = + await this.dialogManager.openWithExplicitCancel(OutroAskUserMode); if (userModeResult === "new-user") { userMode = UserMode.NewUser; } else if (userModeResult === "existing-user") { @@ -321,7 +373,9 @@ export class SetupManager extends AbstractModule { } } const component = userMode === UserMode.NewUser ? OutroNewUser : OutroExistingUser; - const confirm = await this.dialogManager.openWithExplicitCancel(component); + const confirm = await this.dialogManager.openWithExplicitCancel< + OutroNewUserResultType | OutroExistingUserResultType + >(component); if (confirm === "cancelled") { this._log("User cancelled applying settings from wizard..", LOG_LEVEL_NOTICE); return false; @@ -347,10 +401,10 @@ export class SetupManager extends AbstractModule { */ async onPromptQRCodeInstruction(): Promise { - const qrResult = await this.dialogManager.open(ScanQRCode); + const qrResult = await this.dialogManager.open(ScanQRCode); this._log("QR Code dialog closed.", LOG_LEVEL_VERBOSE); // Result is not used, but log it for debugging. - this._log(`QR Code result: ${qrResult}`, LOG_LEVEL_VERBOSE); + this._log(qrResult, LOG_LEVEL_VERBOSE); // QR Code instruction dialog never yields settings directly. return false; } diff --git a/src/modules/features/SetupWizard/dialogs/FetchEverything.svelte b/src/modules/features/SetupWizard/dialogs/FetchEverything.svelte index e704782..9bb06d7 100644 --- a/src/modules/features/SetupWizard/dialogs/FetchEverything.svelte +++ b/src/modules/features/SetupWizard/dialogs/FetchEverything.svelte @@ -1,47 +1,30 @@ diff --git a/src/modules/features/SetupWizard/dialogs/OutroNewUser.svelte b/src/modules/features/SetupWizard/dialogs/OutroNewUser.svelte index ff39c62..3818a29 100644 --- a/src/modules/features/SetupWizard/dialogs/OutroNewUser.svelte +++ b/src/modules/features/SetupWizard/dialogs/OutroNewUser.svelte @@ -5,14 +5,13 @@ import Question from "@/lib/src/UI/components/Question.svelte"; import Instruction from "@/lib/src/UI/components/Instruction.svelte"; import UserDecisions from "@/lib/src/UI/components/UserDecisions.svelte"; - const TYPE_APPLY = "apply"; - const TYPE_CANCELLED = "cancelled"; - type ResultType = typeof TYPE_APPLY | typeof TYPE_CANCELLED; + import { TYPE_APPLY, TYPE_CANCELLED, type OutroNewUserResultType } from "./setupDialogTypes"; + type Props = { - setResult: (result: ResultType) => void; + setResult: (result: OutroNewUserResultType) => void; }; const { setResult }: Props = $props(); - // let userType = $state(TYPE_CANCELLED); + // let userType = $state(TYPE_CANCELLED); diff --git a/src/modules/features/SetupWizard/dialogs/PanelCouchDBCheck.svelte b/src/modules/features/SetupWizard/dialogs/PanelCouchDBCheck.svelte index d2cef32..4ac0f85 100644 --- a/src/modules/features/SetupWizard/dialogs/PanelCouchDBCheck.svelte +++ b/src/modules/features/SetupWizard/dialogs/PanelCouchDBCheck.svelte @@ -2,9 +2,9 @@ /** * Panel to check and fix CouchDB configuration issues */ - import type { ObsidianLiveSyncSettings } from "../../../../lib/src/common/types"; - import Decision from "../../../../lib/src/UI/components/Decision.svelte"; - import UserDecisions from "../../../../lib/src/UI/components/UserDecisions.svelte"; + import type { ObsidianLiveSyncSettings } from "@lib/common/types"; + import Decision from "@lib/UI/components/Decision.svelte"; + import UserDecisions from "@lib/UI/components/UserDecisions.svelte"; import { checkConfig, type ConfigCheckResult, type ResultError, type ResultErrorMessage } from "./utilCheckCouchDB"; type Props = { trialRemoteSetting: ObsidianLiveSyncSettings; diff --git a/src/modules/features/SetupWizard/dialogs/RebuildEverything.svelte b/src/modules/features/SetupWizard/dialogs/RebuildEverything.svelte index 93aa834..1fe06e3 100644 --- a/src/modules/features/SetupWizard/dialogs/RebuildEverything.svelte +++ b/src/modules/features/SetupWizard/dialogs/RebuildEverything.svelte @@ -10,29 +10,17 @@ import InfoNote from "@/lib/src/UI/components/InfoNote.svelte"; import ExtraItems from "@/lib/src/UI/components/ExtraItems.svelte"; import Check from "@/lib/src/UI/components/Check.svelte"; - const TYPE_CANCEL = "cancelled"; + import { + TYPE_CANCEL, + TYPE_BACKUP_DONE, + TYPE_BACKUP_SKIPPED, + TYPE_UNABLE_TO_BACKUP, + type RebuildEverythingResult, + type ResultTypeBackup, + } from "./setupDialogTypes"; - const TYPE_BACKUP_DONE = "backup_done"; - const TYPE_BACKUP_SKIPPED = "backup_skipped"; - const TYPE_UNABLE_TO_BACKUP = "unable_to_backup"; - - type ResultTypeBackup = - | typeof TYPE_BACKUP_DONE - | typeof TYPE_BACKUP_SKIPPED - | typeof TYPE_UNABLE_TO_BACKUP - | typeof TYPE_CANCEL; - - type ResultTypeExtra = { - preventFetchingConfig: boolean; - }; - type ResultType = - | { - backup: ResultTypeBackup; - extra: ResultTypeExtra; - } - | typeof TYPE_CANCEL; type Props = { - setResult: (result: ResultType) => void; + setResult: (result: RebuildEverythingResult) => void; }; const { setResult }: Props = $props(); diff --git a/src/modules/features/SetupWizard/dialogs/ScanQRCode.svelte b/src/modules/features/SetupWizard/dialogs/ScanQRCode.svelte index 57c0621..4a24f46 100644 --- a/src/modules/features/SetupWizard/dialogs/ScanQRCode.svelte +++ b/src/modules/features/SetupWizard/dialogs/ScanQRCode.svelte @@ -4,10 +4,10 @@ import Decision from "@/lib/src/UI/components/Decision.svelte"; import Instruction from "@/lib/src/UI/components/Instruction.svelte"; import UserDecisions from "@/lib/src/UI/components/UserDecisions.svelte"; - const TYPE_CLOSE = "close"; - type ResultType = typeof TYPE_CLOSE; + import { TYPE_CLOSE, type ScanQRCodeResultType } from "./setupDialogTypes"; + type Props = { - setResult: (_result: ResultType) => void; + setResult: (_result: ScanQRCodeResultType) => void; }; const { setResult }: Props = $props(); diff --git a/src/modules/features/SetupWizard/dialogs/SelectMethodExisting.svelte b/src/modules/features/SetupWizard/dialogs/SelectMethodExisting.svelte index b339d3e..982d60c 100644 --- a/src/modules/features/SetupWizard/dialogs/SelectMethodExisting.svelte +++ b/src/modules/features/SetupWizard/dialogs/SelectMethodExisting.svelte @@ -7,19 +7,19 @@ import Options from "@/lib/src/UI/components/Options.svelte"; import Instruction from "@/lib/src/UI/components/Instruction.svelte"; import UserDecisions from "@/lib/src/UI/components/UserDecisions.svelte"; - import InfoNote from "@/lib/src/UI/components/InfoNote.svelte"; - import ExtraItems from "@/lib/src/UI/components/ExtraItems.svelte"; - import Check from "@/lib/src/UI/components/Check.svelte"; - const TYPE_USE_SETUP_URI = "use-setup-uri"; - const TYPE_SCAN_QR_CODE = "scan-qr-code"; - const TYPE_CONFIGURE_MANUALLY = "configure-manually"; - const TYPE_CANCELLED = "cancelled"; - type ResultType = typeof TYPE_USE_SETUP_URI | typeof TYPE_SCAN_QR_CODE | typeof TYPE_CONFIGURE_MANUALLY | typeof TYPE_CANCELLED; + import { + TYPE_USE_SETUP_URI, + TYPE_SCAN_QR_CODE, + TYPE_CONFIGURE_MANUALLY, + TYPE_CANCELLED, + type SelectMethodExistingResultType, + } from "./setupDialogTypes"; + type Props = { - setResult: (result: ResultType) => void; + setResult: (result: SelectMethodExistingResultType) => void; }; const { setResult }: Props = $props(); - let userType = $state(TYPE_CANCELLED); + let userType = $state(TYPE_CANCELLED); let proceedTitle = $derived.by(() => { if (userType === TYPE_USE_SETUP_URI) { return "Proceed with Setup URI"; diff --git a/src/modules/features/SetupWizard/dialogs/SelectMethodNewUser.svelte b/src/modules/features/SetupWizard/dialogs/SelectMethodNewUser.svelte index b4dd9ea..36e90fc 100644 --- a/src/modules/features/SetupWizard/dialogs/SelectMethodNewUser.svelte +++ b/src/modules/features/SetupWizard/dialogs/SelectMethodNewUser.svelte @@ -7,18 +7,18 @@ import Options from "@/lib/src/UI/components/Options.svelte"; import Instruction from "@/lib/src/UI/components/Instruction.svelte"; import UserDecisions from "@/lib/src/UI/components/UserDecisions.svelte"; - import InfoNote from "@/lib/src/UI/components/InfoNote.svelte"; - import ExtraItems from "@/lib/src/UI/components/ExtraItems.svelte"; - import Check from "@/lib/src/UI/components/Check.svelte"; - const TYPE_USE_SETUP_URI = "use-setup-uri"; - const TYPE_CONFIGURE_MANUALLY = "configure-manually"; - const TYPE_CANCELLED = "cancelled"; - type ResultType = typeof TYPE_USE_SETUP_URI | typeof TYPE_CONFIGURE_MANUALLY | typeof TYPE_CANCELLED; + import { + TYPE_USE_SETUP_URI, + TYPE_CONFIGURE_MANUALLY, + TYPE_CANCELLED, + type SelectMethodNewUserResultType, + } from "./setupDialogTypes"; + type Props = { - setResult: (result: ResultType) => void; + setResult: (result: SelectMethodNewUserResultType) => void; }; const { setResult }: Props = $props(); - let userType = $state(TYPE_CANCELLED); + let userType = $state(TYPE_CANCELLED); let proceedTitle = $derived.by(() => { if (userType === TYPE_USE_SETUP_URI) { return "Proceed with Setup URI"; diff --git a/src/modules/features/SetupWizard/dialogs/SetupRemote.svelte b/src/modules/features/SetupWizard/dialogs/SetupRemote.svelte index adcb87a..365f117 100644 --- a/src/modules/features/SetupWizard/dialogs/SetupRemote.svelte +++ b/src/modules/features/SetupWizard/dialogs/SetupRemote.svelte @@ -6,16 +6,19 @@ import Options from "@/lib/src/UI/components/Options.svelte"; import Instruction from "@/lib/src/UI/components/Instruction.svelte"; import UserDecisions from "@/lib/src/UI/components/UserDecisions.svelte"; - const TYPE_COUCHDB = "couchdb"; - const TYPE_BUCKET = "bucket"; - const TYPE_P2P = "p2p"; - const TYPE_CANCELLED = "cancelled"; - type ResultType = typeof TYPE_COUCHDB | typeof TYPE_BUCKET | typeof TYPE_P2P | typeof TYPE_CANCELLED; + import { + TYPE_COUCHDB, + TYPE_BUCKET, + TYPE_P2P, + TYPE_CANCELLED, + type SetupRemoteResultType, + } from "./setupDialogTypes"; + type Props = { - setResult: (result: ResultType) => void; + setResult: (result: SetupRemoteResultType) => void; }; const { setResult }: Props = $props(); - let userType = $state(TYPE_CANCELLED); + let userType = $state(TYPE_CANCELLED); let proceedTitle = $derived.by(() => { if (userType === TYPE_COUCHDB) { return "Continue to CouchDB setup"; diff --git a/src/modules/features/SetupWizard/dialogs/SetupRemoteBucket.svelte b/src/modules/features/SetupWizard/dialogs/SetupRemoteBucket.svelte index f2270dc..7c04c38 100644 --- a/src/modules/features/SetupWizard/dialogs/SetupRemoteBucket.svelte +++ b/src/modules/features/SetupWizard/dialogs/SetupRemoteBucket.svelte @@ -13,19 +13,18 @@ DEFAULT_SETTINGS, PREFERRED_JOURNAL_SYNC, RemoteTypes, - } from "../../../../lib/src/common/types"; + } from "@lib/common/types"; import { onMount } from "svelte"; - import { getDialogContext, type GuestDialogProps } from "../../../../lib/src/UI/svelteDialog"; - import { copyTo, pickBucketSyncSettings } from "../../../../lib/src/common/utils"; + import { getDialogContext, type GuestDialogProps } from "@lib/UI/svelteDialog"; + import { copyTo, pickBucketSyncSettings } from "@lib/common/utils"; + import { TYPE_CANCELLED, type SetupRemoteBucketResultType } from "./setupDialogTypes"; const default_setting = pickBucketSyncSettings(DEFAULT_SETTINGS); let syncSetting = $state({ ...default_setting }); - type ResultType = typeof TYPE_CANCELLED | BucketSyncSetting; - type Props = GuestDialogProps; - const TYPE_CANCELLED = "cancelled"; + type Props = GuestDialogProps; const { setResult, getInitialData }: Props = $props(); diff --git a/src/modules/features/SetupWizard/dialogs/SetupRemoteCouchDB.svelte b/src/modules/features/SetupWizard/dialogs/SetupRemoteCouchDB.svelte index 671af71..39d54c4 100644 --- a/src/modules/features/SetupWizard/dialogs/SetupRemoteCouchDB.svelte +++ b/src/modules/features/SetupWizard/dialogs/SetupRemoteCouchDB.svelte @@ -14,20 +14,19 @@ RemoteTypes, type CouchDBConnection, type ObsidianLiveSyncSettings, - } from "../../../../lib/src/common/types"; - import { isCloudantURI } from "../../../../lib/src/pouchdb/utils_couchdb"; + } from "@lib/common/types"; + import { isCloudantURI } from "@lib/pouchdb/utils_couchdb"; import { onMount } from "svelte"; - import { getDialogContext, type GuestDialogProps } from "../../../../lib/src/UI/svelteDialog"; - import { copyTo, pickCouchDBSyncSettings } from "../../../../lib/src/common/utils"; + import { getDialogContext, type GuestDialogProps } from "@lib/UI/svelteDialog"; + import { copyTo, pickCouchDBSyncSettings } from "@lib/common/utils"; import PanelCouchDBCheck from "./PanelCouchDBCheck.svelte"; + import { TYPE_CANCELLED, type SetupRemoteCouchDBResultType } from "./setupDialogTypes"; const default_setting = pickCouchDBSyncSettings(DEFAULT_SETTINGS); let syncSetting = $state({ ...default_setting }); - type ResultType = typeof TYPE_CANCELLED | CouchDBConnection; - const TYPE_CANCELLED = "cancelled"; - type Props = GuestDialogProps; + type Props = GuestDialogProps; const { setResult, getInitialData }: Props = $props(); onMount(() => { if (getInitialData) { @@ -181,7 +180,7 @@ autocapitalize="off" spellcheck="false" required - pattern="^[a-z0-9][a-z0-9_]*$" + pattern="^[a-z][a-z0-9_$()+/-]*$" bind:value={syncSetting.couchDB_DBNAME} /> diff --git a/src/modules/features/SetupWizard/dialogs/SetupRemoteE2EE.svelte b/src/modules/features/SetupWizard/dialogs/SetupRemoteE2EE.svelte index 052e97d..f3c40ed 100644 --- a/src/modules/features/SetupWizard/dialogs/SetupRemoteE2EE.svelte +++ b/src/modules/features/SetupWizard/dialogs/SetupRemoteE2EE.svelte @@ -12,13 +12,13 @@ E2EEAlgorithmNames, E2EEAlgorithms, type EncryptionSettings, - } from "../../../../lib/src/common/types"; + } from "@lib/common/types"; import { onMount } from "svelte"; - import type { GuestDialogProps } from "../../../../lib/src/UI/svelteDialog"; - import { copyTo, pickEncryptionSettings } from "../../../../lib/src/common/utils"; - const TYPE_CANCELLED = "cancelled"; - type ResultType = typeof TYPE_CANCELLED | EncryptionSettings; - type Props = GuestDialogProps; + import type { GuestDialogProps } from "@lib/UI/svelteDialog"; + import { copyTo, pickEncryptionSettings } from "@lib/common/utils"; + import { TYPE_CANCELLED, type SetupRemoteE2EEResultType } from "./setupDialogTypes"; + + type Props = GuestDialogProps; const { setResult, getInitialData }: Props = $props(); let default_encryption: EncryptionSettings = { encrypt: true, diff --git a/src/modules/features/SetupWizard/dialogs/SetupRemoteP2P.svelte b/src/modules/features/SetupWizard/dialogs/SetupRemoteP2P.svelte index cd2020e..4e1067a 100644 --- a/src/modules/features/SetupWizard/dialogs/SetupRemoteP2P.svelte +++ b/src/modules/features/SetupWizard/dialogs/SetupRemoteP2P.svelte @@ -26,16 +26,14 @@ import { getDialogContext, type GuestDialogProps } from "@lib/UI/svelteDialog"; import { SETTING_KEY_P2P_DEVICE_NAME } from "@lib/common/types"; import ExtraItems from "@lib/UI/components/ExtraItems.svelte"; + import { TYPE_CANCELLED, type SetupRemoteP2PResultType } from "./setupDialogTypes"; const default_setting = pickP2PSyncSettings(DEFAULT_SETTINGS); let syncSetting = $state({ ...default_setting }); const context = getDialogContext(); let error = $state(""); - const TYPE_CANCELLED = "cancelled"; - type SettingInfo = P2PConnectionInfo; - type ResultType = typeof TYPE_CANCELLED | SettingInfo; - type Props = GuestDialogProps; + type Props = GuestDialogProps; const { setResult, getInitialData }: Props = $props(); onMount(() => { diff --git a/src/modules/features/SetupWizard/dialogs/UseSetupURI.svelte b/src/modules/features/SetupWizard/dialogs/UseSetupURI.svelte index d6152b7..d1ed1ac 100644 --- a/src/modules/features/SetupWizard/dialogs/UseSetupURI.svelte +++ b/src/modules/features/SetupWizard/dialogs/UseSetupURI.svelte @@ -1,6 +1,6 @@
{line}