Compare commits

...

3 Commits

Author SHA1 Message Date
vorotamoroz
d4202161e8 bump 2024-05-28 12:27:30 +01:00
vorotamoroz
2a2b39009c Fixed:
- Now we *surely* can set the device name and enable customised synchronisation.
- Unnecessary dialogue update processes have been eliminated.
- Customisation sync no longer stores half-collected files.
- No longer hangs up when removing or renaming files with the `Sync on Save` toggle enabled.
Improved:
- Customisation sync now performs data deserialization more smoothly.
- New translations have been merged.
2024-05-28 12:26:23 +01:00
vorotamoroz
bf3a6e7570 Add the documentation and new a build option (buildDev). 2024-05-28 08:56:26 +01:00
10 changed files with 173 additions and 105 deletions

View File

@@ -0,0 +1,34 @@
# How to add translations
## Getting ready
1. Clone this repository recursively.
```sh
git clone --recursive https://github.com/vrtmrz/obsidian-livesync
```
2. Make `ls-debug` folder under your vault's `.obsidian` folder (as like `.../dev/.obsidian/ls-debug`).
## Add translations for already defined terms
1. Install dependencies, and build the plug-in as dev. build.
```sh
cd obsidian-livesync
npm i -D
npm run buildDev
```
2. Copy the `main.js` to `.obsidian/plugins/obsidian-livesync` folder of your vault, and run Obsidian-Self-hosted LiveSync.
3. You will get the `missing-translation-yyyy-mm-dd.jsonl`, please fill in new translations.
4. Build the plug-in again, and confirm that displayed things were expected.
5. Merge them into `rosetta.ts`, and make the PR to `https://github.com/vrtmrz/livesync-commonlib`.
## Make messages to be translated
1. Find the message that you want to be translated.
2. Change the literal to a tagged template literal using `$f`, like below.
```diff
- Logger("Could not determine passphrase to save data.json! You probably make the configuration sure again!", LOG_LEVEL_URGENT);
+ Logger($f`Could not determine passphrase to save data.json! You probably make the configuration sure again!`, LOG_LEVEL_URGENT);
```
3. Make the PR to `https://github.com/vrtmrz/obsidian-livesync`.
4. Follow the steps of "Add translations for already defined terms" to add the translations.

View File

@@ -14,21 +14,24 @@ if you want to view the source, please visit the github repository of this plugi
*/ */
`; `;
const prod = process.argv[2] === "production"; const prod = process.argv[2] === "production";
const keepTest = !prod; const dev = process.argv[2] === "dev";
const keepTest = !prod || dev;
const terserOpt = { const terserOpt = {
sourceMap: (!prod ? { sourceMap: !prod
url: "inline" ? {
} : {}), url: "inline",
}
: {},
format: { format: {
indent_level: 2, indent_level: 2,
beautify: true, beautify: true,
comments: "some", comments: "some",
ecma: 2018, ecma: 2018,
preamble: banner, preamble: banner,
webkit: true webkit: true,
}, },
parse: { parse: {
// parse options // parse options
@@ -53,16 +56,6 @@ const terserOpt = {
ecma: 2018, ecma: 2018,
unused: true, unused: true,
}, },
// mangle: {
// // mangle options
// keep_classnames: true,
// keep_fnames: true,
// properties: {
// // mangle property options
// }
// },
ecma: 2018, // specify one of: 5, 2015, 2016, etc. ecma: 2018, // specify one of: 5, 2015, 2016, etc.
enclose: false, // or specify true, or "args:values" enclose: false, // or specify true, or "args:values"
@@ -72,41 +65,41 @@ const terserOpt = {
module: false, module: false,
// nameCache: null, // or specify a name cache object // nameCache: null, // or specify a name cache object
safari10: false, safari10: false,
toplevel: false toplevel: false,
} };
const manifestJson = JSON.parse(fs.readFileSync("./manifest.json") + ""); const manifestJson = JSON.parse(fs.readFileSync("./manifest.json") + "");
const packageJson = JSON.parse(fs.readFileSync("./package.json") + ""); const packageJson = JSON.parse(fs.readFileSync("./package.json") + "");
const updateInfo = JSON.stringify(fs.readFileSync("./updates.md") + ""); const updateInfo = JSON.stringify(fs.readFileSync("./updates.md") + "");
/** @type esbuild.Plugin[] */ /** @type esbuild.Plugin[] */
const plugins = [{ const plugins = [
name: 'my-plugin', {
setup(build) { name: "my-plugin",
let count = 0; setup(build) {
build.onEnd(async result => { let count = 0;
if (count++ === 0) { build.onEnd(async (result) => {
console.log('first build:', result); if (count++ === 0) {
console.log("first build:", result);
} else { } else {
console.log('subsequent build:'); console.log("subsequent build:");
}
if (prod) {
console.log("Performing terser");
const src = fs.readFileSync("./main_org.js").toString();
// @ts-ignore
const ret = await minify(src, terserOpt);
if (ret && ret.code) {
fs.writeFileSync("./main.js", ret.code);
} }
console.log("Finished terser"); if (prod) {
} else { console.log("Performing terser");
fs.copyFileSync("./main_org.js", "./main.js"); const src = fs.readFileSync("./main_org.js").toString();
} // @ts-ignore
}); const ret = await minify(src, terserOpt);
if (ret && ret.code) {
fs.writeFileSync("./main.js", ret.code);
}
console.log("Finished terser");
} else {
fs.copyFileSync("./main_org.js", "./main.js");
}
});
},
}, },
}]; ];
const context = await esbuild.context({ const context = await esbuild.context({
banner: { banner: {
@@ -115,26 +108,12 @@ const context = await esbuild.context({
entryPoints: ["src/main.ts"], entryPoints: ["src/main.ts"],
bundle: true, bundle: true,
define: { define: {
"MANIFEST_VERSION": `"${manifestJson.version}"`, MANIFEST_VERSION: `"${manifestJson.version}"`,
"PACKAGE_VERSION": `"${packageJson.version}"`, PACKAGE_VERSION: `"${packageJson.version}"`,
"UPDATE_INFO": `${updateInfo}`, UPDATE_INFO: `${updateInfo}`,
"global": "window", global: "window",
}, },
external: [ external: ["obsidian", "electron", "crypto", "@codemirror/autocomplete", "@codemirror/collab", "@codemirror/commands", "@codemirror/language", "@codemirror/lint", "@codemirror/search", "@codemirror/state", "@codemirror/view", "@lezer/common", "@lezer/highlight", "@lezer/lr"],
"obsidian",
"electron",
"crypto",
"@codemirror/autocomplete",
"@codemirror/collab",
"@codemirror/commands",
"@codemirror/language",
"@codemirror/lint",
"@codemirror/search",
"@codemirror/state",
"@codemirror/view",
"@lezer/common",
"@lezer/highlight",
"@lezer/lr"],
// minifyWhitespace: true, // minifyWhitespace: true,
format: "cjs", format: "cjs",
target: "es2018", target: "es2018",
@@ -155,11 +134,11 @@ const context = await esbuild.context({
preprocess: sveltePreprocess(), preprocess: sveltePreprocess(),
compilerOptions: { css: "injected", preserveComments: false }, compilerOptions: { css: "injected", preserveComments: false },
}), }),
...plugins ...plugins,
], ],
}) });
if (prod) { if (prod || dev) {
await context.rebuild(); await context.rebuild();
process.exit(0); process.exit(0);
} else { } else {

View File

@@ -1,7 +1,7 @@
{ {
"id": "obsidian-livesync", "id": "obsidian-livesync",
"name": "Self-hosted LiveSync", "name": "Self-hosted LiveSync",
"version": "0.23.10", "version": "0.23.11",
"minAppVersion": "0.9.12", "minAppVersion": "0.9.12",
"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.", "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", "author": "vorotamoroz",

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "obsidian-livesync", "name": "obsidian-livesync",
"version": "0.23.10", "version": "0.23.11",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "obsidian-livesync", "name": "obsidian-livesync",
"version": "0.23.10", "version": "0.23.11",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@aws-sdk/client-s3": "^3.556.0", "@aws-sdk/client-s3": "^3.556.0",

View File

@@ -1,12 +1,13 @@
{ {
"name": "obsidian-livesync", "name": "obsidian-livesync",
"version": "0.23.10", "version": "0.23.11",
"description": "Reflect your vault changes to some other devices immediately. Please make sure to disable other synchronize solutions to avoid content corruption or duplication.", "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", "main": "main.js",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "node esbuild.config.mjs", "dev": "node esbuild.config.mjs",
"build": "node esbuild.config.mjs production", "build": "node esbuild.config.mjs production",
"buildDev": "node esbuild.config.mjs dev",
"lint": "eslint src" "lint": "eslint src"
}, },
"keywords": [], "keywords": [],

View File

@@ -4,7 +4,7 @@ import { Notice, type PluginManifest, parseYaml, normalizePath, type ListedFiles
import type { EntryDoc, LoadedEntry, InternalFileEntry, FilePathWithPrefix, FilePath, DocumentID, AnyEntry, SavingEntry } from "../lib/src/common/types.ts"; import type { EntryDoc, LoadedEntry, InternalFileEntry, FilePathWithPrefix, FilePath, DocumentID, AnyEntry, SavingEntry } from "../lib/src/common/types.ts";
import { LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE, MODE_SELECTIVE } from "../lib/src/common/types.ts"; import { LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE, MODE_SELECTIVE } from "../lib/src/common/types.ts";
import { ICXHeader, PERIODIC_PLUGIN_SWEEP, } from "../common/types.ts"; import { ICXHeader, PERIODIC_PLUGIN_SWEEP, } from "../common/types.ts";
import { createSavingEntryFromLoadedEntry, createTextBlob, delay, fireAndForget, getDocDataAsArray, isDocContentSame, throttle } from "../lib/src/common/utils.ts"; import { createSavingEntryFromLoadedEntry, createTextBlob, delay, fireAndForget, getDocDataAsArray, isDocContentSame } from "../lib/src/common/utils.ts";
import { Logger } from "../lib/src/common/logger.ts"; import { Logger } from "../lib/src/common/logger.ts";
import { readString, decodeBinary, arrayBufferToBase64, digestHash } from "../lib/src/string_and_binary/strbin.ts"; import { readString, decodeBinary, arrayBufferToBase64, digestHash } from "../lib/src/string_and_binary/strbin.ts";
import { serialized, shareRunningResult } from "../lib/src/concurrency/lock.ts"; import { serialized, shareRunningResult } from "../lib/src/concurrency/lock.ts";
@@ -19,7 +19,6 @@ import type ObsidianLiveSyncPlugin from '../main.ts';
const d = "\u200b"; const d = "\u200b";
const d2 = "\n"; const d2 = "\n";
const delimiters = /(?<=[\n|\u200b])/g;
function serialize(data: PluginDataEx): string { function serialize(data: PluginDataEx): string {
@@ -42,8 +41,45 @@ function serialize(data: PluginDataEx): string {
return ret; return ret;
} }
function splitWithDelimiters(sources: string[]): string[] {
const result: string[] = [];
for (const str of sources) {
let startIndex = 0;
const maxLen = str.length;
let i = -1;
let i1;
let i2;
do {
i1 = str.indexOf(d, startIndex);
i2 = str.indexOf(d2, startIndex);
if (i1 == -1 && i2 == -1) {
break;
}
if (i1 == -1) {
i = i2;
} else if (i2 == -1) {
i = i1;
} else {
i = i1 < i2 ? i1 : i2;
}
result.push(str.slice(startIndex, i + 1));
startIndex = i + 1;
} while (i < maxLen);
if (startIndex < maxLen) {
result.push(str.slice(startIndex));
}
}
// To keep compatibilities
if (sources[sources.length - 1] == "") {
result.push("");
}
return result;
}
function getTokenizer(source: string[]) { function getTokenizer(source: string[]) {
const sources = source.flatMap(e => e.split(delimiters)) const sources = splitWithDelimiters(source);
sources[0] = sources[0].substring(1); sources[0] = sources[0].substring(1);
let pos = 0; let pos = 0;
let lineRunOut = false; let lineRunOut = false;
@@ -318,8 +354,7 @@ export class ConfigSync extends LiveSyncCommands {
} }
return false; return false;
} }
createMissingConfigurationEntry = throttle(() => this._createMissingConfigurationEntry(), 1000); async createMissingConfigurationEntry() {
_createMissingConfigurationEntry() {
let saveRequired = false; let saveRequired = false;
for (const v of this.pluginList) { for (const v of this.pluginList) {
const key = `${v.category}/${v.name}`; const key = `${v.category}/${v.name}`;
@@ -337,7 +372,7 @@ export class ConfigSync extends LiveSyncCommands {
} }
} }
if (saveRequired) { if (saveRequired) {
this.plugin.saveSettingData(); await this.plugin.saveSettingData();
} }
} }
@@ -365,7 +400,11 @@ export class ConfigSync extends LiveSyncCommands {
} }
return []; return [];
}, { suspended: false, batchSize: 1, concurrentLimit: 10, delay: 100, yieldThreshold: 10, maintainDelay: false, totalRemainingReactiveSource: pluginScanningCount }).startPipeline().root.onUpdateProgress(() => { }, { suspended: false, batchSize: 1, concurrentLimit: 10, delay: 100, yieldThreshold: 10, maintainDelay: false, totalRemainingReactiveSource: pluginScanningCount }).startPipeline().root.onUpdateProgress(() => {
this.createMissingConfigurationEntry(); scheduleTask("checkMissingConfigurations", 250, async () => {
if (this.pluginScanProcessor.isIdle()) {
await this.createMissingConfigurationEntry();
}
});
}); });
@@ -654,7 +693,7 @@ export class ConfigSync extends LiveSyncCommands {
for (const target of fileTargets) { for (const target of fileTargets) {
const data = await this.makeEntryFromFile(target); const data = await this.makeEntryFromFile(target);
if (data == false) { if (data == false) {
// Logger(`Config: skipped: ${target} `, LOG_LEVEL_VERBOSE); Logger(`Config: skipped (Possibly is not exist): ${target} `, LOG_LEVEL_VERBOSE);
continue; continue;
} }
if (data.version) { if (data.version) {
@@ -703,13 +742,15 @@ export class ConfigSync extends LiveSyncCommands {
const oldC = await this.localDatabase.getDBEntryFromMeta(old, {}, false, false); const oldC = await this.localDatabase.getDBEntryFromMeta(old, {}, false, false);
if (oldC) { if (oldC) {
const d = await deserialize(getDocDataAsArray(oldC.data), {}) as PluginDataEx; const d = await deserialize(getDocDataAsArray(oldC.data), {}) as PluginDataEx;
const diffs = (d.files.map(previous => ({ prev: previous, curr: dt.files.find(e => e.filename == previous.filename) })).map(async e => { if (d.files.length == dt.files.length) {
try { return await isDocContentSame(e.curr?.data ?? [], e.prev.data) } catch (_) { return false } const diffs = (d.files.map(previous => ({ prev: previous, curr: dt.files.find(e => e.filename == previous.filename) })).map(async e => {
})) try { return await isDocContentSame(e.curr?.data ?? [], e.prev.data) } catch (_) { return false }
const isSame = (await Promise.all(diffs)).every(e => e == true); }))
if (isSame) { const isSame = (await Promise.all(diffs)).every(e => e == true);
Logger(`STORAGE --> DB:${prefixedFileName}: (config) Skipped (Same content)`, LOG_LEVEL_VERBOSE); if (isSame) {
return true; Logger(`STORAGE --> DB:${prefixedFileName}: (config) Skipped (Same content)`, LOG_LEVEL_VERBOSE);
return true;
}
} }
} }
saveData = saveData =
@@ -757,9 +798,11 @@ export class ConfigSync extends LiveSyncCommands {
return true; return true;
} }
this.recentProcessedInternalFiles = [key, ...this.recentProcessedInternalFiles].slice(0, 100); this.recentProcessedInternalFiles = [key, ...this.recentProcessedInternalFiles].slice(0, 100);
// To prevent saving half-collected file sets.
this.storeCustomizationFiles(path).then(() => {/* Fire and forget */ }); const keySchedule = this.filenameToUnifiedKey(path);
scheduleTask(keySchedule, 100, async () => {
await this.storeCustomizationFiles(path);
})
} }

Submodule src/lib updated: 302a2e7c0b...9825b64d17

View File

@@ -878,6 +878,7 @@ Note: We can always able to read V1 format. It will be progressively converted.
async onload() { async onload() {
logStore.pipeTo(new QueueProcessor(logs => logs.forEach(e => this.addLog(e.message, e.level, e.key)), { suspended: false, batchSize: 20, concurrentLimit: 1, delay: 0 })).startPipeline(); logStore.pipeTo(new QueueProcessor(logs => logs.forEach(e => this.addLog(e.message, e.level, e.key)), { suspended: false, batchSize: 20, concurrentLimit: 1, delay: 0 })).startPipeline();
Logger("loading plugin"); Logger("loading plugin");
__onMissingTranslation(() => { });
// eslint-disable-next-line no-unused-labels // eslint-disable-next-line no-unused-labels
DEV: { DEV: {
__onMissingTranslation((key) => { __onMissingTranslation((key) => {
@@ -1137,12 +1138,14 @@ Note: We can always able to read V1 format. It will be progressively converted.
this.settingTab.requestReload() this.settingTab.requestReload()
} }
async saveSettingData() { saveDeviceAndVaultName() {
const lsKey = "obsidian-live-sync-vaultanddevicename-" + this.getVaultName(); const lsKey = "obsidian-live-sync-vaultanddevicename-" + this.getVaultName();
localStorage.setItem(lsKey, this.deviceAndVaultName || ""); localStorage.setItem(lsKey, this.deviceAndVaultName || "");
}
async saveSettingData() {
this.saveDeviceAndVaultName();
const settings = { ...this.settings }; const settings = { ...this.settings };
settings.deviceAndVaultName = "";
if (this.usedPassphrase == "" && !await this.getPassphrase(settings)) { if (this.usedPassphrase == "" && !await this.getPassphrase(settings)) {
Logger("Could not determine passphrase for saving data.json! Our data.json have insecure items!", LOG_LEVEL_NOTICE); Logger("Could not determine passphrase for saving data.json! Our data.json have insecure items!", LOG_LEVEL_NOTICE);
} else { } else {
@@ -3060,28 +3063,28 @@ Or if you are sure know what had been happened, we can unlock the database from
const ret = await this.localDatabase.putDBEntry(d); const ret = await this.localDatabase.putDBEntry(d);
if (ret !== false) { if (ret !== false) {
Logger(msg + fullPath); Logger(msg + fullPath);
if (this.settings.syncOnSave && !this.suspended) { this.scheduleReplicateIfSyncOnSave();
scheduleTask("perform-replicate-after-save", 250, () => this.replicate());
}
} }
return ret != false; return ret != false;
} }
scheduleReplicateIfSyncOnSave() {
if (this.settings.syncOnSave && !this.suspended) {
scheduleTask("perform-replicate-after-save", 250, () => this.replicate());
}
}
async deleteFromDB(file: TFile) { async deleteFromDB(file: TFile) {
if (!await this.isTargetFile(file)) return; if (!await this.isTargetFile(file)) return;
const fullPath = getPathFromTFile(file); const fullPath = getPathFromTFile(file);
Logger(`deleteDB By path:${fullPath}`); Logger(`deleteDB By path:${fullPath}`);
await this.deleteFromDBbyPath(fullPath); await this.deleteFromDBbyPath(fullPath);
if (this.settings.syncOnSave && !this.suspended) { this.scheduleReplicateIfSyncOnSave();
await this.replicate();
}
} }
async deleteFromDBbyPath(fullPath: FilePath) { async deleteFromDBbyPath(fullPath: FilePath) {
await this.localDatabase.deleteDBEntry(fullPath); await this.localDatabase.deleteDBEntry(fullPath);
if (this.settings.syncOnSave && !this.suspended) { this.scheduleReplicateIfSyncOnSave();
await this.replicate();
}
} }
async resetLocalDatabase() { async resetLocalDatabase() {

View File

@@ -439,6 +439,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
} }
if (key == "deviceAndVaultName") { if (key == "deviceAndVaultName") {
this.plugin.deviceAndVaultName = this.editingSettings?.[key]; this.plugin.deviceAndVaultName = this.editingSettings?.[key];
this.plugin.saveDeviceAndVaultName();
return await Promise.resolve(); return await Promise.resolve();
} }
} }
@@ -519,12 +520,12 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
/** /**
* Reread all settings and request invalidate * Reread all settings and request invalidate
*/ */
reloadAllSettings() { reloadAllSettings(skipUpdate: boolean = false) {
const localSetting = this.reloadAllLocalSettings(); const localSetting = this.reloadAllLocalSettings();
this._editingSettings = { ...this.plugin.settings, ...localSetting }; this._editingSettings = { ...this.plugin.settings, ...localSetting };
this._editingSettings = { ...this.editingSettings, ...this.computeAllLocalSettings() }; this._editingSettings = { ...this.editingSettings, ...this.computeAllLocalSettings() };
this.initialSettings = { ...this.editingSettings, }; this.initialSettings = { ...this.editingSettings, };
this.requestUpdate(); if (!skipUpdate) this.requestUpdate();
} }
/** /**
@@ -667,9 +668,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
this.requestUpdate(); this.requestUpdate();
} }
} else { } else {
Logger(`reread: all! hidden`, LOG_LEVEL_VERBOSE) this.reloadAllSettings(true);
this.reloadAllSettings();
this.display();
} }
} }

View File

@@ -18,6 +18,15 @@ I have a lot of respect for that plugin, even though it is sometimes treated as
Hooray for open source, and generous licences, and the sharing of knowledge by experts. Hooray for open source, and generous licences, and the sharing of knowledge by experts.
#### Version history #### Version history
- 0.23.11:
- Fixed:
- Now we *surely* can set the device name and enable customised synchronisation.
- Unnecessary dialogue update processes have been eliminated.
- Customisation sync no longer stores half-collected files.
- No longer hangs up when removing or renaming files with the `Sync on Save` toggle enabled.
- Improved:
- Customisation sync now performs data deserialization more smoothly.
- New translations have been merged.
- 0.23.10 - 0.23.10
- Fixed: - Fixed:
- No longer configurations have been locked in the minimal setup. - No longer configurations have been locked in the minimal setup.