Skip to content

Commit 76ca92a

Browse files
committed
Merge remote-tracking branch 'origin/master' into v6.0
2 parents 69b82c5 + 1e0ecca commit 76ca92a

6 files changed

+86
-14
lines changed

README.md

+13
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,19 @@ Map View adds an Obsidian command named "New geolocation note", which you can ma
232232
233233
This opens a dialog on which you can search (address or location based on your [configured geocoding provider](#changing-a-geocoding-provider)) or paste a URL using the built-in or custom [URL parsing rules](#url-parsing-rules).
234234
235+
When using Google Maps Places API, templates can extract additional result data.
236+
For instance, templates that match `googleMapsPlaceData.some.path` will be replaced with the value found at `some.path` in the [JSON serach result](https://developers.google.com/maps/documentation/places/web-service/place-data-fields).
237+
For instance, the following template would set a [place_id](https://developers.google.com/maps/documentation/places/web-service/place-id) property and tags the note with the [type](https://developers.google.com/maps/documentation/places/web-service/supported_types) of the place:
238+
239+
```
240+
---
241+
place_id: "{{googleMapsPlaceData.place_id}}"
242+
---
243+
#{{googleMapsPlaceData.types.0}}
244+
```
245+
246+
Currently only Google Maps Places API supports this advanced templating feature.
247+
235248
![](img/new-note-popup.gif)
236249

237250
### In an Existing Note

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
"@types/leaflet": "^1.9.15",
2727
"@types/leaflet.markercluster": "^1.5.5",
2828
"@types/node": "^22.10.1",
29+
"@types/google.maps": "^3.58.1",
2930
"codemirror": "^6.0.1",
3031
"lefthook": "^1.8.5",
3132
"obsidian": "^1.7.2",

src/geosearch.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { type PluginSettings } from 'src/settings';
77
import { UrlConvertor } from 'src/urlConvertor';
88
import { FileMarker } from 'src/markers';
99
import * as consts from 'src/consts';
10+
import * as utils from 'src/utils';
1011

1112
/**
1213
* A generic result of a geosearch
@@ -17,6 +18,7 @@ export class GeoSearchResult {
1718
location: leaflet.LatLng;
1819
resultType: 'searchResult' | 'url' | 'existingMarker';
1920
existingMarker?: FileMarker;
21+
extraLocationData?: utils.ExtraLocationData;
2022
}
2123

2224
export class GeoSearcher {
@@ -76,6 +78,7 @@ export class GeoSearcher {
7678
name: result.name,
7779
location: result.location,
7880
resultType: 'searchResult',
81+
extraLocationData: result.extraLocationData,
7982
});
8083
} catch (e) {
8184
console.log(
@@ -128,7 +131,7 @@ export async function googlePlacesSearch(
128131
'https://maps.googleapis.com/maps/api/place/textsearch/json?' +
129132
queryString.stringify(params);
130133
const googleContent = await request({ url: googleUrl });
131-
const jsonContent = JSON.parse(googleContent) as any;
134+
const jsonContent = JSON.parse(googleContent);
132135
let results: GeoSearchResult[] = [];
133136
if (
134137
jsonContent &&
@@ -146,6 +149,7 @@ export async function googlePlacesSearch(
146149
name: `${result?.name} (${result?.formatted_address})`,
147150
location: geolocation,
148151
resultType: 'searchResult',
152+
extraLocationData: { googleMapsPlaceData: result },
149153
} as GeoSearchResult);
150154
}
151155
}

src/locationSearchDialog.ts

+1
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ export class LocationSearchDialog extends SuggestModal<SuggestInfo> {
143143
value.location,
144144
evt,
145145
utils.sanitizePlaceNameForNoteName(value.name),
146+
value.extraLocationData,
146147
);
147148
else if (this.dialogAction == 'addToNote')
148149
this.addToNote(value.location, evt, value.name);

src/main.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ export default class MapViewPlugin extends Plugin {
121121
)
122122
: null;
123123
if (location) {
124-
this.newFrontMatterNote(location, null, label);
124+
this.newFrontMatterNote(location, null, label, {});
125125
}
126126
} else if (params.mvaction === 'addtocurrentnotefm') {
127127
const location =
@@ -951,6 +951,7 @@ export default class MapViewPlugin extends Plugin {
951951
location: leaflet.LatLng,
952952
ev: MouseEvent | KeyboardEvent | null,
953953
query: string,
954+
extraLocationData: utils.ExtraLocationData,
954955
) {
955956
const locationString = `${location.lat},${location.lng}`;
956957
const newFileName = utils.formatWithTemplates(
@@ -965,6 +966,7 @@ export default class MapViewPlugin extends Plugin {
965966
locationString,
966967
this.settings.frontMatterKey,
967968
this.settings.newNoteTemplate,
969+
extraLocationData,
968970
);
969971
// If there is an open map view, use it to decide how and where to open the file.
970972
// Otherwise, open the file from the active leaf

src/utils.ts

+63-12
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
App,
77
TFile,
88
getAllTags,
9+
getFrontMatterInfo,
910
type CachedMetadata,
1011
type HeadingCache,
1112
type BlockCache,
@@ -39,16 +40,48 @@ export function getLastUsedValidMarkdownLeaf() {
3940
return null;
4041
}
4142

42-
export function formatWithTemplates(s: string, query = '') {
43+
function resolveJsonPath(json: object, path: string): string | undefined {
44+
// Convert a string path like "some.path.to.data.0" to the value at that path in JSON
45+
// Remove leading/trailing curly braces and split the path into parts
46+
const pathParts = path.replace(/[{}]/g, '').split('.');
47+
// Use reduce with optional chaining to traverse the path
48+
return pathParts.reduce((current, part) => (current as any)?.[part], json);
49+
}
50+
51+
export type ExtraLocationData = {
52+
googleMapsPlaceData?: google.maps.places.PlaceResult;
53+
};
54+
55+
function replaceJsonPaths(content: string, json: ExtraLocationData) {
56+
// Use regex to find all patterns like {{some.path.to.data.0}}
57+
58+
// Find patterns to replace that start with an attribute of json
59+
for (const [key, data] of Object.entries(json)) {
60+
const regex = new RegExp(`{{${key}\\.(.*?)}}`, 'g');
61+
return content.replace(regex, (_, path: string) => {
62+
const result = resolveJsonPath(data, path);
63+
return result ? result : '';
64+
});
65+
}
66+
return content;
67+
}
68+
69+
export function formatWithTemplates(
70+
s: string,
71+
query = '',
72+
extraLocationData: ExtraLocationData = {},
73+
) {
4374
const datePattern = /{{date:([a-zA-Z\-\/\.\:]*)}}/g;
4475
const queryPattern = /{{query}}/g;
45-
const replaced = s
76+
let replaced = s
4677
.replace(datePattern, (_, pattern) => {
4778
// @ts-ignore
4879
return moment().format(pattern);
4980
})
5081
.replace(queryPattern, query);
5182

83+
replaced = replaceJsonPaths(replaced, extraLocationData);
84+
5285
return replaced;
5386
}
5487

@@ -99,16 +132,37 @@ export async function newNote(
99132
fileName: string,
100133
location: string,
101134
frontMatterKey: string,
102-
templatePath?: string,
135+
templatePath: string,
136+
extraLocationData?: ExtraLocationData,
103137
): Promise<[TFile, number]> {
104-
// `$CURSOR$` is used to set the cursor
105-
let content =
106-
newNoteType === 'singleLocation'
107-
? `---\n${frontMatterKey}: ${location}\n---\n\n${CURSOR}`
108-
: `---\nlocations:\n---\n\n\[${CURSOR}](geo:${location})\n`;
109138
let templateContent = '';
110139
if (templatePath && templatePath.length > 0)
111140
templateContent = await app.vault.adapter.read(templatePath);
141+
142+
templateContent = formatWithTemplates(
143+
templateContent,
144+
'',
145+
extraLocationData,
146+
);
147+
148+
const templateFrontMatterInfo = getFrontMatterInfo(templateContent);
149+
150+
let newFrontMatterContents;
151+
let contentsBody;
152+
// `$CURSOR$` is used to set the cursor
153+
if (newNoteType === 'singleLocation') {
154+
newFrontMatterContents = `${frontMatterKey}: "${location}"`;
155+
contentsBody = CURSOR;
156+
} else {
157+
newFrontMatterContents = 'locations:';
158+
contentsBody = `[${CURSOR}](geo:${location})\n`;
159+
}
160+
let content = `---\n${newFrontMatterContents}\n${
161+
templateFrontMatterInfo.frontmatter
162+
}---\n\n${contentsBody}${templateContent.substring(
163+
templateFrontMatterInfo.contentStart,
164+
)}`;
165+
112166
if (!directory) directory = '';
113167
if (!fileName) fileName = '';
114168
// Apparently in Obsidian Mobile there is no path.join function, not sure why.
@@ -124,10 +178,7 @@ export async function newNote(
124178
const cursorLocation = content.indexOf(CURSOR);
125179
content = content.replace(CURSOR, '');
126180
try {
127-
const file = await app.vault.create(
128-
fullName + '.md',
129-
content + templateContent,
130-
);
181+
const file = await app.vault.create(fullName + '.md', content);
131182
return [file, cursorLocation];
132183
} catch (e) {
133184
console.log('Map View: cannot create file', fullName);

0 commit comments

Comments
 (0)