From e1952fe6b8d3442752f0e544b9e0df2bd1f82c4d Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 20 Jan 2025 18:45:56 +0200 Subject: [PATCH 01/42] feat(geomap): create geomap note type --- src/becca/entities/rows.ts | 3 +- src/public/app/entities/fnote.ts | 3 +- src/public/app/services/note_types.ts | 3 +- .../app/widgets/buttons/note_actions.js | 16 ++++----- src/public/app/widgets/geo_map.ts | 18 ++++++++++ src/public/app/widgets/note_detail.js | 10 +++--- src/public/app/widgets/note_wrapper.js | 2 +- .../app/widgets/type_widgets/geo_map.ts | 33 +++++++++++++++++++ src/public/translations/en/translation.json | 3 +- src/services/note_types.ts | 3 +- 10 files changed, 76 insertions(+), 18 deletions(-) create mode 100644 src/public/app/widgets/geo_map.ts create mode 100644 src/public/app/widgets/type_widgets/geo_map.ts diff --git a/src/becca/entities/rows.ts b/src/becca/entities/rows.ts index d2d327541..89fa36953 100644 --- a/src/becca/entities/rows.ts +++ b/src/becca/entities/rows.ts @@ -116,7 +116,8 @@ export const ALLOWED_NOTE_TYPES = [ "book", "webView", "code", - "mindMap" + "mindMap", + "geoMap" ] as const; export type NoteType = (typeof ALLOWED_NOTE_TYPES)[number]; diff --git a/src/public/app/entities/fnote.ts b/src/public/app/entities/fnote.ts index 4da479d16..1071ce1a4 100644 --- a/src/public/app/entities/fnote.ts +++ b/src/public/app/entities/fnote.ts @@ -27,7 +27,8 @@ const NOTE_TYPE_ICONS = { launcher: "bx bx-link", doc: "bx bxs-file-doc", contentWidget: "bx bxs-widget", - mindMap: "bx bx-sitemap" + mindMap: "bx bx-sitemap", + geoMap: "bx bx-map-alt" }; /** diff --git a/src/public/app/services/note_types.ts b/src/public/app/services/note_types.ts index 433931ae0..7aebd48ff 100644 --- a/src/public/app/services/note_types.ts +++ b/src/public/app/services/note_types.ts @@ -18,7 +18,8 @@ async function getNoteTypeItems(command?: NoteTypeCommandNames) { { title: t("note_types.mermaid-diagram"), command, type: "mermaid", uiIcon: "bx bx-selection" }, { title: t("note_types.canvas"), command, type: "canvas", uiIcon: "bx bx-pen" }, { title: t("note_types.web-view"), command, type: "webView", uiIcon: "bx bx-globe-alt" }, - { title: t("note_types.mind-map"), command, type: "mindMap", uiIcon: "bx bx-sitemap" } + { title: t("note_types.mind-map"), command, type: "mindMap", uiIcon: "bx bx-sitemap" }, + { title: t("note_types.geo-map"), command, type: "geoMap", uiIcon: "bx bx-map-alt" }, ]; const templateNoteIds = await server.get("search-templates"); diff --git a/src/public/app/widgets/buttons/note_actions.js b/src/public/app/widgets/buttons/note_actions.js index e2efce034..c5c1587ad 100644 --- a/src/public/app/widgets/buttons/note_actions.js +++ b/src/public/app/widgets/buttons/note_actions.js @@ -42,7 +42,7 @@ const TPL = ` - + @@ -54,15 +54,15 @@ const TPL = ` - + - + - + @@ -79,7 +79,7 @@ const TPL = ` ${t("note_actions.note_source")} - + @@ -89,10 +89,10 @@ const TPL = ` - + - + @@ -154,7 +154,7 @@ export default class NoteActionsWidget extends NoteContextAwareWidget { this.toggleDisabled(this.$findInTextButton, ["text", "code", "book"].includes(note.type)); this.toggleDisabled(this.$showAttachmentsButton, !isInOptions); - this.toggleDisabled(this.$showSourceButton, ["text", "code", "relationMap", "mermaid", "canvas", "mindMap"].includes(note.type)); + this.toggleDisabled(this.$showSourceButton, ["text", "code", "relationMap", "mermaid", "canvas", "mindMap", "geoMap"].includes(note.type)); this.toggleDisabled(this.$printActiveNoteButton, ["text", "code"].includes(note.type)); diff --git a/src/public/app/widgets/geo_map.ts b/src/public/app/widgets/geo_map.ts new file mode 100644 index 000000000..32b6db6b1 --- /dev/null +++ b/src/public/app/widgets/geo_map.ts @@ -0,0 +1,18 @@ +import NoteContextAwareWidget from "./note_context_aware_widget.js"; + +const TPL = `\ +
+ Map goes here. +
` + +export default class GeoMapWidget extends NoteContextAwareWidget { + + constructor(widgetMode: "type") { + super(); + } + + doRender() { + this.$widget = $(TPL) + } + +} diff --git a/src/public/app/widgets/note_detail.js b/src/public/app/widgets/note_detail.js index 22850c35e..6ffed4514 100644 --- a/src/public/app/widgets/note_detail.js +++ b/src/public/app/widgets/note_detail.js @@ -31,6 +31,7 @@ import AttachmentListTypeWidget from "./type_widgets/attachment_list.js"; import AttachmentDetailTypeWidget from "./type_widgets/attachment_detail.js"; import MindMapWidget from "./type_widgets/mind_map.js"; import { getStylesheetUrl, isSyntaxHighlightEnabled } from "../services/syntax_highlight.js"; +import GeoMapTypeWidget from "./type_widgets/geo_map.js"; const TPL = `
@@ -39,7 +40,7 @@ const TPL = ` font-family: var(--detail-font-family); font-size: var(--detail-font-size); } - + .note-detail.full-height { height: 100%; } @@ -67,7 +68,8 @@ const typeWidgetClasses = { contentWidget: ContentWidgetTypeWidget, attachmentDetail: AttachmentDetailTypeWidget, attachmentList: AttachmentListTypeWidget, - mindMap: MindMapWidget + mindMap: MindMapWidget, + geoMap: GeoMapTypeWidget }; export default class NoteDetailWidget extends NoteContextAwareWidget { @@ -147,7 +149,7 @@ export default class NoteDetailWidget extends NoteContextAwareWidget { // https://github.com/zadam/trilium/issues/2522 this.$widget.toggleClass( "full-height", - (!this.noteContext.hasNoteList() && ["canvas", "webView", "noteMap", "mindMap"].includes(this.type) && this.mime !== "text/x-sqlite;schema=trilium") || + (!this.noteContext.hasNoteList() && ["canvas", "webView", "noteMap", "mindMap", "geoMap"].includes(this.type) && this.mime !== "text/x-sqlite;schema=trilium") || this.noteContext.viewScope.viewMode === "attachments" ); } @@ -276,7 +278,7 @@ export default class NoteDetailWidget extends NoteContextAwareWidget { `, diff --git a/src/public/app/widgets/note_wrapper.js b/src/public/app/widgets/note_wrapper.js index baf444e61..9f045675c 100644 --- a/src/public/app/widgets/note_wrapper.js +++ b/src/public/app/widgets/note_wrapper.js @@ -41,7 +41,7 @@ export default class NoteWrapperWidget extends FlexContainer { return; } - this.$widget.toggleClass("full-content-width", ["image", "mermaid", "book", "render", "canvas", "webView", "mindMap"].includes(note.type) || !!note?.isLabelTruthy("fullContentWidth")); + this.$widget.toggleClass("full-content-width", ["image", "mermaid", "book", "render", "canvas", "webView", "mindMap", "geoMap"].includes(note.type) || !!note?.isLabelTruthy("fullContentWidth")); this.$widget.addClass(note.getCssClass()); diff --git a/src/public/app/widgets/type_widgets/geo_map.ts b/src/public/app/widgets/type_widgets/geo_map.ts new file mode 100644 index 000000000..f52a7a8e0 --- /dev/null +++ b/src/public/app/widgets/type_widgets/geo_map.ts @@ -0,0 +1,33 @@ +import type FNote from "../../entities/fnote.js"; +import GeoMapWidget from "../geo_map.js"; +import TypeWidget from "./type_widget.js" + +const TPL = `
`; + +export default class GeoMapTypeWidget extends TypeWidget { + + private geoMapWidget: GeoMapWidget; + + static getType() { + return "geoMap"; + } + + constructor() { + super(); + + this.geoMapWidget = new GeoMapWidget("type"); + this.child(this.geoMapWidget); + } + + doRender() { + this.$widget = $(TPL); + this.$widget.append(this.geoMapWidget.render()); + + super.doRender(); + } + + async doRefresh(note: FNote) { + await this.geoMapWidget.refresh(); + } + +} diff --git a/src/public/translations/en/translation.json b/src/public/translations/en/translation.json index 1c65b78fa..4a24da87a 100644 --- a/src/public/translations/en/translation.json +++ b/src/public/translations/en/translation.json @@ -1409,7 +1409,8 @@ "launcher": "Launcher", "doc": "Doc", "widget": "Widget", - "confirm-change": "It is not recommended to change note type when note content is not empty. Do you want to continue anyway?" + "confirm-change": "It is not recommended to change note type when note content is not empty. Do you want to continue anyway?", + "geo-map": "Geo Map (beta)" }, "protect_note": { "toggle-on": "Protect the note", diff --git a/src/services/note_types.ts b/src/services/note_types.ts index 4a810b6a2..3e086acf4 100644 --- a/src/services/note_types.ts +++ b/src/services/note_types.ts @@ -14,7 +14,8 @@ const noteTypes = [ { type: "launcher", defaultMime: "" }, { type: "doc", defaultMime: "" }, { type: "contentWidget", defaultMime: "" }, - { type: "mindMap", defaultMime: "application/json" } + { type: "mindMap", defaultMime: "application/json" }, + { type: "geoMap", defaultMime: "application/json" } ]; function getDefaultMimeForNoteType(typeName: string) { From 94a04039815ba74148f70df4f55764c04e6df01f Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 20 Jan 2025 19:18:29 +0200 Subject: [PATCH 02/42] feat(geomap): load leaflet --- package-lock.json | 17 ++++++++++++++ package.json | 2 ++ src/public/app/services/library_loader.ts | 8 ++++++- src/public/app/widgets/geo_map.ts | 28 ++++++++++++++++++++--- src/routes/assets.ts | 2 ++ 5 files changed, 53 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 428c93da9..fa54b38b2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "@mermaid-js/layout-elk": "0.1.7", "@mind-elixir/node-menu": "1.0.3", "@triliumnext/express-partial-content": "1.0.1", + "@types/leaflet": "1.9.16", "@types/react-dom": "18.3.5", "archiver": "7.0.1", "async-mutex": "0.5.0", @@ -67,6 +68,7 @@ "jsplumb": "2.15.6", "katex": "0.16.21", "knockout": "3.5.1", + "leaflet": "1.9.4", "mark.js": "8.11.1", "marked": "15.0.6", "mermaid": "11.4.1", @@ -3847,6 +3849,15 @@ "@types/node": "*" } }, + "node_modules/@types/leaflet": { + "version": "1.9.16", + "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.16.tgz", + "integrity": "sha512-wzZoyySUxkgMZ0ihJ7IaUIblG8Rdc8AbbZKLneyn+QjYsj5q1QU7TEKYqwTr10BGSzY5LI7tJk9Ifo+mEjdFRw==", + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, "node_modules/@types/linkify-it": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", @@ -11437,6 +11448,12 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/leaflet": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz", + "integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==", + "license": "BSD-2-Clause" + }, "node_modules/limiter": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", diff --git a/package.json b/package.json index a492db6fb..0f27b3582 100644 --- a/package.json +++ b/package.json @@ -61,6 +61,7 @@ "@mermaid-js/layout-elk": "0.1.7", "@mind-elixir/node-menu": "1.0.3", "@triliumnext/express-partial-content": "1.0.1", + "@types/leaflet": "1.9.16", "@types/react-dom": "18.3.5", "archiver": "7.0.1", "async-mutex": "0.5.0", @@ -112,6 +113,7 @@ "jsplumb": "2.15.6", "katex": "0.16.21", "knockout": "3.5.1", + "leaflet": "1.9.4", "mark.js": "8.11.1", "marked": "15.0.6", "mermaid": "11.4.1", diff --git a/src/public/app/services/library_loader.ts b/src/public/app/services/library_loader.ts index 912cb652b..c31b1b14b 100644 --- a/src/public/app/services/library_loader.ts +++ b/src/public/app/services/library_loader.ts @@ -106,6 +106,11 @@ const HIGHLIGHT_JS: Library = { } }; +const LEAFLET: Library = { + js: [ "node_modules/leaflet/dist/leaflet.js" ], + css: [ "node_modules/leaflet/dist/leaflet.css" ], +} + async function requireLibrary(library: Library) { if (library.css) { library.css.map((cssUrl) => requireCss(cssUrl)); @@ -196,5 +201,6 @@ export default { MERMAID, MARKJS, I18NEXT, - HIGHLIGHT_JS + HIGHLIGHT_JS, + LEAFLET }; diff --git a/src/public/app/widgets/geo_map.ts b/src/public/app/widgets/geo_map.ts index 32b6db6b1..b53e4ded9 100644 --- a/src/public/app/widgets/geo_map.ts +++ b/src/public/app/widgets/geo_map.ts @@ -1,9 +1,21 @@ +import library_loader from "../services/library_loader.js"; import NoteContextAwareWidget from "./note_context_aware_widget.js"; const TPL = `\
- Map goes here. -
` + + +
+
+ +` export default class GeoMapWidget extends NoteContextAwareWidget { @@ -12,7 +24,17 @@ export default class GeoMapWidget extends NoteContextAwareWidget { } doRender() { - this.$widget = $(TPL) + this.$widget = $(TPL); + + const $container = this.$widget.find(".geo-map-container"); + + library_loader.requireLibrary(library_loader.LEAFLET) + .then(() => { + //@ts-ignore + L.map($container[0], { + + }); + }); } } diff --git a/src/routes/assets.ts b/src/routes/assets.ts index d70bbade5..a46801559 100644 --- a/src/routes/assets.ts +++ b/src/routes/assets.ts @@ -105,6 +105,8 @@ async function register(app: express.Application) { app.use(`/${assetPath}/node_modules/mind-elixir/dist/`, persistentCacheStatic(path.join(srcRoot, "..", "node_modules/mind-elixir/dist/"))); app.use(`/${assetPath}/node_modules/@mind-elixir/node-menu/dist/`, persistentCacheStatic(path.join(srcRoot, "..", "node_modules/@mind-elixir/node-menu/dist/"))); app.use(`/${assetPath}/node_modules/@highlightjs/cdn-assets/`, persistentCacheStatic(path.join(srcRoot, "..", "node_modules/@highlightjs/cdn-assets/"))); + + app.use(`/${assetPath}/node_modules/leaflet/dist/`, persistentCacheStatic(path.join(srcRoot, "..", "node_modules/leaflet/dist/"))); } export default { From eca3955dc26de297473b077d5bb13e85d92c8f4a Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 20 Jan 2025 19:20:59 +0200 Subject: [PATCH 03/42] feat(geomap): add basic layer --- src/public/app/widgets/geo_map.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/public/app/widgets/geo_map.ts b/src/public/app/widgets/geo_map.ts index b53e4ded9..04fe8965f 100644 --- a/src/public/app/widgets/geo_map.ts +++ b/src/public/app/widgets/geo_map.ts @@ -17,6 +17,7 @@ const TPL = `\ ` +//@ts-nocheck export default class GeoMapWidget extends NoteContextAwareWidget { constructor(widgetMode: "type") { @@ -30,10 +31,15 @@ export default class GeoMapWidget extends NoteContextAwareWidget { library_loader.requireLibrary(library_loader.LEAFLET) .then(() => { - //@ts-ignore - L.map($container[0], { + const map = L.map($container[0], { }); + + map.setView([51.505, -0.09], 13); + + L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', { + attribution: '© OpenStreetMap contributors' + }).addTo(map); }); } From 2b8ee31be32bf30dd07806e27072c8bfa7cb07e2 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 20 Jan 2025 20:36:58 +0200 Subject: [PATCH 04/42] refactor(geomap): skip module loader for JS --- src/public/app/services/library_loader.ts | 1 - src/public/app/widgets/geo_map.ts | 9 ++++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/public/app/services/library_loader.ts b/src/public/app/services/library_loader.ts index c31b1b14b..f8b363302 100644 --- a/src/public/app/services/library_loader.ts +++ b/src/public/app/services/library_loader.ts @@ -107,7 +107,6 @@ const HIGHLIGHT_JS: Library = { }; const LEAFLET: Library = { - js: [ "node_modules/leaflet/dist/leaflet.js" ], css: [ "node_modules/leaflet/dist/leaflet.css" ], } diff --git a/src/public/app/widgets/geo_map.ts b/src/public/app/widgets/geo_map.ts index 04fe8965f..834be6139 100644 --- a/src/public/app/widgets/geo_map.ts +++ b/src/public/app/widgets/geo_map.ts @@ -13,11 +13,8 @@ const TPL = `\
- +` -` - -//@ts-nocheck export default class GeoMapWidget extends NoteContextAwareWidget { constructor(widgetMode: "type") { @@ -30,7 +27,9 @@ export default class GeoMapWidget extends NoteContextAwareWidget { const $container = this.$widget.find(".geo-map-container"); library_loader.requireLibrary(library_loader.LEAFLET) - .then(() => { + .then(async () => { + const L = (await import("leaflet")).default; + const map = L.map($container[0], { }); From 5cefd4f50ad47860ba93f58ab2198d68241186f6 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 20 Jan 2025 21:27:06 +0200 Subject: [PATCH 05/42] feat(mindmap): save view center coordinates --- src/public/app/widgets/geo_map.ts | 11 ++++++++- .../app/widgets/type_widgets/geo_map.ts | 24 ++++++++++++++++++- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/src/public/app/widgets/geo_map.ts b/src/public/app/widgets/geo_map.ts index 834be6139..990b34b6c 100644 --- a/src/public/app/widgets/geo_map.ts +++ b/src/public/app/widgets/geo_map.ts @@ -1,3 +1,4 @@ +import type { Map } from "leaflet"; import library_loader from "../services/library_loader.js"; import NoteContextAwareWidget from "./note_context_aware_widget.js"; @@ -17,8 +18,12 @@ const TPL = `\ export default class GeoMapWidget extends NoteContextAwareWidget { - constructor(widgetMode: "type") { + map?: Map; + private initCallback?: () => void; + + constructor(widgetMode: "type", initCallback?: () => void) { super(); + this.initCallback = initCallback; } doRender() { @@ -35,6 +40,10 @@ export default class GeoMapWidget extends NoteContextAwareWidget { }); map.setView([51.505, -0.09], 13); + this.map = map; + if (this.initCallback) { + this.initCallback(); + } L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: '© OpenStreetMap contributors' diff --git a/src/public/app/widgets/type_widgets/geo_map.ts b/src/public/app/widgets/type_widgets/geo_map.ts index f52a7a8e0..f619b01c8 100644 --- a/src/public/app/widgets/type_widgets/geo_map.ts +++ b/src/public/app/widgets/type_widgets/geo_map.ts @@ -15,7 +15,8 @@ export default class GeoMapTypeWidget extends TypeWidget { constructor() { super(); - this.geoMapWidget = new GeoMapWidget("type"); + this.geoMapWidget = new GeoMapWidget("type", () => this.#onMapInitialized()); + this.child(this.geoMapWidget); } @@ -26,6 +27,27 @@ export default class GeoMapTypeWidget extends TypeWidget { super.doRender(); } + #onMapInitialized() { + this.geoMapWidget.map?.on("moveend", () => this.spacedUpdate.scheduleUpdate()); + } + + getData(): any { + const map = this.geoMapWidget.map; + if (!map) { + return; + } + + const data = { + view: { + center: map.getBounds().getCenter() + } + }; + + return { + content: JSON.stringify(data) + }; + } + async doRefresh(note: FNote) { await this.geoMapWidget.refresh(); } From f66f437c8e1ce0d065d79b13be16cab494ef83c0 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 20 Jan 2025 22:19:07 +0200 Subject: [PATCH 06/42] feat(geomap): restore view coordinates --- src/public/app/widgets/geo_map.ts | 1 - .../app/widgets/type_widgets/geo_map.ts | 28 +++++++++++++++++-- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/public/app/widgets/geo_map.ts b/src/public/app/widgets/geo_map.ts index 990b34b6c..e0dc14aed 100644 --- a/src/public/app/widgets/geo_map.ts +++ b/src/public/app/widgets/geo_map.ts @@ -39,7 +39,6 @@ export default class GeoMapWidget extends NoteContextAwareWidget { }); - map.setView([51.505, -0.09], 13); this.map = map; if (this.initCallback) { this.initCallback(); diff --git a/src/public/app/widgets/type_widgets/geo_map.ts b/src/public/app/widgets/type_widgets/geo_map.ts index f619b01c8..20bf81d6b 100644 --- a/src/public/app/widgets/type_widgets/geo_map.ts +++ b/src/public/app/widgets/type_widgets/geo_map.ts @@ -1,9 +1,16 @@ +import type { LatLng } from "leaflet"; import type FNote from "../../entities/fnote.js"; import GeoMapWidget from "../geo_map.js"; import TypeWidget from "./type_widget.js" const TPL = `
`; +interface MapData { + view?: { + center: LatLng | [ number, number ]; + } +} + export default class GeoMapTypeWidget extends TypeWidget { private geoMapWidget: GeoMapWidget; @@ -27,8 +34,23 @@ export default class GeoMapTypeWidget extends TypeWidget { super.doRender(); } - #onMapInitialized() { - this.geoMapWidget.map?.on("moveend", () => this.spacedUpdate.scheduleUpdate()); + async #onMapInitialized() { + const map = this.geoMapWidget.map; + if (!map) { + throw new Error("Unable to load map."); + } + + const blob = await this.note?.getBlob(); + + let parsedContent: MapData = {}; + if (blob) { + parsedContent = JSON.parse(blob.content); + } + console.log(parsedContent); + const center = parsedContent.view?.center ?? [51.505, -0.09]; + + map.setView(center, 13); + map.on("moveend", () => this.spacedUpdate.scheduleUpdate()); } getData(): any { @@ -37,7 +59,7 @@ export default class GeoMapTypeWidget extends TypeWidget { return; } - const data = { + const data: MapData = { view: { center: map.getBounds().getCenter() } From 4d5e04fc5a074fe85d373fadd56859051ac67d2a Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 20 Jan 2025 22:21:59 +0200 Subject: [PATCH 07/42] feat(geomap): save & restore zoom --- src/public/app/widgets/type_widgets/geo_map.ts | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/public/app/widgets/type_widgets/geo_map.ts b/src/public/app/widgets/type_widgets/geo_map.ts index 20bf81d6b..38a81ab10 100644 --- a/src/public/app/widgets/type_widgets/geo_map.ts +++ b/src/public/app/widgets/type_widgets/geo_map.ts @@ -7,7 +7,8 @@ const TPL = `
`; interface MapData { view?: { - center: LatLng | [ number, number ]; + center?: LatLng | [ number, number ]; + zoom?: number; } } @@ -46,11 +47,14 @@ export default class GeoMapTypeWidget extends TypeWidget { if (blob) { parsedContent = JSON.parse(blob.content); } - console.log(parsedContent); - const center = parsedContent.view?.center ?? [51.505, -0.09]; - map.setView(center, 13); - map.on("moveend", () => this.spacedUpdate.scheduleUpdate()); + const center = parsedContent.view?.center ?? [51.505, -0.09]; + const zoom = parsedContent.view?.zoom ?? 13; + map.setView(center, zoom); + + const updateFn = () => this.spacedUpdate.scheduleUpdate(); + map.on("moveend", updateFn); + map.on("zoomend", updateFn); } getData(): any { @@ -61,7 +65,8 @@ export default class GeoMapTypeWidget extends TypeWidget { const data: MapData = { view: { - center: map.getBounds().getCenter() + center: map.getBounds().getCenter(), + zoom: map.getZoom() } }; From f803b9f8225c8c97b17d58592b1b5ffcbe724824 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 20 Jan 2025 22:39:48 +0200 Subject: [PATCH 08/42] feat(geomap): add floating button section --- src/public/app/entities/fnote.ts | 2 +- src/public/app/layouts/desktop_layout.js | 2 + .../floating_buttons/geo_map_button.ts | 40 +++++++++++++++++++ .../app/widgets/type_widgets/geo_map.ts | 9 ++++- 4 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 src/public/app/widgets/floating_buttons/geo_map_button.ts diff --git a/src/public/app/entities/fnote.ts b/src/public/app/entities/fnote.ts index 1071ce1a4..053ac6be5 100644 --- a/src/public/app/entities/fnote.ts +++ b/src/public/app/entities/fnote.ts @@ -36,7 +36,7 @@ const NOTE_TYPE_ICONS = { * end user. Those types should be used only for checking against, they are * not for direct use. */ -type NoteType = "file" | "image" | "search" | "noteMap" | "launcher" | "doc" | "contentWidget" | "text" | "relationMap" | "render" | "canvas" | "mermaid" | "book" | "webView" | "code" | "mindMap"; +type NoteType = "file" | "image" | "search" | "noteMap" | "launcher" | "doc" | "contentWidget" | "text" | "relationMap" | "render" | "canvas" | "mermaid" | "book" | "webView" | "code" | "mindMap" | "geoMap"; interface NotePathRecord { isArchived: boolean; diff --git a/src/public/app/layouts/desktop_layout.js b/src/public/app/layouts/desktop_layout.js index a50dace76..c5d14ffe9 100644 --- a/src/public/app/layouts/desktop_layout.js +++ b/src/public/app/layouts/desktop_layout.js @@ -85,6 +85,7 @@ import ScrollPaddingWidget from "../widgets/scroll_padding.js"; import ClassicEditorToolbar from "../widgets/ribbon_widgets/classic_editor_toolbar.js"; import options from "../services/options.js"; import utils from "../services/utils.js"; +import GeoMapButtons from "../widgets/floating_buttons/geo_map_button.js"; export default class DesktopLayout { constructor(customWidgets) { @@ -200,6 +201,7 @@ export default class DesktopLayout { .child(new ShowHighlightsListWidgetButton()) .child(new CodeButtonsWidget()) .child(new RelationMapButtons()) + .child(new GeoMapButtons()) .child(new CopyImageReferenceButton()) .child(new SvgExportButton()) .child(new BacklinksWidget()) diff --git a/src/public/app/widgets/floating_buttons/geo_map_button.ts b/src/public/app/widgets/floating_buttons/geo_map_button.ts new file mode 100644 index 000000000..f1fcc9dd9 --- /dev/null +++ b/src/public/app/widgets/floating_buttons/geo_map_button.ts @@ -0,0 +1,40 @@ +import NoteContextAwareWidget from "../note_context_aware_widget.js" + +const TPL = `\ +
+ + +
`; + +export default class GeoMapButtons extends NoteContextAwareWidget { + + isEnabled() { + return super.isEnabled() && this.note?.type === "geoMap"; + } + + doRender() { + super.doRender(); + + this.$widget = $(TPL); + } + +} diff --git a/src/public/app/widgets/type_widgets/geo_map.ts b/src/public/app/widgets/type_widgets/geo_map.ts index 38a81ab10..35a3da790 100644 --- a/src/public/app/widgets/type_widgets/geo_map.ts +++ b/src/public/app/widgets/type_widgets/geo_map.ts @@ -3,7 +3,14 @@ import type FNote from "../../entities/fnote.js"; import GeoMapWidget from "../geo_map.js"; import TypeWidget from "./type_widget.js" -const TPL = `
`; +const TPL = `\ +
+ +
`; interface MapData { view?: { From 2582924046f620b65d398e8735905bde6cd1dd9a Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 20 Jan 2025 22:50:36 +0200 Subject: [PATCH 09/42] feat(geomap): add prompt for creating child note --- src/public/app/components/app_context.ts | 5 ++- src/public/app/components/component.ts | 2 +- .../floating_buttons/geo_map_button.ts | 1 + .../floating_buttons/relation_map_buttons.js | 7 ++-- .../app/widgets/type_widgets/geo_map.ts | 40 +++++++++++++++++++ 5 files changed, 50 insertions(+), 5 deletions(-) diff --git a/src/public/app/components/app_context.ts b/src/public/app/components/app_context.ts index 80ca032d0..fa20630ec 100644 --- a/src/public/app/components/app_context.ts +++ b/src/public/app/components/app_context.ts @@ -266,7 +266,10 @@ type EventMappings = { }; exportSvg: { ntxId: string; - } + }; + geoMapCreateChildNote: { + ntxId: string | null | undefined; // TODO: deduplicate ntxId + }; }; export type EventListener = { diff --git a/src/public/app/components/component.ts b/src/public/app/components/component.ts index 2db9f96a4..3bb389589 100644 --- a/src/public/app/components/component.ts +++ b/src/public/app/components/component.ts @@ -61,7 +61,7 @@ export class TypedComponent> { } } - triggerEvent(name: string, data = {}): Promise | undefined | null { + triggerEvent(name: T, data: EventData): Promise | undefined | null { return this.parent?.triggerEvent(name, data); } diff --git a/src/public/app/widgets/floating_buttons/geo_map_button.ts b/src/public/app/widgets/floating_buttons/geo_map_button.ts index f1fcc9dd9..19e224048 100644 --- a/src/public/app/widgets/floating_buttons/geo_map_button.ts +++ b/src/public/app/widgets/floating_buttons/geo_map_button.ts @@ -35,6 +35,7 @@ export default class GeoMapButtons extends NoteContextAwareWidget { super.doRender(); this.$widget = $(TPL); + this.$widget.find(".geo-map-create-child-note").on("click", () => this.triggerEvent("geoMapCreateChildNote", { ntxId: this.ntxId })); } } diff --git a/src/public/app/widgets/floating_buttons/relation_map_buttons.js b/src/public/app/widgets/floating_buttons/relation_map_buttons.js index 74de1852b..78c3faa1a 100644 --- a/src/public/app/widgets/floating_buttons/relation_map_buttons.js +++ b/src/public/app/widgets/floating_buttons/relation_map_buttons.js @@ -13,16 +13,16 @@ const TPL = ` - + - +
- + @@ -43,6 +43,7 @@ export default class RelationMapButtons extends NoteContextAwareWidget { this.$zoomOutButton = this.$widget.find(".relation-map-zoom-out"); this.$resetPanZoomButton = this.$widget.find(".relation-map-reset-pan-zoom"); + // TODO: Deduplicate object creation here. this.$createChildNote.on("click", () => this.triggerEvent("relationMapCreateChildNote", { ntxId: this.ntxId })); this.$resetPanZoomButton.on("click", () => this.triggerEvent("relationMapResetPanZoom", { ntxId: this.ntxId })); diff --git a/src/public/app/widgets/type_widgets/geo_map.ts b/src/public/app/widgets/type_widgets/geo_map.ts index 35a3da790..bd41e4878 100644 --- a/src/public/app/widgets/type_widgets/geo_map.ts +++ b/src/public/app/widgets/type_widgets/geo_map.ts @@ -2,6 +2,11 @@ import type { LatLng } from "leaflet"; import type FNote from "../../entities/fnote.js"; import GeoMapWidget from "../geo_map.js"; import TypeWidget from "./type_widget.js" +import server from "../../services/server.js"; +import toastService from "../../services/toast.js"; +import dialogService from "../../services/dialog.js"; +import type { EventData } from "../../components/app_context.js"; +import { t } from "../../services/i18n.js"; const TPL = `\
@@ -19,9 +24,22 @@ interface MapData { } } +// TODO: Deduplicate +interface CreateChildResponse { + note: { + noteId: string; + } +} + +interface Clipboard { + noteId: string; + title: string; +} + export default class GeoMapTypeWidget extends TypeWidget { private geoMapWidget: GeoMapWidget; + private clipboard?: Clipboard; static getType() { return "geoMap"; @@ -82,6 +100,28 @@ export default class GeoMapTypeWidget extends TypeWidget { }; } + async geoMapCreateChildNoteEvent({ ntxId }: EventData<"geoMapCreateChildNote">) { + if (!this.isNoteContext(ntxId)) { + return; + } + + const title = await dialogService.prompt({ message: t("relation_map.enter_title_of_new_note"), defaultValue: t("relation_map.default_new_note_title") }); + + if (!title?.trim()) { + return; + } + + const { note } = await server.post(`notes/${this.noteId}/children?target=into`, { + title, + content: "", + type: "text" + }); + + toastService.showMessage(t("relation_map.click_on_canvas_to_place_new_note")); + + this.clipboard = { noteId: note.noteId, title }; + } + async doRefresh(note: FNote) { await this.geoMapWidget.refresh(); } From a3f257f3c583d95879f9f1de14b40c5c72090ede Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 20 Jan 2025 23:14:31 +0200 Subject: [PATCH 10/42] feat(geomap): set location after creating a note --- src/public/app/widgets/type_widgets/geo_map.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/public/app/widgets/type_widgets/geo_map.ts b/src/public/app/widgets/type_widgets/geo_map.ts index bd41e4878..740f9838e 100644 --- a/src/public/app/widgets/type_widgets/geo_map.ts +++ b/src/public/app/widgets/type_widgets/geo_map.ts @@ -1,4 +1,4 @@ -import type { LatLng } from "leaflet"; +import type { LatLng, LeafletMouseEvent } from "leaflet"; import type FNote from "../../entities/fnote.js"; import GeoMapWidget from "../geo_map.js"; import TypeWidget from "./type_widget.js" @@ -7,6 +7,7 @@ import toastService from "../../services/toast.js"; import dialogService from "../../services/dialog.js"; import type { EventData } from "../../components/app_context.js"; import { t } from "../../services/i18n.js"; +import attributes from "../../services/attributes.js"; const TPL = `\
@@ -17,6 +18,8 @@ const TPL = `\
`; +const LOCATION_ATTRIBUTE = "latLng"; + interface MapData { view?: { center?: LatLng | [ number, number ]; @@ -80,6 +83,16 @@ export default class GeoMapTypeWidget extends TypeWidget { const updateFn = () => this.spacedUpdate.scheduleUpdate(); map.on("moveend", updateFn); map.on("zoomend", updateFn); + map.on("click", (e) => this.#onMapClicked(e)); + } + + async #onMapClicked(e: LeafletMouseEvent) { + if (!this.clipboard) { + return; + } + + const { noteId } = this.clipboard; + await attributes.setLabel(noteId, LOCATION_ATTRIBUTE, [e.latlng.lat, e.latlng.lng].join(",")); } getData(): any { From f76b454d5a6d6b0e544ed2aafca8ba32a605ef7a Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 20 Jan 2025 23:27:32 +0200 Subject: [PATCH 11/42] feat(geomap): load markers at startup --- src/public/app/widgets/geo_map.ts | 9 ++++--- .../app/widgets/type_widgets/geo_map.ts | 27 ++++++++++++++++--- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/src/public/app/widgets/geo_map.ts b/src/public/app/widgets/geo_map.ts index e0dc14aed..d49f1c1d3 100644 --- a/src/public/app/widgets/geo_map.ts +++ b/src/public/app/widgets/geo_map.ts @@ -16,12 +16,15 @@ const TPL = `\
` +export type Leaflet = typeof import("leaflet"); +export type InitCallback = ((L: Leaflet) => void); + export default class GeoMapWidget extends NoteContextAwareWidget { map?: Map; - private initCallback?: () => void; + private initCallback?: InitCallback; - constructor(widgetMode: "type", initCallback?: () => void) { + constructor(widgetMode: "type", initCallback?: InitCallback) { super(); this.initCallback = initCallback; } @@ -41,7 +44,7 @@ export default class GeoMapWidget extends NoteContextAwareWidget { this.map = map; if (this.initCallback) { - this.initCallback(); + this.initCallback(L); } L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', { diff --git a/src/public/app/widgets/type_widgets/geo_map.ts b/src/public/app/widgets/type_widgets/geo_map.ts index 740f9838e..7022b9c20 100644 --- a/src/public/app/widgets/type_widgets/geo_map.ts +++ b/src/public/app/widgets/type_widgets/geo_map.ts @@ -1,6 +1,6 @@ import type { LatLng, LeafletMouseEvent } from "leaflet"; import type FNote from "../../entities/fnote.js"; -import GeoMapWidget from "../geo_map.js"; +import GeoMapWidget, { type InitCallback, type Leaflet } from "../geo_map.js"; import TypeWidget from "./type_widget.js" import server from "../../services/server.js"; import toastService from "../../services/toast.js"; @@ -51,7 +51,7 @@ export default class GeoMapTypeWidget extends TypeWidget { constructor() { super(); - this.geoMapWidget = new GeoMapWidget("type", () => this.#onMapInitialized()); + this.geoMapWidget = new GeoMapWidget("type", (L: Leaflet) => this.#onMapInitialized(L)); this.child(this.geoMapWidget); } @@ -63,23 +63,42 @@ export default class GeoMapTypeWidget extends TypeWidget { super.doRender(); } - async #onMapInitialized() { + async #onMapInitialized(L: Leaflet) { const map = this.geoMapWidget.map; if (!map) { throw new Error("Unable to load map."); } - const blob = await this.note?.getBlob(); + if (!this.note) { + return; + } + + const blob = await this.note.getBlob(); let parsedContent: MapData = {}; if (blob) { parsedContent = JSON.parse(blob.content); } + // Restore viewport position & zoom const center = parsedContent.view?.center ?? [51.505, -0.09]; const zoom = parsedContent.view?.zoom ?? 13; map.setView(center, zoom); + // Restore markers. + const childNotes = await this.note.getChildNotes(); + for (const childNote of childNotes) { + const latLng = childNote.getAttributeValue("label", LOCATION_ATTRIBUTE); + if (!latLng) { + continue; + } + + const [ lat, lng ] = latLng.split(",", 2).map((el) => parseFloat(el)); + L.marker(L.latLng(lat, lng)) + .addTo(map) + .bindPopup(childNote.title); + } + const updateFn = () => this.spacedUpdate.scheduleUpdate(); map.on("moveend", updateFn); map.on("zoomend", updateFn); From 986a1c25be27453fd18c56d74abd62e6168fb33f Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 20 Jan 2025 23:53:52 +0200 Subject: [PATCH 12/42] feat(geomap): reload markers after adding a new note --- .../app/widgets/type_widgets/geo_map.ts | 30 +++++++++++++++---- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/src/public/app/widgets/type_widgets/geo_map.ts b/src/public/app/widgets/type_widgets/geo_map.ts index 7022b9c20..71580e5de 100644 --- a/src/public/app/widgets/type_widgets/geo_map.ts +++ b/src/public/app/widgets/type_widgets/geo_map.ts @@ -43,6 +43,7 @@ export default class GeoMapTypeWidget extends TypeWidget { private geoMapWidget: GeoMapWidget; private clipboard?: Clipboard; + private L!: Leaflet; static getType() { return "geoMap"; @@ -64,6 +65,7 @@ export default class GeoMapTypeWidget extends TypeWidget { } async #onMapInitialized(L: Leaflet) { + this.L = L; const map = this.geoMapWidget.map; if (!map) { throw new Error("Unable to load map."); @@ -86,7 +88,23 @@ export default class GeoMapTypeWidget extends TypeWidget { map.setView(center, zoom); // Restore markers. + await this.#reloadMarkers(); + + const updateFn = () => this.spacedUpdate.scheduleUpdate(); + map.on("moveend", updateFn); + map.on("zoomend", updateFn); + map.on("click", (e) => this.#onMapClicked(e)); + } + + async #reloadMarkers() { + const map = this.geoMapWidget.map; + + if (!this.note || !map) { + return; + } + const childNotes = await this.note.getChildNotes(); + const L = this.L; for (const childNote of childNotes) { const latLng = childNote.getAttributeValue("label", LOCATION_ATTRIBUTE); if (!latLng) { @@ -98,11 +116,6 @@ export default class GeoMapTypeWidget extends TypeWidget { .addTo(map) .bindPopup(childNote.title); } - - const updateFn = () => this.spacedUpdate.scheduleUpdate(); - map.on("moveend", updateFn); - map.on("zoomend", updateFn); - map.on("click", (e) => this.#onMapClicked(e)); } async #onMapClicked(e: LeafletMouseEvent) { @@ -158,4 +171,11 @@ export default class GeoMapTypeWidget extends TypeWidget { await this.geoMapWidget.refresh(); } + entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) { + const attributeRows = loadResults.getAttributeRows(); + if (attributeRows.find((at) => at.name === LOCATION_ATTRIBUTE)) { + this.#reloadMarkers(); + } + } + } From ef5b2d60f35235d1703364c2980cfd277e4b56a1 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 20 Jan 2025 23:54:22 +0200 Subject: [PATCH 13/42] fix(geomap): multiple clicks creating multiple markers --- src/public/app/widgets/type_widgets/geo_map.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/public/app/widgets/type_widgets/geo_map.ts b/src/public/app/widgets/type_widgets/geo_map.ts index 71580e5de..3bb69da07 100644 --- a/src/public/app/widgets/type_widgets/geo_map.ts +++ b/src/public/app/widgets/type_widgets/geo_map.ts @@ -125,6 +125,7 @@ export default class GeoMapTypeWidget extends TypeWidget { const { noteId } = this.clipboard; await attributes.setLabel(noteId, LOCATION_ATTRIBUTE, [e.latlng.lat, e.latlng.lng].join(",")); + this.clipboard = undefined; } getData(): any { From fed0598b47c63a0c717459d707239a2fe4c652f1 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 21 Jan 2025 13:46:18 +0200 Subject: [PATCH 14/42] feat(geomap): adjust cursor when adding new note --- src/public/app/widgets/geo_map.ts | 5 +++-- src/public/app/widgets/type_widgets/geo_map.ts | 10 ++++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/public/app/widgets/geo_map.ts b/src/public/app/widgets/geo_map.ts index d49f1c1d3..46fb2e414 100644 --- a/src/public/app/widgets/geo_map.ts +++ b/src/public/app/widgets/geo_map.ts @@ -22,6 +22,7 @@ export type InitCallback = ((L: Leaflet) => void); export default class GeoMapWidget extends NoteContextAwareWidget { map?: Map; + $container!: JQuery; private initCallback?: InitCallback; constructor(widgetMode: "type", initCallback?: InitCallback) { @@ -32,13 +33,13 @@ export default class GeoMapWidget extends NoteContextAwareWidget { doRender() { this.$widget = $(TPL); - const $container = this.$widget.find(".geo-map-container"); + this.$container = this.$widget.find(".geo-map-container"); library_loader.requireLibrary(library_loader.LEAFLET) .then(async () => { const L = (await import("leaflet")).default; - const map = L.map($container[0], { + const map = L.map(this.$container[0], { }); diff --git a/src/public/app/widgets/type_widgets/geo_map.ts b/src/public/app/widgets/type_widgets/geo_map.ts index 3bb69da07..99450f9ff 100644 --- a/src/public/app/widgets/type_widgets/geo_map.ts +++ b/src/public/app/widgets/type_widgets/geo_map.ts @@ -15,6 +15,10 @@ const TPL = `\ .leaflet-pane { z-index: 1; } + + .geo-map-container.placing-note { + cursor: crosshair; + }
`; @@ -118,6 +122,10 @@ export default class GeoMapTypeWidget extends TypeWidget { } } + #adjustCursor() { + this.geoMapWidget.$container.toggleClass("placing-note", !!this.clipboard); + } + async #onMapClicked(e: LeafletMouseEvent) { if (!this.clipboard) { return; @@ -126,6 +134,7 @@ export default class GeoMapTypeWidget extends TypeWidget { const { noteId } = this.clipboard; await attributes.setLabel(noteId, LOCATION_ATTRIBUTE, [e.latlng.lat, e.latlng.lng].join(",")); this.clipboard = undefined; + this.#adjustCursor(); } getData(): any { @@ -166,6 +175,7 @@ export default class GeoMapTypeWidget extends TypeWidget { toastService.showMessage(t("relation_map.click_on_canvas_to_place_new_note")); this.clipboard = { noteId: note.noteId, title }; + this.#adjustCursor(); } async doRefresh(note: FNote) { From 3281bb8e9f9794b05120d2824d921b5965cf2a88 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 21 Jan 2025 14:17:04 +0200 Subject: [PATCH 15/42] feat(geomap): allow dragging --- src/public/app/widgets/type_widgets/geo_map.ts | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/public/app/widgets/type_widgets/geo_map.ts b/src/public/app/widgets/type_widgets/geo_map.ts index 99450f9ff..67d41a53f 100644 --- a/src/public/app/widgets/type_widgets/geo_map.ts +++ b/src/public/app/widgets/type_widgets/geo_map.ts @@ -1,4 +1,4 @@ -import type { LatLng, LeafletMouseEvent } from "leaflet"; +import type { LatLng, LeafletMouseEvent, Marker } from "leaflet"; import type FNote from "../../entities/fnote.js"; import GeoMapWidget, { type InitCallback, type Leaflet } from "../geo_map.js"; import TypeWidget from "./type_widget.js" @@ -116,9 +116,14 @@ export default class GeoMapTypeWidget extends TypeWidget { } const [ lat, lng ] = latLng.split(",", 2).map((el) => parseFloat(el)); - L.marker(L.latLng(lat, lng)) + L.marker(L.latLng(lat, lng), { + draggable: true + }) .addTo(map) - .bindPopup(childNote.title); + .bindPopup(childNote.title) + .on("moveend", e => { + this.moveMarker(childNote.noteId, (e.target as Marker).getLatLng()); + }); } } @@ -131,12 +136,15 @@ export default class GeoMapTypeWidget extends TypeWidget { return; } - const { noteId } = this.clipboard; - await attributes.setLabel(noteId, LOCATION_ATTRIBUTE, [e.latlng.lat, e.latlng.lng].join(",")); + this.moveMarker(this.clipboard.noteId, e.latlng); this.clipboard = undefined; this.#adjustCursor(); } + async moveMarker(noteId: string, latLng: LatLng) { + await attributes.setLabel(noteId, LOCATION_ATTRIBUTE, [latLng.lat, latLng.lng].join(",")); + } + getData(): any { const map = this.geoMapWidget.map; if (!map) { From 04367de1126cf8970a13199ecdfa8cc5cc6ee791 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 21 Jan 2025 14:23:59 +0200 Subject: [PATCH 16/42] fix(geomap): duplicate markers --- src/public/app/widgets/type_widgets/geo_map.ts | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/public/app/widgets/type_widgets/geo_map.ts b/src/public/app/widgets/type_widgets/geo_map.ts index 67d41a53f..77ccfd049 100644 --- a/src/public/app/widgets/type_widgets/geo_map.ts +++ b/src/public/app/widgets/type_widgets/geo_map.ts @@ -1,4 +1,4 @@ -import type { LatLng, LeafletMouseEvent, Marker } from "leaflet"; +import { Marker, type LatLng, type LeafletMouseEvent } from "leaflet"; import type FNote from "../../entities/fnote.js"; import GeoMapWidget, { type InitCallback, type Leaflet } from "../geo_map.js"; import TypeWidget from "./type_widget.js" @@ -43,11 +43,14 @@ interface Clipboard { title: string; } +type MarkerData = Record; + export default class GeoMapTypeWidget extends TypeWidget { private geoMapWidget: GeoMapWidget; private clipboard?: Clipboard; private L!: Leaflet; + private currentMarkerData: MarkerData; static getType() { return "geoMap"; @@ -57,6 +60,7 @@ export default class GeoMapTypeWidget extends TypeWidget { super(); this.geoMapWidget = new GeoMapWidget("type", (L: Leaflet) => this.#onMapInitialized(L)); + this.currentMarkerData = {}; this.child(this.geoMapWidget); } @@ -107,6 +111,13 @@ export default class GeoMapTypeWidget extends TypeWidget { return; } + // Delete all existing markers + for (const marker of Object.values(this.currentMarkerData)) { + marker.remove(); + } + + // Add the new markers. + this.currentMarkerData = {}; const childNotes = await this.note.getChildNotes(); const L = this.L; for (const childNote of childNotes) { @@ -116,7 +127,7 @@ export default class GeoMapTypeWidget extends TypeWidget { } const [ lat, lng ] = latLng.split(",", 2).map((el) => parseFloat(el)); - L.marker(L.latLng(lat, lng), { + const marker = L.marker(L.latLng(lat, lng), { draggable: true }) .addTo(map) @@ -124,6 +135,7 @@ export default class GeoMapTypeWidget extends TypeWidget { .on("moveend", e => { this.moveMarker(childNote.noteId, (e.target as Marker).getLatLng()); }); + this.currentMarkerData[childNote.noteId] = marker; } } From b2a5f066461914b2cc66b3454e8a20e41b05e7eb Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 21 Jan 2025 15:39:20 +0200 Subject: [PATCH 17/42] feat(geomap): enable autopan for dragging markers --- src/public/app/widgets/type_widgets/geo_map.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/public/app/widgets/type_widgets/geo_map.ts b/src/public/app/widgets/type_widgets/geo_map.ts index 77ccfd049..ed61f0a2c 100644 --- a/src/public/app/widgets/type_widgets/geo_map.ts +++ b/src/public/app/widgets/type_widgets/geo_map.ts @@ -128,7 +128,9 @@ export default class GeoMapTypeWidget extends TypeWidget { const [ lat, lng ] = latLng.split(",", 2).map((el) => parseFloat(el)); const marker = L.marker(L.latLng(lat, lng), { - draggable: true + draggable: true, + autoPan: true, + autoPanSpeed: 5 }) .addTo(map) .bindPopup(childNote.title) From 087d4790f4d79554823aa46b19cd610129bdbd30 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 21 Jan 2025 18:53:36 +0200 Subject: [PATCH 18/42] feat(geomap): setup marker based on note icon --- .../app/widgets/type_widgets/geo_map.ts | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/public/app/widgets/type_widgets/geo_map.ts b/src/public/app/widgets/type_widgets/geo_map.ts index ed61f0a2c..39f0a1beb 100644 --- a/src/public/app/widgets/type_widgets/geo_map.ts +++ b/src/public/app/widgets/type_widgets/geo_map.ts @@ -8,6 +8,9 @@ import dialogService from "../../services/dialog.js"; import type { EventData } from "../../components/app_context.js"; import { t } from "../../services/i18n.js"; import attributes from "../../services/attributes.js"; +import asset_path from "../../../../services/asset_path.js"; + +const ICON_WIDTH = 30; const TPL = `\
@@ -19,6 +22,27 @@ const TPL = `\ .geo-map-container.placing-note { cursor: crosshair; } + + .geo-map-container .marker-pin { + position: relative; + } + + .geo-map-container .leaflet-div-icon { + position: relative; + background: transparent; + border: 0; + } + + .geo-map-container .leaflet-div-icon span { + position: absolute; + top: 3px; + left: 3px; + background-color: white; + color: black; + padding: 2px; + border-radius: 50%; + font-size: 16px; + }
`; @@ -127,7 +151,17 @@ export default class GeoMapTypeWidget extends TypeWidget { } const [ lat, lng ] = latLng.split(",", 2).map((el) => parseFloat(el)); + const icon = L.divIcon({ + html: `\ + + + `, + iconSize: [ 25, 42 ], + iconAnchor: [ 7, 42 ] + }) + const marker = L.marker(L.latLng(lat, lng), { + icon, draggable: true, autoPan: true, autoPanSpeed: 5 From 016a9e4a99c3282e5a40be80282d59733be5a5ff Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 21 Jan 2025 19:01:22 +0200 Subject: [PATCH 19/42] fix(geomap): pixel perfect marker positioning --- src/public/app/widgets/type_widgets/geo_map.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/public/app/widgets/type_widgets/geo_map.ts b/src/public/app/widgets/type_widgets/geo_map.ts index 39f0a1beb..4d2693336 100644 --- a/src/public/app/widgets/type_widgets/geo_map.ts +++ b/src/public/app/widgets/type_widgets/geo_map.ts @@ -156,15 +156,15 @@ export default class GeoMapTypeWidget extends TypeWidget { `, - iconSize: [ 25, 42 ], - iconAnchor: [ 7, 42 ] + iconSize: [ 25, 41 ], + iconAnchor: [ 12, 41 ] }) const marker = L.marker(L.latLng(lat, lng), { icon, draggable: true, autoPan: true, - autoPanSpeed: 5 + autoPanSpeed: 5, }) .addTo(map) .bindPopup(childNote.title) From 1be3492f67c771c5ff7c1080955070472207180c Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 21 Jan 2025 19:02:36 +0200 Subject: [PATCH 20/42] style(geomap): improve alignment for marker icon --- src/public/app/widgets/type_widgets/geo_map.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/public/app/widgets/type_widgets/geo_map.ts b/src/public/app/widgets/type_widgets/geo_map.ts index 4d2693336..94f1b1c81 100644 --- a/src/public/app/widgets/type_widgets/geo_map.ts +++ b/src/public/app/widgets/type_widgets/geo_map.ts @@ -36,12 +36,12 @@ const TPL = `\ .geo-map-container .leaflet-div-icon span { position: absolute; top: 3px; - left: 3px; + left: 2px; background-color: white; color: black; padding: 2px; border-radius: 50%; - font-size: 16px; + font-size: 17px; } `; From 08722d59354ad70e57ce36cadbb8203805a416b9 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 21 Jan 2025 19:10:35 +0200 Subject: [PATCH 21/42] feat(geomap): add shadow to marker --- src/public/app/widgets/type_widgets/geo_map.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/public/app/widgets/type_widgets/geo_map.ts b/src/public/app/widgets/type_widgets/geo_map.ts index 94f1b1c81..e2321b4c4 100644 --- a/src/public/app/widgets/type_widgets/geo_map.ts +++ b/src/public/app/widgets/type_widgets/geo_map.ts @@ -33,6 +33,13 @@ const TPL = `\ border: 0; } + .geo-map-container .leaflet-div-icon .icon-shadow { + position: absolute; + top: 0; + left: 0; + z-index: -1; + } + .geo-map-container .leaflet-div-icon span { position: absolute; top: 3px; @@ -153,7 +160,8 @@ export default class GeoMapTypeWidget extends TypeWidget { const [ lat, lng ] = latLng.split(",", 2).map((el) => parseFloat(el)); const icon = L.divIcon({ html: `\ - + + `, iconSize: [ 25, 41 ], From 6b906a91d794dc336e1a271e6609c133da63e5b4 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 21 Jan 2025 19:33:12 +0200 Subject: [PATCH 22/42] feat(geomap): add labels to markers --- .../app/widgets/type_widgets/geo_map.ts | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/public/app/widgets/type_widgets/geo_map.ts b/src/public/app/widgets/type_widgets/geo_map.ts index e2321b4c4..9e13f5925 100644 --- a/src/public/app/widgets/type_widgets/geo_map.ts +++ b/src/public/app/widgets/type_widgets/geo_map.ts @@ -10,8 +10,6 @@ import { t } from "../../services/i18n.js"; import attributes from "../../services/attributes.js"; import asset_path from "../../../../services/asset_path.js"; -const ICON_WIDTH = 30; - const TPL = `\
`; @@ -163,6 +179,7 @@ export default class GeoMapTypeWidget extends TypeWidget { + ${childNote.title} `, iconSize: [ 25, 41 ], iconAnchor: [ 12, 41 ] From 259dcdb568910a959fbdd0e914507463fb4ac613 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 21 Jan 2025 19:53:23 +0200 Subject: [PATCH 23/42] feat(geomap): set custom icon for notes created from within the map --- src/public/app/widgets/type_widgets/geo_map.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/public/app/widgets/type_widgets/geo_map.ts b/src/public/app/widgets/type_widgets/geo_map.ts index 9e13f5925..9cdad23a7 100644 --- a/src/public/app/widgets/type_widgets/geo_map.ts +++ b/src/public/app/widgets/type_widgets/geo_map.ts @@ -70,6 +70,7 @@ const TPL = `\ `; const LOCATION_ATTRIBUTE = "latLng"; +const CHILD_NOTE_ICON = "bx bx-pin"; interface MapData { view?: { @@ -252,6 +253,7 @@ export default class GeoMapTypeWidget extends TypeWidget { content: "", type: "text" }); + attributes.setLabel(note.noteId, "iconClass", CHILD_NOTE_ICON); toastService.showMessage(t("relation_map.click_on_canvas_to_place_new_note")); From d1aa0e5f50c78a1a08342435dfde3c1a99196425 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 21 Jan 2025 20:38:31 +0200 Subject: [PATCH 24/42] feat(geomap): invert note creation workflow --- .../app/widgets/type_widgets/geo_map.ts | 49 ++++++++++--------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/src/public/app/widgets/type_widgets/geo_map.ts b/src/public/app/widgets/type_widgets/geo_map.ts index 9cdad23a7..0815f1da9 100644 --- a/src/public/app/widgets/type_widgets/geo_map.ts +++ b/src/public/app/widgets/type_widgets/geo_map.ts @@ -86,17 +86,17 @@ interface CreateChildResponse { } } -interface Clipboard { - noteId: string; - title: string; -} - type MarkerData = Record; +enum State { + Normal, + NewNote +} + export default class GeoMapTypeWidget extends TypeWidget { private geoMapWidget: GeoMapWidget; - private clipboard?: Clipboard; + private state: State; private L!: Leaflet; private currentMarkerData: MarkerData; @@ -109,6 +109,7 @@ export default class GeoMapTypeWidget extends TypeWidget { this.geoMapWidget = new GeoMapWidget("type", (L: Leaflet) => this.#onMapInitialized(L)); this.currentMarkerData = {}; + this.state = State.Normal; this.child(this.geoMapWidget); } @@ -202,16 +203,29 @@ export default class GeoMapTypeWidget extends TypeWidget { } #adjustCursor() { - this.geoMapWidget.$container.toggleClass("placing-note", !!this.clipboard); + this.geoMapWidget.$container.toggleClass("placing-note", this.state === State.NewNote); } async #onMapClicked(e: LeafletMouseEvent) { - if (!this.clipboard) { + if (this.state !== State.NewNote) { return; } - this.moveMarker(this.clipboard.noteId, e.latlng); - this.clipboard = undefined; + const title = await dialogService.prompt({ message: t("relation_map.enter_title_of_new_note"), defaultValue: t("relation_map.default_new_note_title") }); + + if (!title?.trim()) { + return; + } + + const { note } = await server.post(`notes/${this.noteId}/children?target=into`, { + title, + content: "", + type: "text" + }); + attributes.setLabel(note.noteId, "iconClass", CHILD_NOTE_ICON); + this.moveMarker(note.noteId, e.latlng); + + this.state = State.Normal; this.#adjustCursor(); } @@ -242,22 +256,9 @@ export default class GeoMapTypeWidget extends TypeWidget { return; } - const title = await dialogService.prompt({ message: t("relation_map.enter_title_of_new_note"), defaultValue: t("relation_map.default_new_note_title") }); - - if (!title?.trim()) { - return; - } - - const { note } = await server.post(`notes/${this.noteId}/children?target=into`, { - title, - content: "", - type: "text" - }); - attributes.setLabel(note.noteId, "iconClass", CHILD_NOTE_ICON); - toastService.showMessage(t("relation_map.click_on_canvas_to_place_new_note")); - this.clipboard = { noteId: note.noteId, title }; + this.state = State.NewNote; this.#adjustCursor(); } From c2cb07ed085668a8eda33213e3ad88678058bab6 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 21 Jan 2025 20:40:19 +0200 Subject: [PATCH 25/42] feat(geomap): dismiss creation if dialog is dismissed --- src/public/app/widgets/type_widgets/geo_map.ts | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/public/app/widgets/type_widgets/geo_map.ts b/src/public/app/widgets/type_widgets/geo_map.ts index 0815f1da9..4376dcb4f 100644 --- a/src/public/app/widgets/type_widgets/geo_map.ts +++ b/src/public/app/widgets/type_widgets/geo_map.ts @@ -213,18 +213,16 @@ export default class GeoMapTypeWidget extends TypeWidget { const title = await dialogService.prompt({ message: t("relation_map.enter_title_of_new_note"), defaultValue: t("relation_map.default_new_note_title") }); - if (!title?.trim()) { - return; + if (title?.trim()) { + const { note } = await server.post(`notes/${this.noteId}/children?target=into`, { + title, + content: "", + type: "text" + }); + attributes.setLabel(note.noteId, "iconClass", CHILD_NOTE_ICON); + this.moveMarker(note.noteId, e.latlng); } - const { note } = await server.post(`notes/${this.noteId}/children?target=into`, { - title, - content: "", - type: "text" - }); - attributes.setLabel(note.noteId, "iconClass", CHILD_NOTE_ICON); - this.moveMarker(note.noteId, e.latlng); - this.state = State.Normal; this.#adjustCursor(); } From be4ee4c173ae7b49b91355cd4164b784ab44e65b Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 21 Jan 2025 21:03:27 +0200 Subject: [PATCH 26/42] feat(geomap): dismiss adding with escape --- src/public/app/widgets/type_widgets/geo_map.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/public/app/widgets/type_widgets/geo_map.ts b/src/public/app/widgets/type_widgets/geo_map.ts index 4376dcb4f..d21b5e5d4 100644 --- a/src/public/app/widgets/type_widgets/geo_map.ts +++ b/src/public/app/widgets/type_widgets/geo_map.ts @@ -258,6 +258,14 @@ export default class GeoMapTypeWidget extends TypeWidget { this.state = State.NewNote; this.#adjustCursor(); + + const globalKeyListener: (this: Window, ev: KeyboardEvent) => any = (e) => { + this.state = State.Normal; + this.#adjustCursor(); + + window.removeEventListener("keydown", globalKeyListener); + }; + window.addEventListener("keydown", globalKeyListener); } async doRefresh(note: FNote) { From dc7dd51913bd39fd01cefcbef15a5936daf58313 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 21 Jan 2025 21:06:36 +0200 Subject: [PATCH 27/42] refactor(geomap): simplify changing state --- .../app/widgets/type_widgets/geo_map.ts | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/public/app/widgets/type_widgets/geo_map.ts b/src/public/app/widgets/type_widgets/geo_map.ts index d21b5e5d4..78007090b 100644 --- a/src/public/app/widgets/type_widgets/geo_map.ts +++ b/src/public/app/widgets/type_widgets/geo_map.ts @@ -96,7 +96,7 @@ enum State { export default class GeoMapTypeWidget extends TypeWidget { private geoMapWidget: GeoMapWidget; - private state: State; + private _state: State; private L!: Leaflet; private currentMarkerData: MarkerData; @@ -109,7 +109,7 @@ export default class GeoMapTypeWidget extends TypeWidget { this.geoMapWidget = new GeoMapWidget("type", (L: Leaflet) => this.#onMapInitialized(L)); this.currentMarkerData = {}; - this.state = State.Normal; + this._state = State.Normal; this.child(this.geoMapWidget); } @@ -202,12 +202,13 @@ export default class GeoMapTypeWidget extends TypeWidget { } } - #adjustCursor() { - this.geoMapWidget.$container.toggleClass("placing-note", this.state === State.NewNote); + #changeState(newState: State) { + this._state = newState; + this.geoMapWidget.$container.toggleClass("placing-note", newState === State.NewNote); } async #onMapClicked(e: LeafletMouseEvent) { - if (this.state !== State.NewNote) { + if (this._state !== State.NewNote) { return; } @@ -223,8 +224,7 @@ export default class GeoMapTypeWidget extends TypeWidget { this.moveMarker(note.noteId, e.latlng); } - this.state = State.Normal; - this.#adjustCursor(); + this.#changeState(State.Normal); } async moveMarker(noteId: string, latLng: LatLng) { @@ -256,12 +256,10 @@ export default class GeoMapTypeWidget extends TypeWidget { toastService.showMessage(t("relation_map.click_on_canvas_to_place_new_note")); - this.state = State.NewNote; - this.#adjustCursor(); + this.#changeState(State.NewNote); const globalKeyListener: (this: Window, ev: KeyboardEvent) => any = (e) => { - this.state = State.Normal; - this.#adjustCursor(); + this.#changeState(State.Normal); window.removeEventListener("keydown", globalKeyListener); }; From b813b107a8f78656649af5e8d87111740e9dc2bb Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 21 Jan 2025 21:18:41 +0200 Subject: [PATCH 28/42] chore(geomap): change attribute to `geolocation` --- src/public/app/widgets/type_widgets/geo_map.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/public/app/widgets/type_widgets/geo_map.ts b/src/public/app/widgets/type_widgets/geo_map.ts index 78007090b..37ce837bc 100644 --- a/src/public/app/widgets/type_widgets/geo_map.ts +++ b/src/public/app/widgets/type_widgets/geo_map.ts @@ -69,7 +69,7 @@ const TPL = `\ `; -const LOCATION_ATTRIBUTE = "latLng"; +const LOCATION_ATTRIBUTE = "geolocation"; const CHILD_NOTE_ICON = "bx bx-pin"; interface MapData { From 31491b957b1020e92c6e9708190b94f1c513b548 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 21 Jan 2025 21:29:02 +0200 Subject: [PATCH 29/42] feat(geomap): use persistent notification --- src/public/app/widgets/type_widgets/geo_map.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/public/app/widgets/type_widgets/geo_map.ts b/src/public/app/widgets/type_widgets/geo_map.ts index 37ce837bc..406cd6e00 100644 --- a/src/public/app/widgets/type_widgets/geo_map.ts +++ b/src/public/app/widgets/type_widgets/geo_map.ts @@ -212,6 +212,7 @@ export default class GeoMapTypeWidget extends TypeWidget { return; } + toastService.closePersistent("geo-new-note"); const title = await dialogService.prompt({ message: t("relation_map.enter_title_of_new_note"), defaultValue: t("relation_map.default_new_note_title") }); if (title?.trim()) { @@ -254,7 +255,12 @@ export default class GeoMapTypeWidget extends TypeWidget { return; } - toastService.showMessage(t("relation_map.click_on_canvas_to_place_new_note")); + toastService.showPersistent({ + icon: "plus", + id: "geo-new-note", + title: "New note", + message: "Click on the map to create a new note at that location or press Escape to dismiss." + }); this.#changeState(State.NewNote); @@ -262,6 +268,7 @@ export default class GeoMapTypeWidget extends TypeWidget { this.#changeState(State.Normal); window.removeEventListener("keydown", globalKeyListener); + toastService.closePersistent("geo-new-note"); }; window.addEventListener("keydown", globalKeyListener); } From 65553250b5072acefdee0e777f390faf97cb3add Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 21 Jan 2025 21:50:32 +0200 Subject: [PATCH 30/42] fix(geomap): dismissing add with any key --- src/public/app/widgets/type_widgets/geo_map.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/public/app/widgets/type_widgets/geo_map.ts b/src/public/app/widgets/type_widgets/geo_map.ts index 406cd6e00..d5157301d 100644 --- a/src/public/app/widgets/type_widgets/geo_map.ts +++ b/src/public/app/widgets/type_widgets/geo_map.ts @@ -265,6 +265,10 @@ export default class GeoMapTypeWidget extends TypeWidget { this.#changeState(State.NewNote); const globalKeyListener: (this: Window, ev: KeyboardEvent) => any = (e) => { + if (e.key !== "Escape") { + return; + } + this.#changeState(State.Normal); window.removeEventListener("keydown", globalKeyListener); From ac262228f0b4e7450996f794833a7a475e985d53 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 21 Jan 2025 22:10:20 +0200 Subject: [PATCH 31/42] feat(geomap): note preview on tooltip --- src/public/app/widgets/type_widgets/geo_map.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/public/app/widgets/type_widgets/geo_map.ts b/src/public/app/widgets/type_widgets/geo_map.ts index d5157301d..2f625d738 100644 --- a/src/public/app/widgets/type_widgets/geo_map.ts +++ b/src/public/app/widgets/type_widgets/geo_map.ts @@ -178,11 +178,12 @@ export default class GeoMapTypeWidget extends TypeWidget { const [ lat, lng ] = latLng.split(",", 2).map((el) => parseFloat(el)); const icon = L.divIcon({ html: `\ - - - - ${childNote.title} - `, + + + + + ${childNote.title} + `, iconSize: [ 25, 41 ], iconAnchor: [ 12, 41 ] }) @@ -194,7 +195,6 @@ export default class GeoMapTypeWidget extends TypeWidget { autoPanSpeed: 5, }) .addTo(map) - .bindPopup(childNote.title) .on("moveend", e => { this.moveMarker(childNote.noteId, (e.target as Marker).getLatLng()); }); From dbd38ecedfbaa662beb920ace4f866831544227e Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 22 Jan 2025 18:57:09 +0200 Subject: [PATCH 32/42] fix(geo_map): markers no longer rendering after clicking on a link --- src/public/app/widgets/type_widgets/geo_map.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/public/app/widgets/type_widgets/geo_map.ts b/src/public/app/widgets/type_widgets/geo_map.ts index 2f625d738..027faf559 100644 --- a/src/public/app/widgets/type_widgets/geo_map.ts +++ b/src/public/app/widgets/type_widgets/geo_map.ts @@ -279,6 +279,7 @@ export default class GeoMapTypeWidget extends TypeWidget { async doRefresh(note: FNote) { await this.geoMapWidget.refresh(); + await this.#reloadMarkers(); } entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) { From 2e1ad24584e0b11882280a40db8c9d9b5a83d1d8 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 22 Jan 2025 19:33:53 +0200 Subject: [PATCH 33/42] feat(geo_map): add option to remove from map --- src/public/app/components/app_context.ts | 5 ++++ src/public/app/menus/context_menu.ts | 1 + src/public/app/menus/link_context_menu.ts | 4 +-- .../app/widgets/type_widgets/geo_map.ts | 25 +++++++++++++------ .../type_widgets/geo_map_context_menu.ts | 18 +++++++++++++ 5 files changed, 43 insertions(+), 10 deletions(-) create mode 100644 src/public/app/widgets/type_widgets/geo_map_context_menu.ts diff --git a/src/public/app/components/app_context.ts b/src/public/app/components/app_context.ts index fa20630ec..b15e075b6 100644 --- a/src/public/app/components/app_context.ts +++ b/src/public/app/components/app_context.ts @@ -193,6 +193,11 @@ export type CommandMappings = { setZoomFactorAndSave: { zoomFactor: string; } + + // Geomap + deleteFromMap: { + noteId: string; + } }; type EventMappings = { diff --git a/src/public/app/menus/context_menu.ts b/src/public/app/menus/context_menu.ts index 058178afc..de0fcf38a 100644 --- a/src/public/app/menus/context_menu.ts +++ b/src/public/app/menus/context_menu.ts @@ -31,6 +31,7 @@ export interface MenuCommandItem { export type MenuItem = MenuCommandItem | MenuSeparatorItem; export type MenuHandler = (item: MenuCommandItem, e: JQuery.MouseDownEvent) => void; +export type ContextMenuEvent = PointerEvent | MouseEvent | JQuery.ContextMenuEvent; class ContextMenu { private $widget: JQuery; diff --git a/src/public/app/menus/link_context_menu.ts b/src/public/app/menus/link_context_menu.ts index 3343ec85e..efa4da67e 100644 --- a/src/public/app/menus/link_context_menu.ts +++ b/src/public/app/menus/link_context_menu.ts @@ -1,9 +1,9 @@ import { t } from "../services/i18n.js"; -import contextMenu from "./context_menu.js"; +import contextMenu, { type ContextMenuEvent } from "./context_menu.js"; import appContext from "../components/app_context.js"; import type { ViewScope } from "../services/link.js"; -function openContextMenu(notePath: string, e: PointerEvent | MouseEvent | JQuery.ContextMenuEvent, viewScope: ViewScope = {}, hoistedNoteId: string | null = null) { +function openContextMenu(notePath: string, e: ContextMenuEvent, viewScope: ViewScope = {}, hoistedNoteId: string | null = null) { contextMenu.show({ x: e.pageX, y: e.pageY, diff --git a/src/public/app/widgets/type_widgets/geo_map.ts b/src/public/app/widgets/type_widgets/geo_map.ts index 027faf559..5dd9af475 100644 --- a/src/public/app/widgets/type_widgets/geo_map.ts +++ b/src/public/app/widgets/type_widgets/geo_map.ts @@ -9,6 +9,7 @@ import type { EventData } from "../../components/app_context.js"; import { t } from "../../services/i18n.js"; import attributes from "../../services/attributes.js"; import asset_path from "../../../../services/asset_path.js"; +import openContextMenu from "./geo_map_context_menu.js"; const TPL = `\
@@ -178,12 +179,10 @@ export default class GeoMapTypeWidget extends TypeWidget { const [ lat, lng ] = latLng.split(",", 2).map((el) => parseFloat(el)); const icon = L.divIcon({ html: `\ - - - - - ${childNote.title} - `, + + + + ${childNote.title}`, iconSize: [ 25, 41 ], iconAnchor: [ 12, 41 ] }) @@ -198,6 +197,11 @@ export default class GeoMapTypeWidget extends TypeWidget { .on("moveend", e => { this.moveMarker(childNote.noteId, (e.target as Marker).getLatLng()); }); + + marker.on("contextmenu", (e) => { + openContextMenu(childNote.noteId, e.originalEvent); + }); + this.currentMarkerData[childNote.noteId] = marker; } } @@ -228,8 +232,9 @@ export default class GeoMapTypeWidget extends TypeWidget { this.#changeState(State.Normal); } - async moveMarker(noteId: string, latLng: LatLng) { - await attributes.setLabel(noteId, LOCATION_ATTRIBUTE, [latLng.lat, latLng.lng].join(",")); + async moveMarker(noteId: string, latLng: LatLng | null) { + const value = (latLng ? [latLng.lat, latLng.lng].join(",") : ""); + await attributes.setLabel(noteId, LOCATION_ATTRIBUTE, value); } getData(): any { @@ -289,4 +294,8 @@ export default class GeoMapTypeWidget extends TypeWidget { } } + deleteFromMapEvent({ noteId }: EventData<"deleteFromMap">) { + this.moveMarker(noteId, null); + } + } diff --git a/src/public/app/widgets/type_widgets/geo_map_context_menu.ts b/src/public/app/widgets/type_widgets/geo_map_context_menu.ts new file mode 100644 index 000000000..ef35f06e5 --- /dev/null +++ b/src/public/app/widgets/type_widgets/geo_map_context_menu.ts @@ -0,0 +1,18 @@ +import appContext from "../../components/app_context.js"; +import type { ContextMenuEvent } from "../../menus/context_menu.js"; +import contextMenu from "../../menus/context_menu.js"; + +export default function openContextMenu(noteId: string, e: ContextMenuEvent) { + contextMenu.show({ + x: e.pageX, + y: e.pageY, + items: [ + { title: "Remove from map", command: "deleteFromMap", uiIcon: "bx bx-trash" } + ], + selectMenuItemHandler: ({ command }) => { + if (command) { + appContext.triggerCommand(command, { noteId }); + } + } + }); +} From 47b02da021d6186d02bcf02a0d1d619c6824f34a Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 22 Jan 2025 20:02:20 +0200 Subject: [PATCH 34/42] feat(geo_map): add back open context menu --- src/public/app/menus/link_context_menu.ts | 54 +++++++++++-------- .../type_widgets/geo_map_context_menu.ts | 9 +++- 2 files changed, 39 insertions(+), 24 deletions(-) diff --git a/src/public/app/menus/link_context_menu.ts b/src/public/app/menus/link_context_menu.ts index efa4da67e..634f69db9 100644 --- a/src/public/app/menus/link_context_menu.ts +++ b/src/public/app/menus/link_context_menu.ts @@ -1,36 +1,44 @@ import { t } from "../services/i18n.js"; -import contextMenu, { type ContextMenuEvent } from "./context_menu.js"; -import appContext from "../components/app_context.js"; +import contextMenu, { type ContextMenuEvent, type MenuItem } from "./context_menu.js"; +import appContext, { type CommandNames } from "../components/app_context.js"; import type { ViewScope } from "../services/link.js"; function openContextMenu(notePath: string, e: ContextMenuEvent, viewScope: ViewScope = {}, hoistedNoteId: string | null = null) { contextMenu.show({ x: e.pageX, y: e.pageY, - items: [ - { title: t("link_context_menu.open_note_in_new_tab"), command: "openNoteInNewTab", uiIcon: "bx bx-link-external" }, - { title: t("link_context_menu.open_note_in_new_split"), command: "openNoteInNewSplit", uiIcon: "bx bx-dock-right" }, - { title: t("link_context_menu.open_note_in_new_window"), command: "openNoteInNewWindow", uiIcon: "bx bx-window-open" } - ], - selectMenuItemHandler: ({ command }) => { - if (!hoistedNoteId) { - hoistedNoteId = appContext.tabManager.getActiveContext().hoistedNoteId; - } - - if (command === "openNoteInNewTab") { - appContext.tabManager.openContextWithNote(notePath, { hoistedNoteId, viewScope }); - } else if (command === "openNoteInNewSplit") { - const subContexts = appContext.tabManager.getActiveContext().getSubContexts(); - const { ntxId } = subContexts[subContexts.length - 1]; - - appContext.triggerCommand("openNewNoteSplit", { ntxId, notePath, hoistedNoteId, viewScope }); - } else if (command === "openNoteInNewWindow") { - appContext.triggerCommand("openInWindow", { notePath, hoistedNoteId, viewScope }); - } - } + items: getItems(), + selectMenuItemHandler: ({ command }) => handleLinkContextMenuItem(command, notePath, viewScope, hoistedNoteId) }); } +function getItems(): MenuItem[] { + return [ + { title: t("link_context_menu.open_note_in_new_tab"), command: "openNoteInNewTab", uiIcon: "bx bx-link-external" }, + { title: t("link_context_menu.open_note_in_new_split"), command: "openNoteInNewSplit", uiIcon: "bx bx-dock-right" }, + { title: t("link_context_menu.open_note_in_new_window"), command: "openNoteInNewWindow", uiIcon: "bx bx-window-open" } + ]; +} + +function handleLinkContextMenuItem(command: string | undefined, notePath: string, viewScope = {}, hoistedNoteId: string | null = null) { + if (!hoistedNoteId) { + hoistedNoteId = appContext.tabManager.getActiveContext().hoistedNoteId; + } + + if (command === "openNoteInNewTab") { + appContext.tabManager.openContextWithNote(notePath, { hoistedNoteId, viewScope }); + } else if (command === "openNoteInNewSplit") { + const subContexts = appContext.tabManager.getActiveContext().getSubContexts(); + const { ntxId } = subContexts[subContexts.length - 1]; + + appContext.triggerCommand("openNewNoteSplit", { ntxId, notePath, hoistedNoteId, viewScope }); + } else if (command === "openNoteInNewWindow") { + appContext.triggerCommand("openInWindow", { notePath, hoistedNoteId, viewScope }); + } +} + export default { + getItems, + handleLinkContextMenuItem, openContextMenu }; diff --git a/src/public/app/widgets/type_widgets/geo_map_context_menu.ts b/src/public/app/widgets/type_widgets/geo_map_context_menu.ts index ef35f06e5..aae7746f7 100644 --- a/src/public/app/widgets/type_widgets/geo_map_context_menu.ts +++ b/src/public/app/widgets/type_widgets/geo_map_context_menu.ts @@ -1,18 +1,25 @@ import appContext from "../../components/app_context.js"; import type { ContextMenuEvent } from "../../menus/context_menu.js"; import contextMenu from "../../menus/context_menu.js"; +import linkContextMenu from "../../menus/link_context_menu.js"; export default function openContextMenu(noteId: string, e: ContextMenuEvent) { contextMenu.show({ x: e.pageX, y: e.pageY, items: [ + ...linkContextMenu.getItems(), + { title: "----" }, { title: "Remove from map", command: "deleteFromMap", uiIcon: "bx bx-trash" } ], selectMenuItemHandler: ({ command }) => { - if (command) { + if (command === "deleteFromMap") { appContext.triggerCommand(command, { noteId }); + return; } + + // Pass the events to the link context menu + linkContextMenu.handleLinkContextMenuItem(command, noteId); } }); } From 9b1279ce142ad4c0d2af3952c4c31cfe6a58b270 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 22 Jan 2025 21:07:40 +0200 Subject: [PATCH 35/42] feat(geo_map): add option to open location --- src/public/app/components/app_context.ts | 6 +++--- src/public/app/menus/link_context_menu.ts | 2 +- src/public/app/services/link.ts | 2 +- src/public/app/widgets/type_widgets/geo_map.ts | 12 ++++++++++++ .../app/widgets/type_widgets/geo_map_context_menu.ts | 8 +++++++- 5 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/public/app/components/app_context.ts b/src/public/app/components/app_context.ts index b15e075b6..731f187fb 100644 --- a/src/public/app/components/app_context.ts +++ b/src/public/app/components/app_context.ts @@ -23,6 +23,7 @@ import type LoadResults from "../services/load_results.js"; import type { Attribute } from "../services/attribute_parser.js"; import type NoteTreeWidget from "../widgets/note_tree.js"; import type { default as NoteContext, GetTextEditorCallback } from "./note_context.js"; +import type { ContextMenuEvent } from "../menus/context_menu.js"; interface Layout { getRootWidget: (appContext: AppContext) => RootWidget; @@ -195,9 +196,8 @@ export type CommandMappings = { } // Geomap - deleteFromMap: { - noteId: string; - } + deleteFromMap: { noteId: string }, + openGeoLocation: { noteId: string, event: JQuery.MouseDownEvent } }; type EventMappings = { diff --git a/src/public/app/menus/link_context_menu.ts b/src/public/app/menus/link_context_menu.ts index 634f69db9..6456c6519 100644 --- a/src/public/app/menus/link_context_menu.ts +++ b/src/public/app/menus/link_context_menu.ts @@ -12,7 +12,7 @@ function openContextMenu(notePath: string, e: ContextMenuEvent, viewScope: ViewS }); } -function getItems(): MenuItem[] { +function getItems(): MenuItem[] { return [ { title: t("link_context_menu.open_note_in_new_tab"), command: "openNoteInNewTab", uiIcon: "bx bx-link-external" }, { title: t("link_context_menu.open_note_in_new_split"), command: "openNoteInNewSplit", uiIcon: "bx bx-dock-right" }, diff --git a/src/public/app/services/link.ts b/src/public/app/services/link.ts index 3e9d4f310..f80a3c10a 100644 --- a/src/public/app/services/link.ts +++ b/src/public/app/services/link.ts @@ -234,7 +234,7 @@ function goToLink(evt: MouseEvent | JQuery.ClickEvent) { return goToLinkExt(evt, hrefLink, $link); } -function goToLinkExt(evt: MouseEvent | JQuery.ClickEvent | React.PointerEvent, hrefLink: string | undefined, $link: JQuery | null) { +function goToLinkExt(evt: MouseEvent | JQuery.ClickEvent | JQuery.MouseDownEvent | React.PointerEvent, hrefLink: string | undefined, $link?: JQuery | null) { if (hrefLink?.startsWith("data:")) { return true; } diff --git a/src/public/app/widgets/type_widgets/geo_map.ts b/src/public/app/widgets/type_widgets/geo_map.ts index 5dd9af475..160e37e47 100644 --- a/src/public/app/widgets/type_widgets/geo_map.ts +++ b/src/public/app/widgets/type_widgets/geo_map.ts @@ -10,6 +10,7 @@ import { t } from "../../services/i18n.js"; import attributes from "../../services/attributes.js"; import asset_path from "../../../../services/asset_path.js"; import openContextMenu from "./geo_map_context_menu.js"; +import link from "../../services/link.js"; const TPL = `\
@@ -294,6 +295,17 @@ export default class GeoMapTypeWidget extends TypeWidget { } } + openGeoLocationEvent({ noteId, event }: EventData<"openGeoLocation">) { + const marker = this.currentMarkerData[noteId]; + if (!marker) { + return; + } + + const latLng = this.currentMarkerData[noteId].getLatLng(); + const url = `geo:${latLng.lat},${latLng.lng}`; + link.goToLinkExt(event, url); + } + deleteFromMapEvent({ noteId }: EventData<"deleteFromMap">) { this.moveMarker(noteId, null); } diff --git a/src/public/app/widgets/type_widgets/geo_map_context_menu.ts b/src/public/app/widgets/type_widgets/geo_map_context_menu.ts index aae7746f7..12bbfaa91 100644 --- a/src/public/app/widgets/type_widgets/geo_map_context_menu.ts +++ b/src/public/app/widgets/type_widgets/geo_map_context_menu.ts @@ -9,15 +9,21 @@ export default function openContextMenu(noteId: string, e: ContextMenuEvent) { y: e.pageY, items: [ ...linkContextMenu.getItems(), + { title: "Open location", command: "openGeoLocation", uiIcon: "bx bx-map-alt" }, { title: "----" }, { title: "Remove from map", command: "deleteFromMap", uiIcon: "bx bx-trash" } ], - selectMenuItemHandler: ({ command }) => { + selectMenuItemHandler: ({ command }, e) => { if (command === "deleteFromMap") { appContext.triggerCommand(command, { noteId }); return; } + if (command === "openGeoLocation") { + appContext.triggerCommand(command, { noteId, event: e }); + return; + } + // Pass the events to the link context menu linkContextMenu.handleLinkContextMenuItem(command, noteId); } From e06db0038f3ad9358cf0cf92939dbdb220a4b8a6 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 22 Jan 2025 21:49:23 +0200 Subject: [PATCH 36/42] fix(geomap): display again note tooltip --- src/public/app/widgets/type_widgets/geo_map.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/public/app/widgets/type_widgets/geo_map.ts b/src/public/app/widgets/type_widgets/geo_map.ts index 160e37e47..299003e22 100644 --- a/src/public/app/widgets/type_widgets/geo_map.ts +++ b/src/public/app/widgets/type_widgets/geo_map.ts @@ -11,6 +11,7 @@ import attributes from "../../services/attributes.js"; import asset_path from "../../../../services/asset_path.js"; import openContextMenu from "./geo_map_context_menu.js"; import link from "../../services/link.js"; +import note_tooltip from "../../services/note_tooltip.js"; const TPL = `\
@@ -203,6 +204,13 @@ export default class GeoMapTypeWidget extends TypeWidget { openContextMenu(childNote.noteId, e.originalEvent); }); + const el = marker.getElement(); + if (el) { + const $el = $(el); + $el.attr("data-href", `#${childNote.noteId}`); + note_tooltip.setupElementTooltip($($el)); + } + this.currentMarkerData[childNote.noteId] = marker; } } From 5a40d3f02066ae6295ef332022f387dd4f2e88d7 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 22 Jan 2025 21:55:42 +0200 Subject: [PATCH 37/42] fix(build): build errors --- src/public/app/components/app_context.ts | 15 +++++++++++++-- .../app/widgets/containers/left_pane_container.ts | 2 +- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/public/app/components/app_context.ts b/src/public/app/components/app_context.ts index 731f187fb..992b6a2a0 100644 --- a/src/public/app/components/app_context.ts +++ b/src/public/app/components/app_context.ts @@ -70,6 +70,7 @@ export interface ExecuteCommandData extends CommandData { */ export type CommandMappings = { "api-log-messages": CommandData; + focusTree: CommandData, focusOnDetail: Required; focusOnSearchDefinition: Required; searchNotes: CommandData & { @@ -232,9 +233,12 @@ type EventMappings = { activeContextChanged: { noteContext: NoteContext; }; + beforeNoteSwitch: { + noteContext: NoteContext; + }; noteSwitched: { noteContext: NoteContext; - notePath: string; + notePath: string | null; }; noteSwitchedAndActivatedEvent: { noteContext: NoteContext; @@ -253,12 +257,16 @@ type EventMappings = { noteId: string; }; hoistedNoteChanged: { - ntxId: string; + noteId: string; + ntxId: string | null; }; contextsReopenedEvent: { mainNtxId: string; tabPosition: number; }; + noteDetailRefreshed: { + ntxId?: string | null; + }; noteContextReorderEvent: { oldMainNtxId: string; newMainNtxId: string; @@ -275,6 +283,9 @@ type EventMappings = { geoMapCreateChildNote: { ntxId: string | null | undefined; // TODO: deduplicate ntxId }; + tabReorder: { + ntxIdsInOrder: string[] + }; }; export type EventListener = { diff --git a/src/public/app/widgets/containers/left_pane_container.ts b/src/public/app/widgets/containers/left_pane_container.ts index f5090b4a8..bbfedaa2a 100644 --- a/src/public/app/widgets/containers/left_pane_container.ts +++ b/src/public/app/widgets/containers/left_pane_container.ts @@ -22,7 +22,7 @@ export default class LeftPaneContainer extends FlexContainer { this.toggleInt(visible); if (visible) { - this.triggerEvent("focusTree"); + this.triggerEvent("focusTree", {}); } else { const activeNoteContext = appContext.tabManager.getActiveContext(); this.triggerEvent("focusOnDetail", { ntxId: activeNoteContext.ntxId }); From d814a4d49fc07d5a096ce806aa528263a95f1936 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 22 Jan 2025 22:12:25 +0200 Subject: [PATCH 38/42] chore(i18n): translate geo map messages --- .../app/widgets/floating_buttons/geo_map_button.ts | 3 ++- src/public/app/widgets/type_widgets/geo_map.ts | 4 ++-- .../app/widgets/type_widgets/geo_map_context_menu.ts | 5 +++-- src/public/translations/en/translation.json | 9 +++++++++ src/public/translations/ro/translation.json | 11 ++++++++++- translations/ro/server.json | 3 +++ 6 files changed, 29 insertions(+), 6 deletions(-) diff --git a/src/public/app/widgets/floating_buttons/geo_map_button.ts b/src/public/app/widgets/floating_buttons/geo_map_button.ts index 19e224048..5fd9a341e 100644 --- a/src/public/app/widgets/floating_buttons/geo_map_button.ts +++ b/src/public/app/widgets/floating_buttons/geo_map_button.ts @@ -1,3 +1,4 @@ +import { t } from "../../services/i18n.js"; import NoteContextAwareWidget from "../note_context_aware_widget.js" const TPL = `\ @@ -22,7 +23,7 @@ const TPL = `\
`; export default class GeoMapButtons extends NoteContextAwareWidget { diff --git a/src/public/app/widgets/type_widgets/geo_map.ts b/src/public/app/widgets/type_widgets/geo_map.ts index 299003e22..eb54dd489 100644 --- a/src/public/app/widgets/type_widgets/geo_map.ts +++ b/src/public/app/widgets/type_widgets/geo_map.ts @@ -128,7 +128,7 @@ export default class GeoMapTypeWidget extends TypeWidget { this.L = L; const map = this.geoMapWidget.map; if (!map) { - throw new Error("Unable to load map."); + throw new Error(t("geo-map.unable-to-load-map")); } if (!this.note) { @@ -273,7 +273,7 @@ export default class GeoMapTypeWidget extends TypeWidget { icon: "plus", id: "geo-new-note", title: "New note", - message: "Click on the map to create a new note at that location or press Escape to dismiss." + message: t("geo-map.create-child-note-instruction") }); this.#changeState(State.NewNote); diff --git a/src/public/app/widgets/type_widgets/geo_map_context_menu.ts b/src/public/app/widgets/type_widgets/geo_map_context_menu.ts index 12bbfaa91..f19d655c4 100644 --- a/src/public/app/widgets/type_widgets/geo_map_context_menu.ts +++ b/src/public/app/widgets/type_widgets/geo_map_context_menu.ts @@ -2,6 +2,7 @@ import appContext from "../../components/app_context.js"; import type { ContextMenuEvent } from "../../menus/context_menu.js"; import contextMenu from "../../menus/context_menu.js"; import linkContextMenu from "../../menus/link_context_menu.js"; +import { t } from "../../services/i18n.js"; export default function openContextMenu(noteId: string, e: ContextMenuEvent) { contextMenu.show({ @@ -9,9 +10,9 @@ export default function openContextMenu(noteId: string, e: ContextMenuEvent) { y: e.pageY, items: [ ...linkContextMenu.getItems(), - { title: "Open location", command: "openGeoLocation", uiIcon: "bx bx-map-alt" }, + { title: t("geo-map-context.open-location"), command: "openGeoLocation", uiIcon: "bx bx-map-alt" }, { title: "----" }, - { title: "Remove from map", command: "deleteFromMap", uiIcon: "bx bx-trash" } + { title: t("geo-map-context.remove-from-map"), command: "deleteFromMap", uiIcon: "bx bx-trash" } ], selectMenuItemHandler: ({ command }, e) => { if (command === "deleteFromMap") { diff --git a/src/public/translations/en/translation.json b/src/public/translations/en/translation.json index 4a24da87a..b49188285 100644 --- a/src/public/translations/en/translation.json +++ b/src/public/translations/en/translation.json @@ -1630,5 +1630,14 @@ }, "note_tooltip": { "note-has-been-deleted": "Note has been deleted." + }, + "geo-map": { + "create-child-note-title": "Create a new child note and add it to the map", + "create-child-note-instruction": "Click on the map to create a new note at that location or press Escape to dismiss.", + "unable-to-load-map": "Unable to load map." + }, + "geo-map-context": { + "open-location": "Open location", + "remove-from-map": "Remove from map" } } diff --git a/src/public/translations/ro/translation.json b/src/public/translations/ro/translation.json index cf2ecfb7d..9ffb158fd 100644 --- a/src/public/translations/ro/translation.json +++ b/src/public/translations/ro/translation.json @@ -1379,7 +1379,8 @@ "image": "Imagine", "launcher": "Scurtătură", "widget": "Widget", - "confirm-change": "Nu se recomandă schimbarea tipului notiței atunci când ea are un conținut. Procedați oricum?" + "confirm-change": "Nu se recomandă schimbarea tipului notiței atunci când ea are un conținut. Procedați oricum?", + "geo-map": "Hartă geografică (beta)" }, "protect_note": { "toggle-off": "Deprotejează notița", @@ -1633,5 +1634,13 @@ "notes": { "duplicate-note-suffix": "(dupl.)", "duplicate-note-title": "{{ noteTitle }} {{ duplicateNoteSuffix }}" + }, + "geo-map-context": { + "open-location": "Deschide locația", + "remove-from-map": "Înlătură de pe hartă" + }, + "geo-map": { + "create-child-note-title": "Crează o notiță nouă și adaug-o pe hartă", + "unable-to-load-map": "Nu s-a putut încărca harta." } } diff --git a/translations/ro/server.json b/translations/ro/server.json index 2f25db39d..bc6e7519a 100644 --- a/translations/ro/server.json +++ b/translations/ro/server.json @@ -248,5 +248,8 @@ "backend_log": { "log-does-not-exist": "Fișierul de loguri de backend „{{ fileName }}” nu există (încă).", "reading-log-failed": "Nu s-a putut citi fișierul de loguri de backend „{{fileName}}”." + }, + "geo-map": { + "create-child-note-instruction": "Clic pe hartă pentru a crea o nouă notiță la acea poziție sau apăsați Escape pentru a renunța." } } From 0288ebcad96d2515dd80cd410edd5f686ab7f3e2 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 22 Jan 2025 22:24:42 +0200 Subject: [PATCH 39/42] feat(context_menu): dismiss note tooltip when a context menu is shown --- src/public/app/menus/context_menu.ts | 3 +++ src/public/app/services/note_tooltip.ts | 13 +++++++------ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/public/app/menus/context_menu.ts b/src/public/app/menus/context_menu.ts index de0fcf38a..5752ad648 100644 --- a/src/public/app/menus/context_menu.ts +++ b/src/public/app/menus/context_menu.ts @@ -1,5 +1,6 @@ import type { CommandNames } from "../components/app_context.js"; import keyboardActionService from "../services/keyboard_actions.js"; +import note_tooltip from "../services/note_tooltip.js"; import utils from "../services/utils.js"; interface ContextMenuOptions { @@ -57,6 +58,8 @@ class ContextMenu { async show(options: ContextMenuOptions) { this.options = options; + note_tooltip.dismissAllTooltips(); + if (this.$widget.hasClass("show")) { // The menu is already visible. Hide the menu then open it again // at the new location to re-trigger the opening animation. diff --git a/src/public/app/services/note_tooltip.ts b/src/public/app/services/note_tooltip.ts index 32f79c73d..179ded65a 100644 --- a/src/public/app/services/note_tooltip.ts +++ b/src/public/app/services/note_tooltip.ts @@ -18,11 +18,11 @@ function setupGlobalTooltip() { return; } - cleanUpTooltips(); + dismissAllTooltips(); }); } -function cleanUpTooltips() { +function dismissAllTooltips() { $(".note-tooltip").remove(); } @@ -102,12 +102,12 @@ async function mouseEnterHandler(this: HTMLElement) { customClass: linkId }); - cleanUpTooltips(); + dismissAllTooltips(); $(this).tooltip("show"); // Dismiss the tooltip immediately if a link was clicked inside the tooltip. $(`.${tooltipClass} a`).on("click", (e) => { - cleanUpTooltips(); + dismissAllTooltips(); }); // the purpose of the code below is to: @@ -117,7 +117,7 @@ async function mouseEnterHandler(this: HTMLElement) { const checkTooltip = () => { if (!$(this).filter(":hover").length && !$(`.${linkId}:hover`).length) { // cursor is neither over the link nor over the tooltip, user likely is not interested - cleanUpTooltips(); + dismissAllTooltips(); } else { setTimeout(checkTooltip, 1000); } @@ -172,5 +172,6 @@ function renderFootnote($link: JQuery, url: string) { export default { setupGlobalTooltip, - setupElementTooltip + setupElementTooltip, + dismissAllTooltips }; From 7a3a5141afcd227413dd274a7be609d78f48aa71 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 22 Jan 2025 23:08:25 +0200 Subject: [PATCH 40/42] fix(geomap): not working on electron --- bin/copy-dist.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bin/copy-dist.ts b/bin/copy-dist.ts index 39ddd6cdf..7203b3496 100644 --- a/bin/copy-dist.ts +++ b/bin/copy-dist.ts @@ -100,7 +100,8 @@ const copy = async () => { "node_modules/codemirror/keymap/", "node_modules/mind-elixir/dist/", "node_modules/@highlightjs/cdn-assets/languages", - "node_modules/@highlightjs/cdn-assets/styles" + "node_modules/@highlightjs/cdn-assets/styles", + "node_modules/leaflet/dist" ]; for (const folder of nodeModulesFolder) { From a8e2c2901b72946a246af5d69680c72de4449108 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 22 Jan 2025 23:09:56 +0200 Subject: [PATCH 41/42] fix(geomap): error in creating empty map --- src/public/app/widgets/type_widgets/geo_map.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/public/app/widgets/type_widgets/geo_map.ts b/src/public/app/widgets/type_widgets/geo_map.ts index eb54dd489..a36544217 100644 --- a/src/public/app/widgets/type_widgets/geo_map.ts +++ b/src/public/app/widgets/type_widgets/geo_map.ts @@ -138,7 +138,7 @@ export default class GeoMapTypeWidget extends TypeWidget { const blob = await this.note.getBlob(); let parsedContent: MapData = {}; - if (blob) { + if (blob && blob.content) { parsedContent = JSON.parse(blob.content); } From 9e2c59238307798bc6230848afd1f060db9df173 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 22 Jan 2025 23:18:22 +0200 Subject: [PATCH 42/42] feat(geomap): set default position --- src/public/app/widgets/type_widgets/geo_map.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/public/app/widgets/type_widgets/geo_map.ts b/src/public/app/widgets/type_widgets/geo_map.ts index a36544217..60166cd41 100644 --- a/src/public/app/widgets/type_widgets/geo_map.ts +++ b/src/public/app/widgets/type_widgets/geo_map.ts @@ -74,6 +74,8 @@ const TPL = `\ const LOCATION_ATTRIBUTE = "geolocation"; const CHILD_NOTE_ICON = "bx bx-pin"; +const DEFAULT_COORDINATES: [ number, number ] = [ 3.878638227135724, 446.6630455551659 ]; +const DEFAULT_ZOOM = 2; interface MapData { view?: { @@ -143,8 +145,8 @@ export default class GeoMapTypeWidget extends TypeWidget { } // Restore viewport position & zoom - const center = parsedContent.view?.center ?? [51.505, -0.09]; - const zoom = parsedContent.view?.zoom ?? 13; + const center = parsedContent.view?.center ?? DEFAULT_COORDINATES; + const zoom = parsedContent.view?.zoom ?? DEFAULT_ZOOM; map.setView(center, zoom); // Restore markers.