From e1952fe6b8d3442752f0e544b9e0df2bd1f82c4d Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 20 Jan 2025 18:45:56 +0200 Subject: [PATCH 01/46] 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/46] 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/46] 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/46] 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 8f09b6a192ba1b665e5043c3fd3017440e70975c Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Mon, 20 Jan 2025 20:15:39 +0100 Subject: [PATCH 05/46] fix: fix server build not running due to missing file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit as discussed in #1010 – quick & dirty fix by copying over the file into the server side of things Comment added to both files, about deduplication in the future fixes #1010 --- .../app/services/mime_type_definitions.ts | 2 + src/services/import/markdown.ts | 2 +- src/services/import/mime_type_definitions.ts | 218 ++++++++++++++++++ 3 files changed, 221 insertions(+), 1 deletion(-) create mode 100644 src/services/import/mime_type_definitions.ts diff --git a/src/public/app/services/mime_type_definitions.ts b/src/public/app/services/mime_type_definitions.ts index eb27c7200..0fe738186 100644 --- a/src/public/app/services/mime_type_definitions.ts +++ b/src/public/app/services/mime_type_definitions.ts @@ -1,3 +1,5 @@ +// TODO: deduplicate with /src/services/import/mime_type_definitions.ts + /** * A pseudo-MIME type which is used in the editor to automatically determine the language used in code blocks via heuristics. */ diff --git a/src/services/import/markdown.ts b/src/services/import/markdown.ts index dbd28fbe6..5931daad6 100644 --- a/src/services/import/markdown.ts +++ b/src/services/import/markdown.ts @@ -14,7 +14,7 @@ renderer.code = ({text, lang, escaped}: Tokens.Code) => { import htmlSanitizer from "../html_sanitizer.js"; import importUtils from "./utils.js"; -import { getMimeTypeFromHighlightJs, MIME_TYPE_AUTO, normalizeMimeTypeForCKEditor } from "../../public/app/services/mime_type_definitions.js"; +import { getMimeTypeFromHighlightJs, MIME_TYPE_AUTO, normalizeMimeTypeForCKEditor } from "./mime_type_definitions.js"; function renderToHtml(content: string, title: string) { const html = parse(content, { diff --git a/src/services/import/mime_type_definitions.ts b/src/services/import/mime_type_definitions.ts new file mode 100644 index 000000000..e058f97ec --- /dev/null +++ b/src/services/import/mime_type_definitions.ts @@ -0,0 +1,218 @@ +// TODO: deduplicate with /src/public/app/services/mime_type_definitions.ts + +/** + * A pseudo-MIME type which is used in the editor to automatically determine the language used in code blocks via heuristics. + */ +export const MIME_TYPE_AUTO = "text-x-trilium-auto"; + +export interface MimeTypeDefinition { + default?: boolean; + title: string; + mime: string; + /** The name of the language/mime type as defined by highlight.js (or one of the aliases), in order to be used for syntax highlighting such as inside code blocks. */ + highlightJs?: string; + /** If specified, will load the corresponding highlight.js file from the `libraries/highlightjs/${id}.js` instead of `node_modules/@highlightjs/cdn-assets/languages/${id}.min.js`. */ + highlightJsSource?: "libraries"; + /** If specified, will load the corresponding highlight file from the given path instead of `node_modules`. */ + codeMirrorSource?: string; +} + +/** + * For highlight.js-supported languages, see https://github.com/highlightjs/highlight.js/blob/main/SUPPORTED_LANGUAGES.md. + */ + +export const MIME_TYPES_DICT: readonly MimeTypeDefinition[] = Object.freeze([ + { default: true, title: "Plain text", mime: "text/plain", highlightJs: "plaintext" }, + { title: "APL", mime: "text/apl" }, + { title: "ASN.1", mime: "text/x-ttcn-asn" }, + { title: "ASP.NET", mime: "application/x-aspx" }, + { title: "Asterisk", mime: "text/x-asterisk" }, + { title: "Batch file (DOS)", mime: "application/x-bat", highlightJs: "dos", codeMirrorSource: "libraries/codemirror/batch.js" }, + { title: "Brainfuck", mime: "text/x-brainfuck", highlightJs: "brainfuck" }, + { default: true, title: "C", mime: "text/x-csrc", highlightJs: "c" }, + { default: true, title: "C#", mime: "text/x-csharp", highlightJs: "csharp" }, + { default: true, title: "C++", mime: "text/x-c++src", highlightJs: "cpp" }, + { title: "Clojure", mime: "text/x-clojure", highlightJs: "clojure" }, + { title: "ClojureScript", mime: "text/x-clojurescript" }, + { title: "Closure Stylesheets (GSS)", mime: "text/x-gss" }, + { title: "CMake", mime: "text/x-cmake", highlightJs: "cmake" }, + { title: "Cobol", mime: "text/x-cobol" }, + { title: "CoffeeScript", mime: "text/coffeescript", highlightJs: "coffeescript" }, + { title: "Common Lisp", mime: "text/x-common-lisp", highlightJs: "lisp" }, + { title: "CQL", mime: "text/x-cassandra" }, + { title: "Crystal", mime: "text/x-crystal", highlightJs: "crystal" }, + { default: true, title: "CSS", mime: "text/css", highlightJs: "css" }, + { title: "Cypher", mime: "application/x-cypher-query" }, + { title: "Cython", mime: "text/x-cython" }, + { title: "D", mime: "text/x-d", highlightJs: "d" }, + { title: "Dart", mime: "application/dart", highlightJs: "dart" }, + { title: "diff", mime: "text/x-diff", highlightJs: "diff" }, + { title: "Django", mime: "text/x-django", highlightJs: "django" }, + { title: "Dockerfile", mime: "text/x-dockerfile", highlightJs: "dockerfile" }, + { title: "DTD", mime: "application/xml-dtd" }, + { title: "Dylan", mime: "text/x-dylan" }, + { title: "EBNF", mime: "text/x-ebnf", highlightJs: "ebnf" }, + { title: "ECL", mime: "text/x-ecl" }, + { title: "edn", mime: "application/edn" }, + { title: "Eiffel", mime: "text/x-eiffel" }, + { title: "Elm", mime: "text/x-elm", highlightJs: "elm" }, + { title: "Embedded Javascript", mime: "application/x-ejs" }, + { title: "Embedded Ruby", mime: "application/x-erb", highlightJs: "erb" }, + { title: "Erlang", mime: "text/x-erlang", highlightJs: "erlang" }, + { title: "Esper", mime: "text/x-esper" }, + { title: "F#", mime: "text/x-fsharp", highlightJs: "fsharp" }, + { title: "Factor", mime: "text/x-factor" }, + { title: "FCL", mime: "text/x-fcl" }, + { title: "Forth", mime: "text/x-forth" }, + { title: "Fortran", mime: "text/x-fortran", highlightJs: "fortran" }, + { title: "Gas", mime: "text/x-gas" }, + { title: "Gherkin", mime: "text/x-feature", highlightJs: "gherkin" }, + { title: "GitHub Flavored Markdown", mime: "text/x-gfm", highlightJs: "markdown" }, + { default: true, title: "Go", mime: "text/x-go", highlightJs: "go" }, + { default: true, title: "Groovy", mime: "text/x-groovy", highlightJs: "groovy" }, + { title: "HAML", mime: "text/x-haml", highlightJs: "haml" }, + { default: true, title: "Haskell", mime: "text/x-haskell", highlightJs: "haskell" }, + { title: "Haskell (Literate)", mime: "text/x-literate-haskell" }, + { title: "Haxe", mime: "text/x-haxe", highlightJs: "haxe" }, + { default: true, title: "HTML", mime: "text/html", highlightJs: "xml" }, + { default: true, title: "HTTP", mime: "message/http", highlightJs: "http" }, + { title: "HXML", mime: "text/x-hxml" }, + { title: "IDL", mime: "text/x-idl" }, + { default: true, title: "Java", mime: "text/x-java", highlightJs: "java" }, + { title: "Java Server Pages", mime: "application/x-jsp", highlightJs: "java" }, + { title: "Jinja2", mime: "text/jinja2" }, + { default: true, title: "JS backend", mime: "application/javascript;env=backend", highlightJs: "javascript" }, + { default: true, title: "JS frontend", mime: "application/javascript;env=frontend", highlightJs: "javascript" }, + { default: true, title: "JSON", mime: "application/json", highlightJs: "json" }, + { title: "JSON-LD", mime: "application/ld+json", highlightJs: "json" }, + { title: "JSX", mime: "text/jsx", highlightJs: "javascript" }, + { title: "Julia", mime: "text/x-julia", highlightJs: "julia" }, + { default: true, title: "Kotlin", mime: "text/x-kotlin", highlightJs: "kotlin" }, + { title: "LaTeX", mime: "text/x-latex", highlightJs: "latex" }, + { title: "LESS", mime: "text/x-less", highlightJs: "less" }, + { title: "LiveScript", mime: "text/x-livescript", highlightJs: "livescript" }, + { title: "Lua", mime: "text/x-lua", highlightJs: "lua" }, + { title: "MariaDB SQL", mime: "text/x-mariadb", highlightJs: "sql" }, + { default: true, title: "Markdown", mime: "text/x-markdown", highlightJs: "markdown" }, + { title: "Mathematica", mime: "text/x-mathematica", highlightJs: "mathematica" }, + { title: "mbox", mime: "application/mbox" }, + { title: "mIRC", mime: "text/mirc" }, + { title: "Modelica", mime: "text/x-modelica" }, + { title: "MS SQL", mime: "text/x-mssql", highlightJs: "sql" }, + { title: "mscgen", mime: "text/x-mscgen" }, + { title: "msgenny", mime: "text/x-msgenny" }, + { title: "MUMPS", mime: "text/x-mumps" }, + { title: "MySQL", mime: "text/x-mysql", highlightJs: "sql" }, + { title: "Nginx", mime: "text/x-nginx-conf", highlightJs: "nginx" }, + { title: "NSIS", mime: "text/x-nsis", highlightJs: "nsis" }, + { title: "NTriples", mime: "application/n-triples" }, + { title: "Objective-C", mime: "text/x-objectivec", highlightJs: "objectivec" }, + { title: "OCaml", mime: "text/x-ocaml", highlightJs: "ocaml" }, + { title: "Octave", mime: "text/x-octave" }, + { title: "Oz", mime: "text/x-oz" }, + { title: "Pascal", mime: "text/x-pascal", highlightJs: "delphi" }, + { title: "PEG.js", mime: "null" }, + { default: true, title: "Perl", mime: "text/x-perl" }, + { title: "PGP", mime: "application/pgp" }, + { default: true, title: "PHP", mime: "text/x-php" }, + { title: "Pig", mime: "text/x-pig" }, + { title: "PLSQL", mime: "text/x-plsql", highlightJs: "sql" }, + { title: "PostgreSQL", mime: "text/x-pgsql", highlightJs: "pgsql" }, + { title: "PowerShell", mime: "application/x-powershell", highlightJs: "powershell" }, + { title: "Properties files", mime: "text/x-properties", highlightJs: "properties" }, + { title: "ProtoBuf", mime: "text/x-protobuf", highlightJs: "protobuf" }, + { title: "Pug", mime: "text/x-pug" }, + { title: "Puppet", mime: "text/x-puppet", highlightJs: "puppet" }, + { default: true, title: "Python", mime: "text/x-python", highlightJs: "python" }, + { title: "Q", mime: "text/x-q", highlightJs: "q" }, + { title: "R", mime: "text/x-rsrc", highlightJs: "r" }, + { title: "reStructuredText", mime: "text/x-rst" }, + { title: "RPM Changes", mime: "text/x-rpm-changes" }, + { title: "RPM Spec", mime: "text/x-rpm-spec" }, + { default: true, title: "Ruby", mime: "text/x-ruby", highlightJs: "ruby" }, + { title: "Rust", mime: "text/x-rustsrc", highlightJs: "rust" }, + { title: "SAS", mime: "text/x-sas", highlightJs: "sas" }, + { title: "Sass", mime: "text/x-sass" }, + { title: "Scala", mime: "text/x-scala" }, + { title: "Scheme", mime: "text/x-scheme" }, + { title: "SCSS", mime: "text/x-scss", highlightJs: "scss" }, + { default: true, title: "Shell (bash)", mime: "text/x-sh", highlightJs: "bash" }, + { title: "Sieve", mime: "application/sieve" }, + { title: "Slim", mime: "text/x-slim" }, + { title: "Smalltalk", mime: "text/x-stsrc", highlightJs: "smalltalk" }, + { title: "Smarty", mime: "text/x-smarty" }, + { title: "SML", mime: "text/x-sml", highlightJs: "sml" }, + { title: "Solr", mime: "text/x-solr" }, + { title: "Soy", mime: "text/x-soy" }, + { title: "SPARQL", mime: "application/sparql-query" }, + { title: "Spreadsheet", mime: "text/x-spreadsheet" }, + { default: true, title: "SQL", mime: "text/x-sql", highlightJs: "sql" }, + { title: "SQLite", mime: "text/x-sqlite", highlightJs: "sql" }, + { default: true, title: "SQLite (Trilium)", mime: "text/x-sqlite;schema=trilium", highlightJs: "sql" }, + { title: "Squirrel", mime: "text/x-squirrel" }, + { title: "sTeX", mime: "text/x-stex" }, + { title: "Stylus", mime: "text/x-styl", highlightJs: "stylus" }, + { default: true, title: "Swift", mime: "text/x-swift" }, + { title: "SystemVerilog", mime: "text/x-systemverilog" }, + { title: "Tcl", mime: "text/x-tcl", highlightJs: "tcl" }, + { title: "Terraform (HCL)", mime: "text/x-hcl", highlightJs: "terraform", highlightJsSource: "libraries", codeMirrorSource: "libraries/codemirror/hcl.js" }, + { title: "Textile", mime: "text/x-textile" }, + { title: "TiddlyWiki ", mime: "text/x-tiddlywiki" }, + { title: "Tiki wiki", mime: "text/tiki" }, + { title: "TOML", mime: "text/x-toml", highlightJs: "ini" }, + { title: "Tornado", mime: "text/x-tornado" }, + { title: "troff", mime: "text/troff" }, + { title: "TTCN", mime: "text/x-ttcn" }, + { title: "TTCN_CFG", mime: "text/x-ttcn-cfg" }, + { title: "Turtle", mime: "text/turtle" }, + { title: "Twig", mime: "text/x-twig", highlightJs: "twig" }, + { title: "TypeScript", mime: "application/typescript", highlightJs: "typescript" }, + { title: "TypeScript-JSX", mime: "text/typescript-jsx" }, + { title: "VB.NET", mime: "text/x-vb", highlightJs: "vbnet" }, + { title: "VBScript", mime: "text/vbscript", highlightJs: "vbscript" }, + { title: "Velocity", mime: "text/velocity" }, + { title: "Verilog", mime: "text/x-verilog", highlightJs: "verilog" }, + { title: "VHDL", mime: "text/x-vhdl", highlightJs: "vhdl" }, + { title: "Vue.js Component", mime: "text/x-vue" }, + { title: "Web IDL", mime: "text/x-webidl" }, + { default: true, title: "XML", mime: "text/xml", highlightJs: "xml" }, + { title: "XQuery", mime: "application/xquery", highlightJs: "xquery" }, + { title: "xu", mime: "text/x-xu" }, + { title: "Yacas", mime: "text/x-yacas" }, + { default: true, title: "YAML", mime: "text/x-yaml", highlightJs: "yaml" }, + { title: "Z80", mime: "text/x-z80" } +]); + +/** + * Given a MIME type in the usual format (e.g. `text/csrc`), it returns a MIME type that can be passed down to the CKEditor + * code plugin. + * + * @param mimeType The MIME type to normalize, in the usual format (e.g. `text/c-src`). + * @returns the normalized MIME type (e.g. `text-c-src`). + */ +export function normalizeMimeTypeForCKEditor(mimeType: string) { + return mimeType.toLowerCase().replace(/[\W_]+/g, "-"); +} + +let byHighlightJsNameMappings: Record | null = null; + +/** + * Given a Highlight.js language tag (e.g. `css`), it returns a corresponding {@link MimeTypeDefinition} if found. + * + * If there are multiple {@link MimeTypeDefinition}s for the language tag, then only the first one is retrieved. For example for `javascript`, the "JS frontend" mime type is returned. + * + * @param highlightJsName a language tag. + * @returns the corresponding {@link MimeTypeDefinition} if found, or `undefined` otherwise. + */ +export function getMimeTypeFromHighlightJs(highlightJsName: string) { + if (!byHighlightJsNameMappings) { + byHighlightJsNameMappings = {}; + for (const mimeType of MIME_TYPES_DICT) { + if (mimeType.highlightJs && !byHighlightJsNameMappings[mimeType.highlightJs]) { + byHighlightJsNameMappings[mimeType.highlightJs] = mimeType; + } + } + } + + return byHighlightJsNameMappings[highlightJsName]; +} From 5cefd4f50ad47860ba93f58ab2198d68241186f6 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 20 Jan 2025 21:27:06 +0200 Subject: [PATCH 06/46] 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 07/46] 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 08/46] 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 09/46] 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 3206a7fe73aa7a5647c84f91eb61e57a01b217b2 Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Mon, 20 Jan 2025 21:46:25 +0100 Subject: [PATCH 10/46] chore: move generate_documents to bin folder this file is never called in production code and wouldn't even run, even if it was: the `lorem-ipsum` dependency is in devDependencies, so never gets installed in any dist builds move the file to a place where it makes more sense and avoid it getting packaged without any reason. --- {src/tools => bin}/generate_document.ts | 12 ++++++------ package.json | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) rename {src/tools => bin}/generate_document.ts (88%) diff --git a/src/tools/generate_document.ts b/bin/generate_document.ts similarity index 88% rename from src/tools/generate_document.ts rename to bin/generate_document.ts index 8c2286dda..1ccbb96c0 100644 --- a/src/tools/generate_document.ts +++ b/bin/generate_document.ts @@ -3,13 +3,13 @@ * will create 1000 new notes and some clones into the current document.db */ -import sqlInit from "../services/sql_init.js"; -import noteService from "../services/notes.js"; -import attributeService from "../services/attributes.js"; -import cls from "../services/cls.js"; -import cloningService from "../services/cloning.js"; +import sqlInit from "../src/services/sql_init.js"; +import noteService from "../src/services/notes.js"; +import attributeService from "../src/services/attributes.js"; +import cls from "../src/services/cls.js"; +import cloningService from "../src/services/cloning.js"; import loremIpsum from "lorem-ipsum"; -import "../becca/entity_constructor.js"; +import "../src/becca/entity_constructor.js"; const noteCount = parseInt(process.argv[2]); diff --git a/package.json b/package.json index a492db6fb..b33580ff5 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,7 @@ "integration-edit-db": "cross-env TRILIUM_INTEGRATION_TEST=edit TRILIUM_PORT=8081 TRILIUM_ENV=dev TRILIUM_DATA_DIR=./integration-tests/db nodemon src/main.ts", "integration-mem-db": "cross-env TRILIUM_INTEGRATION_TEST=memory TRILIUM_PORT=8082 TRILIUM_DATA_DIR=./integration-tests/db nodemon src/main.ts", "integration-mem-db-dev": "cross-env TRILIUM_INTEGRATION_TEST=memory TRILIUM_PORT=8082 TRILIUM_ENV=dev TRILIUM_DATA_DIR=./integration-tests/db nodemon src/main.ts", - "generate-document": "cross-env nodemon src/tools/generate_document.ts 1000", + "generate-document": "cross-env nodemon ./bin/generate_document.ts 1000", "ci-update-nightly-version": "tsx ./bin/update-nightly-version.ts", "prettier-check": "prettier . --check", "prettier-fix": "prettier . --write" From 2582924046f620b65d398e8735905bde6cd1dd9a Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 20 Jan 2025 22:50:36 +0200 Subject: [PATCH 11/46] 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 12/46] 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 13/46] 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 14/46] 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 1d0381833757b8c1fb965dfafdf5d1b31e0dfac2 Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Mon, 20 Jan 2025 22:54:12 +0100 Subject: [PATCH 15/46] chore(generate_documents): add note about script being broken currently --- bin/generate_document.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bin/generate_document.ts b/bin/generate_document.ts index 1ccbb96c0..f9d327116 100644 --- a/bin/generate_document.ts +++ b/bin/generate_document.ts @@ -1,5 +1,5 @@ /** - * Usage: node src/tools/generate_document.js 1000 + * Usage: tsx ./generate_document.ts 1000 * will create 1000 new notes and some clones into the current document.db */ @@ -90,4 +90,6 @@ async function start() { process.exit(0); } -sqlInit.dbReady.then(cls.wrap(start)); +// @TriliumNextTODO sqlInit.dbReady never seems to resolve so program hangs +// see https://github.com/TriliumNext/Notes/issues/1020 +sqlInit.dbReady.then(cls.wrap(start)).catch((err) => console.error(err)); From ef5b2d60f35235d1703364c2980cfd277e4b56a1 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 20 Jan 2025 23:54:22 +0200 Subject: [PATCH 16/46] 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 17/46] 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 18/46] 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 19/46] 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 20/46] 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 21/46] 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 22/46] 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 23/46] 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 24/46] 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 25/46] 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 26/46] 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 27/46] 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 28/46] 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 29/46] 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 30/46] 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 31/46] 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 32/46] 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 33/46] 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 34/46] 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 cd22102dabb9eba0e8544adbf1920b4e6c439898 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 22 Jan 2025 00:25:19 +0000 Subject: [PATCH 35/46] chore(deps): update vitest monorepo to v3.0.3 --- package-lock.json | 100 +++++++++++++++++++++++----------------------- package.json | 4 +- 2 files changed, 52 insertions(+), 52 deletions(-) diff --git a/package-lock.json b/package-lock.json index 428c93da9..1d036634f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -148,7 +148,7 @@ "@types/ws": "8.5.13", "@types/xml2js": "0.4.14", "@types/yargs": "17.0.33", - "@vitest/coverage-v8": "3.0.2", + "@vitest/coverage-v8": "3.0.3", "cross-env": "7.0.3", "electron": "34.0.0", "esm": "3.2.25", @@ -163,7 +163,7 @@ "tsx": "4.19.2", "typedoc": "0.27.6", "typescript": "5.7.3", - "vitest": "3.0.2", + "vitest": "3.0.3", "webpack": "5.97.1", "webpack-cli": "6.0.1", "webpack-dev-middleware": "7.4.2" @@ -4164,9 +4164,9 @@ } }, "node_modules/@vitest/coverage-v8": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.0.2.tgz", - "integrity": "sha512-U+hZYb0FtgNDb6B3E9piAHzXXIuxuBw2cd6Lvepc9sYYY4KjgiwCBmo3Sird9ZRu3ggLpLBTfw1ZRr77ipiSfw==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.0.3.tgz", + "integrity": "sha512-uVbJ/xhImdNtzPnLyxCZJMTeTIYdgcC2nWtBBBpR1H6z0w8m7D+9/zrDIx2nNxgMg9r+X8+RY2qVpUDeW2b3nw==", "dev": true, "license": "MIT", "dependencies": { @@ -4187,8 +4187,8 @@ "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@vitest/browser": "3.0.2", - "vitest": "3.0.2" + "@vitest/browser": "3.0.3", + "vitest": "3.0.3" }, "peerDependenciesMeta": { "@vitest/browser": { @@ -4197,14 +4197,14 @@ } }, "node_modules/@vitest/expect": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.0.2.tgz", - "integrity": "sha512-dKSHLBcoZI+3pmP5hiZ7I5grNru2HRtEW8Z5Zp4IXog8QYcxhlox7JUPyIIFWfN53+3HW3KPLIl6nSzUGgKSuQ==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.0.3.tgz", + "integrity": "sha512-SbRCHU4qr91xguu+dH3RUdI5dC86zm8aZWydbp961aIR7G8OYNN6ZiayFuf9WAngRbFOfdrLHCGgXTj3GtoMRQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "3.0.2", - "@vitest/utils": "3.0.2", + "@vitest/spy": "3.0.3", + "@vitest/utils": "3.0.3", "chai": "^5.1.2", "tinyrainbow": "^2.0.0" }, @@ -4213,13 +4213,13 @@ } }, "node_modules/@vitest/mocker": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.0.2.tgz", - "integrity": "sha512-Hr09FoBf0jlwwSyzIF4Xw31OntpO3XtZjkccpcBf8FeVW3tpiyKlkeUzxS/txzHqpUCNIX157NaTySxedyZLvA==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.0.3.tgz", + "integrity": "sha512-XT2XBc4AN9UdaxJAeIlcSZ0ILi/GzmG5G8XSly4gaiqIvPV3HMTSIDZWJVX6QRJ0PX1m+W8Cy0K9ByXNb/bPIA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "3.0.2", + "@vitest/spy": "3.0.3", "estree-walker": "^3.0.3", "magic-string": "^0.30.17" }, @@ -4240,9 +4240,9 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.0.2.tgz", - "integrity": "sha512-yBohcBw/T/p0/JRgYD+IYcjCmuHzjC3WLAKsVE4/LwiubzZkE8N49/xIQ/KGQwDRA8PaviF8IRO8JMWMngdVVQ==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.0.3.tgz", + "integrity": "sha512-gCrM9F7STYdsDoNjGgYXKPq4SkSxwwIU5nkaQvdUxiQ0EcNlez+PdKOVIsUJvh9P9IeIFmjn4IIREWblOBpP2Q==", "dev": true, "license": "MIT", "dependencies": { @@ -4253,13 +4253,13 @@ } }, "node_modules/@vitest/runner": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.0.2.tgz", - "integrity": "sha512-GHEsWoncrGxWuW8s405fVoDfSLk6RF2LCXp6XhevbtDjdDme1WV/eNmUueDfpY1IX3MJaCRelVCEXsT9cArfEg==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.0.3.tgz", + "integrity": "sha512-Rgi2kOAk5ZxWZlwPguRJFOBmWs6uvvyAAR9k3MvjRvYrG7xYvKChZcmnnpJCS98311CBDMqsW9MzzRFsj2gX3g==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "3.0.2", + "@vitest/utils": "3.0.3", "pathe": "^2.0.1" }, "funding": { @@ -4274,13 +4274,13 @@ "license": "MIT" }, "node_modules/@vitest/snapshot": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.0.2.tgz", - "integrity": "sha512-h9s67yD4+g+JoYG0zPCo/cLTabpDqzqNdzMawmNPzDStTiwxwkyYM1v5lWE8gmGv3SVJ2DcxA2NpQJZJv9ym3g==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.0.3.tgz", + "integrity": "sha512-kNRcHlI4txBGztuJfPEJ68VezlPAXLRT1u5UCx219TU3kOG2DplNxhWLwDf2h6emwmTPogzLnGVwP6epDaJN6Q==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "3.0.2", + "@vitest/pretty-format": "3.0.3", "magic-string": "^0.30.17", "pathe": "^2.0.1" }, @@ -4296,9 +4296,9 @@ "license": "MIT" }, "node_modules/@vitest/spy": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.0.2.tgz", - "integrity": "sha512-8mI2iUn+PJFMT44e3ISA1R+K6ALVs47W6eriDTfXe6lFqlflID05MB4+rIFhmDSLBj8iBsZkzBYlgSkinxLzSQ==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.0.3.tgz", + "integrity": "sha512-7/dgux8ZBbF7lEIKNnEqQlyRaER9nkAL9eTmdKJkDO3hS8p59ATGwKOCUDHcBLKr7h/oi/6hP+7djQk8049T2A==", "dev": true, "license": "MIT", "dependencies": { @@ -4309,13 +4309,13 @@ } }, "node_modules/@vitest/utils": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.0.2.tgz", - "integrity": "sha512-Qu01ZYZlgHvDP02JnMBRpX43nRaZtNpIzw3C1clDXmn8eakgX6iQVGzTQ/NjkIr64WD8ioqOjkaYRVvHQI5qiw==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.0.3.tgz", + "integrity": "sha512-f+s8CvyzPtMFY1eZKkIHGhPsQgYo5qCm6O8KZoim9qm1/jT64qBgGpO5tHscNH6BzRHM+edLNOP+3vO8+8pE/A==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "3.0.2", + "@vitest/pretty-format": "3.0.3", "loupe": "^3.1.2", "tinyrainbow": "^2.0.0" }, @@ -16888,9 +16888,9 @@ } }, "node_modules/vite-node": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.0.2.tgz", - "integrity": "sha512-hsEQerBAHvVAbv40m3TFQe/lTEbOp7yDpyqMJqr2Tnd+W58+DEYOt+fluQgekOePcsNBmR77lpVAnIU2Xu4SvQ==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.0.3.tgz", + "integrity": "sha512-0sQcwhwAEw/UJGojbhOrnq3HtiZ3tC7BzpAa0lx3QaTX0S3YX70iGcik25UBdB96pmdwjyY2uyKNYruxCDmiEg==", "dev": true, "license": "MIT", "dependencies": { @@ -17382,19 +17382,19 @@ } }, "node_modules/vitest": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.0.2.tgz", - "integrity": "sha512-5bzaHakQ0hmVVKLhfh/jXf6oETDBtgPo8tQCHYB+wftNgFJ+Hah67IsWc8ivx4vFL025Ow8UiuTf4W57z4izvQ==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.0.3.tgz", + "integrity": "sha512-dWdwTFUW9rcnL0LyF2F+IfvNQWB0w9DERySCk8VMG75F8k25C7LsZoh6XfCjPvcR8Nb+Lqi9JKr6vnzH7HSrpQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/expect": "3.0.2", - "@vitest/mocker": "3.0.2", - "@vitest/pretty-format": "^3.0.2", - "@vitest/runner": "3.0.2", - "@vitest/snapshot": "3.0.2", - "@vitest/spy": "3.0.2", - "@vitest/utils": "3.0.2", + "@vitest/expect": "3.0.3", + "@vitest/mocker": "3.0.3", + "@vitest/pretty-format": "^3.0.3", + "@vitest/runner": "3.0.3", + "@vitest/snapshot": "3.0.3", + "@vitest/spy": "3.0.3", + "@vitest/utils": "3.0.3", "chai": "^5.1.2", "debug": "^4.4.0", "expect-type": "^1.1.0", @@ -17406,7 +17406,7 @@ "tinypool": "^1.0.2", "tinyrainbow": "^2.0.0", "vite": "^5.0.0 || ^6.0.0", - "vite-node": "3.0.2", + "vite-node": "3.0.3", "why-is-node-running": "^2.3.0" }, "bin": { @@ -17421,8 +17421,8 @@ "peerDependencies": { "@edge-runtime/vm": "*", "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "@vitest/browser": "3.0.2", - "@vitest/ui": "3.0.2", + "@vitest/browser": "3.0.3", + "@vitest/ui": "3.0.3", "happy-dom": "*", "jsdom": "*" }, diff --git a/package.json b/package.json index a492db6fb..ed07f4cbd 100644 --- a/package.json +++ b/package.json @@ -190,7 +190,7 @@ "@types/ws": "8.5.13", "@types/xml2js": "0.4.14", "@types/yargs": "17.0.33", - "@vitest/coverage-v8": "3.0.2", + "@vitest/coverage-v8": "3.0.3", "cross-env": "7.0.3", "electron": "34.0.0", "esm": "3.2.25", @@ -205,7 +205,7 @@ "tsx": "4.19.2", "typedoc": "0.27.6", "typescript": "5.7.3", - "vitest": "3.0.2", + "vitest": "3.0.3", "webpack": "5.97.1", "webpack-cli": "6.0.1", "webpack-dev-middleware": "7.4.2" From dbd38ecedfbaa662beb920ace4f866831544227e Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 22 Jan 2025 18:57:09 +0200 Subject: [PATCH 36/46] 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 37/46] 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 38/46] 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 39/46] 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 40/46] 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 41/46] 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 42/46] 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 43/46] 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 44/46] 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 45/46] 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 46/46] 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.