obsidian#Menu TypeScript Examples

The following examples show how to use obsidian#Menu. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: utils.ts    From obsidian-map-view with GNU General Public License v3.0 7 votes vote down vote up
/**
 * Populate a context menu from the user configurable URLs
 * @param menu The menu to attach
 * @param location The geolocation to use in the menu item
 * @param settings Plugin settings
 */
export function populateOpenInItems(
    menu: Menu,
    location: leaflet.LatLng,
    settings: settings.PluginSettings
) {
    for (let setting of settings.openIn) {
        if (!setting.name || !setting.urlPattern) continue;
        const fullUrl = setting.urlPattern
            .replace('{x}', location.lat.toString())
            .replace('{y}', location.lng.toString());
        menu.addItem((item: MenuItem) => {
            item.setTitle(`Open in ${setting.name}`);
            item.onClick((_ev) => {
                open(fullUrl);
            });
        });
    }
}
Example #2
Source File: annotatorView.tsx    From obsidian-annotator with GNU Affero General Public License v3.0 6 votes vote down vote up
onMoreOptionsMenu(menu: Menu) {
        menu.addItem(item => {
            item.setTitle('Open as MD')
                .setIcon('document')
                .onClick(async () => {
                    this.plugin.pdfAnnotatorFileModes[(this.leaf as any).id || this.file.path] = 'markdown'; // eslint-disable-line
                    this.plugin.setMarkdownView(this.leaf);
                });
        });
        menu.addItem(item => {
            item.setTitle('Toggle Dark Mode')
                .setIcon('switch')
                .onClick(async () => {
                    this.useDarkMode = !this.useDarkMode;
                    await this.onDarkReadersUpdated();
                });
        });
        super.onMoreOptionsMenu(menu);
    }
Example #3
Source File: sidebar.ts    From obsidian-spaced-repetition with MIT License 6 votes vote down vote up
public onHeaderMenu(menu: Menu): void {
        menu.addItem((item) => {
            item.setTitle(t("CLOSE"))
                .setIcon("cross")
                .onClick(() => {
                    this.app.workspace.detachLeavesOfType(REVIEW_QUEUE_VIEW_TYPE);
                });
        });
    }
Example #4
Source File: main.ts    From obsidian-linter with MIT License 6 votes vote down vote up
onMenuOpenCallback(menu: Menu, file: TAbstractFile, source: string) {
      if (file instanceof TFile && file.extension === 'md') {
        menu.addItem((item) => {
          item.setIcon('wrench-screwdriver-glyph');
          item.setTitle('Lint file');
          item.onClick(async (evt) => {
            this.runLinterFile(file);
          });
        });
      }
    }
Example #5
Source File: customContextMenu.ts    From obsidian-dictionary with GNU Affero General Public License v3.0 6 votes vote down vote up
export default function handleContextMenu(menu: Menu, instance: Editor, plugin: DictionaryPlugin): void {
    if (!plugin.settings.shouldShowCustomContextMenu) {
        return;
    }
    const selection = instance.getSelection();

    if (selection && selection.trim().split(" ").length === 1) {
        if (!plugin.settings.shouldShowSynonymPopover) {
            menu.addItem((item) => {
                item.setTitle(t('Show Synonyms'))
                    .setIcon('synonyms')
                    .onClick(async (_) => {
                        plugin.handlePointerUp();
                    });
            });
        }
        menu.addItem((item) => {
            item.setTitle(t('Look up'))
                .setIcon('quote-glyph')
                .onClick(async (_) => {
                    let leaf: WorkspaceLeaf = plugin.app.workspace.getLeavesOfType(VIEW_TYPE).first();
                    if(!leaf){
                        leaf = plugin.app.workspace.getRightLeaf(false);
                        await leaf.setViewState({
                            type: VIEW_TYPE,
                        });
                    }
                    // eslint-disable-next-line @typescript-eslint/ban-ts-comment
                    //@ts-ignore
                    leaf.view.query(selection.trim());
                    plugin.app.workspace.revealLeaf(leaf);
                });
        });
    }
}
Example #6
Source File: fileMenu.ts    From obsidian-calendar-plugin with MIT License 6 votes vote down vote up
export function showFileMenu(app: App, file: TFile, position: Point): void {
  const fileMenu = new Menu(app);
  fileMenu.addItem((item) =>
    item
      .setTitle("Delete")
      .setIcon("trash")
      .onClick(() => {
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        (<any>app).fileManager.promptForFileDeletion(file);
      })
  );

  app.workspace.trigger(
    "file-menu",
    fileMenu,
    file,
    "calendar-context-menu",
    null
  );
  fileMenu.showAtPosition(position);
}
Example #7
Source File: contextMenu.ts    From obsidian-todoist-plugin with MIT License 6 votes vote down vote up
export function showTaskContext(
  app: App,
  taskCtx: TaskContext,
  position: Point
) {
  new Menu(app)
    .addItem((menuItem) =>
      menuItem
        .setTitle("Complete task")
        .setIcon("check-small")
        .onClick(async () => taskCtx.onClickTask(taskCtx.task))
    )
    .addItem((menuItem) =>
      menuItem
        .setTitle("Open task in Todoist (app)")
        .setIcon("popup-open")
        .onClick(() => {
          openExternal(`todoist://task?id=${taskCtx.task.id}`);
        })
    )
    .addItem((menuItem) =>
      menuItem
        .setTitle("Open task in Todoist (web)")
        .setIcon("popup-open")
        .onClick(() =>
          openExternal(
            `https://todoist.com/app/project/${taskCtx.task.projectID}/task/${taskCtx.task.id}`
          )
        )
    )
    .showAtPosition(position);
}
Example #8
Source File: EventHandler.ts    From Templater with GNU Affero General Public License v3.0 6 votes vote down vote up
update_file_menu(): void {
        this.plugin.registerEvent(
            this.app.workspace.on("file-menu", (menu: Menu, file: TFile) => {
                if (file instanceof TFolder) {
                    menu.addItem((item: MenuItem) => {
                        item.setTitle("Create new note from template")
                            .setIcon("templater-icon")
                            .onClick(() => {
                                this.plugin.fuzzy_suggester.create_new_note_from_template(
                                    file
                                );
                            });
                    });
                }
            })
        );
    }
Example #9
Source File: mapView.ts    From obsidian-map-view with GNU General Public License v3.0 5 votes vote down vote up
onMoreOptionsMenu(menu: Menu) {
        menu.addItem((item: MenuItem) => {
            item.setTitle('Copy Map View URL').onClick(() => {
                this.copyStateUrl();
            });
        });
        super.onMoreOptionsMenu(menu);
    }
Example #10
Source File: OptionsList.ts    From obsidian_supercharged_links with MIT License 5 votes vote down vote up
constructor(plugin: SuperchargedLinks, file: TFile, category: Menu | SelectModal){
        this.file = file
        this.plugin = plugin
        this.category = category
    }
Example #11
Source File: OptionsList.ts    From obsidian_supercharged_links with MIT License 5 votes vote down vote up
category: Menu | SelectModal
Example #12
Source File: OptionsList.ts    From obsidian_supercharged_links with MIT License 5 votes vote down vote up
function isMenu(category: Menu | SelectModal): category is Menu {
    return (category as Menu).addItem !== undefined
}
Example #13
Source File: OptionsList.ts    From obsidian_supercharged_links with MIT License 5 votes vote down vote up
function isSelect(category: Menu | SelectModal): category is SelectModal {
    return (category as SelectModal).modals !== undefined
}
Example #14
Source File: main.ts    From obsidian-dictionary with GNU Affero General Public License v3.0 5 votes vote down vote up
handleContextMenuHelper = (menu: Menu, editor: Editor, _: MarkdownView): void => {
        handleContextMenu(menu, editor, this);
    };
Example #15
Source File: main.tsx    From obsidian-annotator with GNU Affero General Public License v3.0 5 votes vote down vote up
private registerMonkeyPatches() {
        const self = this;

        // Monkey patch WorkspaceLeaf to open Annotations in the Annotation view by default
        this.register(
            around(WorkspaceLeaf.prototype, {
                detach(next) {
                    return function () {
                        const state = this.view?.getState();

                        if (state?.file && self.pdfAnnotatorFileModes[this.id || state.file]) {
                            delete self.pdfAnnotatorFileModes[this.id || state.file];
                        }

                        return next.apply(this);
                    };
                },

                setViewState(next) {
                    return function (state: ViewState, ...rest: unknown[]) {
                        if (
                            self._loaded &&
                            state.type === 'markdown' &&
                            state.state?.file &&
                            self.pdfAnnotatorFileModes[this.id || state.state.file] !== 'markdown' &&
                            self.settings.annotationMarkdownSettings.annotationModeByDefault === true
                        ) {
                            const file = self.app.vault.getAbstractFileByPath(state.state.file);

                            if (file instanceof TFile && self.getPropertyValue(ANNOTATION_TARGET_PROPERTY, file)) {
                                const newState = {
                                    ...state,
                                    type: VIEW_TYPE_PDF_ANNOTATOR
                                };

                                self.pdfAnnotatorFileModes[state.state.file] = VIEW_TYPE_PDF_ANNOTATOR;

                                return next.apply(this, [newState, ...rest]);
                            }
                        }

                        return next.apply(this, [state, ...rest]);
                    };
                }
            })
        );

        this.register(
            around(MarkdownView.prototype, {
                onMoreOptionsMenu(next) {
                    return function (menu: Menu) {
                        const file = this.file;
                        if (!file || !self.getPropertyValue(ANNOTATION_TARGET_PROPERTY, file)) {
                            return next.call(this, menu);
                        }

                        menu.addItem(item => {
                            item.setTitle('Annotate')
                                .setIcon(ICON_NAME)
                                .onClick(() => {
                                    self.pdfAnnotatorFileModes[this.leaf.id || file.path] = VIEW_TYPE_PDF_ANNOTATOR;
                                    self.setAnnotatorView(this.leaf);
                                });
                        }).addSeparator();

                        return next.call(this, menu);
                    };
                }
            })
        );
    }
Example #16
Source File: sidebar.ts    From obsidian-spaced-repetition with MIT License 5 votes vote down vote up
private createRightPaneFile(
        folderEl: HTMLElement,
        file: TFile,
        fileElActive: boolean,
        hidden: boolean,
        deck: ReviewDeck,
        plugin: SRPlugin
    ): void {
        const navFileEl: HTMLElement = folderEl
            .getElementsByClassName("nav-folder-children")[0]
            .createDiv("nav-file");
        if (hidden) {
            navFileEl.style.display = "none";
        }

        const navFileTitle: HTMLElement = navFileEl.createDiv("nav-file-title");
        if (fileElActive) {
            navFileTitle.addClass("is-active");
        }

        navFileTitle.createDiv("nav-file-title-content").setText(file.basename);
        navFileTitle.addEventListener(
            "click",
            (event: MouseEvent) => {
                event.preventDefault();
                plugin.lastSelectedReviewDeck = deck.deckName;
                this.app.workspace.activeLeaf.openFile(file);
                return false;
            },
            false
        );

        navFileTitle.addEventListener(
            "contextmenu",
            (event: MouseEvent) => {
                event.preventDefault();
                const fileMenu: Menu = new Menu(this.app);
                this.app.workspace.trigger("file-menu", fileMenu, file, "my-context-menu", null);
                fileMenu.showAtPosition({
                    x: event.pageX,
                    y: event.pageY,
                });
                return false;
            },
            false
        );
    }
Example #17
Source File: mapView.ts    From obsidian-map-view with GNU General Public License v3.0 4 votes vote down vote up
private newLeafletMarker(marker: FileMarker): leaflet.Marker {
        let newMarker = leaflet.marker(marker.location, {
            icon: marker.icon || new leaflet.Icon.Default(),
        });
        newMarker.on('click', (event: leaflet.LeafletMouseEvent) => {
            this.goToMarker(marker, event.originalEvent.ctrlKey, true);
        });
        newMarker.on('mouseover', (event: leaflet.LeafletMouseEvent) => {
            if (this.settings.showNotePreview) {
                const previewDetails = {
                    scroll: marker.fileLine,
                    line: marker.fileLine,
                    startLoc: {
                        line: marker.fileLine,
                        col: 0,
                        offset: marker.fileLocation,
                    } as Loc,
                    endLoc: {
                        line: marker.fileLine,
                        col: 0,
                        offset: marker.fileLocation,
                    } as Loc,
                };
                this.app.workspace.trigger(
                    'link-hover',
                    newMarker.getElement(),
                    newMarker.getElement(),
                    marker.file.path,
                    '',
                    previewDetails
                );
            }
            if (this.settings.showNoteNamePopup) {
                const fileName = marker.file.name;
                const fileNameWithoutExtension = fileName.endsWith('.md')
                    ? fileName.substr(0, fileName.lastIndexOf('.md'))
                    : fileName;
                let content = `<p class="map-view-marker-name">${fileNameWithoutExtension}</p>`;
                newMarker
                    .bindPopup(content, {
                        closeButton: true,
                        autoPan: false,
                        className: 'marker-popup',
                    })
                    .openPopup();
            }
        });
        newMarker.on('mouseout', (event: leaflet.LeafletMouseEvent) => {
            newMarker.closePopup();
        });
        newMarker.on('add', (event: leaflet.LeafletEvent) => {
            newMarker
                .getElement()
                .addEventListener('contextmenu', (ev: MouseEvent) => {
                    let mapPopup = new Menu(this.app);
                    mapPopup.setNoIcon();
                    mapPopup.addItem((item: MenuItem) => {
                        item.setTitle('Open note');
                        item.onClick(async (ev) => {
                            this.goToMarker(marker, ev.ctrlKey, true);
                        });
                    });
                    mapPopup.addItem((item: MenuItem) => {
                        item.setTitle('Open geolocation in default app');
                        item.onClick((ev) => {
                            open(
                                `geo:${marker.location.lat},${marker.location.lng}`
                            );
                        });
                    });
                    utils.populateOpenInItems(
                        mapPopup,
                        marker.location,
                        this.settings
                    );
                    mapPopup.showAtPosition(ev);
                    ev.stopPropagation();
                });
        });
        return newMarker;
    }
Example #18
Source File: view.ts    From obsidian-fantasy-calendar with MIT License 4 votes vote down vote up
build() {
        this.contentEl.empty();
        this._app = new CalendarUI({
            target: this.contentEl,
            props: {
                calendar: this.helper,
                fullView: this.full,
                yearView: this.yearView,
                moons: this.moons,
                displayWeeks: this.helper.displayWeeks,
                displayDayNumber: this.dayNumber
            }
        });
        this._app.$on("day-click", (event: CustomEvent<DayHelper>) => {
            const day = event.detail;

            if (day.events.length) return;
            this.createEventForDay(day.date);
        });

        this._app.$on("day-doubleclick", (event: CustomEvent<DayHelper>) => {
            const day = event.detail;
            if (!day.events.length) return;

            this.helper.viewing.day = day.number;
            this.helper.viewing.month = day.month.number;
            this.helper.viewing.year = day.month.year;

            this.yearView = false;

            this._app.$set({ yearView: false });
            this._app.$set({ dayView: true });
            this.triggerHelperEvent("day-update", false);
        });

        this._app.$on(
            "day-context-menu",
            (event: CustomEvent<{ day: DayHelper; evt: MouseEvent }>) => {
                const { day, evt } = event.detail;

                const menu = new Menu(this.app);

                menu.setNoIcon();

                if (!this.full) {
                    menu.addItem((item) => {
                        item.setTitle("Open Day").onClick(() => {
                            this.openDay({
                                day: day.number,
                                month: this.helper.displayed.month,
                                year: this.helper.displayed.year
                            });
                        });
                    });
                }
                menu.addItem((item) => {
                    item.setTitle("Set as Today").onClick(() => {
                        this.calendar.current = day.date;

                        this.helper.current.day = day.number;

                        this.triggerHelperEvent("day-update");

                        this.saveCalendars();
                    });
                });
                menu.addItem((item) =>
                    item.setTitle("New Event").onClick(() => {
                        this.createEventForDay(day.date);
                    })
                );
                menu.showAtMouseEvent(evt);
            }
        );

        this._app.$on("settings", (event: CustomEvent<MouseEvent>) => {
            const evt = event.detail;
            const menu = new Menu(this.app);

            menu.setNoIcon();
            menu.addItem((item) => {
                item.setTitle(
                    `${this.calendar.displayWeeks ? "Hide" : "Show"} Weeks`
                ).onClick(() => {
                    this.calendar.displayWeeks = !this.calendar.displayWeeks;
                    this.helper.update(this.calendar);
                    this._app.$set({
                        displayWeeks: this.calendar.displayWeeks
                    });
                    this.saveCalendars();
                });
            });
            menu.addItem((item) => {
                item.setTitle(
                    `Open ${this.yearView ? "Month" : "Year"}`
                ).onClick(() => {
                    this.yearView = !this.yearView;
                    this._app.$set({ yearView: this.yearView });
                });
            });
            menu.addItem((item) => {
                item.setTitle(
                    this.moons ? "Hide Moons" : "Display Moons"
                ).onClick(() => {
                    this.toggleMoons();
                });
            });
            menu.addItem((item) => {
                item.setTitle(
                    this.dayNumber ? "Hide Day Number" : "Display Day Number"
                ).onClick(() => {
                    this.dayNumber = !this.dayNumber;
                    this.calendar.static.displayDayNumber = this.dayNumber;
                    this._app.$set({ displayDayNumber: this.dayNumber });
                    this.saveCalendars();
                });
            });
            menu.addItem((item) => {
                item.setTitle("View Day");

                item.onClick(() => {
                    this.openDate();
                });
            });
            menu.addItem((item) => {
                item.setTitle("Switch Calendars");
                item.setDisabled(this.plugin.data.calendars.length <= 1);
                item.onClick(() => {
                    const modal = new SwitchModal(this.plugin, this.calendar);

                    modal.onClose = () => {
                        if (!modal.confirmed) return;

                        this.setCurrentCalendar(modal.calendar);
                    };
                    modal.open();
                });
            });

            menu.showAtMouseEvent(evt);
        });

        this._app.$on(
            "event-click",
            (evt: CustomEvent<{ event: Event; modifier: boolean }>) => {
                const { event, modifier } = evt.detail;
                if (event.note) {
                    let leaves: WorkspaceLeaf[] = [];
                    this.app.workspace.iterateAllLeaves((leaf) => {
                        if (!(leaf.view instanceof MarkdownView)) return;
                        if (leaf.view.file.basename === event.note) {
                            leaves.push(leaf);
                        }
                    });
                    if (leaves.length) {
                        this.app.workspace.setActiveLeaf(leaves[0]);
                    } else {
                        this.app.workspace.openLinkText(
                            event.note,
                            "",
                            this.full || modifier
                        );
                    }
                } else {
                    const modal = new ViewEventModal(event, this.plugin);
                    modal.open();
                }
            }
        );

        this._app.$on(
            "event-mouseover",
            (evt: CustomEvent<{ target: HTMLElement; event: Event }>) => {
                if (!this.plugin.data.eventPreview) return;
                const { target, event } = evt.detail;
                if (event.note) {
                    this.app.workspace.trigger(
                        "link-hover",
                        this, //hover popover, but don't need
                        target, //targetEl
                        event.note, //linkText
                        "" //source
                    );
                }
            }
        );

        this._app.$on(
            "event-context",
            (custom: CustomEvent<{ evt: MouseEvent; event: Event }>) => {
                const { evt, event } = custom.detail;

                const menu = new Menu(this.app);

                menu.setNoIcon();

                if (!event.note) {
                    menu.addItem((item) => {
                        item.setTitle("Create Note").onClick(async () => {
                            const path =
                                this.app.workspace.getActiveFile()?.path;
                            const newFilePath = path
                                ? this.app.fileManager.getNewFileParent(path)
                                      ?.parent ?? "/"
                                : "/";

                            const date = `${event.date.year}-${
                                event.date.month + 1
                            }-${event.date.day}`;

                            let end: string;
                            if (event.end) {
                                end = `${event.end.year}-${
                                    event.end.month + 1
                                }-${event.end.day}`;
                            }

                            const content = {
                                "fc-calendar": this.calendar.name,
                                "fc-date": date,
                                ...(event.end ? { "fc-end": end } : {}),
                                ...(event.category
                                    ? {
                                          "fc-category":
                                              this.calendar.categories.find(
                                                  (cat) =>
                                                      cat.id == event.category
                                              )?.name
                                      }
                                    : {}),
                                "fc-display-name": event.name
                            };
                            event.note = normalizePath(
                                `${newFilePath}/${event.name}.md`
                            );

                            let file = this.app.vault.getAbstractFileByPath(
                                event.note
                            );
                            if (!file) {
                                file = await this.app.vault.create(
                                    event.note,
                                    `---\n${stringifyYaml(content)}\n---`
                                );
                            }
                            this.saveCalendars();

                            if (file instanceof TFile) {
                                const fileViews =
                                    this.app.workspace.getLeavesOfType(
                                        "markdown"
                                    );
                                const existing = fileViews.find((l) => {
                                    l.view instanceof FileView &&
                                        l.view.file.path == event.note;
                                });
                                if (existing) {
                                    this.app.workspace.setActiveLeaf(existing);
                                } else {
                                    await this.app.workspace
                                        .getUnpinnedLeaf()
                                        .openFile(file, {
                                            active: true
                                        });
                                }
                            }
                        });
                    });
                }

                menu.addItem((item) => {
                    item.setTitle("Edit Event").onClick(() => {
                        const modal = new CreateEventModal(
                            this.plugin,
                            this.calendar,
                            event
                        );

                        modal.onClose = () => {
                            if (!modal.saved) return;

                            const existing = this.calendar.events.find(
                                (e) => e.id == event.id
                            );

                            this.calendar.events.splice(
                                this.calendar.events.indexOf(existing),
                                1,
                                modal.event
                            );

                            this.helper.refreshMonth(
                                modal.event.date.month,
                                modal.event.date.year
                            );
                            if (
                                modal.event.date.month != existing.date.month ||
                                modal.event.date.year != existing.date.year
                            ) {
                                this.helper.refreshMonth(
                                    existing.date.month,
                                    existing.date.year
                                );
                            }

                            this.saveCalendars();

                            this._app.$set({
                                calendar: this.helper
                            });

                            this.triggerHelperEvent("day-update");
                        };

                        modal.open();
                    });
                });

                menu.addItem((item) => {
                    item.setTitle("Delete Event").onClick(async () => {
                        if (
                            !this.plugin.data.exit.event &&
                            !(await confirmEventDeletion(this.plugin))
                        )
                            return;
                        const existing = this.calendar.events.find(
                            (e) => e.id == event.id
                        );

                        this.calendar.events.splice(
                            this.calendar.events.indexOf(existing),
                            1
                        );

                        this.helper.refreshMonth(
                            existing.date.month,
                            existing.date.year
                        );

                        this.saveCalendars();

                        this._app.$set({
                            calendar: this.helper
                        });

                        this.triggerHelperEvent("day-update");
                    });
                });

                menu.showAtMouseEvent(evt);
            }
        );

        this._app.$on("event", (e: CustomEvent<CurrentCalendarData>) => {
            const date = e.detail;
            this.createEventForDay(date);
        });

        this._app.$on("reset", () => {
            this.helper.reset();

            this.yearView = false;

            this._app.$set({ yearView: false });
            this._app.$set({ dayView: true });
            this.triggerHelperEvent("day-update", false);
        });
    }
Example #19
Source File: mapView.ts    From obsidian-map-view with GNU General Public License v3.0 4 votes vote down vote up
async createMap() {
        // LeafletJS compatability: disable tree-shaking for the full-screen module
        var dummy = leafletFullscreen;
        this.display.map = new leaflet.Map(this.display.mapDiv, {
            center: this.defaultState.mapCenter,
            zoom: this.defaultState.mapZoom,
            zoomControl: false,
            worldCopyJump: true,
            maxBoundsViscosity: 1.0,
        });
        leaflet.control
            .zoom({
                position: 'topright',
            })
            .addTo(this.display.map);
        this.updateTileLayerByState(this.state);
        this.display.clusterGroup = new leaflet.MarkerClusterGroup({
            maxClusterRadius:
                this.settings.maxClusterRadiusPixels ??
                DEFAULT_SETTINGS.maxClusterRadiusPixels,
            animate: false,
        });
        this.display.map.addLayer(this.display.clusterGroup);

        this.display.map.on('zoomend', async (event: leaflet.LeafletEvent) => {
            this.ongoingChanges -= 1;
			await this.changeViewAndSaveHistory({
				mapZoom: this.display.map.getZoom(),
				mapCenter: this.display.map.getCenter()
			});
            this.display?.controls?.invalidateActivePreset();
        });
        this.display.map.on('moveend', async (event: leaflet.LeafletEvent) => {
            this.ongoingChanges -= 1;
			await this.changeViewAndSaveHistory({
				mapZoom: this.display.map.getZoom(),
				mapCenter: this.display.map.getCenter()
			});
            this.display?.controls?.invalidateActivePreset();
        });
        this.display.map.on('movestart', (event: leaflet.LeafletEvent) => {
            this.ongoingChanges += 1;
        });
        this.display.map.on('zoomstart', (event: leaflet.LeafletEvent) => {
            this.ongoingChanges += 1;
        });
        this.display.map.on(
            'doubleClickZoom',
            (event: leaflet.LeafletEvent) => {
                this.ongoingChanges += 1;
            }
        );

        this.display.searchControls = new SearchControl(
            { position: 'topright' },
            this,
            this.app,
            this.settings
        );
        this.display.map.addControl(this.display.searchControls);

        if (this.settings.showClusterPreview) {
            this.display.clusterGroup.on('clustermouseover', (cluster) => {
                let content = this.contentEl.createDiv();
                content.classList.add('clusterPreviewContainer');
                for (const m of cluster.propagatedFrom.getAllChildMarkers()) {
                    const marker = m as leaflet.Marker;
                    const iconElement = marker.options.icon.createIcon();
                    iconElement.classList.add('clusterPreviewIcon');
                    content.appendChild(iconElement);
                    if (
                        content.children.length >=
                        consts.MAX_CLUSTER_PREVIEW_ICONS
                    )
                        break;
                }
                cluster.propagatedFrom
                    .bindPopup(content, {
                        closeButton: true,
                        autoPan: false,
                        className: 'marker-popup',
                    })
                    .openPopup();
                cluster.propagatedFrom.activePopup = content;
            });
            this.display.clusterGroup.on('clustermouseout', (cluster) => {
                cluster.propagatedFrom.closePopup();
            });
        }

        // Build the map marker right-click context menu
        this.display.map.on(
            'contextmenu',
            async (event: leaflet.LeafletMouseEvent) => {
                let mapPopup = new Menu(this.app);
                mapPopup.setNoIcon();
                mapPopup.addItem((item: MenuItem) => {
                    const location = `${event.latlng.lat},${event.latlng.lng}`;
                    item.setTitle('New note here (inline)');
                    item.onClick(async (ev) => {
                        const newFileName = utils.formatWithTemplates(
                            this.settings.newNoteNameFormat
                        );
                        const file: TFile = await utils.newNote(
                            this.app,
                            'multiLocation',
                            this.settings.newNotePath,
                            newFileName,
                            location,
                            this.settings.newNoteTemplate
                        );
                        this.goToFile(
                            file,
                            ev.ctrlKey,
                            utils.handleNewNoteCursorMarker
                        );
                    });
                });
                mapPopup.addItem((item: MenuItem) => {
                    const location = `${event.latlng.lat},${event.latlng.lng}`;
                    item.setTitle('New note here (front matter)');
                    item.onClick(async (ev) => {
                        const newFileName = utils.formatWithTemplates(
                            this.settings.newNoteNameFormat
                        );
                        const file: TFile = await utils.newNote(
                            this.app,
                            'singleLocation',
                            this.settings.newNotePath,
                            newFileName,
                            location,
                            this.settings.newNoteTemplate
                        );
                        this.goToFile(
                            file,
                            ev.ctrlKey,
                            utils.handleNewNoteCursorMarker
                        );
                    });
                });
                mapPopup.addItem((item: MenuItem) => {
                    const location = `${event.latlng.lat},${event.latlng.lng}`;
                    item.setTitle(`Copy geolocation`);
                    item.onClick((_ev) => {
                        navigator.clipboard.writeText(`[](geo:${location})`);
                    });
                });
                mapPopup.addItem((item: MenuItem) => {
                    const location = `${event.latlng.lat},${event.latlng.lng}`;
                    item.setTitle(`Copy geolocation as front matter`);
                    item.onClick((_ev) => {
                        navigator.clipboard.writeText(
                            `---\nlocation: [${location}]\n---\n\n`
                        );
                    });
                });
                mapPopup.addItem((item: MenuItem) => {
                    item.setTitle('Open in default app');
                    item.onClick((_ev) => {
                        open(`geo:${event.latlng.lat},${event.latlng.lng}`);
                    });
                });
                utils.populateOpenInItems(
                    mapPopup,
                    event.latlng,
                    this.settings
                );
                mapPopup.showAtPosition(event.originalEvent);
            }
        );
    }
Example #20
Source File: ItemModal.ts    From obsidian-rss with GNU General Public License v3.0 4 votes vote down vote up
async display(): Promise<void> {
        this.modalEl.addClass("rss-modal");
        const {contentEl} = this;
        contentEl.empty();

        //don't add any scrolling to modal content
        contentEl.style.height = "100%";
        contentEl.style.overflowY = "hidden";

        const topButtons = contentEl.createDiv('topButtons');

        let actions = Array.of(Action.CREATE_NOTE, Action.PASTE, Action.COPY, Action.OPEN);

        if (this.save) {
            this.readButton = new ButtonComponent(topButtons)
                .setIcon(this.item.read ? 'eye-off' : 'eye')
                .setTooltip(this.item.read ? t("mark_as_unread") : t("mark_as_read"))
                .onClick(async () => {
                    await this.markAsRead();
                });
            this.readButton.buttonEl.setAttribute("tabindex", "-1");
            this.readButton.buttonEl.addClass("rss-button");

            this.favoriteButton = new ButtonComponent(topButtons)
                .setIcon(this.item.favorite ? 'star-glyph' : 'star')
                .setTooltip(this.item.favorite ? t("remove_from_favorites") : t("mark_as_favorite"))
                .onClick(async () => {
                    await this.markAsFavorite();
                });
            this.favoriteButton.buttonEl.setAttribute("tabindex", "-1");
            this.favoriteButton.buttonEl.addClass("rss-button");

            actions = Array.of(Action.TAGS, ...actions);
        }


        actions.forEach((action) => {
            const button = new ButtonComponent(topButtons)
                .setIcon(action.icon)
                .setTooltip(action.name)
                .onClick(async () => {
                    await action.processor(this.plugin, this.item);
                });
            button.buttonEl.setAttribute("tabindex", "-1");
            button.buttonEl.addClass("rss-button");
        });
        //@ts-ignore
        if (this.app.plugins.plugins["obsidian-tts"]) {
            const ttsButton = new ButtonComponent(topButtons)
                .setIcon("headphones")
                .setTooltip(t("read_article_tts"))
                .onClick(async () => {
                    const content = htmlToMarkdown(this.item.content);
                    //@ts-ignore
                    await this.app.plugins.plugins["obsidian-tts"].ttsService.say(this.item.title, content, this.item.language);
                });
            ttsButton.buttonEl.addClass("rss-button");
        }

        const prevButton = new ButtonComponent(topButtons)
            .setIcon("left-arrow-with-tail")
            .setTooltip(t("previous"))
            .onClick(() => {
                this.previous();
            });
        prevButton.buttonEl.addClass("rss-button");

        const nextButton = new ButtonComponent(topButtons)
            .setIcon("right-arrow-with-tail")
            .setTooltip(t("next"))
            .onClick(() => {
                this.next();
            });
        nextButton.buttonEl.addClass("rss-button");

        const title = contentEl.createEl('h1', 'rss-title');
        title.addClass("rss-selectable");
        title.setText(this.item.title);

        const subtitle = contentEl.createEl("h3", "rss-subtitle");
        subtitle.addClass("rss-selectable");
        if (this.item.creator) {
            subtitle.appendText(this.item.creator);
        }
        if (this.item.pubDate) {
            subtitle.appendText(" - " + window.moment(this.item.pubDate).format(this.plugin.settings.dateFormat));
        }
        const tagEl = contentEl.createSpan("tags");
        this.item.tags.forEach((tag) => {
            const tagA = tagEl.createEl("a");
            tagA.setText(tag);
            tagA.addClass("tag", "rss-tag");
        });

        const content = contentEl.createDiv('rss-content');
        content.addClass("rss-scrollable-content", "rss-selectable");

        if (this.item.enclosure && this.plugin.settings.displayMedia) {
            if (this.item.enclosureType.toLowerCase().contains("audio")) {
                const audio = content.createEl("audio", {attr: {controls: "controls"}});
                audio.createEl("source", {attr: {src: this.item.enclosure, type: this.item.enclosureType}});
            }
            if (this.item.enclosureType.toLowerCase().contains("video")) {
                const video = content.createEl("video", {attr: {controls: "controls", width: "100%", height: "100%"}});
                video.createEl("source", {attr: {src: this.item.enclosure, type: this.item.enclosureType}});
            }

            //embedded yt player
            if (this.item.enclosure && this.item.id.startsWith("yt:")) {
                content.createEl("iframe", {
                    attr: {
                        type: "text/html",
                        src: "https://www.youtube.com/embed/" + this.item.enclosure,
                        width: "100%",
                        height: "100%",
                        allowFullscreen: "true"
                    }
                });
            }
        }

        if (this.item.content) {
            //prepend empty yaml to fix rendering errors
            const markdown = "---\n---" + rssToMd(this.plugin, this.item.content);

            await MarkdownRenderer.renderMarkdown(markdown, content, "", this.plugin);

            this.item.highlights.forEach(highlight => {
                if (content.innerHTML.includes(highlight)) {
                    const newNode = contentEl.createEl("mark");
                    newNode.innerHTML = highlight;
                    content.innerHTML = content.innerHTML.replace(highlight, newNode.outerHTML);
                    newNode.remove();
                } else {
                    console.log("Highlight not included");
                    console.log(highlight);
                }
            });

            content.addEventListener('contextmenu', (event) => {
                event.preventDefault();

                const selection = document.getSelection();
                const range = selection.getRangeAt(0);

                const div = contentEl.createDiv();
                const htmlContent = range.cloneContents();
                const html = htmlContent.cloneNode(true);
                div.appendChild(html);
                const selected = div.innerHTML;
                div.remove();

                const menu = new Menu(this.app);

                let previousHighlight: HTMLElement;
                if (this.item.highlights.includes(range.startContainer.parentElement.innerHTML)) {
                    previousHighlight = range.startContainer.parentElement;
                }
                if (this.item.highlights.includes(range.startContainer.parentElement.parentElement.innerHTML)) {
                    previousHighlight = range.startContainer.parentElement.parentElement;
                }

                if(previousHighlight) {
                    menu.addItem(item => {
                        item
                            .setIcon("highlight-glyph")
                            .setTitle(t("highlight_remove"))
                            .onClick(async () => {
                                const replacement = contentEl.createSpan();
                                replacement.innerHTML = previousHighlight.innerHTML;
                                previousHighlight.replaceWith(replacement);
                                this.item.highlights.remove(previousHighlight.innerHTML);

                                const feedContents = this.plugin.settings.items;
                                await this.plugin.writeFeedContent(() => {
                                    return feedContents;
                                });
                            });
                    });
                }else if(!this.item.highlights.includes(selected) && selected.length > 0) {
                    menu.addItem(item => {
                        item
                            .setIcon("highlight-glyph")
                            .setTitle(t("highlight"))
                            .onClick(async () => {
                                const newNode = contentEl.createEl("mark");
                                newNode.innerHTML = selected;
                                range.deleteContents();
                                range.insertNode(newNode);
                                this.item.highlights.push(selected);

                                const feedContents = this.plugin.settings.items;
                                await this.plugin.writeFeedContent(() => {
                                    return feedContents;
                                });

                                //cleaning up twice to remove nested elements
                                this.removeDanglingElements(contentEl);
                                this.removeDanglingElements(contentEl);
                            });
                    });
                }

                if(selected.length > 0) {
                    menu
                        .addItem(item => {
                            item
                                .setIcon("documents")
                                .setTitle(t("copy_to_clipboard"))
                                .onClick(async () => {
                                    await copy(selection.toString());
                                });
                        });
                    //@ts-ignore
                    if (this.app.plugins.plugins["obsidian-tts"]) {
                        menu.addItem(item => {
                            item
                                .setIcon("headphones")
                                .setTitle(t("read_article_tts"))
                                .onClick(() => {
                                    //@ts-ignore
                                    const tts = this.app.plugins.plugins["obsidian-tts"].ttsService;
                                    tts.say("", selection.toString());
                                });
                        });
                    }
                }

                menu.showAtMouseEvent(event);
            });
        }
    }
Example #21
Source File: main.ts    From obsidian-charts with GNU Affero General Public License v3.0 4 votes vote down vote up
async onload() {
		console.log('loading plugin: Obsidian Charts');

		await this.loadSettings()

		addIcons();

		this.renderer = new Renderer(this);

		//@ts-ignore
		window.renderChart = this.renderer.renderRaw;

		this.addSettingTab(new ChartSettingTab(this.app, this));

		this.addCommand({
			id: 'creation-helper',
			name: 'Insert new Chart',
			checkCallback: (checking: boolean) => {
				let leaf = this.app.workspace.activeLeaf;
				if (leaf.view instanceof MarkdownView) {
					if (!checking) {
						new CreationHelperModal(this.app, leaf.view, this.settings, this.renderer).open();
					}
					return true;
				}
				return false;
			}
		});

		this.addCommand({
			id: 'chart-from-table-column',
			name: 'Create Chart from Table (Column oriented Layout)',
			editorCheckCallback: (checking: boolean, editor: Editor, view: View) => {
				let selection = editor.getSelection();
				if (view instanceof MarkdownView && selection.split('\n').length >= 3 && selection.split('|').length >= 2) {
					if (!checking) {
						chartFromTable(editor, 'columns');
					}
					return true;
				}
				return false;
			}
		});

		this.addCommand({
			id: 'chart-from-table-row',
			name: 'Create Chart from Table (Row oriented Layout)',
			editorCheckCallback: (checking: boolean, editor: Editor, view: View) => {
				if (view instanceof MarkdownView && editor.getSelection().split('\n').length >= 3 && editor.getSelection().split('|').length >= 2) {
					if (!checking) {
						chartFromTable(editor, 'rows');
					}
					return true;
				}
				return false;
			}
		});

		this.addCommand({
			id: 'chart-to-svg',
			name: 'Create Image from Chart',
			editorCheckCallback: (checking: boolean, editor: Editor, view: View) => {
				if (view instanceof MarkdownView && editor.getSelection().startsWith("```chart") && editor.getSelection().endsWith("```")) {
					if (!checking) {
						new Notice("Rendering Chart...")
						saveImageToVaultAndPaste(editor, this.app, this.renderer, view.file, this.settings);
					}
					return true;
				}
				return false;
			}
		});

		this.registerMarkdownCodeBlockProcessor('chart', this.postprocessor);
		this.registerMarkdownCodeBlockProcessor('advanced-chart', async (data, el) => this.renderer.renderRaw(await JSON.parse(data), el));

		// Remove this ignore when the obsidian package is updated on npm
		// Editor mode
		// @ts-ignore
		this.registerEvent(this.app.workspace.on('editor-menu',
			(menu: Menu, _: Editor, view: MarkdownView) => {
				if (view && this.settings.contextMenu) {
					menu.addItem((item) => {
						item.setTitle("Insert Chart")
							.setIcon("chart")
							.onClick((_) => {
								new CreationHelperModal(this.app, view, this.settings, this.renderer).open();
							});
					});
				}
			}));
	}
Example #22
Source File: main.ts    From obsidian-dictionary with GNU Affero General Public License v3.0 4 votes vote down vote up
async onload(): Promise<void> {
        console.log('loading dictionary');

        await Promise.all([this.loadSettings(), this.loadCache()]);

        addIcons();

        this.addSettingTab(new SettingsTab(this.app, this));

        this.manager = new APIManager(this);

        this.registerView(VIEW_TYPE, (leaf) => {
            return new DictionaryView(leaf, this);
        });

        this.addCommand({
            id: 'dictionary-open-view',
            name: t('Open Dictionary View'),
            callback: async () => {
                if (this.app.workspace.getLeavesOfType(VIEW_TYPE).length == 0) {
                    await this.app.workspace.getRightLeaf(false).setViewState({
                        type: VIEW_TYPE,
                    });
                }
                this.app.workspace.revealLeaf(this.app.workspace.getLeavesOfType(VIEW_TYPE).first());
                dispatchEvent(new Event("dictionary-focus-on-search"));
            },
        });

        this.addCommand({
            id: 'dictionary-open-language-switcher',
            name: t('Open Language Switcher'),
            callback: () => {
                new LanguageChooser(this.app, this).open();
            },
        });

        this.registerDomEvent(document.body, "pointerup", () => {
            if (!this.settings.shouldShowSynonymPopover) {
                return;
            }
            this.handlePointerUp();
        });
        this.registerDomEvent(window, "keydown", () => {
            // Destroy the popover if it's open
            if (this.synonymPopover) {
                this.synonymPopover.destroy();
                this.synonymPopover = null;
            }
        });

        this.registerDomEvent(document.body, "contextmenu", (event) => {
            //@ts-ignore
            if (this.settings.shouldShowCustomContextMenu && event.path.find((el: HTMLElement) => {
                try {
                    return el.hasClass("markdown-preview-view");
                } catch (error) {
                    return false;
                }
            })) {
                const selection = window.getSelection().toString();
                if (selection && this.app.workspace.activeLeaf?.getViewState()?.state.mode === "preview") {
                    event.preventDefault();

                    const fileMenu = new Menu(this.app);

                    fileMenu.addItem((item) => {
                        item.setTitle(t('Copy'))
                            .setIcon('copy')
                            .onClick((_) => {
                                copy(selection);
                            });
                    });

                    if (selection.trim().split(" ").length === 1) {
                        fileMenu.addItem((item) => {
                            item.setTitle(t('Look up'))
                                .setIcon('quote-glyph')
                                .onClick(async (_) => {
                                    let leaf: WorkspaceLeaf = this.app.workspace.getLeavesOfType(VIEW_TYPE).first();
                                    if (!leaf) {
                                        leaf = this.app.workspace.getRightLeaf(false);
                                        await leaf.setViewState({
                                            type: VIEW_TYPE,
                                        });
                                    }
                                    // eslint-disable-next-line @typescript-eslint/ban-ts-comment
                                    //@ts-ignore
                                    leaf.view.query(selection.trim());
                                    this.app.workspace.revealLeaf(leaf);
                                });
                        });
                    }

                    fileMenu.showAtPosition({ x: event.clientX, y: event.clientY });
                }
            }
        });

        this.localDictionary = new LocalDictionaryBuilder(this);
        
        this.registerEvent(this.app.workspace.on('editor-menu', this.handleContextMenuHelper));

        this.registerEvent(this.app.workspace.on('file-open', (file) => {
            if (file && this.settings.getLangFromFile) {
                let lang = this.app.metadataCache.getFileCache(file).frontmatter?.lang ?? null;
                if (!lang) {
                    lang = this.app.metadataCache.getFileCache(file).frontmatter?.language ?? null;
                }
                if (lang && Object.values(RFC).contains(lang)) {
                    this.settings.defaultLanguage = Object.keys(RFC)[Object.values(RFC).indexOf(lang)] as keyof APISettings;
                } else {
                    this.settings.defaultLanguage = this.settings.normalLang;
                }
                this.saveSettings();
            }
        }));
    }
Example #23
Source File: main.ts    From obsidian-map-view with GNU General Public License v3.0 4 votes vote down vote up
async onload() {
        addIcon('globe', consts.RIBBON_ICON);

        await this.loadSettings();

        // Add a new ribbon entry to the left bar
        this.addRibbonIcon('globe', 'Open map view', () => {
            // When clicked change the active view to the map
            this.app.workspace
                .getLeaf()
                .setViewState({ type: consts.MAP_VIEW_NAME });
        });

        this.registerView(consts.MAP_VIEW_NAME, (leaf: WorkspaceLeaf) => {
            return new MapView(leaf, this.settings, this);
        });

        this.registerObsidianProtocolHandler(
            'mapview',
            (params: ObsidianProtocolData) => {
                if (params.action == 'mapview') {
                    const state = stateFromParsedUrl(params);
                    // If a saved URL is opened in another device on which there aren't the same sources, use
                    // the default source instead
                    if (
                        state.chosenMapSource >= this.settings.mapSources.length
                    )
                        state.chosenMapSource =
                            DEFAULT_SETTINGS.defaultState.chosenMapSource;
                    this.openMapWithState(state, false, false);
                }
            }
        );

        this.suggestor = new LocationSuggest(this.app, this.settings);
        this.tagSuggestor = new TagSuggest(this.app, this.settings);
        this.urlConvertor = new UrlConvertor(this.app, this.settings);

        this.registerEditorSuggest(this.suggestor);
        this.registerEditorSuggest(this.tagSuggestor);

        // Convert old settings formats that are no longer supported
        if (convertLegacyMarkerIcons(this.settings)) {
            await this.saveSettings();
            new Notice(
                'Map View: legacy marker icons were converted to the new format'
            );
        }
        if (convertLegacyTilesUrl(this.settings)) {
            await this.saveSettings();
            new Notice(
                'Map View: legacy tiles URL was converted to the new format'
            );
        }
        if (convertLegacyDefaultState(this.settings)) {
            await this.saveSettings();
            new Notice(
                'Map View: legacy default state was converted to the new format'
            );
        }
        if (removeLegacyPresets1(this.settings)) {
            await this.saveSettings();
            new Notice(
                'Map View: legacy URL parsing rules and/or map sources were converted. See the release notes'
            );
        }
        if (convertTagsToQueries(this.settings)) {
            await this.saveSettings();
            new Notice(
                'Map View: legacy tag queries were converted to the new query format'
            );
        }
        if (convertUrlParsingRules1(this.settings)) {
            await this.saveSettings();
            new Notice(
                'Map View: URL parsing rules were converted to the new format'
            );
        }

        // Register commands to the command palette
        // Command that opens the map view (same as clicking the map icon)
        this.addCommand({
            id: 'open-map-view',
            name: 'Open Map View',
            callback: () => {
                this.app.workspace
                    .getLeaf()
                    .setViewState({ type: consts.MAP_VIEW_NAME });
            },
        });

        // Command that looks up the selected text to find the location
        this.addCommand({
            id: 'convert-selection-to-location',
            name: 'Convert Selection to Geolocation',
            editorCheckCallback: (checking, editor, view) => {
                if (checking) return editor.getSelection().length > 0;
                this.suggestor.selectionToLink(editor);
            },
        });

        // Command that adds a blank inline location at the cursor location
        this.addCommand({
            id: 'insert-geolink',
            name: 'Add inline geolocation link',
            editorCallback: (editor, view) => {
                const positionBeforeInsert = editor.getCursor();
                editor.replaceSelection('[](geo:)');
                editor.setCursor({
                    line: positionBeforeInsert.line,
                    ch: positionBeforeInsert.ch + 1,
                });
            },
        });

        // Command that opens the location search dialog and creates a new note from this location
        this.addCommand({
            id: 'new-geolocation-note',
            name: 'New geolocation note',
            callback: () => {
                const dialog = new LocationSearchDialog(
                    this.app,
                    this.settings,
                    'newNote',
                    'New geolocation note'
                );
                dialog.open();
            },
        });

        // Command that opens the location search dialog and adds the location to the current note
        this.addCommand({
            id: 'add-frontmatter-geolocation',
            name: 'Add geolocation (front matter) to current note',
            editorCallback: (editor, view) => {
                const dialog = new LocationSearchDialog(
                    this.app,
                    this.settings,
                    'addToNote',
                    'Add geolocation to note',
                    editor
                );
                dialog.open();
            },
        });

        this.addCommand({
            id: 'open-map-search',
            name: 'Search active map view',
            checkCallback: (checking) => {
                const currentView = this.app.workspace.activeLeaf.view;
                if (
                    currentView &&
                    currentView.getViewType() == consts.MAP_VIEW_NAME
                ) {
                    if (!checking) (currentView as MapView).openSearch();
                    return true;
                } else return false;
            },
        });

        this.addSettingTab(new SettingsTab(this.app, this));

        // Add items to the file context menu (run when the context menu is built)
        // This is the context menu in the File Explorer and clicking "More options" (three dots) from within a file.
        this.app.workspace.on(
            'file-menu',
            async (
                menu: Menu,
                file: TAbstractFile,
                _source: string,
                leaf?: WorkspaceLeaf
            ) => {
                if (file instanceof TFile) {
                    let hasAnyLocation = false;
                    const location = getFrontMatterLocation(file, this.app);
                    if (location) {
                        // If there is a geolocation in the front matter of the file
                        // Add an option to open it in the map
                        menu.addItem((item: MenuItem) => {
                            item.setTitle('Show on map');
                            item.setIcon('globe');
                            item.onClick(
                                async (evt: MouseEvent) =>
                                    await this.openMapWithLocation(
                                        location,
                                        evt.ctrlKey
                                    )
                            );
                        });
                        // Add an option to open it in the default app
                        menu.addItem((item: MenuItem) => {
                            item.setTitle('Open with default app');
                            item.onClick((_ev) => {
                                open(`geo:${location.lat},${location.lng}`);
                            });
                        });
                        // Populate menu items from user defined "Open In" strings
                        utils.populateOpenInItems(
                            menu,
                            location,
                            this.settings
                        );
                        hasAnyLocation = true;
                    } else {
                        if (leaf && leaf.view instanceof MarkdownView) {
                            // If there is no valid geolocation in the front matter, add a menu item to populate it.
                            const editor = leaf.view.editor;
                            menu.addItem((item: MenuItem) => {
                                item.setTitle('Add geolocation (front matter)');
                                item.setIcon('globe');
                                item.onClick(async (evt: MouseEvent) => {
                                    const dialog = new LocationSearchDialog(
                                        this.app,
                                        this.settings,
                                        'addToNote',
                                        'Add geolocation to note',
                                        editor
                                    );
                                    dialog.open();
                                });
                            });
                        }
                    }
                    const contentMarkers = await getMarkersFromFileContent(
                        file,
                        this.settings,
                        this.app
                    );
                    if (contentMarkers.length > 0) {
                        hasAnyLocation = true;
                    }
                    if (hasAnyLocation) {
                        menu.addItem((item: MenuItem) => {
                            item.setTitle('Focus note in Map View');
                            item.setIcon('globe');
                            item.onClick(
                                async (evt: MouseEvent) =>
                                    await this.openMapWithState(
                                        {
                                            query: `path:"${file.path}"`,
                                        } as MapState,
                                        evt.ctrlKey,
                                        true
                                    )
                            );
                        });
                    }
                }
            }
        );

        // Add items to the editor context menu (run when the context menu is built)
        // This is the context menu when right clicking within an editor view.
        this.app.workspace.on(
            'editor-menu',
            async (menu: Menu, editor: Editor, view: MarkdownView) => {
                if (view instanceof FileView) {
                    const location = this.getLocationOnEditorLine(editor, view);
                    if (location) {
                        // If there is a geolocation on the line
                        // Add an option to open it in the map
                        menu.addItem((item: MenuItem) => {
                            item.setTitle('Show on map');
                            item.setIcon('globe');
                            item.onClick(
                                async (evt: MouseEvent) =>
                                    await this.openMapWithLocation(
                                        location,
                                        evt.ctrlKey
                                    )
                            );
                        });
                        // Add an option to open it in the default app
                        menu.addItem((item: MenuItem) => {
                            item.setTitle('Open with default app');
                            item.onClick((_ev) => {
                                open(`geo:${location.lat},${location.lng}`);
                            });
                        });
                        // Populate menu items from user defined "Open In" strings
                        utils.populateOpenInItems(
                            menu,
                            location,
                            this.settings
                        );
                    }
                    if (editor.getSelection()) {
                        // If there is text selected, add a menu item to convert it to coordinates using geosearch
                        menu.addItem((item: MenuItem) => {
                            item.setTitle('Convert to geolocation (geosearch)');
                            item.onClick(
                                async () =>
                                    await this.suggestor.selectionToLink(editor)
                            );
                        });
                    }

                    if (this.urlConvertor.hasMatchInLine(editor))
                        // If the line contains a recognized geolocation that can be converted from a URL parsing rule
                        menu.addItem(async (item: MenuItem) => {
                            item.setTitle('Convert to geolocation');
                            item.onClick(async () => {
                                this.urlConvertor.convertUrlAtCursorToGeolocation(
                                    editor
                                );
                            });
                        });

                    const clipboard = await navigator.clipboard.readText();
                    let clipboardLocation =
                        this.urlConvertor.parseLocationFromUrl(clipboard);
                    if (clipboardLocation) {
                        // If the clipboard contains a recognized geolocation that can be converted from a URL parsing rule
                        menu.addItem((item: MenuItem) => {
                            item.setTitle('Paste as geolocation');
                            item.onClick(async () => {
                                if (clipboardLocation instanceof Promise)
                                    clipboardLocation = await clipboardLocation;
                                if (clipboardLocation)
                                    this.urlConvertor.insertLocationToEditor(
                                        clipboardLocation.location,
                                        editor
                                    );
                            });
                        });
                    }
                }
            }
        );
    }