Merge remote-tracking branch 'origin/develop' into feature/better_sidebar

This commit is contained in:
Elian Doran 2025-01-20 08:36:17 +02:00
commit 74b78e7a2c
No known key found for this signature in database
6 changed files with 62 additions and 45 deletions

View File

@ -464,9 +464,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/better-sqlite3": { "node_modules/better-sqlite3": {
"version": "11.8.0", "version": "11.8.1",
"resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-11.8.0.tgz", "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-11.8.1.tgz",
"integrity": "sha512-aKv9s2dir7bsEX5RIjL9HHWB9uQ+f6Vch5B4qmeAOop4Y9OYHX+PNKLr+mpv6+d8L/ZYh4l7H8zPuVMbWkVMLw==", "integrity": "sha512-9BxNaBkblMjhJW8sMRZxnxVTRgbRmssZW0Oxc1MPBTfiR+WW21e2Mk4qu8CzrcZb1LwPCnFsfDEzq+SNcBU8eg==",
"hasInstallScript": true, "hasInstallScript": true,
"dependencies": { "dependencies": {
"bindings": "^1.5.0", "bindings": "^1.5.0",
@ -1516,9 +1516,9 @@
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="
}, },
"better-sqlite3": { "better-sqlite3": {
"version": "11.8.0", "version": "11.8.1",
"resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-11.8.0.tgz", "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-11.8.1.tgz",
"integrity": "sha512-aKv9s2dir7bsEX5RIjL9HHWB9uQ+f6Vch5B4qmeAOop4Y9OYHX+PNKLr+mpv6+d8L/ZYh4l7H8zPuVMbWkVMLw==", "integrity": "sha512-9BxNaBkblMjhJW8sMRZxnxVTRgbRmssZW0Oxc1MPBTfiR+WW21e2Mk4qu8CzrcZb1LwPCnFsfDEzq+SNcBU8eg==",
"requires": { "requires": {
"bindings": "^1.5.0", "bindings": "^1.5.0",
"prebuild-install": "^7.1.1" "prebuild-install": "^7.1.1"

31
package-lock.json generated
View File

@ -16,12 +16,12 @@
"@mermaid-js/layout-elk": "0.1.7", "@mermaid-js/layout-elk": "0.1.7",
"@mind-elixir/node-menu": "1.0.3", "@mind-elixir/node-menu": "1.0.3",
"@triliumnext/express-partial-content": "1.0.1", "@triliumnext/express-partial-content": "1.0.1",
"@types/react-dom": "18.3.1", "@types/react-dom": "18.3.5",
"archiver": "7.0.1", "archiver": "7.0.1",
"async-mutex": "0.5.0", "async-mutex": "0.5.0",
"autocomplete.js": "0.38.1", "autocomplete.js": "0.38.1",
"axios": "1.7.9", "axios": "1.7.9",
"better-sqlite3": "11.8.0", "better-sqlite3": "11.8.1",
"bootstrap": "5.3.3", "bootstrap": "5.3.3",
"boxicons": "2.1.4", "boxicons": "2.1.4",
"cheerio": "1.0.0", "cheerio": "1.0.0",
@ -134,7 +134,7 @@
"@types/mime-types": "2.1.4", "@types/mime-types": "2.1.4",
"@types/multer": "1.4.12", "@types/multer": "1.4.12",
"@types/node": "22.10.7", "@types/node": "22.10.7",
"@types/react": "18.3.1", "@types/react": "18.3.18",
"@types/safe-compare": "1.1.2", "@types/safe-compare": "1.1.2",
"@types/sanitize-html": "2.13.0", "@types/sanitize-html": "2.13.0",
"@types/sax": "1.2.7", "@types/sax": "1.2.7",
@ -3918,6 +3918,7 @@
"version": "15.7.14", "version": "15.7.14",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz",
"integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==", "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/qs": { "node_modules/@types/qs": {
@ -3935,9 +3936,10 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/react": { "node_modules/@types/react": {
"version": "18.3.1", "version": "18.3.18",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.1.tgz", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.18.tgz",
"integrity": "sha512-V0kuGBX3+prX+DQ/7r2qsv1NsdfnCLnTgnRJ1pYnxykBhGMz+qj+box5lq7XsO5mtZsBqpjwwTu/7wszPfMBcw==", "integrity": "sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@types/prop-types": "*", "@types/prop-types": "*",
@ -3945,12 +3947,12 @@
} }
}, },
"node_modules/@types/react-dom": { "node_modules/@types/react-dom": {
"version": "18.3.1", "version": "18.3.5",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.1.tgz", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.5.tgz",
"integrity": "sha512-qW1Mfv8taImTthu4KoXgDfLuk4bydU6Q/TkADnDWWHwi4NX4BR+LWfTp2sVmTqRrsHvyDDTelgelxJ+SsejKKQ==", "integrity": "sha512-P4t6saawp+b/dFrUr2cvkVsfvPguwsxtH6dNIYRllMsefqFzkZk5UIjzyDOv5g1dXIPdG4Sp1yCR4Z6RCUsG/Q==",
"license": "MIT", "license": "MIT",
"dependencies": { "peerDependencies": {
"@types/react": "*" "@types/react": "^18.0.0"
} }
}, },
"node_modules/@types/readdir-glob": { "node_modules/@types/readdir-glob": {
@ -5140,9 +5142,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/better-sqlite3": { "node_modules/better-sqlite3": {
"version": "11.8.0", "version": "11.8.1",
"resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-11.8.0.tgz", "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-11.8.1.tgz",
"integrity": "sha512-aKv9s2dir7bsEX5RIjL9HHWB9uQ+f6Vch5B4qmeAOop4Y9OYHX+PNKLr+mpv6+d8L/ZYh4l7H8zPuVMbWkVMLw==", "integrity": "sha512-9BxNaBkblMjhJW8sMRZxnxVTRgbRmssZW0Oxc1MPBTfiR+WW21e2Mk4qu8CzrcZb1LwPCnFsfDEzq+SNcBU8eg==",
"hasInstallScript": true, "hasInstallScript": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@ -6683,6 +6685,7 @@
"version": "3.1.3", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/cytoscape": { "node_modules/cytoscape": {

View File

@ -61,12 +61,12 @@
"@mermaid-js/layout-elk": "0.1.7", "@mermaid-js/layout-elk": "0.1.7",
"@mind-elixir/node-menu": "1.0.3", "@mind-elixir/node-menu": "1.0.3",
"@triliumnext/express-partial-content": "1.0.1", "@triliumnext/express-partial-content": "1.0.1",
"@types/react-dom": "18.3.1", "@types/react-dom": "18.3.5",
"archiver": "7.0.1", "archiver": "7.0.1",
"async-mutex": "0.5.0", "async-mutex": "0.5.0",
"autocomplete.js": "0.38.1", "autocomplete.js": "0.38.1",
"axios": "1.7.9", "axios": "1.7.9",
"better-sqlite3": "11.8.0", "better-sqlite3": "11.8.1",
"bootstrap": "5.3.3", "bootstrap": "5.3.3",
"boxicons": "2.1.4", "boxicons": "2.1.4",
"cheerio": "1.0.0", "cheerio": "1.0.0",
@ -176,7 +176,7 @@
"@types/mime-types": "2.1.4", "@types/mime-types": "2.1.4",
"@types/multer": "1.4.12", "@types/multer": "1.4.12",
"@types/node": "22.10.7", "@types/node": "22.10.7",
"@types/react": "18.3.1", "@types/react": "18.3.18",
"@types/safe-compare": "1.1.2", "@types/safe-compare": "1.1.2",
"@types/sanitize-html": "2.13.0", "@types/sanitize-html": "2.13.0",
"@types/sax": "1.2.7", "@types/sax": "1.2.7",

View File

@ -27,7 +27,7 @@ export default class FBlob {
/** /**
* @throws Error in case of invalid JSON * @throws Error in case of invalid JSON
*/ */
getJsonContent(): unknown { getJsonContent<T>(): T | null {
if (!this.content || !this.content.trim()) { if (!this.content || !this.content.trim()) {
return null; return null;
} }

View File

@ -1,6 +1,6 @@
import TypeWidget from "./type_widget.js"; import TypeWidget from "./type_widget.js";
import utils from "../../services/utils.js"; import utils from "../../services/utils.js";
import MindElixir, { type MindElixirCtor } from "mind-elixir"; import type { MindElixirCtor } from "mind-elixir";
import nodeMenu from "@mind-elixir/node-menu"; import nodeMenu from "@mind-elixir/node-menu";
import type FNote from "../../entities/fnote.js"; import type FNote from "../../entities/fnote.js";
import type { EventData } from "../../components/app_context.js"; import type { EventData } from "../../components/app_context.js";
@ -141,11 +141,16 @@ const TPL = `
</div> </div>
`; `;
interface MindmapModel {
direction: number;
}
export default class MindMapWidget extends TypeWidget { export default class MindMapWidget extends TypeWidget {
private $content!: JQuery<HTMLElement>; private $content!: JQuery<HTMLElement>;
private triggeredByUserOperation?: boolean; private triggeredByUserOperation?: boolean;
private mind?: ReturnType<MindElixirCtor["new"]>; private mind?: ReturnType<MindElixirCtor["new"]>;
private MindElixir: any; // TODO: Fix type
static getType() { static getType() {
return "mindMap"; return "mindMap";
@ -170,6 +175,11 @@ export default class MindMapWidget extends TypeWidget {
} }
}); });
// Save the mind map if the user changes the layout direction.
this.$content.on("click", ".mind-elixir-toolbar.lt", () => {
this.spacedUpdate.scheduleUpdate();
});
super.doRender(); super.doRender();
} }
@ -179,7 +189,6 @@ export default class MindMapWidget extends TypeWidget {
return; return;
} }
this.#initLibrary();
await this.#loadData(note); await this.#loadData(note);
} }
@ -189,23 +198,27 @@ export default class MindMapWidget extends TypeWidget {
async #loadData(note: FNote) { async #loadData(note: FNote) {
const blob = await note.getBlob(); const blob = await note.getBlob();
const content = blob?.getJsonContent() || MindElixir.new(NEW_TOPIC_NAME); const content = blob?.getJsonContent<MindmapModel>();
if (this.mind) { if (!this.mind) {
this.mind.refresh(content); await this.#initLibrary(content?.direction);
this.mind.toCenter();
} }
this.mind.refresh(content ?? this.MindElixir.new(NEW_TOPIC_NAME));
this.mind.toCenter();
} }
#initLibrary() { async #initLibrary(direction?: number) {
const mind = new MindElixir({ this.MindElixir = (await import("mind-elixir")).default;
const mind = new this.MindElixir({
el: this.$content[0], el: this.$content[0],
direction: MindElixir.LEFT direction: direction ?? this.MindElixir.LEFT
}); });
mind.install(nodeMenu); mind.install(nodeMenu);
this.mind = mind; this.mind = mind;
mind.init(MindElixir.new(NEW_TOPIC_NAME)); mind.init(this.MindElixir.new(NEW_TOPIC_NAME));
// TODO: See why the typeof mindmap is not correct. // TODO: See why the typeof mindmap is not correct.
mind.bus.addListener("operation", (operation: { name: string }) => { mind.bus.addListener("operation", (operation: { name: string }) => {
this.triggeredByUserOperation = true; this.triggeredByUserOperation = true;

View File

@ -61,27 +61,28 @@ describe("data_dir.ts unit tests", async () => {
}; };
describe("#getPlatformAppDataDir()", () => { describe("#getPlatformAppDataDir()", () => {
type TestCaseGetPlatformAppDataDir = [description: string, fnValue: Parameters<typeof getPlatformAppDataDir>, expectedValueFn: (val: ReturnType<typeof getPlatformAppDataDir>) => boolean]; type TestCaseGetPlatformAppDataDir = [description: string, fnValue: Parameters<typeof getPlatformAppDataDir>, expectedValue: string | null, osHomedirMockValue: string | null];
const testCases: TestCaseGetPlatformAppDataDir[] = [ const testCases: TestCaseGetPlatformAppDataDir[] = [
["w/ unsupported OS it should return 'null'", ["aix", undefined], (val) => val === null], ["w/ unsupported OS it should return 'null'", ["aix", undefined], null, null],
["w/ win32 and no APPDATA set it should return 'null'", ["win32", undefined], (val) => val === null], ["w/ win32 and no APPDATA set it should return 'null'", ["win32", undefined], null, null],
["w/ win32 and set APPDATA it should return set 'APPDATA'", ["win32", "AppData"], (val) => val === "AppData"], ["w/ win32 and set APPDATA it should return set 'APPDATA'", ["win32", "AppData"], "AppData", null],
["w/ linux it should return '/.local/share'", ["linux", undefined], (val) => val !== null && val.endsWith("/.local/share")], ["w/ linux it should return '~/.local/share'", ["linux", undefined], "/home/mock/.local/share", "/home/mock"],
["w/ linux and wrongly set APPDATA it should ignore APPDATA and return /.local/share", ["linux", "FakeAppData"], (val) => val !== null && val.endsWith("/.local/share")], ["w/ linux and wrongly set APPDATA it should ignore APPDATA and return '~/.local/share'", ["linux", "FakeAppData"], "/home/mock/.local/share", "/home/mock"],
["w/ darwin it should return /Library/Application Support", ["darwin", undefined], (val) => val !== null && val.endsWith("/Library/Application Support")] ["w/ darwin it should return '~/Library/Application Support'", ["darwin", undefined], "/Users/mock/Library/Application Support", "/Users/mock"]
]; ];
testCases.forEach((testCase) => { testCases.forEach((testCase) => {
const [testDescription, value, isExpected] = testCase; const [testDescription, fnValues, expected, osHomedirMockValue] = testCase;
return it(testDescription, () => { return it(testDescription, () => {
const actual = getPlatformAppDataDir(...value); mockFn.osHomedirMock.mockReturnValue(osHomedirMockValue);
const result = isExpected(actual); const actual = getPlatformAppDataDir(...fnValues);
expect(result).toBeTruthy(); expect(actual).toEqual(expected);
}); });
}); });
}); });