From 7f7e5007689a49caaeccbd0530a68cc3551ae061 Mon Sep 17 00:00:00 2001 From: gentlegiantJGC Date: Thu, 12 May 2022 22:00:39 +0100 Subject: [PATCH 1/8] Added partial support to reposition pin Added an attribute to the FileMarker class to store the inline match length Added a context menu item to the map pin to edit location which stores the pin attribute When the map is clicked and the pin attribute is not null it will rewrite the pin location with the clicked location. So far implemented inline locations. Still need to implement front matter locations. --- src/mapView.ts | 27 +++++++++++++++++++++++++++ src/markers.ts | 2 ++ 2 files changed, 29 insertions(+) diff --git a/src/mapView.ts b/src/mapView.ts index 8ec1859..c66fd6e 100644 --- a/src/mapView.ts +++ b/src/mapView.ts @@ -73,6 +73,8 @@ export class MapView extends ItemView { /** Is the view currently open */ private isOpen: boolean = false; + private editPin: FileMarker = null; + /** * Construct a new map instance * @param leaf The leaf the map should be put in @@ -449,6 +451,25 @@ export class MapView extends ItemView { mapPopup.showAtPosition(event.originalEvent); } ); + + this.display.map.on( + 'click', + async (event: leaflet.LeafletMouseEvent) => { + if (this.editPin !== null) { + const location = `${event.latlng.lat},${event.latlng.lng}`; + if (this.editPin.fileLocation === null){ + // is a front matter location + + } else { + // is an inline location + const old_file = await this.app.vault.cachedRead(this.editPin.file); + const new_file = old_file.slice(0, this.editPin.fileLocation) + `[](geo:${location})` + old_file.slice(this.editPin.fileLocation + this.editPin.fileLength); + await this.app.vault.modify(this.editPin.file, new_file); + } + this.editPin = null; + } + } + ) } /** @@ -584,6 +605,12 @@ export class MapView extends ItemView { this.goToMarker(marker, ev.ctrlKey, true); }); }); + mapPopup.addItem((item: MenuItem) => { + item.setTitle('Edit location'); + item.onClick(async (ev) => { + this.editPin = marker; + }); + }); mapPopup.addItem((item: MenuItem) => { item.setTitle('Open geolocation in default app'); item.onClick((ev) => { diff --git a/src/markers.ts b/src/markers.ts index 224d185..69a8528 100644 --- a/src/markers.ts +++ b/src/markers.ts @@ -18,6 +18,7 @@ export class FileMarker { file: TFile; /** In the case of an inline location, the position within the file where the location was found */ fileLocation?: number; + fileLength?: number; /** In case of an inline location, the line within the file where the geolocation was found */ fileLine?: number; location: leaflet.LatLng; @@ -249,6 +250,7 @@ async function getMarkersFromFileContent( if (tag[1]) marker.tags.push('#' + tag[1]); } marker.fileLocation = match.index; + marker.fileLength = match[0].length; marker.fileLine = content.substring(0, marker.fileLocation).split('\n').length - 1; From 436e1e8cd092434c5a4b760727d301e185f6d815 Mon Sep 17 00:00:00 2001 From: gentlegiantJGC Date: Fri, 13 May 2022 20:54:34 +0100 Subject: [PATCH 2/8] Added Edit Markers button Switched from the context menu implementation to using Leaflet's dragging mechanism. Editing is toggled from the check box in the left menu. --- src/mapView.ts | 66 +++++++++++++++++++++++++++------------------ src/viewControls.ts | 16 +++++++++++ 2 files changed, 56 insertions(+), 26 deletions(-) diff --git a/src/mapView.ts b/src/mapView.ts index c66fd6e..ef1fa19 100644 --- a/src/mapView.ts +++ b/src/mapView.ts @@ -41,6 +41,7 @@ import { LocationSuggest } from 'src/geosearch'; import MapViewPlugin from 'src/main'; import * as utils from 'src/utils'; import { ViewControls } from 'src/viewControls'; +import { DragEndEvent, LeafletEvent } from 'leaflet'; export class MapView extends ItemView { private settings: PluginSettings; @@ -73,7 +74,7 @@ export class MapView extends ItemView { /** Is the view currently open */ private isOpen: boolean = false; - private editPin: FileMarker = null; + private editable: boolean = false; /** * Construct a new map instance @@ -451,25 +452,6 @@ export class MapView extends ItemView { mapPopup.showAtPosition(event.originalEvent); } ); - - this.display.map.on( - 'click', - async (event: leaflet.LeafletMouseEvent) => { - if (this.editPin !== null) { - const location = `${event.latlng.lat},${event.latlng.lng}`; - if (this.editPin.fileLocation === null){ - // is a front matter location - - } else { - // is an inline location - const old_file = await this.app.vault.cachedRead(this.editPin.file); - const new_file = old_file.slice(0, this.editPin.fileLocation) + `[](geo:${location})` + old_file.slice(this.editPin.fileLocation + this.editPin.fileLength); - await this.app.vault.modify(this.editPin.file, new_file); - } - this.editPin = null; - } - } - ) } /** @@ -593,6 +575,30 @@ export class MapView extends ItemView { newMarker.on('mouseout', (event: leaflet.LeafletMouseEvent) => { newMarker.closePopup(); }); + newMarker.on('add', (event: leaflet.LeafletEvent) => { + if (this.editable) { + newMarker.dragging.enable(); + } + }); + newMarker.on('dragstart', async (event: leaflet.LeafletEvent) => { + // if dragging the balloons are quite irritating + newMarker.closePopup(); + }); + newMarker.on('dragend', async (event: leaflet.DragEndEvent) => { + const latlng = newMarker.getLatLng(); + const location = `${latlng.lat},${latlng.lng}`; + if (marker.fileLocation === null) { + // is a front matter location + } else { + // is an inline location + const old_file = await this.app.vault.cachedRead(marker.file); + const new_file = + old_file.slice(0, marker.fileLocation) + + `[](geo:${location})` + + old_file.slice(marker.fileLocation + marker.fileLength); + await this.app.vault.modify(marker.file, new_file); + } + }); newMarker.on('add', (event: leaflet.LeafletEvent) => { newMarker .getElement() @@ -605,12 +611,6 @@ export class MapView extends ItemView { this.goToMarker(marker, ev.ctrlKey, true); }); }); - mapPopup.addItem((item: MenuItem) => { - item.setTitle('Edit location'); - item.onClick(async (ev) => { - this.editPin = marker; - }); - }); mapPopup.addItem((item: MenuItem) => { item.setTitle('Open geolocation in default app'); item.onClick((ev) => { @@ -755,4 +755,18 @@ export class MapView extends ItemView { ); this.updateMapMarkers(newMarkers); } + + /** Set the markers edit state. */ + setEditable(editable: boolean) { + this.editable = editable; + for (let [markerId, fileMarker] of this.display.markers) { + if (fileMarker.mapMarker.dragging) { + if (editable) { + fileMarker.mapMarker.dragging.enable(); + } else { + fileMarker.mapMarker.dragging.disable(); + } + } + } + } } diff --git a/src/viewControls.ts b/src/viewControls.ts index 15dfd6a..c368a2d 100644 --- a/src/viewControls.ts +++ b/src/viewControls.ts @@ -226,6 +226,22 @@ export class ViewControls { }; this.refreshPresets(); + // this could probably be better but I don't know what I am doing + const editMarkers = this.controlsDiv.createDiv({ + cls: 'graph-control-div', + }); + editMarkers.innerHTML = ` + + + `; + const editMarkersButton = editMarkers.getElementsByClassName( + 'toggle' + )[0] as HTMLInputElement; + editMarkersButton.checked = false; + editMarkersButton.onclick = async () => { + this.view.setEditable(editMarkersButton.checked); + }; + this.parentElement.append(this.controlsDiv); } From 53f76f883a208b7c20993b613e116a16ff5c7660 Mon Sep 17 00:00:00 2001 From: gentlegiantJGC Date: Sun, 15 May 2022 09:29:33 +0100 Subject: [PATCH 3/8] Added utility functions to modify front matter There may be bugs in these because I cannot package them correctly to test. --- package.json | 4 +++- src/utils.ts | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index c37b806..389d8e9 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "@types/leaflet": "^1.7.2", "@types/leaflet.markercluster": "^1.4.5", "@types/node": "^14.14.37", + "@types/yawn-yaml": "^1.4.0", "obsidian": "^0.12.5", "postcss-less": "^4.0.1", "postcss-url": "^10.1.3", @@ -41,6 +42,7 @@ "leaflet.markercluster": "^1.5.3", "moment": "^2.29.1", "open": "^8.2.1", - "wildcard": "^2.0.0" + "wildcard": "^2.0.0", + "yawn-yaml": "^1.5.0" } } diff --git a/src/utils.ts b/src/utils.ts index 73aab9d..08765d5 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -6,6 +6,8 @@ import { TFile, Menu, MenuItem, + Vault, + stringifyYaml, } from 'obsidian'; import * as moment_ from 'moment'; @@ -15,6 +17,8 @@ import * as settings from './settings'; import * as consts from './consts'; import { MapView } from './mapView'; +import YAWN from 'yawn-yaml'; + export function formatWithTemplates(s: string, query = '') { const datePattern = /{{date:([a-zA-Z\-\/\.\:]*)}}/g; const queryPattern = /{{query}}/g; @@ -31,6 +35,7 @@ export function formatWithTemplates(s: string, query = '') { type NewNoteType = 'singleLocation' | 'multiLocation'; const CURSOR = '$CURSOR$'; +const FRONT_MATTER_PATTERN = /^---\n(?.*)^---/ms; function sanitizeFileName(s: string) { const illegalChars = /[\?<>\\:\*\|":]/g; @@ -108,6 +113,53 @@ export async function handleNewNoteCursorMarker(editor: Editor) { } } +// Set/setdefault a top level YAML value in a file content string +// If the YAML front matter does not exist it will be created +export function stringFrontMatterSet( + content: string, + fieldName: string, + fieldValue: any, + set_default: boolean = false +): string { + const frontMatterMatch = content.match(FRONT_MATTER_PATTERN); + if (frontMatterMatch !== null) { + console.log(frontMatterMatch); + let frontMatterYaml = frontMatterMatch.groups.yaml; + console.log(frontMatterYaml); + content = content.slice(frontMatterMatch[0].length); + let yawn = new YAWN(frontMatterYaml); + if (set_default ? !yawn.json.hasOwnProperty(fieldName) : true) { + yawn.json[fieldName] = fieldValue; + return `---\n${yawn.yaml}\n---` + content; + } + return `---\n${frontMatterYaml}\n---` + content; + } else { + const frontMatterYaml = stringifyYaml({ [fieldName]: fieldValue }); + const newFrontMatter = `---\n${frontMatterYaml}\n---\n`; + return newFrontMatter + content; + } +} + +// Set/setdefault a top level value in the front matter of a file. +export async function vaultFrontMatterSet( + vault: Vault, + file: TFile, + fieldName: string, + fieldValue: any, + set_default: boolean = false +) { + const old_file = await vault.cachedRead(file); + const new_file = stringFrontMatterSet( + old_file, + fieldName, + fieldValue, + set_default + ); + if (old_file != new_file) { + await vault.modify(file, new_file); + } +} + // Creates or modifies a front matter that has the field `fieldName: fieldValue`. // Returns true if a change to the note was made. export function verifyOrAddFrontMatter( From 37c38017dba1c7977aee858d18e4c223cabefb05 Mon Sep 17 00:00:00 2001 From: gentlegiantJGC Date: Sun, 15 May 2022 09:30:18 +0100 Subject: [PATCH 4/8] Modify the front matter location --- src/mapView.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/mapView.ts b/src/mapView.ts index ef1fa19..b60bd48 100644 --- a/src/mapView.ts +++ b/src/mapView.ts @@ -1,5 +1,4 @@ import { - App, TAbstractFile, Loc, Editor, @@ -9,7 +8,6 @@ import { TFile, WorkspaceLeaf, Notice, - ViewState, } from 'obsidian'; import * as leaflet from 'leaflet'; // Ugly hack for obsidian-leaflet compatability, see https://github.com/esm7/obsidian-map-view/issues/6 @@ -41,7 +39,6 @@ import { LocationSuggest } from 'src/geosearch'; import MapViewPlugin from 'src/main'; import * as utils from 'src/utils'; import { ViewControls } from 'src/viewControls'; -import { DragEndEvent, LeafletEvent } from 'leaflet'; export class MapView extends ItemView { private settings: PluginSettings; @@ -586,11 +583,17 @@ export class MapView extends ItemView { }); newMarker.on('dragend', async (event: leaflet.DragEndEvent) => { const latlng = newMarker.getLatLng(); - const location = `${latlng.lat},${latlng.lng}`; - if (marker.fileLocation === null) { + if (marker.fileLocation === undefined) { // is a front matter location + await utils.vaultFrontMatterSet( + this.app.vault, + marker.file, + 'location', + [latlng.lat, latlng.lng] + ); } else { // is an inline location + const location = `${latlng.lat},${latlng.lng}`; const old_file = await this.app.vault.cachedRead(marker.file); const new_file = old_file.slice(0, marker.fileLocation) + From 891e2c0fc55477861e2a106f490e3d14872b2c7e Mon Sep 17 00:00:00 2001 From: gentlegiantJGC Date: Sun, 15 May 2022 19:53:51 +0100 Subject: [PATCH 5/8] Switched yawn-yaml to obsidian API Switched yaml handling to the parseYaml and stringifyYaml functions from the obsidian API. They reformat the yaml so it is not ideal but does functionally work. --- src/utils.ts | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/src/utils.ts b/src/utils.ts index 08765d5..f304125 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -8,6 +8,7 @@ import { MenuItem, Vault, stringifyYaml, + parseYaml, } from 'obsidian'; import * as moment_ from 'moment'; @@ -17,7 +18,7 @@ import * as settings from './settings'; import * as consts from './consts'; import { MapView } from './mapView'; -import YAWN from 'yawn-yaml'; +// import YAWN from 'yawn-yaml'; export function formatWithTemplates(s: string, query = '') { const datePattern = /{{date:([a-zA-Z\-\/\.\:]*)}}/g; @@ -127,16 +128,27 @@ export function stringFrontMatterSet( let frontMatterYaml = frontMatterMatch.groups.yaml; console.log(frontMatterYaml); content = content.slice(frontMatterMatch[0].length); - let yawn = new YAWN(frontMatterYaml); - if (set_default ? !yawn.json.hasOwnProperty(fieldName) : true) { - yawn.json[fieldName] = fieldValue; - return `---\n${yawn.yaml}\n---` + content; + + // this works but modifies formatting + let yaml = parseYaml(frontMatterYaml); + if (set_default ? !yaml.hasOwnProperty(fieldName) : true) { + yaml[fieldName] = fieldValue; + frontMatterYaml = stringifyYaml(yaml); } + + // this is a better way to do it if we can get yawn-yaml to work + // let yawn = new YAWN(frontMatterYaml); + // if (set_default ? !yawn.json.hasOwnProperty(fieldName) : true) { + // yawn.json[fieldName] = fieldValue; + // return `---\n${yawn.yaml}\n---` + content; + // } + + // this does not have a trailing newline to preserve the formatting. If it had one, a new one would be added each time return `---\n${frontMatterYaml}\n---` + content; } else { const frontMatterYaml = stringifyYaml({ [fieldName]: fieldValue }); - const newFrontMatter = `---\n${frontMatterYaml}\n---\n`; - return newFrontMatter + content; + // this has a trailing newline to shift the old first line down below the front matter + return `---\n${frontMatterYaml}\n---\n` + content; } } From 2ba1760864d2dd5aa7cba5432532d54d9d9192cc Mon Sep 17 00:00:00 2001 From: gentlegiantJGC Date: Sun, 15 May 2022 20:49:35 +0100 Subject: [PATCH 6/8] Fixed formatting frontMatterYaml already ends in a newline so adding another one leads to a blank line --- src/utils.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/utils.ts b/src/utils.ts index f304125..b5d9312 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -124,9 +124,7 @@ export function stringFrontMatterSet( ): string { const frontMatterMatch = content.match(FRONT_MATTER_PATTERN); if (frontMatterMatch !== null) { - console.log(frontMatterMatch); let frontMatterYaml = frontMatterMatch.groups.yaml; - console.log(frontMatterYaml); content = content.slice(frontMatterMatch[0].length); // this works but modifies formatting @@ -144,11 +142,11 @@ export function stringFrontMatterSet( // } // this does not have a trailing newline to preserve the formatting. If it had one, a new one would be added each time - return `---\n${frontMatterYaml}\n---` + content; + return `---\n${frontMatterYaml}---` + content; } else { const frontMatterYaml = stringifyYaml({ [fieldName]: fieldValue }); // this has a trailing newline to shift the old first line down below the front matter - return `---\n${frontMatterYaml}\n---\n` + content; + return `---\n${frontMatterYaml}---\n` + content; } } From b2ddc48915254e86cd6ad54aee0ed1f294976066 Mon Sep 17 00:00:00 2001 From: gentlegiantJGC Date: Sun, 15 May 2022 21:54:24 +0100 Subject: [PATCH 7/8] Fixed front matter regex pattern On windows the end of line can be \r\n (the \r is optional) Make yaml match lazily so if the front matter is duplicated it only matches the first one. --- src/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils.ts b/src/utils.ts index b5d9312..b8334b9 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -36,7 +36,7 @@ export function formatWithTemplates(s: string, query = '') { type NewNoteType = 'singleLocation' | 'multiLocation'; const CURSOR = '$CURSOR$'; -const FRONT_MATTER_PATTERN = /^---\n(?.*)^---/ms; +const FRONT_MATTER_PATTERN = /^---\r?\n(?.*?)^---/ms; function sanitizeFileName(s: string) { const illegalChars = /[\?<>\\:\*\|":]/g; From 95616f44cf8bf12c2fc23eb221eac8233f0e5be5 Mon Sep 17 00:00:00 2001 From: gentlegiantJGC Date: Tue, 17 May 2022 08:52:39 +0100 Subject: [PATCH 8/8] Improved the front matter pattern There can be whitespace before the first --- in the yaml --- src/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils.ts b/src/utils.ts index b8334b9..bdf61f4 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -36,7 +36,7 @@ export function formatWithTemplates(s: string, query = '') { type NewNoteType = 'singleLocation' | 'multiLocation'; const CURSOR = '$CURSOR$'; -const FRONT_MATTER_PATTERN = /^---\r?\n(?.*?)^---/ms; +const FRONT_MATTER_PATTERN = /\s*^---\r?\n(?.*?)^---/ms; function sanitizeFileName(s: string) { const illegalChars = /[\?<>\\:\*\|":]/g;