Compare commits

...

15 Commits

Author SHA1 Message Date
vorotamoroz
2e6ed4777c Added postcss for ci 2022-07-25 13:22:37 +09:00
vorotamoroz
ab6ff01f1a Bumped 2022-07-25 13:16:49 +09:00
vorotamoroz
c836953fa9 Fixed:
- Leaked prototyping code.

    New feature:
    - `Pick file to show history` shows a file list and you can choose one for showing history.
    - `Back to this revision` implemented on the Document history dialog.
2022-07-25 13:14:59 +09:00
vorotamoroz
e69371ff24 Degraded! 2022-07-25 13:05:00 +09:00
vorotamoroz
d324add086 Fixed:
- Leaked prototyping code.

New feature:
- `Pick file to show history` shows a file list and you can choose one for showing history.
- `Back to this revision` implemented on the Document history dialog.
2022-07-25 12:48:48 +09:00
vorotamoroz
0caf330f39 Merge remote-tracking branch 'origin/snyk-upgrade-52349e72cdcadcd0ba41dd967c715aa7' 2022-07-21 16:40:18 +09:00
vorotamoroz
3a147ca427 Merge branch 'snyk-upgrade-6a3786fc4cbe394542e9d1b6058c46e7' 2022-07-21 16:26:54 +09:00
vorotamoroz
8266cfba40 Merge remote-tracking branch 'origin/snyk-upgrade-d6d88a887d6bc2d451a6e9c7a7759727' 2022-07-21 16:15:35 +09:00
vorotamoroz
e2f06181fa bumped 2022-07-21 13:06:57 +09:00
vorotamoroz
bb6d787607 Imprinting version numbers to boot log. 2022-07-21 13:06:42 +09:00
vorotamoroz
cb406e2db6 New feature.
- Local database name can now be customized.
- Buttons to back skip-patterns of Hidden file sync to default.
2022-07-21 13:05:35 +09:00
vorotamoroz
0a1248c5fc Fixed:
- Now Notification is less noisy.
- Some synchronization won't be missed.
- Scanning speed improved.

Implemented:

- Implemented notifications to reload the plugin.
- Rescue button to updating all hidden files for overwriting them on other vaults.
2022-07-20 16:57:21 +09:00
snyk-bot
d5a95d43dd fix: upgrade idb from 7.0.1 to 7.0.2
Snyk has created this PR to upgrade idb from 7.0.1 to 7.0.2.

See this package in npm:
https://www.npmjs.com/package/idb

See this project in Snyk:
https://app.snyk.io/org/vrtmrz/project/d2c9b72d-6e38-433f-bbad-725719c0fa4d?utm_source=github&utm_medium=referral&page=upgrade-pr
2022-07-09 05:02:39 +00:00
snyk-bot
7da930a8bb fix: upgrade svelte-preprocess from 4.10.3 to 4.10.5
Snyk has created this PR to upgrade svelte-preprocess from 4.10.3 to 4.10.5.

See this package in npm:
https://www.npmjs.com/package/svelte-preprocess

See this project in Snyk:
https://app.snyk.io/org/vrtmrz/project/d2c9b72d-6e38-433f-bbad-725719c0fa4d?utm_source=github&utm_medium=referral&page=upgrade-pr
2022-04-29 03:54:29 +00:00
snyk-bot
a632b79726 fix: upgrade esbuild-svelte from 0.6.2 to 0.7.0
Snyk has created this PR to upgrade esbuild-svelte from 0.6.2 to 0.7.0.

See this package in npm:
https://www.npmjs.com/package/esbuild-svelte

See this project in Snyk:
https://app.snyk.io/org/vrtmrz/project/d2c9b72d-6e38-433f-bbad-725719c0fa4d?utm_source=github&utm_medium=referral&page=upgrade-pr
2022-04-29 03:54:25 +00:00
9 changed files with 623 additions and 127 deletions

View File

@@ -3,7 +3,7 @@ import process from "process";
import builtins from "builtin-modules"; import builtins from "builtin-modules";
import sveltePlugin from "esbuild-svelte"; import sveltePlugin from "esbuild-svelte";
import sveltePreprocess from "svelte-preprocess"; import sveltePreprocess from "svelte-preprocess";
import fs from "node:fs";
const banner = `/* const banner = `/*
THIS IS A GENERATED/BUNDLED FILE BY ESBUILD THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
if you want to view the source, please visit the github repository of this plugin if you want to view the source, please visit the github repository of this plugin
@@ -11,7 +11,8 @@ 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 manifestJson = JSON.parse(fs.readFileSync("./manifest.json"));
const packageJson = JSON.parse(fs.readFileSync("./package.json"));
esbuild esbuild
.build({ .build({
banner: { banner: {
@@ -19,6 +20,10 @@ esbuild
}, },
entryPoints: ["src/main.ts"], entryPoints: ["src/main.ts"],
bundle: true, bundle: true,
define: {
"MANIFEST_VERSION": `"${manifestJson.version}"`,
"PACKAGE_VERSION": `"${packageJson.version}"`,
},
external: ["obsidian", "electron", ...builtins], external: ["obsidian", "electron", ...builtins],
format: "cjs", format: "cjs",
watch: !prod, watch: !prod,

View File

@@ -1,7 +1,7 @@
{ {
"id": "obsidian-livesync", "id": "obsidian-livesync",
"name": "Self-hosted LiveSync", "name": "Self-hosted LiveSync",
"version": "0.12.0", "version": "0.12.3",
"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",

215
package-lock.json generated
View File

@@ -1,19 +1,19 @@
{ {
"name": "obsidian-livesync", "name": "obsidian-livesync",
"version": "0.12.0", "version": "0.12.3",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "obsidian-livesync", "name": "obsidian-livesync",
"version": "0.12.0", "version": "0.12.3",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"diff-match-patch": "^1.0.5", "diff-match-patch": "^1.0.5",
"esbuild": "0.13.12", "esbuild": "0.13.12",
"esbuild-svelte": "^0.6.0", "esbuild-svelte": "^0.7.0",
"idb": "^7.0.1", "idb": "^7.0.2",
"svelte-preprocess": "^4.10.2", "svelte-preprocess": "^4.10.5",
"xxhash-wasm": "^0.4.2" "xxhash-wasm": "^0.4.2"
}, },
"devDependencies": { "devDependencies": {
@@ -27,13 +27,15 @@
"@typescript-eslint/parser": "^5.0.0", "@typescript-eslint/parser": "^5.0.0",
"builtin-modules": "^3.2.0", "builtin-modules": "^3.2.0",
"esbuild": "0.13.12", "esbuild": "0.13.12",
"esbuild-svelte": "^0.6.0", "esbuild-svelte": "^0.7.0",
"eslint": "^7.32.0", "eslint": "^7.32.0",
"eslint-config-airbnb-base": "^14.2.1", "eslint-config-airbnb-base": "^14.2.1",
"eslint-plugin-import": "^2.25.2", "eslint-plugin-import": "^2.25.2",
"obsidian": "^0.15.4", "obsidian": "^0.15.4",
"postcss": "^8.4.14",
"postcss-load-config": "^3.1.4",
"rollup": "^2.32.1", "rollup": "^2.32.1",
"svelte-preprocess": "^4.10.2", "svelte-preprocess": "^4.10.5",
"tslib": "^2.2.0", "tslib": "^2.2.0",
"typescript": "^4.2.4" "typescript": "^4.2.4"
} }
@@ -1416,18 +1418,16 @@
] ]
}, },
"node_modules/esbuild-svelte": { "node_modules/esbuild-svelte": {
"version": "0.6.2", "version": "0.7.0",
"resolved": "https://registry.npmjs.org/esbuild-svelte/-/esbuild-svelte-0.6.2.tgz", "resolved": "https://registry.npmjs.org/esbuild-svelte/-/esbuild-svelte-0.7.0.tgz",
"integrity": "sha512-FRHcyaQqIm4ncFsbk97b+80fHAI0VA15Ty56zOai9zpOPOQ1kgqWJt7JYn0jNGm+1VSvJNaGUj8QB85H/P43jA==", "integrity": "sha512-hfiauhEXtGocUf7oVcxTrLhhF57ajBbvNCCClsS3KntEeITddKU+1WFhmsCt9SO0dQJlCFzJtpPu2dI7dRkXBw==",
"dev": true, "dev": true,
"dependencies": {
"svelte": "^3.46.2"
},
"engines": { "engines": {
"node": ">=12" "node": ">=12"
}, },
"peerDependencies": { "peerDependencies": {
"esbuild": ">=0.9.6" "esbuild": ">=0.9.6",
"svelte": ">=3.43.0"
} }
}, },
"node_modules/esbuild-windows-32": { "node_modules/esbuild-windows-32": {
@@ -2109,9 +2109,9 @@
} }
}, },
"node_modules/idb": { "node_modules/idb": {
"version": "7.0.1", "version": "7.0.2",
"resolved": "https://registry.npmjs.org/idb/-/idb-7.0.1.tgz", "resolved": "https://registry.npmjs.org/idb/-/idb-7.0.2.tgz",
"integrity": "sha512-UUxlE7vGWK5RfB/fDwEGgRf84DY/ieqNha6msMV99UsEMQhJ1RwbCd8AYBj3QMgnE3VZnfQvm4oKVCJTYlqIgg==" "integrity": "sha512-jjKrT1EnyZewQ/gCBb/eyiYrhGzws2FeY92Yx8qT9S9GeQAmo4JFVIiWRIfKW/6Ob9A+UDAOW9j9jn58fy2HIg=="
}, },
"node_modules/ignore": { "node_modules/ignore": {
"version": "5.1.9", "version": "5.1.9",
@@ -2454,6 +2454,15 @@
"node": ">= 0.8.0" "node": ">= 0.8.0"
} }
}, },
"node_modules/lilconfig": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.6.tgz",
"integrity": "sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==",
"dev": true,
"engines": {
"node": ">=10"
}
},
"node_modules/locate-path": { "node_modules/locate-path": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz",
@@ -2576,6 +2585,18 @@
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true "dev": true
}, },
"node_modules/nanoid": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
"integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
"dev": true,
"bin": {
"nanoid": "bin/nanoid.cjs"
},
"engines": {
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
"node_modules/natural-compare": { "node_modules/natural-compare": {
"version": "1.4.0", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
@@ -2776,6 +2797,12 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/picocolors": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
"dev": true
},
"node_modules/picomatch": { "node_modules/picomatch": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz",
@@ -2800,6 +2827,59 @@
"node": ">=4" "node": ">=4"
} }
}, },
"node_modules/postcss": {
"version": "8.4.14",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz",
"integrity": "sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==",
"dev": true,
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/postcss"
}
],
"dependencies": {
"nanoid": "^3.3.4",
"picocolors": "^1.0.0",
"source-map-js": "^1.0.2"
},
"engines": {
"node": "^10 || ^12 || >=14"
}
},
"node_modules/postcss-load-config": {
"version": "3.1.4",
"resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz",
"integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==",
"dev": true,
"dependencies": {
"lilconfig": "^2.0.5",
"yaml": "^1.10.2"
},
"engines": {
"node": ">= 10"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
"peerDependencies": {
"postcss": ">=8.0.9",
"ts-node": ">=9.0.0"
},
"peerDependenciesMeta": {
"postcss": {
"optional": true
},
"ts-node": {
"optional": true
}
}
},
"node_modules/prelude-ls": { "node_modules/prelude-ls": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@@ -3068,6 +3148,15 @@
"sorcery": "bin/index.js" "sorcery": "bin/index.js"
} }
}, },
"node_modules/source-map-js": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/sourcemap-codec": { "node_modules/sourcemap-codec": {
"version": "1.4.8", "version": "1.4.8",
"resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
@@ -3189,14 +3278,15 @@
"resolved": "https://registry.npmjs.org/svelte/-/svelte-3.46.4.tgz", "resolved": "https://registry.npmjs.org/svelte/-/svelte-3.46.4.tgz",
"integrity": "sha512-qKJzw6DpA33CIa+C/rGp4AUdSfii0DOTCzj/2YpSKKayw5WGSS624Et9L1nU1k2OVRS9vaENQXp2CVZNU+xvIg==", "integrity": "sha512-qKJzw6DpA33CIa+C/rGp4AUdSfii0DOTCzj/2YpSKKayw5WGSS624Et9L1nU1k2OVRS9vaENQXp2CVZNU+xvIg==",
"dev": true, "dev": true,
"peer": true,
"engines": { "engines": {
"node": ">= 8" "node": ">= 8"
} }
}, },
"node_modules/svelte-preprocess": { "node_modules/svelte-preprocess": {
"version": "4.10.3", "version": "4.10.5",
"resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-4.10.3.tgz", "resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-4.10.5.tgz",
"integrity": "sha512-ttw17lJfb/dx2ZJT9sesaXT5l7mPQ9Apx1H496Kli3Hkk7orIRGpOw6rCPkRNzr6ueVPqb4vzodS5x7sBFhKHw==", "integrity": "sha512-VKXPRScCzAZqeBZOGq4LLwtNrAu++mVn7XvQox3eFDV7Ciq0Lg70Q8QWjH9iXF7J+pMlXhPsSFwpCb2E+hoeyA==",
"dev": true, "dev": true,
"hasInstallScript": true, "hasInstallScript": true,
"dependencies": { "dependencies": {
@@ -3484,6 +3574,15 @@
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true "dev": true
},
"node_modules/yaml": {
"version": "1.10.2",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
"dev": true,
"engines": {
"node": ">= 6"
}
} }
}, },
"dependencies": { "dependencies": {
@@ -4526,13 +4625,11 @@
"optional": true "optional": true
}, },
"esbuild-svelte": { "esbuild-svelte": {
"version": "0.6.2", "version": "0.7.0",
"resolved": "https://registry.npmjs.org/esbuild-svelte/-/esbuild-svelte-0.6.2.tgz", "resolved": "https://registry.npmjs.org/esbuild-svelte/-/esbuild-svelte-0.7.0.tgz",
"integrity": "sha512-FRHcyaQqIm4ncFsbk97b+80fHAI0VA15Ty56zOai9zpOPOQ1kgqWJt7JYn0jNGm+1VSvJNaGUj8QB85H/P43jA==", "integrity": "sha512-hfiauhEXtGocUf7oVcxTrLhhF57ajBbvNCCClsS3KntEeITddKU+1WFhmsCt9SO0dQJlCFzJtpPu2dI7dRkXBw==",
"dev": true, "dev": true,
"requires": { "requires": {}
"svelte": "^3.46.2"
}
}, },
"esbuild-windows-32": { "esbuild-windows-32": {
"version": "0.13.12", "version": "0.13.12",
@@ -5052,9 +5149,9 @@
} }
}, },
"idb": { "idb": {
"version": "7.0.1", "version": "7.0.2",
"resolved": "https://registry.npmjs.org/idb/-/idb-7.0.1.tgz", "resolved": "https://registry.npmjs.org/idb/-/idb-7.0.2.tgz",
"integrity": "sha512-UUxlE7vGWK5RfB/fDwEGgRf84DY/ieqNha6msMV99UsEMQhJ1RwbCd8AYBj3QMgnE3VZnfQvm4oKVCJTYlqIgg==" "integrity": "sha512-jjKrT1EnyZewQ/gCBb/eyiYrhGzws2FeY92Yx8qT9S9GeQAmo4JFVIiWRIfKW/6Ob9A+UDAOW9j9jn58fy2HIg=="
}, },
"ignore": { "ignore": {
"version": "5.1.9", "version": "5.1.9",
@@ -5301,6 +5398,12 @@
"type-check": "~0.4.0" "type-check": "~0.4.0"
} }
}, },
"lilconfig": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.6.tgz",
"integrity": "sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==",
"dev": true
},
"locate-path": { "locate-path": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz",
@@ -5399,6 +5502,12 @@
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true "dev": true
}, },
"nanoid": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
"integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
"dev": true
},
"natural-compare": { "natural-compare": {
"version": "1.4.0", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
@@ -5547,6 +5656,12 @@
"integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
"dev": true "dev": true
}, },
"picocolors": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
"dev": true
},
"picomatch": { "picomatch": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz",
@@ -5562,6 +5677,27 @@
"find-up": "^2.1.0" "find-up": "^2.1.0"
} }
}, },
"postcss": {
"version": "8.4.14",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz",
"integrity": "sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==",
"dev": true,
"requires": {
"nanoid": "^3.3.4",
"picocolors": "^1.0.0",
"source-map-js": "^1.0.2"
}
},
"postcss-load-config": {
"version": "3.1.4",
"resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz",
"integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==",
"dev": true,
"requires": {
"lilconfig": "^2.0.5",
"yaml": "^1.10.2"
}
},
"prelude-ls": { "prelude-ls": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@@ -5734,6 +5870,12 @@
"sourcemap-codec": "^1.3.0" "sourcemap-codec": "^1.3.0"
} }
}, },
"source-map-js": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
"dev": true
},
"sourcemap-codec": { "sourcemap-codec": {
"version": "1.4.8", "version": "1.4.8",
"resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
@@ -5827,12 +5969,13 @@
"version": "3.46.4", "version": "3.46.4",
"resolved": "https://registry.npmjs.org/svelte/-/svelte-3.46.4.tgz", "resolved": "https://registry.npmjs.org/svelte/-/svelte-3.46.4.tgz",
"integrity": "sha512-qKJzw6DpA33CIa+C/rGp4AUdSfii0DOTCzj/2YpSKKayw5WGSS624Et9L1nU1k2OVRS9vaENQXp2CVZNU+xvIg==", "integrity": "sha512-qKJzw6DpA33CIa+C/rGp4AUdSfii0DOTCzj/2YpSKKayw5WGSS624Et9L1nU1k2OVRS9vaENQXp2CVZNU+xvIg==",
"dev": true "dev": true,
"peer": true
}, },
"svelte-preprocess": { "svelte-preprocess": {
"version": "4.10.3", "version": "4.10.5",
"resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-4.10.3.tgz", "resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-4.10.5.tgz",
"integrity": "sha512-ttw17lJfb/dx2ZJT9sesaXT5l7mPQ9Apx1H496Kli3Hkk7orIRGpOw6rCPkRNzr6ueVPqb4vzodS5x7sBFhKHw==", "integrity": "sha512-VKXPRScCzAZqeBZOGq4LLwtNrAu++mVn7XvQox3eFDV7Ciq0Lg70Q8QWjH9iXF7J+pMlXhPsSFwpCb2E+hoeyA==",
"dev": true, "dev": true,
"requires": { "requires": {
"@types/pug": "^2.0.4", "@types/pug": "^2.0.4",
@@ -6025,6 +6168,12 @@
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true "dev": true
},
"yaml": {
"version": "1.10.2",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
"dev": true
} }
} }
} }

View File

@@ -1,6 +1,6 @@
{ {
"name": "obsidian-livesync", "name": "obsidian-livesync",
"version": "0.12.0", "version": "0.12.3",
"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",
@@ -23,22 +23,24 @@
"@typescript-eslint/parser": "^5.0.0", "@typescript-eslint/parser": "^5.0.0",
"builtin-modules": "^3.2.0", "builtin-modules": "^3.2.0",
"esbuild": "0.13.12", "esbuild": "0.13.12",
"esbuild-svelte": "^0.6.0", "esbuild-svelte": "^0.7.0",
"eslint": "^7.32.0", "eslint": "^7.32.0",
"eslint-config-airbnb-base": "^14.2.1", "eslint-config-airbnb-base": "^14.2.1",
"eslint-plugin-import": "^2.25.2", "eslint-plugin-import": "^2.25.2",
"obsidian": "^0.15.4", "obsidian": "^0.15.4",
"postcss": "^8.4.14",
"postcss-load-config": "^3.1.4",
"rollup": "^2.32.1", "rollup": "^2.32.1",
"svelte-preprocess": "^4.10.2", "svelte-preprocess": "^4.10.5",
"tslib": "^2.2.0", "tslib": "^2.2.0",
"typescript": "^4.2.4" "typescript": "^4.2.4"
}, },
"dependencies": { "dependencies": {
"diff-match-patch": "^1.0.5", "diff-match-patch": "^1.0.5",
"esbuild": "0.13.12", "esbuild": "0.13.12",
"esbuild-svelte": "^0.6.0", "esbuild-svelte": "^0.7.0",
"idb": "^7.0.1", "idb": "^7.0.2",
"svelte-preprocess": "^4.10.2", "svelte-preprocess": "^4.10.5",
"xxhash-wasm": "^0.4.2" "xxhash-wasm": "^0.4.2"
} }
} }

View File

@@ -1,9 +1,9 @@
import { TFile, Modal, App } from "obsidian"; import { TFile, Modal, App } from "obsidian";
import { path2id } from "./utils"; import { path2id } from "./utils";
import { escapeStringToHTML } from "./lib/src/utils"; import { base64ToArrayBuffer, base64ToString, escapeStringToHTML, isValidPath } from "./lib/src/utils";
import ObsidianLiveSyncPlugin from "./main"; import ObsidianLiveSyncPlugin from "./main";
import { DIFF_DELETE, DIFF_EQUAL, DIFF_INSERT, diff_match_patch } from "diff-match-patch"; import { DIFF_DELETE, DIFF_EQUAL, DIFF_INSERT, diff_match_patch } from "diff-match-patch";
import { LOG_LEVEL } from "./lib/src/types"; import { LoadedEntry, LOG_LEVEL } from "./lib/src/types";
import { Logger } from "./lib/src/logger"; import { Logger } from "./lib/src/logger";
export class DocumentHistoryModal extends Modal { export class DocumentHistoryModal extends Modal {
@@ -17,12 +17,13 @@ export class DocumentHistoryModal extends Modal {
file: string; file: string;
revs_info: PouchDB.Core.RevisionInfo[] = []; revs_info: PouchDB.Core.RevisionInfo[] = [];
currentDoc: LoadedEntry;
currentText = ""; currentText = "";
constructor(app: App, plugin: ObsidianLiveSyncPlugin, file: TFile) { constructor(app: App, plugin: ObsidianLiveSyncPlugin, file: TFile | string) {
super(app); super(app);
this.plugin = plugin; this.plugin = plugin;
this.file = file.path; this.file = (file instanceof TFile) ? file.path : file;
if (localStorage.getItem("ols-history-highlightdiff") == "1") { if (localStorage.getItem("ols-history-highlightdiff") == "1") {
this.showDiff = true; this.showDiff = true;
} }
@@ -47,9 +48,12 @@ export class DocumentHistoryModal extends Modal {
this.info.innerHTML = ""; this.info.innerHTML = "";
this.contentView.innerHTML = `Could not read this revision<br>(${rev.rev})`; this.contentView.innerHTML = `Could not read this revision<br>(${rev.rev})`;
} else { } else {
this.currentDoc = w;
this.info.innerHTML = `Modified:${new Date(w.mtime).toLocaleString()}`; this.info.innerHTML = `Modified:${new Date(w.mtime).toLocaleString()}`;
let result = ""; let result = "";
this.currentText = w.data; const w1data = w.datatype == "plain" ? w.data : base64ToString(w.data);
this.currentText = w1data;
if (this.showDiff) { if (this.showDiff) {
const prevRevIdx = this.revs_info.length - 1 - ((this.range.value as any) / 1 - 1); const prevRevIdx = this.revs_info.length - 1 - ((this.range.value as any) / 1 - 1);
if (prevRevIdx >= 0 && prevRevIdx < this.revs_info.length) { if (prevRevIdx >= 0 && prevRevIdx < this.revs_info.length) {
@@ -57,7 +61,8 @@ export class DocumentHistoryModal extends Modal {
const w2 = await db.getDBEntry(path2id(this.file), { rev: oldRev }, false, false); const w2 = await db.getDBEntry(path2id(this.file), { rev: oldRev }, false, false);
if (w2 != false) { if (w2 != false) {
const dmp = new diff_match_patch(); const dmp = new diff_match_patch();
const diff = dmp.diff_main(w2.data, w.data); const w2data = w2.datatype == "plain" ? w2.data : base64ToString(w2.data);
const diff = dmp.diff_main(w2data, w1data);
dmp.diff_cleanupSemantic(diff); dmp.diff_cleanupSemantic(diff);
for (const v of diff) { for (const v of diff) {
const x1 = v[0]; const x1 = v[0];
@@ -73,13 +78,13 @@ export class DocumentHistoryModal extends Modal {
result = result.replace(/\n/g, "<br>"); result = result.replace(/\n/g, "<br>");
} else { } else {
result = escapeStringToHTML(w.data); result = escapeStringToHTML(w1data);
} }
} else { } else {
result = escapeStringToHTML(w.data); result = escapeStringToHTML(w1data);
} }
} else { } else {
result = escapeStringToHTML(w.data); result = escapeStringToHTML(w1data);
} }
this.contentView.innerHTML = result; this.contentView.innerHTML = result;
} }
@@ -138,6 +143,25 @@ export class DocumentHistoryModal extends Modal {
Logger(`Old content copied to clipboard`, LOG_LEVEL.NOTICE); Logger(`Old content copied to clipboard`, LOG_LEVEL.NOTICE);
}); });
}); });
buttons.createEl("button", { text: "Back to this revision" }, (e) => {
e.addClass("mod-cta");
e.addEventListener("click", async () => {
const pathToWrite = this.file.startsWith("i:") ? this.file.substring("i:".length) : this.file;
if (!isValidPath(pathToWrite)) {
Logger("Path is not vaild to write content.", LOG_LEVEL.INFO);
}
if (this.currentDoc?.datatype == "plain") {
await this.app.vault.adapter.write(pathToWrite, this.currentDoc.data);
this.close();
} else if (this.currentDoc?.datatype == "newnote") {
await this.app.vault.adapter.writeBinary(pathToWrite, base64ToArrayBuffer(this.currentDoc.data));
this.close();
} else {
Logger(`Could not parse entry`, LOG_LEVEL.NOTICE);
}
});
});
} }
onClose() { onClose() {
const { contentEl } = this; const { contentEl } = this;

View File

@@ -1,4 +1,4 @@
import { App, PluginSettingTab, Setting, sanitizeHTMLToDom, RequestUrlParam, requestUrl } from "obsidian"; import { App, PluginSettingTab, Setting, sanitizeHTMLToDom, RequestUrlParam, requestUrl, TextAreaComponent } from "obsidian";
import { EntryDoc, LOG_LEVEL, RemoteDBSettings } from "./lib/src/types"; import { EntryDoc, LOG_LEVEL, RemoteDBSettings } from "./lib/src/types";
import { path2id, id2path } from "./utils"; import { path2id, id2path } from "./utils";
import { delay, runWithLock } from "./lib/src/utils"; import { delay, runWithLock } from "./lib/src/utils";
@@ -591,6 +591,31 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
}); });
text.inputEl.setAttribute("type", "number"); text.inputEl.setAttribute("type", "number");
}); });
let newDatabaseName = this.plugin.settings.additionalSuffixOfDatabaseName + "";
new Setting(containerLocalDatabaseEl)
.setName("Database suffix")
.setDesc("Set unique name for using same vault name on different directory.")
.addText((text) => {
text.setPlaceholder("")
.setValue(newDatabaseName)
.onChange((value) => {
newDatabaseName = value;
});
}).addButton((button) => {
button.setButtonText("Change")
.onClick(async () => {
if (this.plugin.settings.additionalSuffixOfDatabaseName == newDatabaseName) {
Logger("Suffix was not changed.", LOG_LEVEL.NOTICE);
return;
}
this.plugin.settings.additionalSuffixOfDatabaseName = newDatabaseName;
await this.plugin.saveSettings();
Logger("Suffix has been changed. Reopening database...", LOG_LEVEL.NOTICE);
await this.plugin.initializeDatabase();
})
})
addScreenElement("10", containerLocalDatabaseEl); addScreenElement("10", containerLocalDatabaseEl);
const containerGeneralSettingsEl = containerEl.createDiv(); const containerGeneralSettingsEl = containerEl.createDiv();
@@ -787,6 +812,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
); );
new Setting(containerSyncSettingEl) new Setting(containerSyncSettingEl)
.setName("Scan hidden files periodicaly.") .setName("Scan hidden files periodicaly.")
.setDesc("Seconds, zero to disable.")
.addText((text) => { .addText((text) => {
text.setPlaceholder("") text.setPlaceholder("")
.setValue(this.plugin.settings.syncInternalFilesInterval + "") .setValue(this.plugin.settings.syncInternalFilesInterval + "")
@@ -800,12 +826,15 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
}); });
text.inputEl.setAttribute("type", "number"); text.inputEl.setAttribute("type", "number");
}); });
let skipPatternTextArea: TextAreaComponent = null;
const defaultSkipPattern = "\\/node_modules\\/, \\/\\.git\\/, \\/obsidian-livesync\\/";
const defaultSkipPatternXPlat = defaultSkipPattern + ",\\/workspace$";
new Setting(containerSyncSettingEl) new Setting(containerSyncSettingEl)
.setName("Skip patterns") .setName("Skip patterns")
.setDesc( .setDesc(
"Regular expression" "Regular expression, If you use hidden file sync between desktop and mobile, adding `workspace$` is recommended."
) )
.addTextArea((text) => .addTextArea((text) => {
text text
.setValue(this.plugin.settings.syncInternalFilesIgnorePatterns) .setValue(this.plugin.settings.syncInternalFilesIgnorePatterns)
.setPlaceholder("\\/node_modules\\/, \\/\\.git\\/") .setPlaceholder("\\/node_modules\\/, \\/\\.git\\/")
@@ -813,7 +842,52 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
this.plugin.settings.syncInternalFilesIgnorePatterns = value; this.plugin.settings.syncInternalFilesIgnorePatterns = value;
await this.plugin.saveSettings(); await this.plugin.saveSettings();
}) })
skipPatternTextArea = text;
return text;
}
); );
new Setting(containerSyncSettingEl)
.setName("Skip patterns defaults")
.addButton((button) => {
button.setButtonText("Default")
.onClick(async () => {
skipPatternTextArea.setValue(defaultSkipPattern);
this.plugin.settings.syncInternalFilesIgnorePatterns = defaultSkipPattern;
await this.plugin.saveSettings();
})
}).addButton((button) => {
button.setButtonText("Cross-platform")
.onClick(async () => {
skipPatternTextArea.setValue(defaultSkipPatternXPlat);
this.plugin.settings.syncInternalFilesIgnorePatterns = defaultSkipPatternXPlat;
await this.plugin.saveSettings();
})
})
new Setting(containerSyncSettingEl)
.setName("Touch hidden files")
.setDesc("Update the modified time of all hidden files to the current time.")
.addButton((button) =>
button
.setButtonText("Touch")
.setWarning()
.setDisabled(false)
.setClass("sls-btn-left")
.onClick(async () => {
const filesAll = await this.plugin.scanInternalFiles();
const targetFiles = await this.plugin.filterTargetFiles(filesAll);
const now = Date.now();
const newFiles = targetFiles.map(e => ({ ...e, mtime: now }));
let i = 0;
const maxFiles = newFiles.length;
for (const file of newFiles) {
i++;
Logger(`Touched:${file.path} (${i}/${maxFiles})`, LOG_LEVEL.NOTICE, "touch-files");
await this.plugin.applyMTimeToFile(file);
}
})
)
containerSyncSettingEl.createEl("h3", { containerSyncSettingEl.createEl("h3", {
text: sanitizeHTMLToDom(`Advanced settings`), text: sanitizeHTMLToDom(`Advanced settings`),
}); });

Submodule src/lib updated: 1f67fb604c...1133f82732

View File

@@ -29,12 +29,14 @@ import { DocumentHistoryModal } from "./DocumentHistoryModal";
//@ts-ignore //@ts-ignore
import PluginPane from "./PluginPane.svelte"; import PluginPane from "./PluginPane.svelte";
import { id2path, path2id } from "./utils";
import { clearAllPeriodic, clearAllTriggers, disposeMemoObject, id2path, memoIfNotExist, memoObject, path2id, retriveMemoObject, setTrigger } from "./utils";
import { decrypt, encrypt } from "./lib/src/e2ee_v2"; import { decrypt, encrypt } from "./lib/src/e2ee_v2";
const isDebug = false; const isDebug = false;
setNoticeClass(Notice); setNoticeClass(Notice);
class PluginDialogModal extends Modal { class PluginDialogModal extends Modal {
plugin: ObsidianLiveSyncPlugin; plugin: ObsidianLiveSyncPlugin;
logEl: HTMLDivElement; logEl: HTMLDivElement;
@@ -118,19 +120,24 @@ class InputStringDialog extends Modal {
} }
} }
} }
class PopoverYesNo extends FuzzySuggestModal<string> { class PopoverSelectString extends FuzzySuggestModal<string> {
app: App; app: App;
callback: (e: string) => void = () => { }; callback: (e: string) => void = () => { };
getItemsFun: () => string[] = () => {
return ["yes", "no"];
constructor(app: App, note: string, callback: (e: string) => void) { }
constructor(app: App, note: string, placeholder: string | null, getItemsFun: () => string[], callback: (e: string) => void) {
super(app); super(app);
this.app = app; this.app = app;
this.setPlaceholder("y/n) " + note); this.setPlaceholder(placeholder ?? "y/n) " + note);
if (getItemsFun) this.getItemsFun = getItemsFun;
this.callback = callback; this.callback = callback;
} }
getItems(): string[] { getItems(): string[] {
return ["yes", "no"]; return this.getItemsFun();
} }
getItemText(item: string): string { getItemText(item: string): string {
@@ -153,11 +160,20 @@ class PopoverYesNo extends FuzzySuggestModal<string> {
const askYesNo = (app: App, message: string): Promise<"yes" | "no"> => { const askYesNo = (app: App, message: string): Promise<"yes" | "no"> => {
return new Promise((res) => { return new Promise((res) => {
const popover = new PopoverYesNo(app, message, (result) => res(result as "yes" | "no")); const popover = new PopoverSelectString(app, message, null, null, (result) => res(result as "yes" | "no"));
popover.open(); popover.open();
}); });
}; };
const askSelectString = (app: App, message: string, items: string[]): Promise<string> => {
const getItemsFun = () => items;
return new Promise((res) => {
const popover = new PopoverSelectString(app, message, "Select file)", getItemsFun, (result) => res(result));
popover.open();
});
};
const askString = (app: App, title: string, key: string, placeholder: string): Promise<string | false> => { const askString = (app: App, title: string, key: string, placeholder: string): Promise<string | false> => {
return new Promise((res) => { return new Promise((res) => {
const dialog = new InputStringDialog(app, title, key, placeholder, (result) => res(result)); const dialog = new InputStringDialog(app, title, key, placeholder, (result) => res(result));
@@ -189,6 +205,10 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
deviceAndVaultName: string; deviceAndVaultName: string;
isMobile = false; isMobile = false;
getVaultName(): string {
return this.app.vault.getName() + (this.settings?.additionalSuffixOfDatabaseName ? ("-" + this.settings.additionalSuffixOfDatabaseName) : "");
}
setInterval(handler: () => any, timeout?: number): number { setInterval(handler: () => any, timeout?: number): number {
const timer = window.setInterval(handler, timeout); const timer = window.setInterval(handler, timeout);
this.registerInterval(timer); this.registerInterval(timer);
@@ -203,7 +223,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
return false; return false;
} }
showHistory(file: TFile) { showHistory(file: TFile | string) {
if (!this.settings.useHistory) { if (!this.settings.useHistory) {
Logger("You have to enable Use History in misc.", LOG_LEVEL.NOTICE); Logger("You have to enable Use History in misc.", LOG_LEVEL.NOTICE);
} else { } else {
@@ -211,10 +231,38 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
} }
} }
async fileHistory() {
const pageLimit = 2500;
let nextKey = "";
const notes = [];
do {
const docs = await this.localDatabase.localDatabase.allDocs({ limit: pageLimit, startkey: nextKey, include_docs: true });
nextKey = "";
for (const row of docs.rows) {
const doc = row.doc;
nextKey = `${row.id}\u{10ffff}`;
if (!("type" in doc)) continue;
if (doc.type == "newnote" || doc.type == "plain") {
// const docId = doc._id.startsWith("i:") ? doc._id.substring("i:".length) : doc._id;
notes.push(id2path(doc._id))
}
}
} while (nextKey != "");
const target = await askSelectString(this.app, "File to view History", notes);
if (target) {
this.showHistory(target);
}
}
async onload() { async onload() {
setLogger(this.addLog.bind(this)); // Logger moved to global. setLogger(this.addLog.bind(this)); // Logger moved to global.
Logger("loading plugin"); Logger("loading plugin");
const lsname = "obsidian-live-sync-ver" + this.app.vault.getName(); //@ts-ignore
const manifestVersion = MANIFEST_VERSION || "-";
//@ts-ignore
const packageVersion = PACKAGE_VERSION || "-";
Logger(`Self-hosted LiveSync v${manifestVersion} ${packageVersion} `);
const lsname = "obsidian-live-sync-ver" + this.getVaultName();
const last_version = localStorage.getItem(lsname); const last_version = localStorage.getItem(lsname);
await this.loadSettings(); await this.loadSettings();
//@ts-ignore //@ts-ignore
@@ -296,6 +344,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
this.settings.autoSweepPlugins = false; this.settings.autoSweepPlugins = false;
this.settings.usePluginSync = false; this.settings.usePluginSync = false;
this.settings.suspendFileWatching = true; this.settings.suspendFileWatching = true;
this.settings.syncInternalFiles = false;
await this.saveSettings(); await this.saveSettings();
await this.openDatabase(); await this.openDatabase();
const warningMessage = "The red flag is raised! The whole initialize steps are skipped, and any file changes are not captured."; const warningMessage = "The red flag is raised! The whole initialize steps are skipped, and any file changes are not captured.";
@@ -489,6 +538,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
this.showHistory(view.file); this.showHistory(view.file);
}, },
}); });
this.triggerRealizeSettingSyncMode = debounce(this.triggerRealizeSettingSyncMode.bind(this), 1000); this.triggerRealizeSettingSyncMode = debounce(this.triggerRealizeSettingSyncMode.bind(this), 1000);
this.triggerCheckPluginUpdate = debounce(this.triggerCheckPluginUpdate.bind(this), 3000); this.triggerCheckPluginUpdate = debounce(this.triggerCheckPluginUpdate.bind(this), 3000);
setLockNotifier(() => { setLockNotifier(() => {
@@ -509,6 +559,14 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
this.syncInternalFilesAndDatabase("safe", true); this.syncInternalFilesAndDatabase("safe", true);
}, },
}); });
this.addCommand({
id: "livesync-filehistory",
name: "Pick file to show history",
callback: () => {
this.fileHistory();
},
})
} }
pluginDialog: PluginDialogModal = null; pluginDialog: PluginDialogModal = null;
@@ -545,6 +603,8 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
this.localDatabase.closeReplication(); this.localDatabase.closeReplication();
this.localDatabase.close(); this.localDatabase.close();
} }
clearAllPeriodic();
clearAllTriggers();
window.removeEventListener("visibilitychange", this.watchWindowVisiblity); window.removeEventListener("visibilitychange", this.watchWindowVisiblity);
Logger("unloading plugin"); Logger("unloading plugin");
} }
@@ -553,7 +613,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
if (this.localDatabase != null) { if (this.localDatabase != null) {
this.localDatabase.close(); this.localDatabase.close();
} }
const vaultName = this.app.vault.getName(); const vaultName = this.getVaultName();
Logger("Open Database..."); Logger("Open Database...");
//@ts-ignore //@ts-ignore
const isMobile = this.app.isMobile; const isMobile = this.app.isMobile;
@@ -580,7 +640,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
// So, use history is always enabled. // So, use history is always enabled.
this.settings.useHistory = true; this.settings.useHistory = true;
const lsname = "obsidian-live-sync-vaultanddevicename-" + this.app.vault.getName(); const lsname = "obsidian-live-sync-vaultanddevicename-" + this.getVaultName();
if (this.settings.deviceAndVaultName != "") { if (this.settings.deviceAndVaultName != "") {
if (!localStorage.getItem(lsname)) { if (!localStorage.getItem(lsname)) {
this.deviceAndVaultName = this.settings.deviceAndVaultName; this.deviceAndVaultName = this.settings.deviceAndVaultName;
@@ -596,7 +656,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
} }
async saveSettings() { async saveSettings() {
const lsname = "obsidian-live-sync-vaultanddevicename-" + this.app.vault.getName(); const lsname = "obsidian-live-sync-vaultanddevicename-" + this.getVaultName();
localStorage.setItem(lsname, this.deviceAndVaultName || ""); localStorage.setItem(lsname, this.deviceAndVaultName || "");
await this.saveData(this.settings); await this.saveData(this.settings);
@@ -859,7 +919,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
if (this.settings && !this.settings.showVerboseLog && level == LOG_LEVEL.VERBOSE) { if (this.settings && !this.settings.showVerboseLog && level == LOG_LEVEL.VERBOSE) {
return; return;
} }
const valutName = this.app.vault.getName(); const valutName = this.getVaultName();
const timestamp = new Date().toLocaleString(); const timestamp = new Date().toLocaleString();
const messagecontent = typeof message == "string" ? message : message instanceof Error ? `${message.name}:${message.message}` : JSON.stringify(message, null, 2); const messagecontent = typeof message == "string" ? message : message instanceof Error ? `${message.name}:${message.message}` : JSON.stringify(message, null, 2);
const newmessage = timestamp + "->" + messagecontent; const newmessage = timestamp + "->" + messagecontent;
@@ -1113,11 +1173,11 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
saveQueuedFiles() { saveQueuedFiles() {
const saveData = JSON.stringify(this.queuedFiles.filter((e) => !e.done).map((e) => e.entry._id)); const saveData = JSON.stringify(this.queuedFiles.filter((e) => !e.done).map((e) => e.entry._id));
const lsname = "obsidian-livesync-queuefiles-" + this.app.vault.getName(); const lsname = "obsidian-livesync-queuefiles-" + this.getVaultName();
localStorage.setItem(lsname, saveData); localStorage.setItem(lsname, saveData);
} }
async loadQueuedFiles() { async loadQueuedFiles() {
const lsname = "obsidian-livesync-queuefiles-" + this.app.vault.getName(); const lsname = "obsidian-livesync-queuefiles-" + this.getVaultName();
const ids = JSON.parse(localStorage.getItem(lsname) || "[]") as string[]; const ids = JSON.parse(localStorage.getItem(lsname) || "[]") as string[];
const ret = await this.localDatabase.localDatabase.allDocs({ keys: ids, include_docs: true }); const ret = await this.localDatabase.localDatabase.allDocs({ keys: ids, include_docs: true });
for (const doc of ret.rows) { for (const doc of ret.rows) {
@@ -1499,7 +1559,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
Logger("Updating database by new files"); Logger("Updating database by new files");
this.setStatusBarText(`UPDATE DATABASE`); this.setStatusBarText(`UPDATE DATABASE`);
const runAll = async <T>(procedurename: string, objects: T[], callback: (arg: T) => Promise<void>) => { const runAll = async<T>(procedurename: string, objects: T[], callback: (arg: T) => Promise<void>) => {
const count = objects.length; const count = objects.length;
Logger(procedurename); Logger(procedurename);
let i = 0; let i = 0;
@@ -1535,6 +1595,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
await p.all(); await p.all();
Logger(`${procedurename} done.`); Logger(`${procedurename} done.`);
}; };
await runAll("UPDATE DATABASE", onlyInStorage, async (e) => { await runAll("UPDATE DATABASE", onlyInStorage, async (e) => {
Logger(`Update into ${e.path}`); Logger(`Update into ${e.path}`);
@@ -1563,7 +1624,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
} }
this.setStatusBarText(`NOW TRACKING!`); this.setStatusBarText(`NOW TRACKING!`);
Logger("Initialized,NOW TRACKING!"); Logger("Initialized, NOW TRACKING!");
if (!isInitialized) { if (!isInitialized) {
await (this.localDatabase.kvDB.set("initialized", true)) await (this.localDatabase.kvDB.set("initialized", true))
} }
@@ -2145,30 +2206,39 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
async getFiles( async getFiles(
path: string, path: string,
ignoreList: string[], ignoreList: string[],
filter: RegExp[] filter: RegExp[],
ignoreFilter: RegExp[],
) { ) {
const w = await this.app.vault.adapter.list(path); const w = await this.app.vault.adapter.list(path);
let files = [ let files = [
...w.files ...w.files
.filter((e) => !ignoreList.some((ee) => e.endsWith(ee))) .filter((e) => !ignoreList.some((ee) => e.endsWith(ee)))
.filter((e) => !filter || filter.some((ee) => e.match(ee))), .filter((e) => !filter || filter.some((ee) => e.match(ee)))
.filter((e) => !ignoreFilter || ignoreFilter.every((ee) => !e.match(ee))),
]; ];
L1: for (const v of w.folders) { L1: for (const v of w.folders) {
for (const ignore of ignoreList) { for (const ignore of ignoreList) {
if (v.endsWith(ignore)) { if (v.endsWith(ignore)) {
continue L1; continue L1;
} }
} }
files = files.concat(await this.getFiles(v, ignoreList, filter)); if (ignoreFilter && ignoreFilter.some(e => v.match(e))) {
continue L1;
}
files = files.concat(await this.getFiles(v, ignoreList, filter, ignoreFilter));
} }
return files; return files;
} }
async scanInternalFiles(): Promise<InternalFileInfo[]> { async scanInternalFiles(): Promise<InternalFileInfo[]> {
const ignoreFiles = ["node_modules", ".git", "obsidian-pouch"]; const ignoreFilter = this.settings.syncInternalFilesIgnorePatterns.toLocaleLowerCase()
.replace(/\n| /g, "")
.split(",").filter(e => e).map(e => new RegExp(e));
const root = this.app.vault.getRoot(); const root = this.app.vault.getRoot();
const findRoot = root.path; const findRoot = root.path;
const filenames = (await this.getFiles(findRoot, ignoreFiles, null)).filter(e => e.startsWith(".")).filter(e => !e.startsWith(".trash")); const filenames = (await this.getFiles(findRoot, [], null, ignoreFilter)).filter(e => e.startsWith(".")).filter(e => !e.startsWith(".trash"));
const files = filenames.map(async e => { const files = filenames.map(async e => {
return { return {
path: e, path: e,
@@ -2224,7 +2294,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
} }
} }
await this.localDatabase.putDBEntry(saveData, true); await this.localDatabase.putDBEntry(saveData, true);
Logger(`internal files STORAGE --> DB:${file.path}: Done`); Logger(`STORAGE --> DB:${file.path}: (hidden) Done`);
}); });
} }
@@ -2328,8 +2398,19 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
}); });
} }
filterTargetFiles(files: InternalFileInfo[], targetFiles: string[] | false = false) {
const ignorePatterns = this.settings.syncInternalFilesIgnorePatterns.toLocaleLowerCase()
.replace(/\n| /g, "")
.split(",").filter(e => e).map(e => new RegExp(e));
return files.filter(file => !ignorePatterns.some(e => file.path.match(e))).filter(file => !targetFiles || (targetFiles && targetFiles.indexOf(file.path) !== -1))
}
async applyMTimeToFile(file: InternalFileInfo) {
await this.app.vault.adapter.append(file.path, "", { ctime: file.ctime, mtime: file.mtime });
}
confirmPopup: WrappedNotice = null; confirmPopup: WrappedNotice = null;
confirmPopupTimer: number = null;
//TODO: Tidy up. Even though it is experimental feature, So dirty...
async syncInternalFilesAndDatabase(direction: "push" | "pull" | "safe", showMessage: boolean, files: InternalFileInfo[] | false = false, targetFiles: string[] | false = false) { async syncInternalFilesAndDatabase(direction: "push" | "pull" | "safe", showMessage: boolean, files: InternalFileInfo[] | false = false, targetFiles: string[] | false = false) {
const logLevel = showMessage ? LOG_LEVEL.NOTICE : LOG_LEVEL.INFO; const logLevel = showMessage ? LOG_LEVEL.NOTICE : LOG_LEVEL.INFO;
Logger("Scanning hidden files.", logLevel, "sync_internal"); Logger("Scanning hidden files.", logLevel, "sync_internal");
@@ -2338,7 +2419,8 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
.split(",").filter(e => e).map(e => new RegExp(e)); .split(",").filter(e => e).map(e => new RegExp(e));
if (!files) files = await this.scanInternalFiles(); if (!files) files = await this.scanInternalFiles();
const filesOnDB = (await this.localDatabase.localDatabase.allDocs({ startkey: "i:", endkey: "i;", include_docs: true })).rows.map(e => e.doc) as InternalFileEntry[]; const filesOnDB = (await this.localDatabase.localDatabase.allDocs({ startkey: "i:", endkey: "i;", include_docs: true })).rows.map(e => e.doc) as InternalFileEntry[];
const allFileNames = [...new Set([...files.map(e => normalizePath(e.path)), ...filesOnDB.map(e => normalizePath(id2path(e._id.substring("i:".length))))])]; const allFileNamesSrc = [...new Set([...files.map(e => normalizePath(e.path)), ...filesOnDB.map(e => normalizePath(id2path(e._id.substring("i:".length))))])];
const allFileNames = allFileNamesSrc.filter(filename => !targetFiles || (targetFiles && targetFiles.indexOf(filename) !== -1))
function compareMTime(a: number, b: number) { function compareMTime(a: number, b: number) {
const wa = ~~(a / 1000); const wa = ~~(a / 1000);
const wb = ~~(b / 1000); const wb = ~~(b / 1000);
@@ -2351,35 +2433,68 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
let filesChanged = 0; let filesChanged = 0;
const p = Parallels(); const p = Parallels();
const limit = 10; const limit = 10;
// count updated files up as like this below:
// .obsidian: 2
// .obsidian/workspace: 1
// .obsidian/plugins: 1
// .obsidian/plugins/recent-files-obsidian: 1
// .obsidian/plugins/recent-files-obsidian/data.json: 1
const updatedFolders: { [key: string]: number } = {}
const countUpdatedFolder = (path: string) => {
const pieces = path.split("/");
let c = pieces.shift();
let pathPieces = "";
filesChanged++;
while (c) {
pathPieces += (pathPieces != "" ? "/" : "") + c;
pathPieces = normalizePath(pathPieces);
if (!(pathPieces in updatedFolders)) {
updatedFolders[pathPieces] = 0;
}
updatedFolders[pathPieces]++;
c = pieces.shift();
}
}
// Cache update time information for files which have already been processed (mainly for files that were skipped due to the same content)
let caches: { [key: string]: { storageMtime: number; docMtime: number } } = {};
caches = await this.localDatabase.kvDB.get<{ [key: string]: { storageMtime: number; docMtime: number } }>("diff-caches-internal") || {};
for (const filename of allFileNames) { for (const filename of allFileNames) {
// Logger(`Processing:${filename}`, LOG_LEVEL.VERBOSE);
processed++; processed++;
if (processed % 100 == 0) Logger(`Hidden file: ${processed}/${fileCount}`, logLevel, "sync_internal"); if (processed % 100 == 0) Logger(`Hidden file: ${processed}/${fileCount}`, logLevel, "sync_internal");
if (ignorePatterns.some(e => filename.match(e))) continue; if (ignorePatterns.some(e => filename.match(e))) continue;
if (targetFiles !== false && targetFiles.indexOf(filename) == -1) continue;
const fileOnStorage = files.find(e => e.path == filename); const fileOnStorage = files.find(e => e.path == filename);
const fileOnDatabase = filesOnDB.find(e => e._id == "i:" + id2path(filename)); const fileOnDatabase = filesOnDB.find(e => e._id == "i:" + id2path(filename));
let proc: () => Promise<void> | null = null; // TODO: Fix this somehow smart.
let proc: Promise<void> | null;
if (fileOnStorage && fileOnDatabase) { if (fileOnStorage && fileOnDatabase) {
// Both => Synchronize // Both => Synchronize
const cache = filename in caches ? caches[filename] : { storageMtime: 0, docMtime: 0 };
if (fileOnDatabase.mtime == cache.docMtime && fileOnStorage.mtime == cache.storageMtime) {
continue;
}
const nw = compareMTime(fileOnStorage.mtime, fileOnDatabase.mtime); const nw = compareMTime(fileOnStorage.mtime, fileOnDatabase.mtime);
if (nw == 0) continue; if (nw == 0) continue;
if (nw > 0) { if (nw > 0) {
proc = async () => { proc = (async (fileOnStorage) => {
await this.storeInternaFileToDatabase(fileOnStorage); await this.storeInternaFileToDatabase(fileOnStorage);
} cache.docMtime = fileOnDatabase.mtime;
cache.storageMtime = fileOnStorage.mtime;
caches[filename] = cache;
})(fileOnStorage);
} }
if (nw < 0) { if (nw < 0) {
proc = async () => { proc = (async (filename) => {
if (await this.extractInternaFileFromDatabase(filename)) { if (await this.extractInternaFileFromDatabase(filename)) {
filesChanged++; cache.docMtime = fileOnDatabase.mtime;
cache.storageMtime = fileOnStorage.mtime;
caches[filename] = cache;
countUpdatedFolder(filename);
} }
} })(filename);
} }
} else if (!fileOnStorage && fileOnDatabase) { } else if (!fileOnStorage && fileOnDatabase) {
@@ -2387,71 +2502,137 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
if (fileOnDatabase.deleted) { if (fileOnDatabase.deleted) {
// await this.storeInternaFileToDatabase(fileOnStorage); // await this.storeInternaFileToDatabase(fileOnStorage);
} else { } else {
proc = async () => { proc = (async () => {
await this.deleteInternaFileOnDatabase(filename); await this.deleteInternaFileOnDatabase(filename);
} })();
} }
} else if (direction == "pull") { } else if (direction == "pull") {
proc = async () => { proc = (async () => {
if (await this.extractInternaFileFromDatabase(filename)) { if (await this.extractInternaFileFromDatabase(filename)) {
filesChanged++; countUpdatedFolder(filename);
} }
} })();
} else if (direction == "safe") { } else if (direction == "safe") {
if (fileOnDatabase.deleted) { if (fileOnDatabase.deleted) {
// await this.storeInternaFileToDatabase(fileOnStorage); // await this.storeInternaFileToDatabase(fileOnStorage);
} else { } else {
proc = async () => { proc = (async () => {
if (await this.extractInternaFileFromDatabase(filename)) { if (await this.extractInternaFileFromDatabase(filename)) {
filesChanged++; countUpdatedFolder(filename);
} }
} })();
} }
} }
} else if (fileOnStorage && !fileOnDatabase) { } else if (fileOnStorage && !fileOnDatabase) {
proc = async () => { proc = (async () => {
await this.storeInternaFileToDatabase(fileOnStorage); await this.storeInternaFileToDatabase(fileOnStorage);
} })();
} else { } else {
throw new Error("Invalid state on hidden file sync"); throw new Error("Invalid state on hidden file sync");
// Something corrupted? // Something corrupted?
} }
if (proc) p.add(proc()); if (proc) p.add(proc);
proc = null;
await p.wait(limit); await p.wait(limit);
} }
await p.all(); await p.all();
// Show notification to restart obsidian. await this.localDatabase.kvDB.set("diff-caches-internal", caches);
// When files has been retreived from the database. they must be reloaded.
if (direction == "pull" && filesChanged != 0) { if (direction == "pull" && filesChanged != 0) {
const configDir = normalizePath(this.app.vault.configDir);
// Show notification to restart obsidian when something has been changed in configDir.
if (configDir in updatedFolders) {
// Numbers of updated files that is below of configDir.
let updatedCount = updatedFolders[configDir];
try {
//@ts-ignore
const manifests = Object.values(this.app.plugins.manifests) as PluginManifest[];
//@ts-ignore
const enabledPlugins = this.app.plugins.enabledPlugins as Set<string>;
const enabledPluginManifests = manifests.filter(e => enabledPlugins.has(e.id));
for (const manifest of enabledPluginManifests) {
if (manifest.dir in updatedFolders) {
// If notified about plug-ins, reloading Obsidian may not be necessary.
updatedCount -= updatedFolders[manifest.dir];
const updatePluginId = manifest.id;
const updatePluginName = manifest.name;
const fragment = createFragment((doc) => {
doc.createEl("span", null, (a) => {
a.appendText(`Files in ${updatePluginName} has been updated, Press `)
a.appendChild(a.createEl("a", null, (anchor) => {
anchor.text = "HERE";
anchor.addEventListener("click", async () => {
Logger(`Unloading plugin: ${updatePluginName}`, LOG_LEVEL.NOTICE, "pluin-reload-" + updatePluginId);
// @ts-ignore
await this.app.plugins.unloadPlugin(updatePluginId);
// @ts-ignore
await this.app.plugins.loadPlugin(updatePluginId);
Logger(`Plugin reloaded: ${updatePluginName}`, LOG_LEVEL.NOTICE, "pluin-reload-" + updatePluginId);
});
}))
const fragment = createFragment((doc) => { a.appendText(` to reload ${updatePluginName}, or press elsewhere to dismiss this message.`)
doc.createEl("span", null, (a) => { });
a.appendText(`Hidden files have been synchronized, Press `) });
a.appendChild(a.createEl("a", null, (anchor) => {
anchor.text = "HERE"; const updatedPluginKey = "popupUpdated-" + updatePluginId;
anchor.addEventListener("click", () => { setTrigger(updatedPluginKey, 1000, async () => {
// @ts-ignore const popup = await memoIfNotExist(updatedPluginKey, () => new Notice(fragment, 0));
this.app.commands.executeCommandById("app:reload") //@ts-ignore
const isShown = popup?.noticeEl?.isShown();
if (!isShown) {
memoObject(updatedPluginKey, new Notice(fragment, 0))
}
setTrigger(updatedPluginKey + "-close", 20000, () => {
const popup = retriveMemoObject<Notice>(updatedPluginKey)
if (!popup) return;
//@ts-ignore
if (popup?.noticeEl?.isShown()) {
popup.hide();
}
disposeMemoObject(updatedPluginKey);
})
})
}
}
} catch (ex) {
Logger("Error on checking plugin status.");
Logger(ex, LOG_LEVEL.VERBOSE);
}
// If something changes left, notify for reloading Obsidian.
if (updatedCount != 0) {
const fragment = createFragment((doc) => {
doc.createEl("span", null, (a) => {
a.appendText(`Hidden files have been synchronized, Press `)
a.appendChild(a.createEl("a", null, (anchor) => {
anchor.text = "HERE";
anchor.addEventListener("click", () => {
// @ts-ignore
this.app.commands.executeCommandById("app:reload")
});
}))
a.appendText(` to reload obsidian, or press elsewhere to dismiss this message.`)
}); });
})) });
a.appendText(` to reload obsidian, or press elsewhere to dismiss this message.`) setTrigger("popupUpdated-" + configDir, 1000, () => {
}); //@ts-ignore
}); const isShown = this.confirmPopup?.noticeEl?.isShown();
//@ts-ignore if (!isShown) {
const isShown = this.confirmPopup?.noticeEl?.isShown(); this.confirmPopup = new Notice(fragment, 0);
if (!isShown) { }
this.confirmPopup = new Notice(fragment, 0); setTrigger("popupClose" + configDir, 20000, () => {
this.confirmPopup?.hide();
this.confirmPopup = null;
})
})
}
} }
if (this.confirmPopupTimer != null) {
clearTimeout(this.confirmPopupTimer);
}
setTimeout(() => {
this.confirmPopup?.hide();
this.confirmPopup = null;
}, 10000)
} }
Logger(`Hidden files scanned`, logLevel, "sync_internal"); Logger(`Hidden files scanned: ${filesChanged} files had been modified`, logLevel, "sync_internal");
} }
} }

View File

@@ -12,3 +12,64 @@ export function path2id(filename: string): string {
export function id2path(filename: string): string { export function id2path(filename: string): string {
return id2path_base(normalizePath(filename)); return id2path_base(normalizePath(filename));
} }
const triggers: { [key: string]: ReturnType<typeof setTimeout> } = {};
export function setTrigger(key: string, timeout: number, proc: (() => Promise<any> | void)) {
clearTrigger(key);
triggers[key] = setTimeout(async () => {
delete triggers[key];
await proc();
}, timeout);
}
export function clearTrigger(key: string) {
if (key in triggers) {
clearTimeout(triggers[key]);
}
}
export function clearAllTriggers() {
for (const v in triggers) {
clearTimeout(triggers[v]);
}
}
const intervals: { [key: string]: ReturnType<typeof setInterval> } = {};
export function setPeriodic(key: string, timeout: number, proc: (() => Promise<any> | void)) {
clearPeriodic(key);
intervals[key] = setInterval(async () => {
delete intervals[key];
await proc();
}, timeout);
}
export function clearPeriodic(key: string) {
if (key in intervals) {
clearInterval(intervals[key]);
}
}
export function clearAllPeriodic() {
for (const v in intervals) {
clearInterval(intervals[v]);
}
}
const memos: { [key: string]: any } = {};
export function memoObject<T>(key: string, obj: T): T {
memos[key] = obj;
return memos[key] as T;
}
export async function memoIfNotExist<T>(key: string, func: () => T | Promise<T>): Promise<T> {
if (!(key in memos)) {
const w = func();
const v = w instanceof Promise ? (await w) : w;
memos[key] = v;
}
return memos[key] as T;
}
export function retriveMemoObject<T>(key: string): T | false {
if (key in memos) {
return memos[key];
} else {
return false;
}
}
export function disposeMemoObject(key: string) {
delete memos[key];
}