Compare commits

...

5 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
vorotamoroz
069b8513d1 bump 2024-05-27 12:21:08 +01:00
vorotamoroz
128b1843df Fixed: No longer configurations have been locked in the minimal setup. 2024-05-27 12:20:18 +01:00
10 changed files with 177 additions and 106 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 keepTest = !prod;
const dev = process.argv[2] === "dev";
const keepTest = !prod || dev;
const terserOpt = {
sourceMap: (!prod ? {
url: "inline"
} : {}),
sourceMap: !prod
? {
url: "inline",
}
: {},
format: {
indent_level: 2,
beautify: true,
comments: "some",
ecma: 2018,
preamble: banner,
webkit: true
webkit: true,
},
parse: {
// parse options
@@ -53,16 +56,6 @@ const terserOpt = {
ecma: 2018,
unused: true,
},
// mangle: {
// // mangle options
// keep_classnames: true,
// keep_fnames: true,
// properties: {
// // mangle property options
// }
// },
ecma: 2018, // specify one of: 5, 2015, 2016, etc.
enclose: false, // or specify true, or "args:values"
@@ -72,41 +65,41 @@ const terserOpt = {
module: false,
// nameCache: null, // or specify a name cache object
safari10: false,
toplevel: false
}
toplevel: false,
};
const manifestJson = JSON.parse(fs.readFileSync("./manifest.json") + "");
const packageJson = JSON.parse(fs.readFileSync("./package.json") + "");
const updateInfo = JSON.stringify(fs.readFileSync("./updates.md") + "");
/** @type esbuild.Plugin[] */
const plugins = [{
name: 'my-plugin',
setup(build) {
let count = 0;
build.onEnd(async result => {
if (count++ === 0) {
console.log('first build:', result);
} else {
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);
const plugins = [
{
name: "my-plugin",
setup(build) {
let count = 0;
build.onEnd(async (result) => {
if (count++ === 0) {
console.log("first build:", result);
} else {
console.log("subsequent build:");
}
console.log("Finished terser");
} else {
fs.copyFileSync("./main_org.js", "./main.js");
}
});
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");
} else {
fs.copyFileSync("./main_org.js", "./main.js");
}
});
},
},
}];
];
const context = await esbuild.context({
banner: {
@@ -115,26 +108,12 @@ const context = await esbuild.context({
entryPoints: ["src/main.ts"],
bundle: true,
define: {
"MANIFEST_VERSION": `"${manifestJson.version}"`,
"PACKAGE_VERSION": `"${packageJson.version}"`,
"UPDATE_INFO": `${updateInfo}`,
"global": "window",
MANIFEST_VERSION: `"${manifestJson.version}"`,
PACKAGE_VERSION: `"${packageJson.version}"`,
UPDATE_INFO: `${updateInfo}`,
global: "window",
},
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"],
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"],
// minifyWhitespace: true,
format: "cjs",
target: "es2018",
@@ -155,11 +134,11 @@ const context = await esbuild.context({
preprocess: sveltePreprocess(),
compilerOptions: { css: "injected", preserveComments: false },
}),
...plugins
...plugins,
],
})
});
if (prod) {
if (prod || dev) {
await context.rebuild();
process.exit(0);
} else {

View File

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

4
package-lock.json generated
View File

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

View File

@@ -1,12 +1,13 @@
{
"name": "obsidian-livesync",
"version": "0.23.9",
"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.",
"main": "main.js",
"type": "module",
"scripts": {
"dev": "node esbuild.config.mjs",
"build": "node esbuild.config.mjs production",
"buildDev": "node esbuild.config.mjs dev",
"lint": "eslint src"
},
"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 { 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 { 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 { readString, decodeBinary, arrayBufferToBase64, digestHash } from "../lib/src/string_and_binary/strbin.ts";
import { serialized, shareRunningResult } from "../lib/src/concurrency/lock.ts";
@@ -19,7 +19,6 @@ import type ObsidianLiveSyncPlugin from '../main.ts';
const d = "\u200b";
const d2 = "\n";
const delimiters = /(?<=[\n|\u200b])/g;
function serialize(data: PluginDataEx): string {
@@ -42,8 +41,45 @@ function serialize(data: PluginDataEx): string {
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[]) {
const sources = source.flatMap(e => e.split(delimiters))
const sources = splitWithDelimiters(source);
sources[0] = sources[0].substring(1);
let pos = 0;
let lineRunOut = false;
@@ -318,8 +354,7 @@ export class ConfigSync extends LiveSyncCommands {
}
return false;
}
createMissingConfigurationEntry = throttle(() => this._createMissingConfigurationEntry(), 1000);
_createMissingConfigurationEntry() {
async createMissingConfigurationEntry() {
let saveRequired = false;
for (const v of this.pluginList) {
const key = `${v.category}/${v.name}`;
@@ -337,7 +372,7 @@ export class ConfigSync extends LiveSyncCommands {
}
}
if (saveRequired) {
this.plugin.saveSettingData();
await this.plugin.saveSettingData();
}
}
@@ -365,7 +400,11 @@ export class ConfigSync extends LiveSyncCommands {
}
return [];
}, { 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) {
const data = await this.makeEntryFromFile(target);
if (data == false) {
// Logger(`Config: skipped: ${target} `, LOG_LEVEL_VERBOSE);
Logger(`Config: skipped (Possibly is not exist): ${target} `, LOG_LEVEL_VERBOSE);
continue;
}
if (data.version) {
@@ -703,13 +742,15 @@ export class ConfigSync extends LiveSyncCommands {
const oldC = await this.localDatabase.getDBEntryFromMeta(old, {}, false, false);
if (oldC) {
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 => {
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) {
Logger(`STORAGE --> DB:${prefixedFileName}: (config) Skipped (Same content)`, LOG_LEVEL_VERBOSE);
return true;
if (d.files.length == dt.files.length) {
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) {
Logger(`STORAGE --> DB:${prefixedFileName}: (config) Skipped (Same content)`, LOG_LEVEL_VERBOSE);
return true;
}
}
}
saveData =
@@ -757,9 +798,11 @@ export class ConfigSync extends LiveSyncCommands {
return true;
}
this.recentProcessedInternalFiles = [key, ...this.recentProcessedInternalFiles].slice(0, 100);
this.storeCustomizationFiles(path).then(() => {/* Fire and forget */ });
// To prevent saving half-collected file sets.
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() {
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");
__onMissingTranslation(() => { });
// eslint-disable-next-line no-unused-labels
DEV: {
__onMissingTranslation((key) => {
@@ -1137,12 +1138,14 @@ Note: We can always able to read V1 format. It will be progressively converted.
this.settingTab.requestReload()
}
async saveSettingData() {
saveDeviceAndVaultName() {
const lsKey = "obsidian-live-sync-vaultanddevicename-" + this.getVaultName();
localStorage.setItem(lsKey, this.deviceAndVaultName || "");
}
async saveSettingData() {
this.saveDeviceAndVaultName();
const settings = { ...this.settings };
settings.deviceAndVaultName = "";
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);
} 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);
if (ret !== false) {
Logger(msg + fullPath);
if (this.settings.syncOnSave && !this.suspended) {
scheduleTask("perform-replicate-after-save", 250, () => this.replicate());
}
this.scheduleReplicateIfSyncOnSave();
}
return ret != false;
}
scheduleReplicateIfSyncOnSave() {
if (this.settings.syncOnSave && !this.suspended) {
scheduleTask("perform-replicate-after-save", 250, () => this.replicate());
}
}
async deleteFromDB(file: TFile) {
if (!await this.isTargetFile(file)) return;
const fullPath = getPathFromTFile(file);
Logger(`deleteDB By path:${fullPath}`);
await this.deleteFromDBbyPath(fullPath);
if (this.settings.syncOnSave && !this.suspended) {
await this.replicate();
}
this.scheduleReplicateIfSyncOnSave();
}
async deleteFromDBbyPath(fullPath: FilePath) {
await this.localDatabase.deleteDBEntry(fullPath);
if (this.settings.syncOnSave && !this.suspended) {
await this.replicate();
}
this.scheduleReplicateIfSyncOnSave();
}
async resetLocalDatabase() {

View File

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

View File

@@ -18,6 +18,18 @@ 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.
#### 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
- Fixed:
- No longer configurations have been locked in the minimal setup.
- 0.23.9
- Fixed:
- No longer unexpected parallel replication is performed.