From 1b1f1957c3c7380eac3e65c807134f81c03d74d2 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 6 Jul 2025 22:11:03 +0300 Subject: [PATCH 01/14] chore(views/help): reintroduce help button --- apps/client/src/widgets/floating_buttons/help_button.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/client/src/widgets/floating_buttons/help_button.ts b/apps/client/src/widgets/floating_buttons/help_button.ts index f0403bfd7..31b031c9d 100644 --- a/apps/client/src/widgets/floating_buttons/help_button.ts +++ b/apps/client/src/widgets/floating_buttons/help_button.ts @@ -17,7 +17,6 @@ export const byNoteType: Record, string | null> = { contentWidget: null, doc: null, file: null, - geoMap: "81SGnPGMk7Xc", image: null, launcher: null, mermaid: null, @@ -35,7 +34,8 @@ export const byBookType: Record = { list: null, grid: null, calendar: "xWbu3jpNWapp", - table: "2FvYrpmOXm29" + table: "2FvYrpmOXm29", + geoMap: "81SGnPGMk7Xc" }; export default class ContextualHelpButton extends NoteContextAwareWidget { From dd188661566daa1bc5eec029e6882d4bc5a8712a Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 6 Jul 2025 22:22:19 +0300 Subject: [PATCH 02/14] refactor(server): convert to switch --- apps/server/src/services/import/zip.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/apps/server/src/services/import/zip.ts b/apps/server/src/services/import/zip.ts index e7da680bb..4a7303915 100644 --- a/apps/server/src/services/import/zip.ts +++ b/apps/server/src/services/import/zip.ts @@ -656,12 +656,13 @@ export function readZipFile(buffer: Buffer, processEntryCallback: (zipfile: yauz function resolveNoteType(type: string | undefined): NoteType { // BC for ZIPs created in Trilium 0.57 and older - if (type === "relation-map") { - return "relationMap"; - } else if (type === "note-map") { - return "noteMap"; - } else if (type === "web-view") { - return "webView"; + switch (type) { + case "relation-map": + return "relationMap"; + case "note-map": + return "noteMap"; + case "web-view": + return "webView"; } if (type && (ALLOWED_NOTE_TYPES as readonly string[]).includes(type)) { From 68e258f23b8d15b9089be4c44e8db1614c5233c0 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 6 Jul 2025 22:32:24 +0300 Subject: [PATCH 03/14] fix(views/geomap): unable to change note type to geomap --- apps/client/src/widgets/ribbon_widgets/book_properties.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/client/src/widgets/ribbon_widgets/book_properties.ts b/apps/client/src/widgets/ribbon_widgets/book_properties.ts index f552bc280..655512f9e 100644 --- a/apps/client/src/widgets/ribbon_widgets/book_properties.ts +++ b/apps/client/src/widgets/ribbon_widgets/book_properties.ts @@ -127,7 +127,7 @@ export default class BookPropertiesWidget extends NoteContextAwareWidget { return; } - if (!["list", "grid", "calendar", "table"].includes(type)) { + if (!["list", "grid", "calendar", "table", "geoMap"].includes(type)) { throw new Error(t("book_properties.invalid_view_type", { type })); } From a58e5789bc034c6d1414096db812efb9e5240184 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 6 Jul 2025 22:33:19 +0300 Subject: [PATCH 04/14] feat(import/zip): backward compatibility --- apps/server/src/services/import/zip.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/server/src/services/import/zip.ts b/apps/server/src/services/import/zip.ts index 4a7303915..67deb1056 100644 --- a/apps/server/src/services/import/zip.ts +++ b/apps/server/src/services/import/zip.ts @@ -663,6 +663,8 @@ function resolveNoteType(type: string | undefined): NoteType { return "noteMap"; case "web-view": return "webView"; + case "geoMap": + return "book"; } if (type && (ALLOWED_NOTE_TYPES as readonly string[]).includes(type)) { From a563330136e4623ffd91d4a6aeca803d7be88f8b Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 6 Jul 2025 23:05:15 +0300 Subject: [PATCH 05/14] feat(import/zip): improve geomap compatibility --- .../src/services/import/samples/geomap.zip | Bin 0 -> 5413 bytes apps/server/src/services/import/zip.spec.ts | 13 +++++++++++ apps/server/src/services/import/zip.ts | 22 ++++++++++++++++++ 3 files changed, 35 insertions(+) create mode 100644 apps/server/src/services/import/samples/geomap.zip diff --git a/apps/server/src/services/import/samples/geomap.zip b/apps/server/src/services/import/samples/geomap.zip new file mode 100644 index 0000000000000000000000000000000000000000..6443c19971d292478defb975aefe4de8d42ea1b2 GIT binary patch literal 5413 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 literal 0 HcmV?d00001 diff --git a/apps/server/src/services/import/zip.spec.ts b/apps/server/src/services/import/zip.spec.ts index 4860f7b94..db2c7ba76 100644 --- a/apps/server/src/services/import/zip.spec.ts +++ b/apps/server/src/services/import/zip.spec.ts @@ -70,6 +70,19 @@ describe("processNoteContent", () => { expect(content).toContain(` { + const { importedNote } = await testImport("geomap.zip"); + expect(importedNote.type).toBe("book"); + expect(importedNote.mime).toBe(""); + expect(importedNote.getRelationValue("template")).toBe("_template_geo_map"); + + const attachment = importedNote.getAttachmentsByRole("viewConfig")[0]; + expect(attachment.title).toBe("geoMap.json"); + expect(attachment.mime).toBe("application/json"); + const content = attachment.getContent(); + expect(content).toStrictEqual(`{"view":{"center":{"lat":49.19598332223546,"lng":-2.1414576506668808},"zoom":12}}`); + }); }); function getNoteByTitlePath(parentNote: BNote, ...titlePath: string[]) { diff --git a/apps/server/src/services/import/zip.ts b/apps/server/src/services/import/zip.ts index 67deb1056..b2d83bdc6 100644 --- a/apps/server/src/services/import/zip.ts +++ b/apps/server/src/services/import/zip.ts @@ -502,6 +502,28 @@ async function importZip(taskContext: TaskContext, fileBuffer: Buffer, importRoo firstNote = firstNote || note; } } else { + if (detectedType as string === "geoMap") { + attributes.push({ + noteId, + type: "relation", + name: "template", + value: "_template_geo_map" + }); + + const attachment = new BAttachment({ + attachmentId: getNewAttachmentId(newEntityId()), + ownerId: noteId, + title: "geoMap.json", + role: "viewConfig", + mime: "application/json", + position: 0 + }); + + attachment.setContent(content, { forceSave: true }); + content = ""; + mime = ""; + } + ({ note } = noteService.createNewNote({ parentNoteId: parentNoteId, title: noteTitle || "", From d31af2ddc2ccfa8fe55d13a0dde982812169cadf Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 6 Jul 2025 23:34:07 +0300 Subject: [PATCH 06/14] feat(views/geomap): add a context menu for empty area --- .../view_widgets/geo_view/context_menu.ts | 21 +++++++++++++ .../widgets/view_widgets/geo_view/editing.ts | 28 ++++++++++++++++- .../widgets/view_widgets/geo_view/index.ts | 30 ++++--------------- 3 files changed, 53 insertions(+), 26 deletions(-) diff --git a/apps/client/src/widgets/view_widgets/geo_view/context_menu.ts b/apps/client/src/widgets/view_widgets/geo_view/context_menu.ts index 1e1a4b3d7..410064f0f 100644 --- a/apps/client/src/widgets/view_widgets/geo_view/context_menu.ts +++ b/apps/client/src/widgets/view_widgets/geo_view/context_menu.ts @@ -1,8 +1,10 @@ +import { LeafletMouseEvent } from "leaflet"; 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"; +import { createNewNote } from "./editing.js"; export default function openContextMenu(noteId: string, e: ContextMenuEvent) { contextMenu.show({ @@ -30,3 +32,22 @@ export default function openContextMenu(noteId: string, e: ContextMenuEvent) { } }); } + +export function openMapContextMenu(noteId: string, e: LeafletMouseEvent) { + contextMenu.show({ + x: e.originalEvent.pageX, + y: e.originalEvent.pageY, + items: [ + { title: t("geo-map-context.add-note"), command: "addNoteToMap", uiIcon: "bx bx-plus" } + ], + selectMenuItemHandler: ({ command }) => { + switch (command) { + case "addNoteToMap": + createNewNote(noteId, e); + break; + default: + appContext.triggerCommand(command); + } + } + }); +} diff --git a/apps/client/src/widgets/view_widgets/geo_view/editing.ts b/apps/client/src/widgets/view_widgets/geo_view/editing.ts index 863305ebc..7d70ba370 100644 --- a/apps/client/src/widgets/view_widgets/geo_view/editing.ts +++ b/apps/client/src/widgets/view_widgets/geo_view/editing.ts @@ -1,8 +1,34 @@ -import { LatLng } from "leaflet"; +import { LatLng, LeafletMouseEvent } from "leaflet"; import attributes from "../../../services/attributes"; import { LOCATION_ATTRIBUTE } from "./index.js"; +import dialog from "../../../services/dialog"; +import server from "../../../services/server"; +import { t } from "../../../services/i18n"; + +const CHILD_NOTE_ICON = "bx bx-pin"; + +// TODO: Deduplicate +interface CreateChildResponse { + note: { + noteId: string; + }; +} export async function moveMarker(noteId: string, latLng: LatLng | null) { const value = latLng ? [latLng.lat, latLng.lng].join(",") : ""; await attributes.setLabel(noteId, LOCATION_ATTRIBUTE, value); } + +export async function createNewNote(noteId: string, e: LeafletMouseEvent) { + const title = await dialog.prompt({ message: t("relation_map.enter_title_of_new_note"), defaultValue: t("relation_map.default_new_note_title") }); + + if (title?.trim()) { + const { note } = await server.post(`notes/${noteId}/children?target=into`, { + title, + content: "", + type: "text" + }); + attributes.setLabel(note.noteId, "iconClass", CHILD_NOTE_ICON); + moveMarker(note.noteId, e.latlng); + } +} diff --git a/apps/client/src/widgets/view_widgets/geo_view/index.ts b/apps/client/src/widgets/view_widgets/geo_view/index.ts index 5791ecf38..e67768fce 100644 --- a/apps/client/src/widgets/view_widgets/geo_view/index.ts +++ b/apps/client/src/widgets/view_widgets/geo_view/index.ts @@ -7,18 +7,9 @@ import processNoteWithMarker, { processNoteWithGpxTrack } from "./markers.js"; import { hasTouchBar } from "../../../services/utils.js"; import toast from "../../../services/toast.js"; import { CommandListenerData, EventData } from "../../../components/app_context.js"; -import dialog from "../../../services/dialog.js"; -import server from "../../../services/server.js"; -import attributes from "../../../services/attributes.js"; -import { moveMarker } from "./editing.js"; +import { createNewNote, moveMarker } from "./editing.js"; import link from "../../../services/link.js"; - -// TODO: Deduplicate -interface CreateChildResponse { - note: { - noteId: string; - }; -} +import { openMapContextMenu } from "./context_menu.js"; const TPL = /*html*/`
@@ -102,7 +93,6 @@ interface MapData { const DEFAULT_COORDINATES: [number, number] = [3.878638227135724, 446.6630455551659]; const DEFAULT_ZOOM = 2; export const LOCATION_ATTRIBUTE = "geolocation"; -const CHILD_NOTE_ICON = "bx bx-pin"; enum State { Normal, @@ -166,7 +156,8 @@ export default class GeoView extends ViewMode { const updateFn = () => this.spacedUpdate.scheduleUpdate(); map.on("moveend", updateFn); map.on("zoomend", updateFn); - map.on("click", (e) => this.#onMapClicked(e)); + map.on("click", (e) => this.#onMapClicked(e)) + map.on("contextmenu", (e) => openMapContextMenu(this.parentNote.noteId, e)); this.#reloadMarkers(); @@ -299,18 +290,7 @@ export default class GeoView extends ViewMode { } toast.closePersistent("geo-new-note"); - const title = await dialog.prompt({ message: t("relation_map.enter_title_of_new_note"), defaultValue: t("relation_map.default_new_note_title") }); - - if (title?.trim()) { - const { note } = await server.post(`notes/${this.parentNote.noteId}/children?target=into`, { - title, - content: "", - type: "text" - }); - attributes.setLabel(note.noteId, "iconClass", CHILD_NOTE_ICON); - moveMarker(note.noteId, e.latlng); - } - + await createNewNote(this.parentNote.noteId, e); this.#changeState(State.Normal); } From a1341e6036f1b68a57c0b6e66b204b3b43e4ebac Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 6 Jul 2025 23:41:55 +0300 Subject: [PATCH 07/14] feat(views/geomap): display geolocation in empty menu --- apps/client/src/translations/en/translation.json | 3 ++- .../src/widgets/view_widgets/geo_view/context_menu.ts | 11 ++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/apps/client/src/translations/en/translation.json b/apps/client/src/translations/en/translation.json index 4470852a9..00cb9f227 100644 --- a/apps/client/src/translations/en/translation.json +++ b/apps/client/src/translations/en/translation.json @@ -1860,7 +1860,8 @@ }, "geo-map-context": { "open-location": "Open location", - "remove-from-map": "Remove from map" + "remove-from-map": "Remove from map", + "add-note": "Add a marker at this location" }, "help-button": { "title": "Open the relevant help page" diff --git a/apps/client/src/widgets/view_widgets/geo_view/context_menu.ts b/apps/client/src/widgets/view_widgets/geo_view/context_menu.ts index 410064f0f..331beac05 100644 --- a/apps/client/src/widgets/view_widgets/geo_view/context_menu.ts +++ b/apps/client/src/widgets/view_widgets/geo_view/context_menu.ts @@ -1,10 +1,11 @@ -import { LeafletMouseEvent } from "leaflet"; +import type { LatLng, LeafletMouseEvent } from "leaflet"; 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"; import { createNewNote } from "./editing.js"; +import { copyTextWithToast } from "../../../services/clipboard_ext.js"; export default function openContextMenu(noteId: string, e: ContextMenuEvent) { contextMenu.show({ @@ -38,6 +39,10 @@ export function openMapContextMenu(noteId: string, e: LeafletMouseEvent) { x: e.originalEvent.pageX, y: e.originalEvent.pageY, items: [ + { + title: formatGeoLocation(e.latlng), + handler: () => copyTextWithToast(formatGeoLocation(e.latlng, 15)) + }, { title: t("geo-map-context.add-note"), command: "addNoteToMap", uiIcon: "bx bx-plus" } ], selectMenuItemHandler: ({ command }) => { @@ -51,3 +56,7 @@ export function openMapContextMenu(noteId: string, e: LeafletMouseEvent) { } }); } + +function formatGeoLocation(latlng: LatLng, precision: number = 6) { + return `${latlng.lat.toFixed(precision)}, ${latlng.lng.toFixed(precision)}`; +} From 810217255794a09d0e75dbaf7b8cba0bf8064e9e Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 6 Jul 2025 23:48:51 +0300 Subject: [PATCH 08/14] feat(views/geomap): display geolocation in both context menus --- .../view_widgets/geo_view/context_menu.ts | 29 ++++++++++++------- .../widgets/view_widgets/geo_view/markers.ts | 2 +- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/apps/client/src/widgets/view_widgets/geo_view/context_menu.ts b/apps/client/src/widgets/view_widgets/geo_view/context_menu.ts index 331beac05..4eec35887 100644 --- a/apps/client/src/widgets/view_widgets/geo_view/context_menu.ts +++ b/apps/client/src/widgets/view_widgets/geo_view/context_menu.ts @@ -1,17 +1,17 @@ import type { LatLng, LeafletMouseEvent } from "leaflet"; 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"; import { createNewNote } from "./editing.js"; import { copyTextWithToast } from "../../../services/clipboard_ext.js"; -export default function openContextMenu(noteId: string, e: ContextMenuEvent) { +export default function openContextMenu(noteId: string, e: LeafletMouseEvent) { contextMenu.show({ - x: e.pageX, - y: e.pageY, + x: e.originalEvent.pageX, + y: e.originalEvent.pageY, items: [ + ...buildGeoLocationItem(e), ...linkContextMenu.getItems(), { title: t("geo-map-context.open-location"), command: "openGeoLocation", uiIcon: "bx bx-map-alt" }, { title: "----" }, @@ -39,10 +39,7 @@ export function openMapContextMenu(noteId: string, e: LeafletMouseEvent) { x: e.originalEvent.pageX, y: e.originalEvent.pageY, items: [ - { - title: formatGeoLocation(e.latlng), - handler: () => copyTextWithToast(formatGeoLocation(e.latlng, 15)) - }, + ...buildGeoLocationItem(e), { title: t("geo-map-context.add-note"), command: "addNoteToMap", uiIcon: "bx bx-plus" } ], selectMenuItemHandler: ({ command }) => { @@ -57,6 +54,18 @@ export function openMapContextMenu(noteId: string, e: LeafletMouseEvent) { }); } -function formatGeoLocation(latlng: LatLng, precision: number = 6) { - return `${latlng.lat.toFixed(precision)}, ${latlng.lng.toFixed(precision)}`; +function buildGeoLocationItem(e: LeafletMouseEvent) { + function formatGeoLocation(latlng: LatLng, precision: number = 6) { + return `${latlng.lat.toFixed(precision)}, ${latlng.lng.toFixed(precision)}`; + } + + return [ + { + title: formatGeoLocation(e.latlng), + handler: () => copyTextWithToast(formatGeoLocation(e.latlng, 15)) + }, + { + title: "----" + } + ]; } diff --git a/apps/client/src/widgets/view_widgets/geo_view/markers.ts b/apps/client/src/widgets/view_widgets/geo_view/markers.ts index ca4170f6e..8853734ec 100644 --- a/apps/client/src/widgets/view_widgets/geo_view/markers.ts +++ b/apps/client/src/widgets/view_widgets/geo_view/markers.ts @@ -33,7 +33,7 @@ export default function processNoteWithMarker(map: Map, note: FNote, location: s } }); newMarker.on("contextmenu", (e) => { - openContextMenu(note.noteId, e.originalEvent); + openContextMenu(note.noteId, e); }); const el = newMarker.getElement(); From 6509acd6eeaffa82b427742592c57ab0b7b729e4 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 7 Jul 2025 17:21:55 +0300 Subject: [PATCH 09/14] feat(views/geomap): add open location to blank item as well --- apps/client/src/components/app_context.ts | 1 - apps/client/src/services/link.ts | 18 +++++++++--------- .../view_widgets/geo_view/context_menu.ts | 15 ++++++++------- .../src/widgets/view_widgets/geo_view/index.ts | 11 ----------- 4 files changed, 17 insertions(+), 28 deletions(-) diff --git a/apps/client/src/components/app_context.ts b/apps/client/src/components/app_context.ts index 443014572..161846dde 100644 --- a/apps/client/src/components/app_context.ts +++ b/apps/client/src/components/app_context.ts @@ -261,7 +261,6 @@ export type CommandMappings = { // Geomap deleteFromMap: { noteId: string }; - openGeoLocation: { noteId: string; event: JQuery.MouseDownEvent }; toggleZenMode: CommandData; diff --git a/apps/client/src/services/link.ts b/apps/client/src/services/link.ts index 41533647c..107a9c9e3 100644 --- a/apps/client/src/services/link.ts +++ b/apps/client/src/services/link.ts @@ -277,13 +277,13 @@ function goToLink(evt: MouseEvent | JQuery.ClickEvent | JQuery.MouseDownEvent) { return goToLinkExt(evt, hrefLink, $link); } -function goToLinkExt(evt: MouseEvent | JQuery.ClickEvent | JQuery.MouseDownEvent | React.PointerEvent, hrefLink: string | undefined, $link?: JQuery | null) { +function goToLinkExt(evt: MouseEvent | JQuery.ClickEvent | JQuery.MouseDownEvent | React.PointerEvent | null, hrefLink: string | undefined, $link?: JQuery | null) { if (hrefLink?.startsWith("data:")) { return true; } - evt.preventDefault(); - evt.stopPropagation(); + evt?.preventDefault(); + evt?.stopPropagation(); if (hrefLink && hrefLink.startsWith("#") && !hrefLink.startsWith("#root/") && $link) { if (handleAnchor(hrefLink, $link)) { @@ -293,14 +293,14 @@ function goToLinkExt(evt: MouseEvent | JQuery.ClickEvent | JQuery.MouseDownEvent const { notePath, viewScope } = parseNavigationStateFromUrl(hrefLink); - const ctrlKey = utils.isCtrlKey(evt); - const shiftKey = evt.shiftKey; - const isLeftClick = "which" in evt && evt.which === 1; - const isMiddleClick = "which" in evt && evt.which === 2; + const ctrlKey = evt && utils.isCtrlKey(evt); + const shiftKey = evt?.shiftKey; + const isLeftClick = !evt || ("which" in evt && evt.which === 1); + const isMiddleClick = evt && "which" in evt && evt.which === 2; const targetIsBlank = ($link?.attr("target") === "_blank"); const openInNewTab = (isLeftClick && ctrlKey) || isMiddleClick || targetIsBlank; const activate = (isLeftClick && ctrlKey && shiftKey) || (isMiddleClick && shiftKey); - const openInNewWindow = isLeftClick && evt.shiftKey && !ctrlKey; + const openInNewWindow = isLeftClick && evt?.shiftKey && !ctrlKey; if (notePath) { if (openInNewWindow) { @@ -311,7 +311,7 @@ function goToLinkExt(evt: MouseEvent | JQuery.ClickEvent | JQuery.MouseDownEvent viewScope }); } else if (isLeftClick) { - const ntxId = $(evt.target as any) + const ntxId = $(evt?.target as any) .closest("[data-ntx-id]") .attr("data-ntx-id"); diff --git a/apps/client/src/widgets/view_widgets/geo_view/context_menu.ts b/apps/client/src/widgets/view_widgets/geo_view/context_menu.ts index 4eec35887..768a17d10 100644 --- a/apps/client/src/widgets/view_widgets/geo_view/context_menu.ts +++ b/apps/client/src/widgets/view_widgets/geo_view/context_menu.ts @@ -5,6 +5,7 @@ import linkContextMenu from "../../../menus/link_context_menu.js"; import { t } from "../../../services/i18n.js"; import { createNewNote } from "./editing.js"; import { copyTextWithToast } from "../../../services/clipboard_ext.js"; +import link from "../../../services/link.js"; export default function openContextMenu(noteId: string, e: LeafletMouseEvent) { contextMenu.show({ @@ -13,7 +14,6 @@ export default function openContextMenu(noteId: string, e: LeafletMouseEvent) { items: [ ...buildGeoLocationItem(e), ...linkContextMenu.getItems(), - { title: t("geo-map-context.open-location"), command: "openGeoLocation", uiIcon: "bx bx-map-alt" }, { title: "----" }, { title: t("geo-map-context.remove-from-map"), command: "deleteFromMap", uiIcon: "bx bx-trash" } ], @@ -23,11 +23,6 @@ export default function openContextMenu(noteId: string, e: LeafletMouseEvent) { return; } - if (command === "openGeoLocation") { - appContext.triggerCommand(command, { noteId, event: e }); - return; - } - // Pass the events to the link context menu linkContextMenu.handleLinkContextMenuItem(command, noteId); } @@ -48,7 +43,7 @@ export function openMapContextMenu(noteId: string, e: LeafletMouseEvent) { createNewNote(noteId, e); break; default: - appContext.triggerCommand(command); + return; } } }); @@ -62,8 +57,14 @@ function buildGeoLocationItem(e: LeafletMouseEvent) { return [ { title: formatGeoLocation(e.latlng), + uiIcon: "bx bx-current-location", handler: () => copyTextWithToast(formatGeoLocation(e.latlng, 15)) }, + { + title: t("geo-map-context.open-location"), + uiIcon: "bx bx-map-alt", + handler: () => link.goToLinkExt(null, `geo:${e.latlng.lat},${e.latlng.lng}`) + }, { title: "----" } diff --git a/apps/client/src/widgets/view_widgets/geo_view/index.ts b/apps/client/src/widgets/view_widgets/geo_view/index.ts index e67768fce..d68eb489a 100644 --- a/apps/client/src/widgets/view_widgets/geo_view/index.ts +++ b/apps/client/src/widgets/view_widgets/geo_view/index.ts @@ -294,17 +294,6 @@ export default class GeoView extends ViewMode { this.#changeState(State.Normal); } - 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">) { moveMarker(noteId, null); } From 2a665dffbc64437f057146c408cd7ea09d9f4a43 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 7 Jul 2025 17:55:16 +0300 Subject: [PATCH 10/14] feat(views/geomap): dragging notes that are children --- apps/client/src/widgets/note_tree.ts | 6 +++ .../widgets/view_widgets/geo_view/dragging.ts | 38 +++++++++++++++++++ .../widgets/view_widgets/geo_view/index.ts | 2 + 3 files changed, 46 insertions(+) create mode 100644 apps/client/src/widgets/view_widgets/geo_view/dragging.ts diff --git a/apps/client/src/widgets/note_tree.ts b/apps/client/src/widgets/note_tree.ts index 46abe10a0..ea6e50325 100644 --- a/apps/client/src/widgets/note_tree.ts +++ b/apps/client/src/widgets/note_tree.ts @@ -186,6 +186,12 @@ interface RefreshContext { noteIdsToReload: Set; } +export interface DragData { + noteId: string; + branchId: string; + title: string; +} + export default class NoteTreeWidget extends NoteContextAwareWidget { private $tree!: JQuery; private $treeActions!: JQuery; diff --git a/apps/client/src/widgets/view_widgets/geo_view/dragging.ts b/apps/client/src/widgets/view_widgets/geo_view/dragging.ts new file mode 100644 index 000000000..df056aad5 --- /dev/null +++ b/apps/client/src/widgets/view_widgets/geo_view/dragging.ts @@ -0,0 +1,38 @@ +import type { Map } from "leaflet"; +import type { DragData } from "../../note_tree.js"; +import { moveMarker } from "./editing"; + +export default function setupDragging($container: JQuery, map: Map) { + $container.on("dragover", (e) => { + // Allow drag. + e.preventDefault(); + }); + $container.on("drop", (e) => { + if (!e.originalEvent) { + return; + } + + const data = e.originalEvent.dataTransfer?.getData('text'); + if (!data) { + return; + } + + try { + const parsedData = JSON.parse(data) as DragData[]; + if (!parsedData.length) { + return; + } + + const { noteId } = parsedData[0]; + + var offset = $container.offset(); + var x = e.originalEvent.clientX - (offset?.left ?? 0); + var y = e.originalEvent.clientY - (offset?.top ?? 0); + + const latlng = map.containerPointToLatLng([ x, y ]); + moveMarker(noteId, latlng); + } catch (e) { + console.warn(e); + } + }); +} diff --git a/apps/client/src/widgets/view_widgets/geo_view/index.ts b/apps/client/src/widgets/view_widgets/geo_view/index.ts index d68eb489a..ebb060693 100644 --- a/apps/client/src/widgets/view_widgets/geo_view/index.ts +++ b/apps/client/src/widgets/view_widgets/geo_view/index.ts @@ -10,6 +10,7 @@ import { CommandListenerData, EventData } from "../../../components/app_context. import { createNewNote, moveMarker } from "./editing.js"; import link from "../../../services/link.js"; import { openMapContextMenu } from "./context_menu.js"; +import setupDragging from "./dragging.js"; const TPL = /*html*/`
@@ -158,6 +159,7 @@ export default class GeoView extends ViewMode { map.on("zoomend", updateFn); map.on("click", (e) => this.#onMapClicked(e)) map.on("contextmenu", (e) => openMapContextMenu(this.parentNote.noteId, e)); + setupDragging(this.$container, map); this.#reloadMarkers(); From 63c408c45b6d348498532b944a3c7df6ecf6b07c Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 7 Jul 2025 18:02:32 +0300 Subject: [PATCH 11/14] feat(views/geomap): dragging notes that are not children --- .../widgets/view_widgets/geo_view/dragging.ts | 16 +++++++++++++--- .../src/widgets/view_widgets/geo_view/index.ts | 2 +- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/apps/client/src/widgets/view_widgets/geo_view/dragging.ts b/apps/client/src/widgets/view_widgets/geo_view/dragging.ts index df056aad5..bc1b0aa97 100644 --- a/apps/client/src/widgets/view_widgets/geo_view/dragging.ts +++ b/apps/client/src/widgets/view_widgets/geo_view/dragging.ts @@ -1,13 +1,15 @@ import type { Map } from "leaflet"; import type { DragData } from "../../note_tree.js"; import { moveMarker } from "./editing"; +import froca from "../../../services/froca.js"; +import branches from "../../../services/branches.js"; -export default function setupDragging($container: JQuery, map: Map) { +export default function setupDragging($container: JQuery, map: Map, mapNoteId: string) { $container.on("dragover", (e) => { // Allow drag. e.preventDefault(); }); - $container.on("drop", (e) => { + $container.on("drop", async (e) => { if (!e.originalEvent) { return; } @@ -30,7 +32,15 @@ export default function setupDragging($container: JQuery, map: Map) var y = e.originalEvent.clientY - (offset?.top ?? 0); const latlng = map.containerPointToLatLng([ x, y ]); - moveMarker(noteId, latlng); + + const note = await froca.getNote(noteId, true); + const parents = note?.getParentNoteIds(); + if (parents?.includes(mapNoteId)) { + await moveMarker(noteId, latlng); + } else { + await branches.cloneNoteToParentNote(noteId, mapNoteId); + await moveMarker(noteId, latlng); + } } catch (e) { console.warn(e); } diff --git a/apps/client/src/widgets/view_widgets/geo_view/index.ts b/apps/client/src/widgets/view_widgets/geo_view/index.ts index ebb060693..3181773d8 100644 --- a/apps/client/src/widgets/view_widgets/geo_view/index.ts +++ b/apps/client/src/widgets/view_widgets/geo_view/index.ts @@ -159,7 +159,7 @@ export default class GeoView extends ViewMode { map.on("zoomend", updateFn); map.on("click", (e) => this.#onMapClicked(e)) map.on("contextmenu", (e) => openMapContextMenu(this.parentNote.noteId, e)); - setupDragging(this.$container, map); + setupDragging(this.$container, map, this.parentNote.noteId); this.#reloadMarkers(); From 5c6bb99d78ed7864aab790072cf683a5b1b890aa Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 7 Jul 2025 18:04:00 +0300 Subject: [PATCH 12/14] refactor(views/geomap): integrate drag into editing --- .../widgets/view_widgets/geo_view/dragging.ts | 48 ------------------- .../widgets/view_widgets/geo_view/editing.ts | 46 ++++++++++++++++++ .../widgets/view_widgets/geo_view/index.ts | 4 +- 3 files changed, 47 insertions(+), 51 deletions(-) delete mode 100644 apps/client/src/widgets/view_widgets/geo_view/dragging.ts diff --git a/apps/client/src/widgets/view_widgets/geo_view/dragging.ts b/apps/client/src/widgets/view_widgets/geo_view/dragging.ts deleted file mode 100644 index bc1b0aa97..000000000 --- a/apps/client/src/widgets/view_widgets/geo_view/dragging.ts +++ /dev/null @@ -1,48 +0,0 @@ -import type { Map } from "leaflet"; -import type { DragData } from "../../note_tree.js"; -import { moveMarker } from "./editing"; -import froca from "../../../services/froca.js"; -import branches from "../../../services/branches.js"; - -export default function setupDragging($container: JQuery, map: Map, mapNoteId: string) { - $container.on("dragover", (e) => { - // Allow drag. - e.preventDefault(); - }); - $container.on("drop", async (e) => { - if (!e.originalEvent) { - return; - } - - const data = e.originalEvent.dataTransfer?.getData('text'); - if (!data) { - return; - } - - try { - const parsedData = JSON.parse(data) as DragData[]; - if (!parsedData.length) { - return; - } - - const { noteId } = parsedData[0]; - - var offset = $container.offset(); - var x = e.originalEvent.clientX - (offset?.left ?? 0); - var y = e.originalEvent.clientY - (offset?.top ?? 0); - - const latlng = map.containerPointToLatLng([ x, y ]); - - const note = await froca.getNote(noteId, true); - const parents = note?.getParentNoteIds(); - if (parents?.includes(mapNoteId)) { - await moveMarker(noteId, latlng); - } else { - await branches.cloneNoteToParentNote(noteId, mapNoteId); - await moveMarker(noteId, latlng); - } - } catch (e) { - console.warn(e); - } - }); -} diff --git a/apps/client/src/widgets/view_widgets/geo_view/editing.ts b/apps/client/src/widgets/view_widgets/geo_view/editing.ts index 7d70ba370..c9dd7368c 100644 --- a/apps/client/src/widgets/view_widgets/geo_view/editing.ts +++ b/apps/client/src/widgets/view_widgets/geo_view/editing.ts @@ -4,6 +4,10 @@ import { LOCATION_ATTRIBUTE } from "./index.js"; import dialog from "../../../services/dialog"; import server from "../../../services/server"; import { t } from "../../../services/i18n"; +import type { Map } from "leaflet"; +import type { DragData } from "../../note_tree.js"; +import froca from "../../../services/froca.js"; +import branches from "../../../services/branches.js"; const CHILD_NOTE_ICON = "bx bx-pin"; @@ -32,3 +36,45 @@ export async function createNewNote(noteId: string, e: LeafletMouseEvent) { moveMarker(note.noteId, e.latlng); } } + +export function setupDragging($container: JQuery, map: Map, mapNoteId: string) { + $container.on("dragover", (e) => { + // Allow drag. + e.preventDefault(); + }); + $container.on("drop", async (e) => { + if (!e.originalEvent) { + return; + } + + const data = e.originalEvent.dataTransfer?.getData('text'); + if (!data) { + return; + } + + try { + const parsedData = JSON.parse(data) as DragData[]; + if (!parsedData.length) { + return; + } + + const { noteId } = parsedData[0]; + + const offset = $container.offset(); + const x = e.originalEvent.clientX - (offset?.left ?? 0); + const y = e.originalEvent.clientY - (offset?.top ?? 0); + const latlng = map.containerPointToLatLng([ x, y ]); + + const note = await froca.getNote(noteId, true); + const parents = note?.getParentNoteIds(); + if (parents?.includes(mapNoteId)) { + await moveMarker(noteId, latlng); + } else { + await branches.cloneNoteToParentNote(noteId, mapNoteId); + await moveMarker(noteId, latlng); + } + } catch (e) { + console.warn(e); + } + }); +} diff --git a/apps/client/src/widgets/view_widgets/geo_view/index.ts b/apps/client/src/widgets/view_widgets/geo_view/index.ts index 3181773d8..4af9b47cb 100644 --- a/apps/client/src/widgets/view_widgets/geo_view/index.ts +++ b/apps/client/src/widgets/view_widgets/geo_view/index.ts @@ -7,10 +7,8 @@ import processNoteWithMarker, { processNoteWithGpxTrack } from "./markers.js"; import { hasTouchBar } from "../../../services/utils.js"; import toast from "../../../services/toast.js"; import { CommandListenerData, EventData } from "../../../components/app_context.js"; -import { createNewNote, moveMarker } from "./editing.js"; -import link from "../../../services/link.js"; +import { createNewNote, moveMarker, setupDragging } from "./editing.js"; import { openMapContextMenu } from "./context_menu.js"; -import setupDragging from "./dragging.js"; const TPL = /*html*/`
From c1a5808f37d10988b11e27d375916de412bb6762 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 7 Jul 2025 19:04:47 +0300 Subject: [PATCH 13/14] feat(views/geomap): allow disabling editing --- .../floating_buttons/geo_map_button.ts | 4 +- .../toggle_read_only_button.ts | 18 +++++-- .../view_widgets/geo_view/context_menu.ts | 49 ++++++++++++------- .../widgets/view_widgets/geo_view/index.ts | 11 +++-- .../widgets/view_widgets/geo_view/markers.ts | 15 +++--- .../src/widgets/view_widgets/view_mode.ts | 4 ++ 6 files changed, 70 insertions(+), 31 deletions(-) diff --git a/apps/client/src/widgets/floating_buttons/geo_map_button.ts b/apps/client/src/widgets/floating_buttons/geo_map_button.ts index 945be41d1..7e59eeaf2 100644 --- a/apps/client/src/widgets/floating_buttons/geo_map_button.ts +++ b/apps/client/src/widgets/floating_buttons/geo_map_button.ts @@ -23,7 +23,9 @@ const TPL = /*html*/`\ export default class GeoMapButtons extends NoteContextAwareWidget { isEnabled() { - return super.isEnabled() && this.note?.getLabelValue("viewType") === "geoMap"; + return super.isEnabled() + && this.note?.getLabelValue("viewType") === "geoMap" + && !this.note.hasLabel("readOnly"); } doRender() { diff --git a/apps/client/src/widgets/floating_buttons/toggle_read_only_button.ts b/apps/client/src/widgets/floating_buttons/toggle_read_only_button.ts index f436c820c..571e99017 100644 --- a/apps/client/src/widgets/floating_buttons/toggle_read_only_button.ts +++ b/apps/client/src/widgets/floating_buttons/toggle_read_only_button.ts @@ -39,10 +39,20 @@ export default class ToggleReadOnlyButton extends OnClickButtonWidget { } isEnabled() { - return super.isEnabled() - && this.note?.type === "mermaid" - && this.note?.isContentAvailable() - && this.noteContext?.viewScope?.viewMode === "default"; + if (!super.isEnabled()) { + return false; + } + + if (!this?.note?.isContentAvailable()) { + return false; + } + + if (this.noteContext?.viewScope?.viewMode !== "default") { + return false; + } + + return this.note.type === "mermaid" || + (this.note.getLabelValue("viewType") === "geoMap"); } } diff --git a/apps/client/src/widgets/view_widgets/geo_view/context_menu.ts b/apps/client/src/widgets/view_widgets/geo_view/context_menu.ts index 768a17d10..c2b405ecf 100644 --- a/apps/client/src/widgets/view_widgets/geo_view/context_menu.ts +++ b/apps/client/src/widgets/view_widgets/geo_view/context_menu.ts @@ -1,22 +1,31 @@ import type { LatLng, LeafletMouseEvent } from "leaflet"; -import appContext from "../../../components/app_context.js"; -import contextMenu from "../../../menus/context_menu.js"; +import appContext, { type CommandMappings } from "../../../components/app_context.js"; +import contextMenu, { type MenuItem } from "../../../menus/context_menu.js"; import linkContextMenu from "../../../menus/link_context_menu.js"; import { t } from "../../../services/i18n.js"; import { createNewNote } from "./editing.js"; import { copyTextWithToast } from "../../../services/clipboard_ext.js"; import link from "../../../services/link.js"; -export default function openContextMenu(noteId: string, e: LeafletMouseEvent) { +export default function openContextMenu(noteId: string, e: LeafletMouseEvent, isEditable: boolean) { + let items: MenuItem[] = [ + ...buildGeoLocationItem(e), + { title: "----" }, + ...linkContextMenu.getItems(), + ]; + + if (isEditable) { + items = [ + ...items, + { title: "----" }, + { title: t("geo-map-context.remove-from-map"), command: "deleteFromMap", uiIcon: "bx bx-trash" } + ]; + } + contextMenu.show({ x: e.originalEvent.pageX, y: e.originalEvent.pageY, - items: [ - ...buildGeoLocationItem(e), - ...linkContextMenu.getItems(), - { title: "----" }, - { title: t("geo-map-context.remove-from-map"), command: "deleteFromMap", uiIcon: "bx bx-trash" } - ], + items, selectMenuItemHandler: ({ command }, e) => { if (command === "deleteFromMap") { appContext.triggerCommand(command, { noteId }); @@ -29,14 +38,23 @@ export default function openContextMenu(noteId: string, e: LeafletMouseEvent) { }); } -export function openMapContextMenu(noteId: string, e: LeafletMouseEvent) { +export function openMapContextMenu(noteId: string, e: LeafletMouseEvent, isEditable: boolean) { + let items: MenuItem[] = [ + ...buildGeoLocationItem(e) + ]; + + if (isEditable) { + items = [ + ...items, + { title: "----" }, + { title: t("geo-map-context.add-note"), command: "addNoteToMap", uiIcon: "bx bx-plus" } + ] + } + contextMenu.show({ x: e.originalEvent.pageX, y: e.originalEvent.pageY, - items: [ - ...buildGeoLocationItem(e), - { title: t("geo-map-context.add-note"), command: "addNoteToMap", uiIcon: "bx bx-plus" } - ], + items, selectMenuItemHandler: ({ command }) => { switch (command) { case "addNoteToMap": @@ -64,9 +82,6 @@ function buildGeoLocationItem(e: LeafletMouseEvent) { title: t("geo-map-context.open-location"), uiIcon: "bx bx-map-alt", handler: () => link.goToLinkExt(null, `geo:${e.latlng.lat},${e.latlng.lng}`) - }, - { - title: "----" } ]; } diff --git a/apps/client/src/widgets/view_widgets/geo_view/index.ts b/apps/client/src/widgets/view_widgets/geo_view/index.ts index 4af9b47cb..00db8be25 100644 --- a/apps/client/src/widgets/view_widgets/geo_view/index.ts +++ b/apps/client/src/widgets/view_widgets/geo_view/index.ts @@ -152,12 +152,16 @@ export default class GeoView extends ViewMode { this.#restoreViewportAndZoom(); + const isEditable = !this.isReadOnly; const updateFn = () => this.spacedUpdate.scheduleUpdate(); map.on("moveend", updateFn); map.on("zoomend", updateFn); map.on("click", (e) => this.#onMapClicked(e)) - map.on("contextmenu", (e) => openMapContextMenu(this.parentNote.noteId, e)); - setupDragging(this.$container, map, this.parentNote.noteId); + map.on("contextmenu", (e) => openMapContextMenu(this.parentNote.noteId, e, isEditable)); + + if (isEditable) { + setupDragging(this.$container, map, this.parentNote.noteId); + } this.#reloadMarkers(); @@ -219,6 +223,7 @@ export default class GeoView extends ViewMode { // Add the new markers. this.currentMarkerData = {}; const notes = await this.parentNote.getChildNotes(); + const draggable = !this.isReadOnly; for (const childNote of notes) { if (childNote.mime === "application/gpx+xml") { const track = await processNoteWithGpxTrack(this.map, childNote); @@ -228,7 +233,7 @@ export default class GeoView extends ViewMode { const latLng = childNote.getAttributeValue("label", LOCATION_ATTRIBUTE); if (latLng) { - const marker = processNoteWithMarker(this.map, childNote, latLng); + const marker = processNoteWithMarker(this.map, childNote, latLng, draggable); this.currentMarkerData[childNote.noteId] = marker; } } diff --git a/apps/client/src/widgets/view_widgets/geo_view/markers.ts b/apps/client/src/widgets/view_widgets/geo_view/markers.ts index 8853734ec..10a7fae65 100644 --- a/apps/client/src/widgets/view_widgets/geo_view/markers.ts +++ b/apps/client/src/widgets/view_widgets/geo_view/markers.ts @@ -9,20 +9,23 @@ import { moveMarker } from "./editing.js"; let gpxLoaded = false; -export default function processNoteWithMarker(map: Map, note: FNote, location: string) { +export default function processNoteWithMarker(map: Map, note: FNote, location: string, isEditable: boolean) { const [lat, lng] = location.split(",", 2).map((el) => parseFloat(el)); const icon = buildIcon(note.getIcon(), note.getColorClass(), note.title); const newMarker = marker(latLng(lat, lng), { icon, - draggable: true, + draggable: isEditable, autoPan: true, autoPanSpeed: 5 - }) - .addTo(map) - .on("moveend", (e) => { + }).addTo(map); + + if (isEditable) { + newMarker.on("moveend", (e) => { moveMarker(note.noteId, (e.target as Marker).getLatLng()); }); + } + newMarker.on("mousedown", ({ originalEvent }) => { // Middle click to open in new tab if (originalEvent.button === 1) { @@ -33,7 +36,7 @@ export default function processNoteWithMarker(map: Map, note: FNote, location: s } }); newMarker.on("contextmenu", (e) => { - openContextMenu(note.noteId, e); + openContextMenu(note.noteId, e, isEditable); }); const el = newMarker.getElement(); diff --git a/apps/client/src/widgets/view_widgets/view_mode.ts b/apps/client/src/widgets/view_widgets/view_mode.ts index d8c4314eb..f3706da4a 100644 --- a/apps/client/src/widgets/view_widgets/view_mode.ts +++ b/apps/client/src/widgets/view_widgets/view_mode.ts @@ -44,6 +44,10 @@ export default abstract class ViewMode extends Component { return false; } + get isReadOnly() { + return this.parentNote.hasLabel("readOnly"); + } + get viewStorage() { if (this._viewStorage) { return this._viewStorage; From 242a5765485abb30e947c6cd1bb644cabbfb0b0f Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 7 Jul 2025 19:15:38 +0300 Subject: [PATCH 14/14] refactor(views/geomap): solve type errors --- .../view_widgets/geo_view/context_menu.ts | 16 +++++++--------- .../src/widgets/view_widgets/geo_view/markers.ts | 6 ++++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/apps/client/src/widgets/view_widgets/geo_view/context_menu.ts b/apps/client/src/widgets/view_widgets/geo_view/context_menu.ts index c2b405ecf..26d91df27 100644 --- a/apps/client/src/widgets/view_widgets/geo_view/context_menu.ts +++ b/apps/client/src/widgets/view_widgets/geo_view/context_menu.ts @@ -47,7 +47,11 @@ export function openMapContextMenu(noteId: string, e: LeafletMouseEvent, isEdita items = [ ...items, { title: "----" }, - { title: t("geo-map-context.add-note"), command: "addNoteToMap", uiIcon: "bx bx-plus" } + { + title: t("geo-map-context.add-note"), + handler: () => createNewNote(noteId, e), + uiIcon: "bx bx-plus" + } ] } @@ -55,14 +59,8 @@ export function openMapContextMenu(noteId: string, e: LeafletMouseEvent, isEdita x: e.originalEvent.pageX, y: e.originalEvent.pageY, items, - selectMenuItemHandler: ({ command }) => { - switch (command) { - case "addNoteToMap": - createNewNote(noteId, e); - break; - default: - return; - } + selectMenuItemHandler: () => { + // Nothing to do, as the commands handle themselves. } }); } diff --git a/apps/client/src/widgets/view_widgets/geo_view/markers.ts b/apps/client/src/widgets/view_widgets/geo_view/markers.ts index 10a7fae65..71737df49 100644 --- a/apps/client/src/widgets/view_widgets/geo_view/markers.ts +++ b/apps/client/src/widgets/view_widgets/geo_view/markers.ts @@ -6,6 +6,8 @@ import note_tooltip from "../../../services/note_tooltip.js"; import openContextMenu from "./context_menu.js"; import server from "../../../services/server.js"; import { moveMarker } from "./editing.js"; +import appContext from "../../../components/app_context.js"; +import L from "leaflet"; let gpxLoaded = false; @@ -29,7 +31,7 @@ export default function processNoteWithMarker(map: Map, note: FNote, location: s newMarker.on("mousedown", ({ originalEvent }) => { // Middle click to open in new tab if (originalEvent.button === 1) { - const hoistedNoteId = this.hoistedNoteId; + const hoistedNoteId = appContext.tabManager.getActiveContext()?.hoistedNoteId; //@ts-ignore, fix once tab manager is ported. appContext.tabManager.openInNewTab(note.noteId, hoistedNoteId); return true; @@ -51,7 +53,7 @@ export default function processNoteWithMarker(map: Map, note: FNote, location: s export async function processNoteWithGpxTrack(map: Map, note: FNote) { if (!gpxLoaded) { - await import("leaflet-gpx"); + const GPX = await import("leaflet-gpx"); gpxLoaded = true; }