mirror of
				https://github.com/zadam/trilium.git
				synced 2025-11-04 05:28:59 +01:00 
			
		
		
		
	Merge pull request #1017 from TriliumNext/feature/map_note_type
Map note type
This commit is contained in:
		
						commit
						16b5eef650
					
				@ -100,7 +100,8 @@ const copy = async () => {
 | 
				
			|||||||
        "node_modules/codemirror/keymap/",
 | 
					        "node_modules/codemirror/keymap/",
 | 
				
			||||||
        "node_modules/mind-elixir/dist/",
 | 
					        "node_modules/mind-elixir/dist/",
 | 
				
			||||||
        "node_modules/@highlightjs/cdn-assets/languages",
 | 
					        "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) {
 | 
					    for (const folder of nodeModulesFolder) {
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										17
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										17
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							@ -16,6 +16,7 @@
 | 
				
			|||||||
        "@mermaid-js/layout-elk": "0.1.7",
 | 
					        "@mermaid-js/layout-elk": "0.1.7",
 | 
				
			||||||
        "@mind-elixir/node-menu": "1.0.3",
 | 
					        "@mind-elixir/node-menu": "1.0.3",
 | 
				
			||||||
        "@triliumnext/express-partial-content": "1.0.1",
 | 
					        "@triliumnext/express-partial-content": "1.0.1",
 | 
				
			||||||
 | 
					        "@types/leaflet": "1.9.16",
 | 
				
			||||||
        "@types/react-dom": "18.3.5",
 | 
					        "@types/react-dom": "18.3.5",
 | 
				
			||||||
        "archiver": "7.0.1",
 | 
					        "archiver": "7.0.1",
 | 
				
			||||||
        "async-mutex": "0.5.0",
 | 
					        "async-mutex": "0.5.0",
 | 
				
			||||||
@ -67,6 +68,7 @@
 | 
				
			|||||||
        "jsplumb": "2.15.6",
 | 
					        "jsplumb": "2.15.6",
 | 
				
			||||||
        "katex": "0.16.21",
 | 
					        "katex": "0.16.21",
 | 
				
			||||||
        "knockout": "3.5.1",
 | 
					        "knockout": "3.5.1",
 | 
				
			||||||
 | 
					        "leaflet": "1.9.4",
 | 
				
			||||||
        "mark.js": "8.11.1",
 | 
					        "mark.js": "8.11.1",
 | 
				
			||||||
        "marked": "15.0.6",
 | 
					        "marked": "15.0.6",
 | 
				
			||||||
        "mermaid": "11.4.1",
 | 
					        "mermaid": "11.4.1",
 | 
				
			||||||
@ -3847,6 +3849,15 @@
 | 
				
			|||||||
        "@types/node": "*"
 | 
					        "@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": {
 | 
					    "node_modules/@types/linkify-it": {
 | 
				
			||||||
      "version": "5.0.0",
 | 
					      "version": "5.0.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz",
 | 
				
			||||||
@ -11437,6 +11448,12 @@
 | 
				
			|||||||
        "safe-buffer": "~5.1.0"
 | 
					        "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": {
 | 
					    "node_modules/limiter": {
 | 
				
			||||||
      "version": "1.1.5",
 | 
					      "version": "1.1.5",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz",
 | 
				
			||||||
 | 
				
			|||||||
@ -61,6 +61,7 @@
 | 
				
			|||||||
    "@mermaid-js/layout-elk": "0.1.7",
 | 
					    "@mermaid-js/layout-elk": "0.1.7",
 | 
				
			||||||
    "@mind-elixir/node-menu": "1.0.3",
 | 
					    "@mind-elixir/node-menu": "1.0.3",
 | 
				
			||||||
    "@triliumnext/express-partial-content": "1.0.1",
 | 
					    "@triliumnext/express-partial-content": "1.0.1",
 | 
				
			||||||
 | 
					    "@types/leaflet": "1.9.16",
 | 
				
			||||||
    "@types/react-dom": "18.3.5",
 | 
					    "@types/react-dom": "18.3.5",
 | 
				
			||||||
    "archiver": "7.0.1",
 | 
					    "archiver": "7.0.1",
 | 
				
			||||||
    "async-mutex": "0.5.0",
 | 
					    "async-mutex": "0.5.0",
 | 
				
			||||||
@ -112,6 +113,7 @@
 | 
				
			|||||||
    "jsplumb": "2.15.6",
 | 
					    "jsplumb": "2.15.6",
 | 
				
			||||||
    "katex": "0.16.21",
 | 
					    "katex": "0.16.21",
 | 
				
			||||||
    "knockout": "3.5.1",
 | 
					    "knockout": "3.5.1",
 | 
				
			||||||
 | 
					    "leaflet": "1.9.4",
 | 
				
			||||||
    "mark.js": "8.11.1",
 | 
					    "mark.js": "8.11.1",
 | 
				
			||||||
    "marked": "15.0.6",
 | 
					    "marked": "15.0.6",
 | 
				
			||||||
    "mermaid": "11.4.1",
 | 
					    "mermaid": "11.4.1",
 | 
				
			||||||
 | 
				
			|||||||
@ -116,7 +116,8 @@ export const ALLOWED_NOTE_TYPES = [
 | 
				
			|||||||
    "book",
 | 
					    "book",
 | 
				
			||||||
    "webView",
 | 
					    "webView",
 | 
				
			||||||
    "code",
 | 
					    "code",
 | 
				
			||||||
    "mindMap"
 | 
					    "mindMap",
 | 
				
			||||||
 | 
					    "geoMap"
 | 
				
			||||||
] as const;
 | 
					] as const;
 | 
				
			||||||
export type NoteType = (typeof ALLOWED_NOTE_TYPES)[number];
 | 
					export type NoteType = (typeof ALLOWED_NOTE_TYPES)[number];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -23,6 +23,7 @@ import type LoadResults from "../services/load_results.js";
 | 
				
			|||||||
import type { Attribute } from "../services/attribute_parser.js";
 | 
					import type { Attribute } from "../services/attribute_parser.js";
 | 
				
			||||||
import type NoteTreeWidget from "../widgets/note_tree.js";
 | 
					import type NoteTreeWidget from "../widgets/note_tree.js";
 | 
				
			||||||
import type { default as NoteContext, GetTextEditorCallback } from "./note_context.js";
 | 
					import type { default as NoteContext, GetTextEditorCallback } from "./note_context.js";
 | 
				
			||||||
 | 
					import type { ContextMenuEvent } from "../menus/context_menu.js";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface Layout {
 | 
					interface Layout {
 | 
				
			||||||
    getRootWidget: (appContext: AppContext) => RootWidget;
 | 
					    getRootWidget: (appContext: AppContext) => RootWidget;
 | 
				
			||||||
@ -69,6 +70,7 @@ export interface ExecuteCommandData extends CommandData {
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
export type CommandMappings = {
 | 
					export type CommandMappings = {
 | 
				
			||||||
    "api-log-messages": CommandData;
 | 
					    "api-log-messages": CommandData;
 | 
				
			||||||
 | 
					    focusTree: CommandData,
 | 
				
			||||||
    focusOnDetail: Required<CommandData>;
 | 
					    focusOnDetail: Required<CommandData>;
 | 
				
			||||||
    focusOnSearchDefinition: Required<CommandData>;
 | 
					    focusOnSearchDefinition: Required<CommandData>;
 | 
				
			||||||
    searchNotes: CommandData & {
 | 
					    searchNotes: CommandData & {
 | 
				
			||||||
@ -193,6 +195,10 @@ export type CommandMappings = {
 | 
				
			|||||||
    setZoomFactorAndSave: {
 | 
					    setZoomFactorAndSave: {
 | 
				
			||||||
        zoomFactor: string;
 | 
					        zoomFactor: string;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Geomap
 | 
				
			||||||
 | 
					    deleteFromMap: { noteId: string },
 | 
				
			||||||
 | 
					    openGeoLocation: { noteId: string, event: JQuery.MouseDownEvent }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type EventMappings = {
 | 
					type EventMappings = {
 | 
				
			||||||
@ -227,9 +233,12 @@ type EventMappings = {
 | 
				
			|||||||
    activeContextChanged: {
 | 
					    activeContextChanged: {
 | 
				
			||||||
        noteContext: NoteContext;
 | 
					        noteContext: NoteContext;
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					    beforeNoteSwitch: {
 | 
				
			||||||
 | 
					        noteContext: NoteContext;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
    noteSwitched: {
 | 
					    noteSwitched: {
 | 
				
			||||||
        noteContext: NoteContext;
 | 
					        noteContext: NoteContext;
 | 
				
			||||||
        notePath: string;
 | 
					        notePath: string | null;
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    noteSwitchedAndActivatedEvent: {
 | 
					    noteSwitchedAndActivatedEvent: {
 | 
				
			||||||
        noteContext: NoteContext;
 | 
					        noteContext: NoteContext;
 | 
				
			||||||
@ -248,12 +257,16 @@ type EventMappings = {
 | 
				
			|||||||
        noteId: string;
 | 
					        noteId: string;
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    hoistedNoteChanged: {
 | 
					    hoistedNoteChanged: {
 | 
				
			||||||
        ntxId: string;
 | 
					        noteId: string;
 | 
				
			||||||
 | 
					        ntxId: string | null;
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    contextsReopenedEvent: {
 | 
					    contextsReopenedEvent: {
 | 
				
			||||||
        mainNtxId: string;
 | 
					        mainNtxId: string;
 | 
				
			||||||
        tabPosition: number;
 | 
					        tabPosition: number;
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					    noteDetailRefreshed: {
 | 
				
			||||||
 | 
					        ntxId?: string | null;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
    noteContextReorderEvent: {
 | 
					    noteContextReorderEvent: {
 | 
				
			||||||
        oldMainNtxId: string;
 | 
					        oldMainNtxId: string;
 | 
				
			||||||
        newMainNtxId: string;
 | 
					        newMainNtxId: string;
 | 
				
			||||||
@ -266,7 +279,13 @@ type EventMappings = {
 | 
				
			|||||||
    };
 | 
					    };
 | 
				
			||||||
    exportSvg: {
 | 
					    exportSvg: {
 | 
				
			||||||
        ntxId: string;
 | 
					        ntxId: string;
 | 
				
			||||||
    }
 | 
					    };
 | 
				
			||||||
 | 
					    geoMapCreateChildNote: {
 | 
				
			||||||
 | 
					        ntxId: string | null | undefined; // TODO: deduplicate ntxId
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    tabReorder: {
 | 
				
			||||||
 | 
					        ntxIdsInOrder: string[]
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type EventListener<T extends EventNames> = {
 | 
					export type EventListener<T extends EventNames> = {
 | 
				
			||||||
 | 
				
			|||||||
@ -61,7 +61,7 @@ export class TypedComponent<ChildT extends TypedComponent<ChildT>> {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    triggerEvent(name: string, data = {}): Promise<unknown> | undefined | null {
 | 
					    triggerEvent<T extends EventNames>(name: T, data: EventData<T>): Promise<unknown> | undefined | null {
 | 
				
			||||||
        return this.parent?.triggerEvent(name, data);
 | 
					        return this.parent?.triggerEvent(name, data);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -27,7 +27,8 @@ const NOTE_TYPE_ICONS = {
 | 
				
			|||||||
    launcher: "bx bx-link",
 | 
					    launcher: "bx bx-link",
 | 
				
			||||||
    doc: "bx bxs-file-doc",
 | 
					    doc: "bx bxs-file-doc",
 | 
				
			||||||
    contentWidget: "bx bxs-widget",
 | 
					    contentWidget: "bx bxs-widget",
 | 
				
			||||||
    mindMap: "bx bx-sitemap"
 | 
					    mindMap: "bx bx-sitemap",
 | 
				
			||||||
 | 
					    geoMap: "bx bx-map-alt"
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
@ -35,7 +36,7 @@ const NOTE_TYPE_ICONS = {
 | 
				
			|||||||
 * end user. Those types should be used only for checking against, they are
 | 
					 * end user. Those types should be used only for checking against, they are
 | 
				
			||||||
 * not for direct use.
 | 
					 * 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 {
 | 
					interface NotePathRecord {
 | 
				
			||||||
    isArchived: boolean;
 | 
					    isArchived: boolean;
 | 
				
			||||||
 | 
				
			|||||||
@ -85,6 +85,7 @@ import ScrollPaddingWidget from "../widgets/scroll_padding.js";
 | 
				
			|||||||
import ClassicEditorToolbar from "../widgets/ribbon_widgets/classic_editor_toolbar.js";
 | 
					import ClassicEditorToolbar from "../widgets/ribbon_widgets/classic_editor_toolbar.js";
 | 
				
			||||||
import options from "../services/options.js";
 | 
					import options from "../services/options.js";
 | 
				
			||||||
import utils from "../services/utils.js";
 | 
					import utils from "../services/utils.js";
 | 
				
			||||||
 | 
					import GeoMapButtons from "../widgets/floating_buttons/geo_map_button.js";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default class DesktopLayout {
 | 
					export default class DesktopLayout {
 | 
				
			||||||
    constructor(customWidgets) {
 | 
					    constructor(customWidgets) {
 | 
				
			||||||
@ -200,6 +201,7 @@ export default class DesktopLayout {
 | 
				
			|||||||
                                                                .child(new ShowHighlightsListWidgetButton())
 | 
					                                                                .child(new ShowHighlightsListWidgetButton())
 | 
				
			||||||
                                                                .child(new CodeButtonsWidget())
 | 
					                                                                .child(new CodeButtonsWidget())
 | 
				
			||||||
                                                                .child(new RelationMapButtons())
 | 
					                                                                .child(new RelationMapButtons())
 | 
				
			||||||
 | 
					                                                                .child(new GeoMapButtons())
 | 
				
			||||||
                                                                .child(new CopyImageReferenceButton())
 | 
					                                                                .child(new CopyImageReferenceButton())
 | 
				
			||||||
                                                                .child(new SvgExportButton())
 | 
					                                                                .child(new SvgExportButton())
 | 
				
			||||||
                                                                .child(new BacklinksWidget())
 | 
					                                                                .child(new BacklinksWidget())
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,6 @@
 | 
				
			|||||||
import type { CommandNames } from "../components/app_context.js";
 | 
					import type { CommandNames } from "../components/app_context.js";
 | 
				
			||||||
import keyboardActionService from "../services/keyboard_actions.js";
 | 
					import keyboardActionService from "../services/keyboard_actions.js";
 | 
				
			||||||
 | 
					import note_tooltip from "../services/note_tooltip.js";
 | 
				
			||||||
import utils from "../services/utils.js";
 | 
					import utils from "../services/utils.js";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface ContextMenuOptions<T extends CommandNames> {
 | 
					interface ContextMenuOptions<T extends CommandNames> {
 | 
				
			||||||
@ -31,6 +32,7 @@ export interface MenuCommandItem<T extends CommandNames> {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
export type MenuItem<T extends CommandNames> = MenuCommandItem<T> | MenuSeparatorItem;
 | 
					export type MenuItem<T extends CommandNames> = MenuCommandItem<T> | MenuSeparatorItem;
 | 
				
			||||||
export type MenuHandler<T extends CommandNames> = (item: MenuCommandItem<T>, e: JQuery.MouseDownEvent<HTMLElement, undefined, HTMLElement, HTMLElement>) => void;
 | 
					export type MenuHandler<T extends CommandNames> = (item: MenuCommandItem<T>, e: JQuery.MouseDownEvent<HTMLElement, undefined, HTMLElement, HTMLElement>) => void;
 | 
				
			||||||
 | 
					export type ContextMenuEvent = PointerEvent | MouseEvent | JQuery.ContextMenuEvent;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ContextMenu {
 | 
					class ContextMenu {
 | 
				
			||||||
    private $widget: JQuery<HTMLElement>;
 | 
					    private $widget: JQuery<HTMLElement>;
 | 
				
			||||||
@ -56,6 +58,8 @@ class ContextMenu {
 | 
				
			|||||||
    async show<T extends CommandNames>(options: ContextMenuOptions<T>) {
 | 
					    async show<T extends CommandNames>(options: ContextMenuOptions<T>) {
 | 
				
			||||||
        this.options = options;
 | 
					        this.options = options;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        note_tooltip.dismissAllTooltips();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (this.$widget.hasClass("show")) {
 | 
					        if (this.$widget.hasClass("show")) {
 | 
				
			||||||
            // The menu is already visible. Hide the menu then open it again
 | 
					            // The menu is already visible. Hide the menu then open it again
 | 
				
			||||||
            // at the new location to re-trigger the opening animation.
 | 
					            // at the new location to re-trigger the opening animation.
 | 
				
			||||||
 | 
				
			|||||||
@ -1,18 +1,26 @@
 | 
				
			|||||||
import { t } from "../services/i18n.js";
 | 
					import { t } from "../services/i18n.js";
 | 
				
			||||||
import contextMenu from "./context_menu.js";
 | 
					import contextMenu, { type ContextMenuEvent, type MenuItem } from "./context_menu.js";
 | 
				
			||||||
import appContext from "../components/app_context.js";
 | 
					import appContext, { type CommandNames } from "../components/app_context.js";
 | 
				
			||||||
import type { ViewScope } from "../services/link.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({
 | 
					    contextMenu.show({
 | 
				
			||||||
        x: e.pageX,
 | 
					        x: e.pageX,
 | 
				
			||||||
        y: e.pageY,
 | 
					        y: e.pageY,
 | 
				
			||||||
        items: [
 | 
					        items: getItems(),
 | 
				
			||||||
 | 
					        selectMenuItemHandler: ({ command }) => handleLinkContextMenuItem(command, notePath, viewScope, hoistedNoteId)
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function getItems(): MenuItem<CommandNames>[] {
 | 
				
			||||||
 | 
					    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_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_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" }
 | 
					        { title: t("link_context_menu.open_note_in_new_window"), command: "openNoteInNewWindow", uiIcon: "bx bx-window-open" }
 | 
				
			||||||
        ],
 | 
					    ];
 | 
				
			||||||
        selectMenuItemHandler: ({ command }) => {
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function handleLinkContextMenuItem(command: string | undefined, notePath: string, viewScope = {}, hoistedNoteId: string | null = null) {
 | 
				
			||||||
    if (!hoistedNoteId) {
 | 
					    if (!hoistedNoteId) {
 | 
				
			||||||
        hoistedNoteId = appContext.tabManager.getActiveContext().hoistedNoteId;
 | 
					        hoistedNoteId = appContext.tabManager.getActiveContext().hoistedNoteId;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -28,9 +36,9 @@ function openContextMenu(notePath: string, e: PointerEvent | MouseEvent | JQuery
 | 
				
			|||||||
        appContext.triggerCommand("openInWindow", { notePath, hoistedNoteId, viewScope });
 | 
					        appContext.triggerCommand("openInWindow", { notePath, hoistedNoteId, viewScope });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default {
 | 
					export default {
 | 
				
			||||||
 | 
					    getItems,
 | 
				
			||||||
 | 
					    handleLinkContextMenuItem,
 | 
				
			||||||
    openContextMenu
 | 
					    openContextMenu
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -106,6 +106,10 @@ const HIGHLIGHT_JS: Library = {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const LEAFLET: Library = {
 | 
				
			||||||
 | 
					    css: [ "node_modules/leaflet/dist/leaflet.css" ],
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function requireLibrary(library: Library) {
 | 
					async function requireLibrary(library: Library) {
 | 
				
			||||||
    if (library.css) {
 | 
					    if (library.css) {
 | 
				
			||||||
        library.css.map((cssUrl) => requireCss(cssUrl));
 | 
					        library.css.map((cssUrl) => requireCss(cssUrl));
 | 
				
			||||||
@ -196,5 +200,6 @@ export default {
 | 
				
			|||||||
    MERMAID,
 | 
					    MERMAID,
 | 
				
			||||||
    MARKJS,
 | 
					    MARKJS,
 | 
				
			||||||
    I18NEXT,
 | 
					    I18NEXT,
 | 
				
			||||||
    HIGHLIGHT_JS
 | 
					    HIGHLIGHT_JS,
 | 
				
			||||||
 | 
					    LEAFLET
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -234,7 +234,7 @@ function goToLink(evt: MouseEvent | JQuery.ClickEvent) {
 | 
				
			|||||||
    return goToLinkExt(evt, hrefLink, $link);
 | 
					    return goToLinkExt(evt, hrefLink, $link);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function goToLinkExt(evt: MouseEvent | JQuery.ClickEvent | React.PointerEvent<HTMLCanvasElement>, hrefLink: string | undefined, $link: JQuery<HTMLElement> | null) {
 | 
					function goToLinkExt(evt: MouseEvent | JQuery.ClickEvent | JQuery.MouseDownEvent | React.PointerEvent<HTMLCanvasElement>, hrefLink: string | undefined, $link?: JQuery<HTMLElement> | null) {
 | 
				
			||||||
    if (hrefLink?.startsWith("data:")) {
 | 
					    if (hrefLink?.startsWith("data:")) {
 | 
				
			||||||
        return true;
 | 
					        return true;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -18,11 +18,11 @@ function setupGlobalTooltip() {
 | 
				
			|||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        cleanUpTooltips();
 | 
					        dismissAllTooltips();
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function cleanUpTooltips() {
 | 
					function dismissAllTooltips() {
 | 
				
			||||||
    $(".note-tooltip").remove();
 | 
					    $(".note-tooltip").remove();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -102,12 +102,12 @@ async function mouseEnterHandler(this: HTMLElement) {
 | 
				
			|||||||
            customClass: linkId
 | 
					            customClass: linkId
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        cleanUpTooltips();
 | 
					        dismissAllTooltips();
 | 
				
			||||||
        $(this).tooltip("show");
 | 
					        $(this).tooltip("show");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Dismiss the tooltip immediately if a link was clicked inside the tooltip.
 | 
					        // Dismiss the tooltip immediately if a link was clicked inside the tooltip.
 | 
				
			||||||
        $(`.${tooltipClass} a`).on("click", (e) => {
 | 
					        $(`.${tooltipClass} a`).on("click", (e) => {
 | 
				
			||||||
            cleanUpTooltips();
 | 
					            dismissAllTooltips();
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // the purpose of the code below is to:
 | 
					        // the purpose of the code below is to:
 | 
				
			||||||
@ -117,7 +117,7 @@ async function mouseEnterHandler(this: HTMLElement) {
 | 
				
			|||||||
        const checkTooltip = () => {
 | 
					        const checkTooltip = () => {
 | 
				
			||||||
            if (!$(this).filter(":hover").length && !$(`.${linkId}:hover`).length) {
 | 
					            if (!$(this).filter(":hover").length && !$(`.${linkId}:hover`).length) {
 | 
				
			||||||
                // cursor is neither over the link nor over the tooltip, user likely is not interested
 | 
					                // cursor is neither over the link nor over the tooltip, user likely is not interested
 | 
				
			||||||
                cleanUpTooltips();
 | 
					                dismissAllTooltips();
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                setTimeout(checkTooltip, 1000);
 | 
					                setTimeout(checkTooltip, 1000);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@ -172,5 +172,6 @@ function renderFootnote($link: JQuery<HTMLElement>, url: string) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
export default {
 | 
					export default {
 | 
				
			||||||
    setupGlobalTooltip,
 | 
					    setupGlobalTooltip,
 | 
				
			||||||
    setupElementTooltip
 | 
					    setupElementTooltip,
 | 
				
			||||||
 | 
					    dismissAllTooltips
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -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.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.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.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<string[]>("search-templates");
 | 
					    const templateNoteIds = await server.get<string[]>("search-templates");
 | 
				
			||||||
 | 
				
			|||||||
@ -154,7 +154,7 @@ export default class NoteActionsWidget extends NoteContextAwareWidget {
 | 
				
			|||||||
        this.toggleDisabled(this.$findInTextButton, ["text", "code", "book"].includes(note.type));
 | 
					        this.toggleDisabled(this.$findInTextButton, ["text", "code", "book"].includes(note.type));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.toggleDisabled(this.$showAttachmentsButton, !isInOptions);
 | 
					        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));
 | 
					        this.toggleDisabled(this.$printActiveNoteButton, ["text", "code"].includes(note.type));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -22,7 +22,7 @@ export default class LeftPaneContainer extends FlexContainer<Component> {
 | 
				
			|||||||
            this.toggleInt(visible);
 | 
					            this.toggleInt(visible);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (visible) {
 | 
					            if (visible) {
 | 
				
			||||||
                this.triggerEvent("focusTree");
 | 
					                this.triggerEvent("focusTree", {});
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                const activeNoteContext = appContext.tabManager.getActiveContext();
 | 
					                const activeNoteContext = appContext.tabManager.getActiveContext();
 | 
				
			||||||
                this.triggerEvent("focusOnDetail", { ntxId: activeNoteContext.ntxId });
 | 
					                this.triggerEvent("focusOnDetail", { ntxId: activeNoteContext.ntxId });
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										42
									
								
								src/public/app/widgets/floating_buttons/geo_map_button.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								src/public/app/widgets/floating_buttons/geo_map_button.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,42 @@
 | 
				
			|||||||
 | 
					import { t } from "../../services/i18n.js";
 | 
				
			||||||
 | 
					import NoteContextAwareWidget from "../note_context_aware_widget.js"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const TPL = `\
 | 
				
			||||||
 | 
					<div class="geo-map-buttons">
 | 
				
			||||||
 | 
					    <style>
 | 
				
			||||||
 | 
					        .geo-map-buttons {
 | 
				
			||||||
 | 
					            display: flex;
 | 
				
			||||||
 | 
					            gap: 10px;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .leaflet-pane {
 | 
				
			||||||
 | 
					            z-index: 50;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .geo-map-buttons {
 | 
				
			||||||
 | 
					            contain: none;
 | 
				
			||||||
 | 
					            background: var(--main-background-color);
 | 
				
			||||||
 | 
					            box-shadow: 0px 10px 20px rgba(0, 0, 0, var(--dropdown-shadow-opacity));
 | 
				
			||||||
 | 
					            border-radius: 4px;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    </style>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <button type="button"
 | 
				
			||||||
 | 
					        class="geo-map-create-child-note floating-button btn bx bx-folder-plus"
 | 
				
			||||||
 | 
					        title="${t("geo-map.create-child-note-title")}" />
 | 
				
			||||||
 | 
					</div>`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default class GeoMapButtons extends NoteContextAwareWidget {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    isEnabled() {
 | 
				
			||||||
 | 
					        return super.isEnabled() && this.note?.type === "geoMap";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    doRender() {
 | 
				
			||||||
 | 
					        super.doRender();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.$widget = $(TPL);
 | 
				
			||||||
 | 
					        this.$widget.find(".geo-map-create-child-note").on("click", () => this.triggerEvent("geoMapCreateChildNote", { ntxId: this.ntxId }));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -43,6 +43,7 @@ export default class RelationMapButtons extends NoteContextAwareWidget {
 | 
				
			|||||||
        this.$zoomOutButton = this.$widget.find(".relation-map-zoom-out");
 | 
					        this.$zoomOutButton = this.$widget.find(".relation-map-zoom-out");
 | 
				
			||||||
        this.$resetPanZoomButton = this.$widget.find(".relation-map-reset-pan-zoom");
 | 
					        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.$createChildNote.on("click", () => this.triggerEvent("relationMapCreateChildNote", { ntxId: this.ntxId }));
 | 
				
			||||||
        this.$resetPanZoomButton.on("click", () => this.triggerEvent("relationMapResetPanZoom", { ntxId: this.ntxId }));
 | 
					        this.$resetPanZoomButton.on("click", () => this.triggerEvent("relationMapResetPanZoom", { ntxId: this.ntxId }));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										57
									
								
								src/public/app/widgets/geo_map.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								src/public/app/widgets/geo_map.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,57 @@
 | 
				
			|||||||
 | 
					import type { Map } from "leaflet";
 | 
				
			||||||
 | 
					import library_loader from "../services/library_loader.js";
 | 
				
			||||||
 | 
					import NoteContextAwareWidget from "./note_context_aware_widget.js";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const TPL = `\
 | 
				
			||||||
 | 
					<div class="geo-map-widget">
 | 
				
			||||||
 | 
					    <style>
 | 
				
			||||||
 | 
					        .note-detail-geo-map,
 | 
				
			||||||
 | 
					        .geo-map-widget,
 | 
				
			||||||
 | 
					        .geo-map-container {
 | 
				
			||||||
 | 
					            height: 100%;
 | 
				
			||||||
 | 
					            overflow: hidden;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    </style>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <div class="geo-map-container"></div>
 | 
				
			||||||
 | 
					</div>`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type Leaflet = typeof import("leaflet");
 | 
				
			||||||
 | 
					export type InitCallback = ((L: Leaflet) => void);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default class GeoMapWidget extends NoteContextAwareWidget {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    map?: Map;
 | 
				
			||||||
 | 
					    $container!: JQuery<HTMLElement>;
 | 
				
			||||||
 | 
					    private initCallback?: InitCallback;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    constructor(widgetMode: "type", initCallback?: InitCallback) {
 | 
				
			||||||
 | 
					        super();
 | 
				
			||||||
 | 
					        this.initCallback = initCallback;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    doRender() {
 | 
				
			||||||
 | 
					        this.$widget = $(TPL);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        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(this.$container[0], {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                this.map = map;
 | 
				
			||||||
 | 
					                if (this.initCallback) {
 | 
				
			||||||
 | 
					                    this.initCallback(L);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
 | 
				
			||||||
 | 
					                    attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
 | 
				
			||||||
 | 
					                }).addTo(map);
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -31,6 +31,7 @@ import AttachmentListTypeWidget from "./type_widgets/attachment_list.js";
 | 
				
			|||||||
import AttachmentDetailTypeWidget from "./type_widgets/attachment_detail.js";
 | 
					import AttachmentDetailTypeWidget from "./type_widgets/attachment_detail.js";
 | 
				
			||||||
import MindMapWidget from "./type_widgets/mind_map.js";
 | 
					import MindMapWidget from "./type_widgets/mind_map.js";
 | 
				
			||||||
import { getStylesheetUrl, isSyntaxHighlightEnabled } from "../services/syntax_highlight.js";
 | 
					import { getStylesheetUrl, isSyntaxHighlightEnabled } from "../services/syntax_highlight.js";
 | 
				
			||||||
 | 
					import GeoMapTypeWidget from "./type_widgets/geo_map.js";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const TPL = `
 | 
					const TPL = `
 | 
				
			||||||
<div class="note-detail">
 | 
					<div class="note-detail">
 | 
				
			||||||
@ -67,7 +68,8 @@ const typeWidgetClasses = {
 | 
				
			|||||||
    contentWidget: ContentWidgetTypeWidget,
 | 
					    contentWidget: ContentWidgetTypeWidget,
 | 
				
			||||||
    attachmentDetail: AttachmentDetailTypeWidget,
 | 
					    attachmentDetail: AttachmentDetailTypeWidget,
 | 
				
			||||||
    attachmentList: AttachmentListTypeWidget,
 | 
					    attachmentList: AttachmentListTypeWidget,
 | 
				
			||||||
    mindMap: MindMapWidget
 | 
					    mindMap: MindMapWidget,
 | 
				
			||||||
 | 
					    geoMap: GeoMapTypeWidget
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default class NoteDetailWidget extends NoteContextAwareWidget {
 | 
					export default class NoteDetailWidget extends NoteContextAwareWidget {
 | 
				
			||||||
@ -147,7 +149,7 @@ export default class NoteDetailWidget extends NoteContextAwareWidget {
 | 
				
			|||||||
        // https://github.com/zadam/trilium/issues/2522
 | 
					        // https://github.com/zadam/trilium/issues/2522
 | 
				
			||||||
        this.$widget.toggleClass(
 | 
					        this.$widget.toggleClass(
 | 
				
			||||||
            "full-height",
 | 
					            "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"
 | 
					                this.noteContext.viewScope.viewMode === "attachments"
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -41,7 +41,7 @@ export default class NoteWrapperWidget extends FlexContainer {
 | 
				
			|||||||
            return;
 | 
					            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());
 | 
					        this.$widget.addClass(note.getCssClass());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										323
									
								
								src/public/app/widgets/type_widgets/geo_map.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										323
									
								
								src/public/app/widgets/type_widgets/geo_map.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,323 @@
 | 
				
			|||||||
 | 
					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"
 | 
				
			||||||
 | 
					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";
 | 
				
			||||||
 | 
					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 = `\
 | 
				
			||||||
 | 
					<div class="note-detail-geo-map note-detail-printable">
 | 
				
			||||||
 | 
					    <style>
 | 
				
			||||||
 | 
					        .leaflet-pane {
 | 
				
			||||||
 | 
					            z-index: 1;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .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;
 | 
				
			||||||
 | 
					            overflow: visible;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .geo-map-container .leaflet-div-icon .icon-shadow {
 | 
				
			||||||
 | 
					            position: absolute;
 | 
				
			||||||
 | 
					            top: 0;
 | 
				
			||||||
 | 
					            left: 0;
 | 
				
			||||||
 | 
					            z-index: -1;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .geo-map-container .leaflet-div-icon .bx {
 | 
				
			||||||
 | 
					            position: absolute;
 | 
				
			||||||
 | 
					            top: 3px;
 | 
				
			||||||
 | 
					            left: 2px;
 | 
				
			||||||
 | 
					            background-color: white;
 | 
				
			||||||
 | 
					            color: black;
 | 
				
			||||||
 | 
					            padding: 2px;
 | 
				
			||||||
 | 
					            border-radius: 50%;
 | 
				
			||||||
 | 
					            font-size: 17px;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .geo-map-container .leaflet-div-icon .title-label {
 | 
				
			||||||
 | 
					            display: block;
 | 
				
			||||||
 | 
					            position: absolute;
 | 
				
			||||||
 | 
					            top: 100%;
 | 
				
			||||||
 | 
					            left: 50%;
 | 
				
			||||||
 | 
					            transform: translateX(-50%);
 | 
				
			||||||
 | 
					            font-size: 0.75rem;
 | 
				
			||||||
 | 
					            height: 1rem;
 | 
				
			||||||
 | 
					            color: black;
 | 
				
			||||||
 | 
					            width: 100px;
 | 
				
			||||||
 | 
					            text-align: center;
 | 
				
			||||||
 | 
					            text-overflow: ellipsis;
 | 
				
			||||||
 | 
					            text-shadow: -1px -1px 0 white, 1px -1px 0 white, -1px 1px 0 white, 1px 1px 0 white;
 | 
				
			||||||
 | 
					            white-space: no-wrap;
 | 
				
			||||||
 | 
					            overflow: hidden;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    </style>
 | 
				
			||||||
 | 
					</div>`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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?: {
 | 
				
			||||||
 | 
					        center?: LatLng | [ number, number ];
 | 
				
			||||||
 | 
					        zoom?: number;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TODO: Deduplicate
 | 
				
			||||||
 | 
					interface CreateChildResponse {
 | 
				
			||||||
 | 
					    note: {
 | 
				
			||||||
 | 
					        noteId: string;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type MarkerData = Record<string, Marker>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enum State {
 | 
				
			||||||
 | 
					    Normal,
 | 
				
			||||||
 | 
					    NewNote
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default class GeoMapTypeWidget extends TypeWidget {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private geoMapWidget: GeoMapWidget;
 | 
				
			||||||
 | 
					    private _state: State;
 | 
				
			||||||
 | 
					    private L!: Leaflet;
 | 
				
			||||||
 | 
					    private currentMarkerData: MarkerData;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    static getType() {
 | 
				
			||||||
 | 
					        return "geoMap";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    constructor() {
 | 
				
			||||||
 | 
					        super();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.geoMapWidget = new GeoMapWidget("type", (L: Leaflet) => this.#onMapInitialized(L));
 | 
				
			||||||
 | 
					        this.currentMarkerData = {};
 | 
				
			||||||
 | 
					        this._state = State.Normal;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.child(this.geoMapWidget);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    doRender() {
 | 
				
			||||||
 | 
					        this.$widget = $(TPL);
 | 
				
			||||||
 | 
					        this.$widget.append(this.geoMapWidget.render());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        super.doRender();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async #onMapInitialized(L: Leaflet) {
 | 
				
			||||||
 | 
					        this.L = L;
 | 
				
			||||||
 | 
					        const map = this.geoMapWidget.map;
 | 
				
			||||||
 | 
					        if (!map) {
 | 
				
			||||||
 | 
					            throw new Error(t("geo-map.unable-to-load-map"));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!this.note) {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const blob = await this.note.getBlob();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let parsedContent: MapData = {};
 | 
				
			||||||
 | 
					        if (blob && blob.content) {
 | 
				
			||||||
 | 
					            parsedContent = JSON.parse(blob.content);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Restore viewport position & zoom
 | 
				
			||||||
 | 
					        const center = parsedContent.view?.center ?? DEFAULT_COORDINATES;
 | 
				
			||||||
 | 
					        const zoom = parsedContent.view?.zoom ?? DEFAULT_ZOOM;
 | 
				
			||||||
 | 
					        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;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 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) {
 | 
				
			||||||
 | 
					            const latLng = childNote.getAttributeValue("label", LOCATION_ATTRIBUTE);
 | 
				
			||||||
 | 
					            if (!latLng) {
 | 
				
			||||||
 | 
					                continue;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const [ lat, lng ] = latLng.split(",", 2).map((el) => parseFloat(el));
 | 
				
			||||||
 | 
					            const icon = L.divIcon({
 | 
				
			||||||
 | 
					                html: `\
 | 
				
			||||||
 | 
					                    <img class="icon" src="${asset_path}/node_modules/leaflet/dist/images/marker-icon.png" />
 | 
				
			||||||
 | 
					                    <img class="icon-shadow" src="${asset_path}/node_modules/leaflet/dist/images/marker-shadow.png" />
 | 
				
			||||||
 | 
					                    <span class="bx ${childNote.getIcon()}"></span>
 | 
				
			||||||
 | 
					                    <span class="title-label">${childNote.title}</span>`,
 | 
				
			||||||
 | 
					                iconSize: [ 25, 41 ],
 | 
				
			||||||
 | 
					                iconAnchor: [ 12, 41 ]
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const marker = L.marker(L.latLng(lat, lng), {
 | 
				
			||||||
 | 
					                icon,
 | 
				
			||||||
 | 
					                draggable: true,
 | 
				
			||||||
 | 
					                autoPan: true,
 | 
				
			||||||
 | 
					                autoPanSpeed: 5,
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					                .addTo(map)
 | 
				
			||||||
 | 
					                .on("moveend", e => {
 | 
				
			||||||
 | 
					                    this.moveMarker(childNote.noteId, (e.target as Marker).getLatLng());
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            marker.on("contextmenu", (e) => {
 | 
				
			||||||
 | 
					                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;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #changeState(newState: State) {
 | 
				
			||||||
 | 
					        this._state = newState;
 | 
				
			||||||
 | 
					        this.geoMapWidget.$container.toggleClass("placing-note", newState === State.NewNote);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async #onMapClicked(e: LeafletMouseEvent) {
 | 
				
			||||||
 | 
					        if (this._state !== State.NewNote) {
 | 
				
			||||||
 | 
					            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()) {
 | 
				
			||||||
 | 
					            const { note } = await server.post<CreateChildResponse>(`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.#changeState(State.Normal);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async moveMarker(noteId: string, latLng: LatLng | null) {
 | 
				
			||||||
 | 
					        const value = (latLng ? [latLng.lat, latLng.lng].join(",") : "");
 | 
				
			||||||
 | 
					        await attributes.setLabel(noteId, LOCATION_ATTRIBUTE, value);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    getData(): any {
 | 
				
			||||||
 | 
					        const map = this.geoMapWidget.map;
 | 
				
			||||||
 | 
					        if (!map) {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const data: MapData = {
 | 
				
			||||||
 | 
					            view: {
 | 
				
			||||||
 | 
					                center: map.getBounds().getCenter(),
 | 
				
			||||||
 | 
					                zoom: map.getZoom()
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					            content: JSON.stringify(data)
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async geoMapCreateChildNoteEvent({ ntxId }: EventData<"geoMapCreateChildNote">) {
 | 
				
			||||||
 | 
					        if (!this.isNoteContext(ntxId)) {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        toastService.showPersistent({
 | 
				
			||||||
 | 
					            icon: "plus",
 | 
				
			||||||
 | 
					            id: "geo-new-note",
 | 
				
			||||||
 | 
					            title: "New note",
 | 
				
			||||||
 | 
					            message: t("geo-map.create-child-note-instruction")
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        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);
 | 
				
			||||||
 | 
					            toastService.closePersistent("geo-new-note");
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        window.addEventListener("keydown", globalKeyListener);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async doRefresh(note: FNote) {
 | 
				
			||||||
 | 
					        await this.geoMapWidget.refresh();
 | 
				
			||||||
 | 
					        await this.#reloadMarkers();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) {
 | 
				
			||||||
 | 
					        const attributeRows = loadResults.getAttributeRows();
 | 
				
			||||||
 | 
					        if (attributeRows.find((at) => at.name === LOCATION_ATTRIBUTE)) {
 | 
				
			||||||
 | 
					            this.#reloadMarkers();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										32
									
								
								src/public/app/widgets/type_widgets/geo_map_context_menu.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								src/public/app/widgets/type_widgets/geo_map_context_menu.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,32 @@
 | 
				
			|||||||
 | 
					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({
 | 
				
			||||||
 | 
					        x: e.pageX,
 | 
				
			||||||
 | 
					        y: e.pageY,
 | 
				
			||||||
 | 
					        items: [
 | 
				
			||||||
 | 
					            ...linkContextMenu.getItems(),
 | 
				
			||||||
 | 
					            { title: t("geo-map-context.open-location"), command: "openGeoLocation", uiIcon: "bx bx-map-alt" },
 | 
				
			||||||
 | 
					            { title: "----" },
 | 
				
			||||||
 | 
					            { title: t("geo-map-context.remove-from-map"), command: "deleteFromMap", uiIcon: "bx bx-trash" }
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        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);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1409,7 +1409,8 @@
 | 
				
			|||||||
    "launcher": "Launcher",
 | 
					    "launcher": "Launcher",
 | 
				
			||||||
    "doc": "Doc",
 | 
					    "doc": "Doc",
 | 
				
			||||||
    "widget": "Widget",
 | 
					    "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": {
 | 
					  "protect_note": {
 | 
				
			||||||
    "toggle-on": "Protect the note",
 | 
					    "toggle-on": "Protect the note",
 | 
				
			||||||
@ -1629,5 +1630,14 @@
 | 
				
			|||||||
  },
 | 
					  },
 | 
				
			||||||
  "note_tooltip": {
 | 
					  "note_tooltip": {
 | 
				
			||||||
    "note-has-been-deleted": "Note has been deleted."
 | 
					    "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"
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1379,7 +1379,8 @@
 | 
				
			|||||||
    "image": "Imagine",
 | 
					    "image": "Imagine",
 | 
				
			||||||
    "launcher": "Scurtătură",
 | 
					    "launcher": "Scurtătură",
 | 
				
			||||||
    "widget": "Widget",
 | 
					    "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": {
 | 
					  "protect_note": {
 | 
				
			||||||
    "toggle-off": "Deprotejează notița",
 | 
					    "toggle-off": "Deprotejează notița",
 | 
				
			||||||
@ -1633,5 +1634,13 @@
 | 
				
			|||||||
  "notes": {
 | 
					  "notes": {
 | 
				
			||||||
    "duplicate-note-suffix": "(dupl.)",
 | 
					    "duplicate-note-suffix": "(dupl.)",
 | 
				
			||||||
    "duplicate-note-title": "{{ noteTitle }} {{ duplicateNoteSuffix }}"
 | 
					    "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."
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -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/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/@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/@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 {
 | 
					export default {
 | 
				
			||||||
 | 
				
			|||||||
@ -14,7 +14,8 @@ const noteTypes = [
 | 
				
			|||||||
    { type: "launcher", defaultMime: "" },
 | 
					    { type: "launcher", defaultMime: "" },
 | 
				
			||||||
    { type: "doc", defaultMime: "" },
 | 
					    { type: "doc", defaultMime: "" },
 | 
				
			||||||
    { type: "contentWidget", defaultMime: "" },
 | 
					    { type: "contentWidget", defaultMime: "" },
 | 
				
			||||||
    { type: "mindMap", defaultMime: "application/json" }
 | 
					    { type: "mindMap", defaultMime: "application/json" },
 | 
				
			||||||
 | 
					    { type: "geoMap", defaultMime: "application/json" }
 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function getDefaultMimeForNoteType(typeName: string) {
 | 
					function getDefaultMimeForNoteType(typeName: string) {
 | 
				
			||||||
 | 
				
			|||||||
@ -248,5 +248,8 @@
 | 
				
			|||||||
  "backend_log": {
 | 
					  "backend_log": {
 | 
				
			||||||
    "log-does-not-exist": "Fișierul de loguri de backend „{{ fileName }}” nu există (încă).",
 | 
					    "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}}”."
 | 
					    "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."
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user