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/mapView.ts b/src/mapView.ts index 550407c..9d5d25b 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 @@ -74,6 +72,8 @@ export class MapView extends ItemView { /** Is the view currently open */ private isOpen: boolean = false; + private editable: boolean = false; + /** * Construct a new map instance * @param leaf The leaf the map should be put in @@ -577,6 +577,36 @@ 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(); + 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) + + `[](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() @@ -733,4 +763,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/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; diff --git a/src/utils.ts b/src/utils.ts index 73aab9d..bdf61f4 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -6,6 +6,9 @@ import { TFile, Menu, MenuItem, + Vault, + stringifyYaml, + parseYaml, } from 'obsidian'; import * as moment_ from 'moment'; @@ -15,6 +18,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 +36,7 @@ export function formatWithTemplates(s: string, query = '') { type NewNoteType = 'singleLocation' | 'multiLocation'; const CURSOR = '$CURSOR$'; +const FRONT_MATTER_PATTERN = /\s*^---\r?\n(?.*?)^---/ms; function sanitizeFileName(s: string) { const illegalChars = /[\?<>\\:\*\|":]/g; @@ -108,6 +114,62 @@ 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) { + let frontMatterYaml = frontMatterMatch.groups.yaml; + content = content.slice(frontMatterMatch[0].length); + + // 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}---` + 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` + 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( 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); }