Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
3bada05b63
|
|||
|
f01730fef5
|
|||
|
448c51bab6
|
|||
|
0bcbae374b
|
|||
|
c33fe3b268
|
|||
|
18bda60310
|
|||
|
86d25dc6ba
|
|||
|
5b8bec6a00
|
|||
|
f2c2eb1fdc
|
|||
|
b42c4881f6
|
|||
|
b18e299eca
|
|||
|
401ed41fcd
|
|||
|
504e8fab94
|
|||
|
76897c9e69
|
@@ -5,7 +5,11 @@ import { tracked } from '@glimmer/tracking';
|
|||||||
import { service } from '@ember/service';
|
import { service } from '@ember/service';
|
||||||
import { fn } from '@ember/helper';
|
import { fn } from '@ember/helper';
|
||||||
import Icon from '#components/icon';
|
import Icon from '#components/icon';
|
||||||
import { normalizeRelayUrl } from '../../../utils/nostr';
|
import {
|
||||||
|
excludeRequiredRelays,
|
||||||
|
mergeRequiredRelays,
|
||||||
|
normalizeRelayUrl,
|
||||||
|
} from '../../../utils/nostr';
|
||||||
|
|
||||||
const stripProtocol = (url) => (url ? url.replace(/^wss?:\/\//, '') : '');
|
const stripProtocol = (url) => (url ? url.replace(/^wss?:\/\//, '') : '');
|
||||||
|
|
||||||
@@ -17,6 +21,74 @@ export default class AppMenuSettingsNostr extends Component {
|
|||||||
@tracked newReadRelay = '';
|
@tracked newReadRelay = '';
|
||||||
@tracked newWriteRelay = '';
|
@tracked newWriteRelay = '';
|
||||||
|
|
||||||
|
get customReadRelays() {
|
||||||
|
return excludeRequiredRelays(
|
||||||
|
this.settings.nostrReadRelays || [],
|
||||||
|
this.nostrData.requiredReadRelays
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
get customWriteRelays() {
|
||||||
|
return excludeRequiredRelays(
|
||||||
|
this.settings.nostrWriteRelays || [],
|
||||||
|
this.nostrData.requiredWriteRelays
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
get readRelayExclusions() {
|
||||||
|
return this.settings.nostrReadRelayExclusions || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
get writeRelayExclusions() {
|
||||||
|
return this.settings.nostrWriteRelayExclusions || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
get requiredReadRelaySet() {
|
||||||
|
return new Set(this.nostrData.requiredReadRelays.filter(Boolean));
|
||||||
|
}
|
||||||
|
|
||||||
|
get requiredWriteRelaySet() {
|
||||||
|
return new Set(this.nostrData.requiredWriteRelays.filter(Boolean));
|
||||||
|
}
|
||||||
|
|
||||||
|
get mailboxReadRelaySet() {
|
||||||
|
return new Set(this.nostrData.mailboxReadRelays);
|
||||||
|
}
|
||||||
|
|
||||||
|
get mailboxWriteRelaySet() {
|
||||||
|
return new Set(this.nostrData.mailboxWriteRelays);
|
||||||
|
}
|
||||||
|
|
||||||
|
get hasReadOverrides() {
|
||||||
|
return (
|
||||||
|
this.customReadRelays.length > 0 || this.readRelayExclusions.length > 0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
get hasWriteOverrides() {
|
||||||
|
return (
|
||||||
|
this.customWriteRelays.length > 0 || this.writeRelayExclusions.length > 0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
get readRelaysForDisplay() {
|
||||||
|
return this.nostrData.activeReadRelays.map((url) => {
|
||||||
|
return {
|
||||||
|
url,
|
||||||
|
isRequired: this.requiredReadRelaySet.has(url),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
get writeRelaysForDisplay() {
|
||||||
|
return this.nostrData.activeWriteRelays.map((url) => {
|
||||||
|
return {
|
||||||
|
url,
|
||||||
|
isRequired: this.requiredWriteRelaySet.has(url),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
updateNewReadRelay(event) {
|
updateNewReadRelay(event) {
|
||||||
this.newReadRelay = event.target.value;
|
this.newReadRelay = event.target.value;
|
||||||
@@ -32,19 +104,51 @@ export default class AppMenuSettingsNostr extends Component {
|
|||||||
const url = normalizeRelayUrl(this.newReadRelay);
|
const url = normalizeRelayUrl(this.newReadRelay);
|
||||||
if (!url) return;
|
if (!url) return;
|
||||||
|
|
||||||
const current =
|
const merged = mergeRequiredRelays(this.nostrData.requiredReadRelays, [
|
||||||
this.settings.nostrReadRelays || this.nostrData.defaultReadRelays;
|
...this.customReadRelays,
|
||||||
const set = new Set([...current, url]);
|
url,
|
||||||
this.settings.update('nostrReadRelays', Array.from(set));
|
]);
|
||||||
|
const custom = excludeRequiredRelays(
|
||||||
|
merged,
|
||||||
|
this.nostrData.requiredReadRelays
|
||||||
|
);
|
||||||
|
|
||||||
|
const readExclusions = this.readRelayExclusions.filter((relay) => {
|
||||||
|
return normalizeRelayUrl(relay) !== url;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.settings.update('nostrReadRelays', custom.length > 0 ? custom : null);
|
||||||
|
this.settings.update(
|
||||||
|
'nostrReadRelayExclusions',
|
||||||
|
readExclusions.length > 0 ? readExclusions : null
|
||||||
|
);
|
||||||
this.newReadRelay = '';
|
this.newReadRelay = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
removeReadRelay(url) {
|
removeReadRelay(url) {
|
||||||
const current =
|
if (this.requiredReadRelaySet.has(url)) {
|
||||||
this.settings.nostrReadRelays || this.nostrData.defaultReadRelays;
|
return;
|
||||||
const filtered = current.filter((r) => r !== url);
|
}
|
||||||
this.settings.update('nostrReadRelays', filtered);
|
|
||||||
|
const normalizedUrl = normalizeRelayUrl(url);
|
||||||
|
|
||||||
|
const remainingCustom = this.customReadRelays.filter((relay) => {
|
||||||
|
return normalizeRelayUrl(relay) !== normalizedUrl;
|
||||||
|
});
|
||||||
|
|
||||||
|
const nextExclusions = this.mailboxReadRelaySet.has(normalizedUrl)
|
||||||
|
? Array.from(new Set([...this.readRelayExclusions, normalizedUrl]))
|
||||||
|
: this.readRelayExclusions;
|
||||||
|
|
||||||
|
this.settings.update(
|
||||||
|
'nostrReadRelays',
|
||||||
|
remainingCustom.length > 0 ? remainingCustom : null
|
||||||
|
);
|
||||||
|
this.settings.update(
|
||||||
|
'nostrReadRelayExclusions',
|
||||||
|
nextExclusions.length > 0 ? nextExclusions : null
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
@@ -64,6 +168,7 @@ export default class AppMenuSettingsNostr extends Component {
|
|||||||
@action
|
@action
|
||||||
resetReadRelays() {
|
resetReadRelays() {
|
||||||
this.settings.update('nostrReadRelays', null);
|
this.settings.update('nostrReadRelays', null);
|
||||||
|
this.settings.update('nostrReadRelayExclusions', null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
@@ -71,24 +176,57 @@ export default class AppMenuSettingsNostr extends Component {
|
|||||||
const url = normalizeRelayUrl(this.newWriteRelay);
|
const url = normalizeRelayUrl(this.newWriteRelay);
|
||||||
if (!url) return;
|
if (!url) return;
|
||||||
|
|
||||||
const current =
|
const merged = mergeRequiredRelays(this.nostrData.requiredWriteRelays, [
|
||||||
this.settings.nostrWriteRelays || this.nostrData.defaultWriteRelays;
|
...this.customWriteRelays,
|
||||||
const set = new Set([...current, url]);
|
url,
|
||||||
this.settings.update('nostrWriteRelays', Array.from(set));
|
]);
|
||||||
|
const custom = excludeRequiredRelays(
|
||||||
|
merged,
|
||||||
|
this.nostrData.requiredWriteRelays
|
||||||
|
);
|
||||||
|
|
||||||
|
const writeExclusions = this.writeRelayExclusions.filter((relay) => {
|
||||||
|
return normalizeRelayUrl(relay) !== url;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.settings.update('nostrWriteRelays', custom.length > 0 ? custom : null);
|
||||||
|
this.settings.update(
|
||||||
|
'nostrWriteRelayExclusions',
|
||||||
|
writeExclusions.length > 0 ? writeExclusions : null
|
||||||
|
);
|
||||||
this.newWriteRelay = '';
|
this.newWriteRelay = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
removeWriteRelay(url) {
|
removeWriteRelay(url) {
|
||||||
const current =
|
if (this.requiredWriteRelaySet.has(url)) {
|
||||||
this.settings.nostrWriteRelays || this.nostrData.defaultWriteRelays;
|
return;
|
||||||
const filtered = current.filter((r) => r !== url);
|
}
|
||||||
this.settings.update('nostrWriteRelays', filtered);
|
|
||||||
|
const normalizedUrl = normalizeRelayUrl(url);
|
||||||
|
|
||||||
|
const remainingCustom = this.customWriteRelays.filter((relay) => {
|
||||||
|
return normalizeRelayUrl(relay) !== normalizedUrl;
|
||||||
|
});
|
||||||
|
|
||||||
|
const nextExclusions = this.mailboxWriteRelaySet.has(normalizedUrl)
|
||||||
|
? Array.from(new Set([...this.writeRelayExclusions, normalizedUrl]))
|
||||||
|
: this.writeRelayExclusions;
|
||||||
|
|
||||||
|
this.settings.update(
|
||||||
|
'nostrWriteRelays',
|
||||||
|
remainingCustom.length > 0 ? remainingCustom : null
|
||||||
|
);
|
||||||
|
this.settings.update(
|
||||||
|
'nostrWriteRelayExclusions',
|
||||||
|
nextExclusions.length > 0 ? nextExclusions : null
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
resetWriteRelays() {
|
resetWriteRelays() {
|
||||||
this.settings.update('nostrWriteRelays', null);
|
this.settings.update('nostrWriteRelays', null);
|
||||||
|
this.settings.update('nostrWriteRelayExclusions', null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
@@ -112,18 +250,20 @@ export default class AppMenuSettingsNostr extends Component {
|
|||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="new-read-relay">Read Relays</label>
|
<label for="new-read-relay">Read Relays</label>
|
||||||
<ul class="relay-list">
|
<ul class="relay-list">
|
||||||
{{#each this.nostrData.activeReadRelays as |relay|}}
|
{{#each this.readRelaysForDisplay as |relay|}}
|
||||||
<li>
|
<li>
|
||||||
<span>{{stripProtocol relay}}</span>
|
<span>{{stripProtocol relay.url}}</span>
|
||||||
|
{{#unless relay.isRequired}}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="btn-remove-relay"
|
class="btn-remove-relay"
|
||||||
title="Remove relay"
|
title="Remove relay"
|
||||||
aria-label="Remove"
|
aria-label="Remove"
|
||||||
{{on "click" (fn this.removeReadRelay relay)}}
|
{{on "click" (fn this.removeReadRelay relay.url)}}
|
||||||
>
|
>
|
||||||
<Icon @name="x" @size={{14}} @color="currentColor" />
|
<Icon @name="x" @size={{14}} @color="currentColor" />
|
||||||
</button>
|
</button>
|
||||||
|
{{/unless}}
|
||||||
</li>
|
</li>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</ul>
|
</ul>
|
||||||
@@ -143,7 +283,7 @@ export default class AppMenuSettingsNostr extends Component {
|
|||||||
{{on "click" this.addReadRelay}}
|
{{on "click" this.addReadRelay}}
|
||||||
>Add</button>
|
>Add</button>
|
||||||
</div>
|
</div>
|
||||||
{{#if this.settings.nostrReadRelays}}
|
{{#if this.hasReadOverrides}}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="btn-link reset-relays"
|
class="btn-link reset-relays"
|
||||||
@@ -157,18 +297,20 @@ export default class AppMenuSettingsNostr extends Component {
|
|||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="new-write-relay">Write Relays</label>
|
<label for="new-write-relay">Write Relays</label>
|
||||||
<ul class="relay-list">
|
<ul class="relay-list">
|
||||||
{{#each this.nostrData.activeWriteRelays as |relay|}}
|
{{#each this.writeRelaysForDisplay as |relay|}}
|
||||||
<li>
|
<li>
|
||||||
<span>{{stripProtocol relay}}</span>
|
<span>{{stripProtocol relay.url}}</span>
|
||||||
|
{{#unless relay.isRequired}}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="btn-remove-relay"
|
class="btn-remove-relay"
|
||||||
title="Remove relay"
|
title="Remove relay"
|
||||||
aria-label="Remove"
|
aria-label="Remove"
|
||||||
{{on "click" (fn this.removeWriteRelay relay)}}
|
{{on "click" (fn this.removeWriteRelay relay.url)}}
|
||||||
>
|
>
|
||||||
<Icon @name="x" @size={{14}} @color="currentColor" />
|
<Icon @name="x" @size={{14}} @color="currentColor" />
|
||||||
</button>
|
</button>
|
||||||
|
{{/unless}}
|
||||||
</li>
|
</li>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</ul>
|
</ul>
|
||||||
@@ -188,7 +330,7 @@ export default class AppMenuSettingsNostr extends Component {
|
|||||||
{{on "click" this.addWriteRelay}}
|
{{on "click" this.addWriteRelay}}
|
||||||
>Add</button>
|
>Add</button>
|
||||||
</div>
|
</div>
|
||||||
{{#if this.settings.nostrWriteRelays}}
|
{{#if this.hasWriteOverrides}}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="btn-link reset-relays"
|
class="btn-link reset-relays"
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import { Style, Circle, Fill, Stroke, Icon } from 'ol/style.js';
|
|||||||
import { apply } from 'ol-mapbox-style';
|
import { apply } from 'ol-mapbox-style';
|
||||||
import { getIcon } from '../utils/icons';
|
import { getIcon } from '../utils/icons';
|
||||||
import { getIconNameForTags } from '../utils/osm-icons';
|
import { getIconNameForTags } from '../utils/osm-icons';
|
||||||
import config from 'marco/config/environment';
|
|
||||||
|
|
||||||
export default class MapComponent extends Component {
|
export default class MapComponent extends Component {
|
||||||
@service osm;
|
@service osm;
|
||||||
@@ -287,26 +286,9 @@ export default class MapComponent extends Component {
|
|||||||
this.mapUi.updateCenter(initialCenter[1], initialCenter[0]);
|
this.mapUi.updateCenter(initialCenter[1], initialCenter[0]);
|
||||||
this.mapUi.updateZoom(view.getZoom());
|
this.mapUi.updateZoom(view.getZoom());
|
||||||
|
|
||||||
if (config.environment === 'test') {
|
|
||||||
apply(this.mapInstance, {
|
|
||||||
version: 8,
|
|
||||||
name: 'Test Style',
|
|
||||||
sources: {},
|
|
||||||
layers: [
|
|
||||||
{
|
|
||||||
id: 'background',
|
|
||||||
type: 'background',
|
|
||||||
paint: {
|
|
||||||
'background-color': '#f8f4f0',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
apply(this.mapInstance, 'https://tiles.openfreemap.org/styles/liberty', {
|
apply(this.mapInstance, 'https://tiles.openfreemap.org/styles/liberty', {
|
||||||
webfonts: 'data:text/css,',
|
webfonts: 'data:text/css,',
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
this.searchOverlayElement = document.createElement('div');
|
this.searchOverlayElement = document.createElement('div');
|
||||||
this.searchOverlayElement.className = 'search-pulse';
|
this.searchOverlayElement.className = 'search-pulse';
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ export default class PlaceDetails extends Component {
|
|||||||
@service storage;
|
@service storage;
|
||||||
@service nostrAuth;
|
@service nostrAuth;
|
||||||
@service nostrData;
|
@service nostrData;
|
||||||
|
@service mapUi;
|
||||||
@tracked isEditing = false;
|
@tracked isEditing = false;
|
||||||
@tracked showLists = false;
|
@tracked showLists = false;
|
||||||
@tracked isPhotoUploadActive = false;
|
@tracked isPhotoUploadActive = false;
|
||||||
@@ -345,11 +346,23 @@ export default class PlaceDetails extends Component {
|
|||||||
|
|
||||||
get osmUrl() {
|
get osmUrl() {
|
||||||
const id = this.place.osmId;
|
const id = this.place.osmId;
|
||||||
if (!id) return null;
|
if (id) {
|
||||||
const type = this.place.osmType || 'node';
|
const type = this.place.osmType || 'node';
|
||||||
return `https://www.openstreetmap.org/${type}/${id}`;
|
return `https://www.openstreetmap.org/${type}/${id}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const lat = this.place.lat;
|
||||||
|
const lon = this.place.lon;
|
||||||
|
if (!lat || !lon) return null;
|
||||||
|
|
||||||
|
const viewLat = this.mapUi.currentCenter?.lat ?? lat;
|
||||||
|
const viewLon = this.mapUi.currentCenter?.lon ?? lon;
|
||||||
|
const zoom = this.mapUi.currentZoom ?? 17;
|
||||||
|
const roundedZoom = Math.round(zoom);
|
||||||
|
|
||||||
|
return `https://www.openstreetmap.org/search?lat=${lat}&lon=${lon}&zoom=${roundedZoom}#map=${roundedZoom}/${Number(viewLat).toFixed(5)}/${Number(viewLon).toFixed(5)}`;
|
||||||
|
}
|
||||||
|
|
||||||
get gmapsUrl() {
|
get gmapsUrl() {
|
||||||
const id = this.place.gmapsId || this.place.osmId;
|
const id = this.place.gmapsId || this.place.osmId;
|
||||||
if (!id) return null;
|
if (!id) return null;
|
||||||
@@ -591,7 +604,7 @@ export default class PlaceDetails extends Component {
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{#if this.osmUrl}}
|
{{#if this.place.osmId}}
|
||||||
<div class="meta-info">
|
<div class="meta-info">
|
||||||
<p class="content-with-icon">
|
<p class="content-with-icon">
|
||||||
<Icon @name="feather-camera" />
|
<Icon @name="feather-camera" />
|
||||||
|
|||||||
+50
-26
@@ -6,7 +6,12 @@ import { MailboxesModel } from 'applesauce-core/models/mailboxes';
|
|||||||
import { npubEncode } from 'applesauce-core/helpers/pointers';
|
import { npubEncode } from 'applesauce-core/helpers/pointers';
|
||||||
import { persistEventsToCache } from 'applesauce-core/helpers/event-cache';
|
import { persistEventsToCache } from 'applesauce-core/helpers/event-cache';
|
||||||
import { NostrIDB, openDB } from 'nostr-idb';
|
import { NostrIDB, openDB } from 'nostr-idb';
|
||||||
import { normalizeRelayUrl } from '../utils/nostr';
|
import {
|
||||||
|
excludeRequiredRelays,
|
||||||
|
mergeRequiredRelays,
|
||||||
|
normalizeRelayUrl,
|
||||||
|
uniqNormalizedRelays,
|
||||||
|
} from '../utils/nostr';
|
||||||
import { getGeohashPrefixesInBbox } from '../utils/geohash-coverage';
|
import { getGeohashPrefixesInBbox } from '../utils/geohash-coverage';
|
||||||
|
|
||||||
const DIRECTORY_RELAYS = [
|
const DIRECTORY_RELAYS = [
|
||||||
@@ -83,44 +88,63 @@ export default class NostrDataService extends Service {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
get defaultReadRelays() {
|
get requiredReadRelays() {
|
||||||
const mailboxes = (this.mailboxes?.inboxes || [])
|
return DEFAULT_READ_RELAYS;
|
||||||
.map(normalizeRelayUrl)
|
|
||||||
.filter(Boolean);
|
|
||||||
const defaults = DEFAULT_READ_RELAYS.map(normalizeRelayUrl).filter(Boolean);
|
|
||||||
return Array.from(new Set([...defaults, ...mailboxes]));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get defaultWriteRelays() {
|
get requiredWriteRelays() {
|
||||||
const mailboxes = (this.mailboxes?.outboxes || [])
|
return DEFAULT_WRITE_RELAYS;
|
||||||
|
}
|
||||||
|
|
||||||
|
get mailboxReadRelays() {
|
||||||
|
return (this.mailboxes?.inboxes || [])
|
||||||
.map(normalizeRelayUrl)
|
.map(normalizeRelayUrl)
|
||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
const defaults =
|
}
|
||||||
DEFAULT_WRITE_RELAYS.map(normalizeRelayUrl).filter(Boolean);
|
|
||||||
return Array.from(new Set([...defaults, ...mailboxes]));
|
get mailboxWriteRelays() {
|
||||||
|
return (this.mailboxes?.outboxes || [])
|
||||||
|
.map(normalizeRelayUrl)
|
||||||
|
.filter(Boolean);
|
||||||
|
}
|
||||||
|
|
||||||
|
get configuredReadRelays() {
|
||||||
|
const configured = uniqNormalizedRelays([
|
||||||
|
...this.mailboxReadRelays,
|
||||||
|
...(this.settings.nostrReadRelays || []),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return excludeRequiredRelays(
|
||||||
|
configured,
|
||||||
|
this.settings.nostrReadRelayExclusions || []
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
get configuredWriteRelays() {
|
||||||
|
const configured = uniqNormalizedRelays([
|
||||||
|
...this.mailboxWriteRelays,
|
||||||
|
...(this.settings.nostrWriteRelays || []),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return excludeRequiredRelays(
|
||||||
|
configured,
|
||||||
|
this.settings.nostrWriteRelayExclusions || []
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
get activeReadRelays() {
|
get activeReadRelays() {
|
||||||
if (this.settings.nostrReadRelays) {
|
return mergeRequiredRelays(
|
||||||
return Array.from(
|
this.requiredReadRelays,
|
||||||
new Set(
|
this.configuredReadRelays
|
||||||
this.settings.nostrReadRelays.map(normalizeRelayUrl).filter(Boolean)
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return this.defaultReadRelays;
|
|
||||||
}
|
|
||||||
|
|
||||||
get activeWriteRelays() {
|
get activeWriteRelays() {
|
||||||
if (this.settings.nostrWriteRelays) {
|
return mergeRequiredRelays(
|
||||||
return Array.from(
|
this.requiredWriteRelays,
|
||||||
new Set(
|
this.configuredWriteRelays
|
||||||
this.settings.nostrWriteRelays.map(normalizeRelayUrl).filter(Boolean)
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return this.defaultWriteRelays;
|
|
||||||
}
|
|
||||||
|
|
||||||
async loadPlacesInBounds(bbox) {
|
async loadPlacesInBounds(bbox) {
|
||||||
const requiredPrefixes = getGeohashPrefixesInBbox(bbox);
|
const requiredPrefixes = getGeohashPrefixesInBbox(bbox);
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ const DEFAULT_SETTINGS = {
|
|||||||
nostrPhotoFallbackUploads: false,
|
nostrPhotoFallbackUploads: false,
|
||||||
nostrReadRelays: null,
|
nostrReadRelays: null,
|
||||||
nostrWriteRelays: null,
|
nostrWriteRelays: null,
|
||||||
|
nostrReadRelayExclusions: null,
|
||||||
|
nostrWriteRelayExclusions: null,
|
||||||
experimentalEnablePhotoDeletion: false,
|
experimentalEnablePhotoDeletion: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -21,6 +23,9 @@ export default class SettingsService extends Service {
|
|||||||
DEFAULT_SETTINGS.nostrPhotoFallbackUploads;
|
DEFAULT_SETTINGS.nostrPhotoFallbackUploads;
|
||||||
@tracked nostrReadRelays = DEFAULT_SETTINGS.nostrReadRelays;
|
@tracked nostrReadRelays = DEFAULT_SETTINGS.nostrReadRelays;
|
||||||
@tracked nostrWriteRelays = DEFAULT_SETTINGS.nostrWriteRelays;
|
@tracked nostrWriteRelays = DEFAULT_SETTINGS.nostrWriteRelays;
|
||||||
|
@tracked nostrReadRelayExclusions = DEFAULT_SETTINGS.nostrReadRelayExclusions;
|
||||||
|
@tracked nostrWriteRelayExclusions =
|
||||||
|
DEFAULT_SETTINGS.nostrWriteRelayExclusions;
|
||||||
@tracked experimentalEnablePhotoDeletion =
|
@tracked experimentalEnablePhotoDeletion =
|
||||||
DEFAULT_SETTINGS.experimentalEnablePhotoDeletion;
|
DEFAULT_SETTINGS.experimentalEnablePhotoDeletion;
|
||||||
|
|
||||||
@@ -111,6 +116,8 @@ export default class SettingsService extends Service {
|
|||||||
this.nostrPhotoFallbackUploads = finalSettings.nostrPhotoFallbackUploads;
|
this.nostrPhotoFallbackUploads = finalSettings.nostrPhotoFallbackUploads;
|
||||||
this.nostrReadRelays = finalSettings.nostrReadRelays;
|
this.nostrReadRelays = finalSettings.nostrReadRelays;
|
||||||
this.nostrWriteRelays = finalSettings.nostrWriteRelays;
|
this.nostrWriteRelays = finalSettings.nostrWriteRelays;
|
||||||
|
this.nostrReadRelayExclusions = finalSettings.nostrReadRelayExclusions;
|
||||||
|
this.nostrWriteRelayExclusions = finalSettings.nostrWriteRelayExclusions;
|
||||||
this.experimentalEnablePhotoDeletion =
|
this.experimentalEnablePhotoDeletion =
|
||||||
finalSettings.experimentalEnablePhotoDeletion;
|
finalSettings.experimentalEnablePhotoDeletion;
|
||||||
|
|
||||||
@@ -127,6 +134,8 @@ export default class SettingsService extends Service {
|
|||||||
nostrPhotoFallbackUploads: this.nostrPhotoFallbackUploads,
|
nostrPhotoFallbackUploads: this.nostrPhotoFallbackUploads,
|
||||||
nostrReadRelays: this.nostrReadRelays,
|
nostrReadRelays: this.nostrReadRelays,
|
||||||
nostrWriteRelays: this.nostrWriteRelays,
|
nostrWriteRelays: this.nostrWriteRelays,
|
||||||
|
nostrReadRelayExclusions: this.nostrReadRelayExclusions,
|
||||||
|
nostrWriteRelayExclusions: this.nostrWriteRelayExclusions,
|
||||||
experimentalEnablePhotoDeletion: this.experimentalEnablePhotoDeletion,
|
experimentalEnablePhotoDeletion: this.experimentalEnablePhotoDeletion,
|
||||||
};
|
};
|
||||||
localStorage.setItem('marco:settings', JSON.stringify(settings));
|
localStorage.setItem('marco:settings', JSON.stringify(settings));
|
||||||
|
|||||||
@@ -1266,6 +1266,20 @@ span.icon {
|
|||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.content-with-icon > span:not(.icon) {
|
||||||
|
min-width: 0;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-with-icon > span:not(.icon) a {
|
||||||
|
display: inline-block;
|
||||||
|
max-width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
vertical-align: bottom;
|
||||||
|
}
|
||||||
|
|
||||||
.content-with-icon .icon {
|
.content-with-icon .icon {
|
||||||
margin-top: 0.15rem;
|
margin-top: 0.15rem;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
// AGENT: Keep imports sorted alphabetically, grouped by feather-icons → pinhead → custom/local
|
// AGENT: Keep imports sorted alphabetically, grouped by feather-icons → pinhead → custom/local
|
||||||
|
/*
|
||||||
|
* Feather icons
|
||||||
|
*/
|
||||||
import activity from 'feather-icons/dist/icons/activity.svg?raw';
|
import activity from 'feather-icons/dist/icons/activity.svg?raw';
|
||||||
import arrowLeft from 'feather-icons/dist/icons/arrow-left.svg?raw';
|
import arrowLeft from 'feather-icons/dist/icons/arrow-left.svg?raw';
|
||||||
import bookmark from 'feather-icons/dist/icons/bookmark.svg?raw';
|
import bookmark from 'feather-icons/dist/icons/bookmark.svg?raw';
|
||||||
@@ -40,6 +43,9 @@ import check from 'feather-icons/dist/icons/check.svg?raw';
|
|||||||
import alertCircle from 'feather-icons/dist/icons/alert-circle.svg?raw';
|
import alertCircle from 'feather-icons/dist/icons/alert-circle.svg?raw';
|
||||||
import zap from 'feather-icons/dist/icons/zap.svg?raw';
|
import zap from 'feather-icons/dist/icons/zap.svg?raw';
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Pinhead icons
|
||||||
|
*/
|
||||||
import angelfish from '@waysidemapping/pinhead/dist/icons/angelfish.svg?raw';
|
import angelfish from '@waysidemapping/pinhead/dist/icons/angelfish.svg?raw';
|
||||||
import barbell from '@waysidemapping/pinhead/dist/icons/barbell.svg?raw';
|
import barbell from '@waysidemapping/pinhead/dist/icons/barbell.svg?raw';
|
||||||
import climbingWall from '@waysidemapping/pinhead/dist/icons/climbing_wall.svg?raw';
|
import climbingWall from '@waysidemapping/pinhead/dist/icons/climbing_wall.svg?raw';
|
||||||
@@ -53,8 +59,12 @@ import bus from '@waysidemapping/pinhead/dist/icons/bus.svg?raw';
|
|||||||
import camera from '@waysidemapping/pinhead/dist/icons/camera.svg?raw';
|
import camera from '@waysidemapping/pinhead/dist/icons/camera.svg?raw';
|
||||||
import boxingGloveUp from '@waysidemapping/pinhead/dist/icons/boxing_glove_up.svg?raw';
|
import boxingGloveUp from '@waysidemapping/pinhead/dist/icons/boxing_glove_up.svg?raw';
|
||||||
import car from '@waysidemapping/pinhead/dist/icons/car.svg?raw';
|
import car from '@waysidemapping/pinhead/dist/icons/car.svg?raw';
|
||||||
|
import carAndWrench from '@waysidemapping/pinhead/dist/icons/car_and_wrench.svg?raw';
|
||||||
|
import castleKeep from '@waysidemapping/pinhead/dist/icons/castle_keep.svg?raw';
|
||||||
import cigaretteWithSmokeCurl from '@waysidemapping/pinhead/dist/icons/cigarette_with_smoke_curl.svg?raw';
|
import cigaretteWithSmokeCurl from '@waysidemapping/pinhead/dist/icons/cigarette_with_smoke_curl.svg?raw';
|
||||||
|
import cityGate from '@waysidemapping/pinhead/dist/icons/city_gate.svg?raw';
|
||||||
import classicalBuilding from '@waysidemapping/pinhead/dist/icons/classical_building.svg?raw';
|
import classicalBuilding from '@waysidemapping/pinhead/dist/icons/classical_building.svg?raw';
|
||||||
|
import classicalBuildingWithClock from '@waysidemapping/pinhead/dist/icons/classical_building_with_clock.svg?raw';
|
||||||
import classicalBuildingWithDomeAndFlag from '@waysidemapping/pinhead/dist/icons/classical_building_with_dome_and_flag.svg?raw';
|
import classicalBuildingWithDomeAndFlag from '@waysidemapping/pinhead/dist/icons/classical_building_with_dome_and_flag.svg?raw';
|
||||||
import classicalBuildingWithFlag from '@waysidemapping/pinhead/dist/icons/classical_building_with_flag.svg?raw';
|
import classicalBuildingWithFlag from '@waysidemapping/pinhead/dist/icons/classical_building_with_flag.svg?raw';
|
||||||
import commercialBuilding from '@waysidemapping/pinhead/dist/icons/commercial_building.svg?raw';
|
import commercialBuilding from '@waysidemapping/pinhead/dist/icons/commercial_building.svg?raw';
|
||||||
@@ -82,8 +92,13 @@ import grecianVase from '@waysidemapping/pinhead/dist/icons/grecian_vase.svg?raw
|
|||||||
import greekCross from '@waysidemapping/pinhead/dist/icons/greek_cross.svg?raw';
|
import greekCross from '@waysidemapping/pinhead/dist/icons/greek_cross.svg?raw';
|
||||||
import iceCreamOnCone from '@waysidemapping/pinhead/dist/icons/ice_cream_on_cone.svg?raw';
|
import iceCreamOnCone from '@waysidemapping/pinhead/dist/icons/ice_cream_on_cone.svg?raw';
|
||||||
import industrialBuilding from '@waysidemapping/pinhead/dist/icons/industrial_building.svg?raw';
|
import industrialBuilding from '@waysidemapping/pinhead/dist/icons/industrial_building.svg?raw';
|
||||||
|
import infoI from '@waysidemapping/pinhead/dist/icons/info_i.svg?raw';
|
||||||
import jewel from '@waysidemapping/pinhead/dist/icons/jewel.svg?raw';
|
import jewel from '@waysidemapping/pinhead/dist/icons/jewel.svg?raw';
|
||||||
import lowriseBuilding from '@waysidemapping/pinhead/dist/icons/lowrise_building.svg?raw';
|
import lowriseBuilding from '@waysidemapping/pinhead/dist/icons/lowrise_building.svg?raw';
|
||||||
|
import marketStall from '@waysidemapping/pinhead/dist/icons/market_stall.svg?raw';
|
||||||
|
import memorialStoneWithInscription from '@waysidemapping/pinhead/dist/icons/memorial_stone_with_inscription.svg?raw';
|
||||||
|
import mobilePhoneWithKeypadAndAntenna from '@waysidemapping/pinhead/dist/icons/mobile_phone_with_keypad_and_antenna.svg?raw';
|
||||||
|
import molarTooth from '@waysidemapping/pinhead/dist/icons/molar_tooth.svg?raw';
|
||||||
import needleAndSpoolOfThread from '@waysidemapping/pinhead/dist/icons/needle_and_spool_of_thread.svg?raw';
|
import needleAndSpoolOfThread from '@waysidemapping/pinhead/dist/icons/needle_and_spool_of_thread.svg?raw';
|
||||||
import openBook from '@waysidemapping/pinhead/dist/icons/open_book.svg?raw';
|
import openBook from '@waysidemapping/pinhead/dist/icons/open_book.svg?raw';
|
||||||
import palace from '@waysidemapping/pinhead/dist/icons/palace.svg?raw';
|
import palace from '@waysidemapping/pinhead/dist/icons/palace.svg?raw';
|
||||||
@@ -118,6 +133,9 @@ import wallHangingWithMountainsAndSun from '@waysidemapping/pinhead/dist/icons/w
|
|||||||
import windingWayWide from '@waysidemapping/pinhead/dist/icons/winding_way_wide.svg?raw';
|
import windingWayWide from '@waysidemapping/pinhead/dist/icons/winding_way_wide.svg?raw';
|
||||||
import womensAndMensRestroomSymbol from '@waysidemapping/pinhead/dist/icons/womens_and_mens_restroom_symbol.svg?raw';
|
import womensAndMensRestroomSymbol from '@waysidemapping/pinhead/dist/icons/womens_and_mens_restroom_symbol.svg?raw';
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Custom/local icons
|
||||||
|
*/
|
||||||
import loadingRing from '../icons/270-ring.svg?raw';
|
import loadingRing from '../icons/270-ring.svg?raw';
|
||||||
import nostrich from '../icons/nostrich-2.svg?raw';
|
import nostrich from '../icons/nostrich-2.svg?raw';
|
||||||
import remotestorage from '../icons/remotestorage.svg?raw';
|
import remotestorage from '../icons/remotestorage.svg?raw';
|
||||||
@@ -144,11 +162,13 @@ const ICONS = {
|
|||||||
'chevron-left': chevronLeft,
|
'chevron-left': chevronLeft,
|
||||||
'chevron-right': chevronRight,
|
'chevron-right': chevronRight,
|
||||||
'cigarette-with-smoke-curl': cigaretteWithSmokeCurl,
|
'cigarette-with-smoke-curl': cigaretteWithSmokeCurl,
|
||||||
|
'city-gate': cityGate,
|
||||||
climbing_wall: climbingWall,
|
climbing_wall: climbingWall,
|
||||||
check,
|
check,
|
||||||
'alert-circle': alertCircle,
|
'alert-circle': alertCircle,
|
||||||
'alert-triangle': alertTriangle,
|
'alert-triangle': alertTriangle,
|
||||||
'classical-building': classicalBuilding,
|
'classical-building': classicalBuilding,
|
||||||
|
'classical-building-with-clock': classicalBuildingWithClock,
|
||||||
'classical-building-with-dome-and-flag': classicalBuildingWithDomeAndFlag,
|
'classical-building-with-dome-and-flag': classicalBuildingWithDomeAndFlag,
|
||||||
'classical-building-with-flag': classicalBuildingWithFlag,
|
'classical-building-with-flag': classicalBuildingWithFlag,
|
||||||
'commercial-building': commercialBuilding,
|
'commercial-building': commercialBuilding,
|
||||||
@@ -185,6 +205,7 @@ const ICONS = {
|
|||||||
'ice-cream-on-cone': iceCreamOnCone,
|
'ice-cream-on-cone': iceCreamOnCone,
|
||||||
'industrial-building': industrialBuilding,
|
'industrial-building': industrialBuilding,
|
||||||
info,
|
info,
|
||||||
|
'info-i': infoI,
|
||||||
instagram,
|
instagram,
|
||||||
jewel,
|
jewel,
|
||||||
'log-in': logIn,
|
'log-in': logIn,
|
||||||
@@ -193,7 +214,11 @@ const ICONS = {
|
|||||||
mail,
|
mail,
|
||||||
map,
|
map,
|
||||||
'map-pin': mapPin,
|
'map-pin': mapPin,
|
||||||
|
'market-stall': marketStall,
|
||||||
|
'memorial-stone-with-inscription': memorialStoneWithInscription,
|
||||||
menu,
|
menu,
|
||||||
|
'mobile-phone-with-keypad-and-antenna': mobilePhoneWithKeypadAndAntenna,
|
||||||
|
'molar-tooth': molarTooth,
|
||||||
'more-horizontal': moreHorizontal,
|
'more-horizontal': moreHorizontal,
|
||||||
'more-vertical': moreVertical,
|
'more-vertical': moreVertical,
|
||||||
navigation,
|
navigation,
|
||||||
@@ -245,6 +270,8 @@ const ICONS = {
|
|||||||
winding_way_wide: windingWayWide,
|
winding_way_wide: windingWayWide,
|
||||||
parking_p: parkingP,
|
parking_p: parkingP,
|
||||||
car,
|
car,
|
||||||
|
'car-and-wrench': carAndWrench,
|
||||||
|
'castle-keep': castleKeep,
|
||||||
x,
|
x,
|
||||||
zap,
|
zap,
|
||||||
'loading-ring': loadingRing,
|
'loading-ring': loadingRing,
|
||||||
|
|||||||
@@ -14,6 +14,30 @@ export function normalizeRelayUrl(url) {
|
|||||||
return normalized;
|
return normalized;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function uniqNormalizedRelays(relays = []) {
|
||||||
|
return Array.from(new Set(relays.map(normalizeRelayUrl).filter(Boolean)));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mergeRequiredRelays(requiredRelays = [], customRelays = []) {
|
||||||
|
const requiredSet = new Set(requiredRelays.filter(Boolean));
|
||||||
|
const merged = [...requiredRelays.filter(Boolean)];
|
||||||
|
|
||||||
|
for (const relay of uniqNormalizedRelays(customRelays)) {
|
||||||
|
if (!requiredSet.has(relay)) {
|
||||||
|
merged.push(relay);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return merged;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function excludeRequiredRelays(customRelays = [], requiredRelays = []) {
|
||||||
|
const requiredSet = new Set(requiredRelays.filter(Boolean));
|
||||||
|
return uniqNormalizedRelays(customRelays).filter((relay) => {
|
||||||
|
return !requiredSet.has(relay);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extracts and normalizes photo data from NIP-360 (Place Photos) events.
|
* Extracts and normalizes photo data from NIP-360 (Place Photos) events.
|
||||||
* Sorts chronologically and guarantees the first landscape photo (or first portrait) is at index 0.
|
* Sorts chronologically and guarantees the first landscape photo (or first portrait) is at index 0.
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ export const POI_ICON_RULES = [
|
|||||||
|
|
||||||
{ tags: { amenity: 'bank' }, icon: 'banknote' },
|
{ tags: { amenity: 'bank' }, icon: 'banknote' },
|
||||||
{ tags: { amenity: 'place_of_worship' }, icon: 'place-of-worship-building' },
|
{ tags: { amenity: 'place_of_worship' }, icon: 'place-of-worship-building' },
|
||||||
|
{ tags: { amenity: 'townhall' }, icon: 'classical-building-with-clock' },
|
||||||
|
{ tags: { building: 'townhall' }, icon: 'classical-building-with-clock' },
|
||||||
{ tags: { amenity: 'fire_station' }, icon: 'badge-shield-with-fire' },
|
{ tags: { amenity: 'fire_station' }, icon: 'badge-shield-with-fire' },
|
||||||
{ tags: { amenity: 'police' }, icon: 'police-officer-with-stop-arm' },
|
{ tags: { amenity: 'police' }, icon: 'police-officer-with-stop-arm' },
|
||||||
{ tags: { amenity: 'toilets' }, icon: 'womens-and-mens-restroom-symbol' },
|
{ tags: { amenity: 'toilets' }, icon: 'womens-and-mens-restroom-symbol' },
|
||||||
@@ -72,6 +74,7 @@ export const POI_ICON_RULES = [
|
|||||||
tags: { shop: 'beauty' },
|
tags: { shop: 'beauty' },
|
||||||
icon: 'fancy-mirror-with-reflection-and-stars',
|
icon: 'fancy-mirror-with-reflection-and-stars',
|
||||||
},
|
},
|
||||||
|
{ tags: { shop: 'car_repair' }, icon: 'car-and-wrench' },
|
||||||
{ tags: { craft: 'tailor' }, icon: 'needle-and-spool-of-thread' },
|
{ tags: { craft: 'tailor' }, icon: 'needle-and-spool-of-thread' },
|
||||||
{ tags: { office: 'estate_agent' }, icon: 'village-buildings' },
|
{ tags: { office: 'estate_agent' }, icon: 'village-buildings' },
|
||||||
{ tags: { office: true }, icon: 'commercial-building' },
|
{ tags: { office: true }, icon: 'commercial-building' },
|
||||||
@@ -103,6 +106,7 @@ export const POI_ICON_RULES = [
|
|||||||
{ tags: { tourism: 'viewpoint' }, icon: 'camera' },
|
{ tags: { tourism: 'viewpoint' }, icon: 'camera' },
|
||||||
{ tags: { tourism: 'zoo' }, icon: 'camera' },
|
{ tags: { tourism: 'zoo' }, icon: 'camera' },
|
||||||
{ tags: { tourism: 'artwork' }, icon: 'camera' },
|
{ tags: { tourism: 'artwork' }, icon: 'camera' },
|
||||||
|
{ tags: { tourism: 'information' }, icon: 'info-i' },
|
||||||
{ tags: { amenity: 'cinema' }, icon: 'film' },
|
{ tags: { amenity: 'cinema' }, icon: 'film' },
|
||||||
{ tags: { amenity: 'theatre' }, icon: 'camera' },
|
{ tags: { amenity: 'theatre' }, icon: 'camera' },
|
||||||
{ tags: { amenity: 'arts_centre' }, icon: 'comedy-mask-and-tragedy-mask' },
|
{ tags: { amenity: 'arts_centre' }, icon: 'comedy-mask-and-tragedy-mask' },
|
||||||
@@ -113,7 +117,9 @@ export const POI_ICON_RULES = [
|
|||||||
{ tags: { historic: 'bridge' }, icon: 'bridge' },
|
{ tags: { historic: 'bridge' }, icon: 'bridge' },
|
||||||
{ tags: { historic: 'bridge_site' }, icon: 'bridge' },
|
{ tags: { historic: 'bridge_site' }, icon: 'bridge' },
|
||||||
{ tags: { historic: 'fort' }, icon: 'fort' },
|
{ tags: { historic: 'fort' }, icon: 'fort' },
|
||||||
|
{ tags: { historic: 'city_gate' }, icon: 'city-gate' },
|
||||||
{ tags: { historic: 'castle' }, icon: 'palace' },
|
{ tags: { historic: 'castle' }, icon: 'palace' },
|
||||||
|
{ tags: { building: 'tower', historic: 'yes' }, icon: 'castle-keep' },
|
||||||
{ tags: { historic: 'building' }, icon: 'classical-building-with-flag' },
|
{ tags: { historic: 'building' }, icon: 'classical-building-with-flag' },
|
||||||
{ tags: { historic: 'archaeological_site' }, icon: 'grecian-vase' },
|
{ tags: { historic: 'archaeological_site' }, icon: 'grecian-vase' },
|
||||||
{ tags: { historic: 'memorial' }, icon: 'memorial-stone-with-inscription' },
|
{ tags: { historic: 'memorial' }, icon: 'memorial-stone-with-inscription' },
|
||||||
|
|||||||
+10
-1
@@ -56,15 +56,24 @@ const PLACE_TYPE_KEYS = [
|
|||||||
export function getPlaceType(tags) {
|
export function getPlaceType(tags) {
|
||||||
if (!tags) return null;
|
if (!tags) return null;
|
||||||
|
|
||||||
|
let fallbackKey = null;
|
||||||
|
|
||||||
for (const key of PLACE_TYPE_KEYS) {
|
for (const key of PLACE_TYPE_KEYS) {
|
||||||
const value = tags[key];
|
const value = tags[key];
|
||||||
if (value) {
|
if (value) {
|
||||||
if (value === 'yes') {
|
if (value === 'yes') {
|
||||||
return humanizeOsmTag(key);
|
if (!fallbackKey) {
|
||||||
|
fallbackKey = key;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
return humanizeOsmTag(value);
|
return humanizeOsmTag(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (fallbackKey) {
|
||||||
|
return humanizeOsmTag(fallbackKey);
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ export const POI_CATEGORIES = [
|
|||||||
label: 'Things to do',
|
label: 'Things to do',
|
||||||
icon: 'feather-camera',
|
icon: 'feather-camera',
|
||||||
filter: [
|
filter: [
|
||||||
'["tourism"~"^(museum|gallery|attraction|viewpoint|zoo|theme_park|aquarium|artwork)$"]',
|
'["tourism"~"^(museum|gallery|attraction|viewpoint|zoo|theme_park|aquarium|artwork|information)$"]',
|
||||||
'["amenity"~"^(cinema|theatre|arts_centre|planetarium)$"]',
|
'["amenity"~"^(cinema|theatre|arts_centre|planetarium)$"]',
|
||||||
'["leisure"~"^(sports_centre|stadium|water_park)$"]',
|
'["leisure"~"^(sports_centre|stadium|water_park)$"]',
|
||||||
'["historic"]',
|
'["historic"]',
|
||||||
@@ -55,7 +55,7 @@ export const POI_CATEGORIES = [
|
|||||||
id: 'accommodation',
|
id: 'accommodation',
|
||||||
label: 'Hotels',
|
label: 'Hotels',
|
||||||
icon: 'person-sleeping-in-bed',
|
icon: 'person-sleeping-in-bed',
|
||||||
filter: ['["tourism"~"^(hotel|hostel|motel|chalet)$"]'],
|
filter: ['["tourism"~"^(hotel|hostel|motel|chalet|guest_house)$"]'],
|
||||||
types: ['node', 'way', 'relation'],
|
types: ['node', 'way', 'relation'],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { module, test } from 'qunit';
|
|||||||
import { visit, currentURL, waitFor, triggerEvent } from '@ember/test-helpers';
|
import { visit, currentURL, waitFor, triggerEvent } from '@ember/test-helpers';
|
||||||
import { setupApplicationTest } from 'marco/tests/helpers';
|
import { setupApplicationTest } from 'marco/tests/helpers';
|
||||||
import Service from '@ember/service';
|
import Service from '@ember/service';
|
||||||
|
import sinon from 'sinon';
|
||||||
|
|
||||||
module('Acceptance | map search reset', function (hooks) {
|
module('Acceptance | map search reset', function (hooks) {
|
||||||
setupApplicationTest(hooks);
|
setupApplicationTest(hooks);
|
||||||
@@ -16,10 +17,58 @@ module('Acceptance | map search reset', function (hooks) {
|
|||||||
'marco:map-view',
|
'marco:map-view',
|
||||||
JSON.stringify(highZoomState)
|
JSON.stringify(highZoomState)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Stub window.fetch using Sinon
|
||||||
|
// We want to intercept map style requests and let everything else through
|
||||||
|
this.fetchStub = sinon.stub(window, 'fetch');
|
||||||
|
|
||||||
|
this.fetchStub.callsFake(async (input, init) => {
|
||||||
|
let url = input;
|
||||||
|
if (typeof input === 'object' && input !== null && 'url' in input) {
|
||||||
|
url = input.url;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
typeof url === 'string' &&
|
||||||
|
url.includes('tiles.openfreemap.org/styles/liberty')
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
ok: true,
|
||||||
|
status: 200,
|
||||||
|
json: async () => ({
|
||||||
|
version: 8,
|
||||||
|
name: 'Liberty',
|
||||||
|
sources: {
|
||||||
|
openmaptiles: {
|
||||||
|
type: 'vector',
|
||||||
|
url: 'https://tiles.openfreemap.org/planet',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
layers: [
|
||||||
|
{
|
||||||
|
id: 'background',
|
||||||
|
type: 'background',
|
||||||
|
paint: {
|
||||||
|
'background-color': '#123456',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
glyphs:
|
||||||
|
'https://tiles.openfreemap.org/fonts/{fontstack}/{range}.pbf',
|
||||||
|
sprite: 'https://tiles.openfreemap.org/sprites/liberty',
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pass through to the original implementation
|
||||||
|
return this.fetchStub.wrappedMethod(input, init);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
hooks.afterEach(function () {
|
hooks.afterEach(function () {
|
||||||
window.localStorage.removeItem('marco:map-view');
|
window.localStorage.removeItem('marco:map-view');
|
||||||
|
// Restore the original fetch
|
||||||
|
this.fetchStub.restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('clicking the map clears the category search parameter', async function (assert) {
|
test('clicking the map clears the category search parameter', async function (assert) {
|
||||||
|
|||||||
@@ -49,11 +49,19 @@ export class MockNostrDataService extends Service {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
get defaultReadRelays() {
|
get requiredReadRelays() {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
get defaultWriteRelays() {
|
get requiredWriteRelays() {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
get mailboxReadRelays() {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
get mailboxWriteRelays() {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,188 @@
|
|||||||
|
import { module, test } from 'qunit';
|
||||||
|
import { setupRenderingTest } from 'marco/tests/helpers';
|
||||||
|
import { click, fillIn, render } from '@ember/test-helpers';
|
||||||
|
import Service, { service } from '@ember/service';
|
||||||
|
import AppMenuSettingsNostr from 'marco/components/app-menu/settings/nostr';
|
||||||
|
import {
|
||||||
|
excludeRequiredRelays,
|
||||||
|
mergeRequiredRelays,
|
||||||
|
uniqNormalizedRelays,
|
||||||
|
} from 'marco/utils/nostr';
|
||||||
|
|
||||||
|
class MockNostrDataService extends Service {
|
||||||
|
@service settings;
|
||||||
|
|
||||||
|
requiredReadRelays = ['wss://nostr.kosmos.org'];
|
||||||
|
requiredWriteRelays = [];
|
||||||
|
|
||||||
|
mailboxReadRelays = ['wss://mailbox.example.com'];
|
||||||
|
mailboxWriteRelays = ['wss://mailbox-write.example.com'];
|
||||||
|
|
||||||
|
get configuredReadRelays() {
|
||||||
|
const configured = uniqNormalizedRelays([
|
||||||
|
...this.mailboxReadRelays,
|
||||||
|
...(this.settings.nostrReadRelays || []),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return excludeRequiredRelays(
|
||||||
|
configured,
|
||||||
|
this.settings.nostrReadRelayExclusions || []
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
get configuredWriteRelays() {
|
||||||
|
const configured = uniqNormalizedRelays([
|
||||||
|
...this.mailboxWriteRelays,
|
||||||
|
...(this.settings.nostrWriteRelays || []),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return excludeRequiredRelays(
|
||||||
|
configured,
|
||||||
|
this.settings.nostrWriteRelayExclusions || []
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
get activeReadRelays() {
|
||||||
|
return mergeRequiredRelays(
|
||||||
|
this.requiredReadRelays,
|
||||||
|
this.configuredReadRelays
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
get activeWriteRelays() {
|
||||||
|
return mergeRequiredRelays(
|
||||||
|
this.requiredWriteRelays,
|
||||||
|
this.configuredWriteRelays
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async clearCache() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
function readRows(element) {
|
||||||
|
const list = element.querySelectorAll('.relay-list')[0];
|
||||||
|
return [...list.querySelectorAll('li')];
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeRows(element) {
|
||||||
|
const list = element.querySelectorAll('.relay-list')[1];
|
||||||
|
return [...list.querySelectorAll('li')];
|
||||||
|
}
|
||||||
|
|
||||||
|
function rowByText(rows, text) {
|
||||||
|
return rows.find((row) => row.textContent.includes(text));
|
||||||
|
}
|
||||||
|
|
||||||
|
module('Integration | Component | app-menu/settings/nostr', function (hooks) {
|
||||||
|
setupRenderingTest(hooks);
|
||||||
|
|
||||||
|
hooks.beforeEach(function () {
|
||||||
|
localStorage.removeItem('marco:settings');
|
||||||
|
|
||||||
|
this.owner.register('service:nostrData', MockNostrDataService);
|
||||||
|
this.settings = this.owner.lookup('service:settings');
|
||||||
|
this.onChange = () => {};
|
||||||
|
});
|
||||||
|
|
||||||
|
hooks.afterEach(function () {
|
||||||
|
localStorage.removeItem('marco:settings');
|
||||||
|
});
|
||||||
|
|
||||||
|
async function renderAndOpenDetails(context) {
|
||||||
|
await render(
|
||||||
|
<template><AppMenuSettingsNostr @onChange={{this.onChange}} /></template>
|
||||||
|
);
|
||||||
|
await click('summary');
|
||||||
|
return context.element;
|
||||||
|
}
|
||||||
|
|
||||||
|
test('required read relay is first and non-removable', async function (assert) {
|
||||||
|
const element = await renderAndOpenDetails(this);
|
||||||
|
const rows = readRows(element);
|
||||||
|
|
||||||
|
assert.dom(rows[0]).includesText('nostr.kosmos.org');
|
||||||
|
|
||||||
|
const requiredRow = rowByText(rows, 'nostr.kosmos.org');
|
||||||
|
const mailboxRow = rowByText(rows, 'mailbox.example.com');
|
||||||
|
|
||||||
|
assert.dom(requiredRow.querySelector('.btn-remove-relay')).doesNotExist();
|
||||||
|
assert.dom(mailboxRow.querySelector('.btn-remove-relay')).exists();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('removing mailbox read relay stores exclusion override', async function (assert) {
|
||||||
|
const element = await renderAndOpenDetails(this);
|
||||||
|
const mailboxRow = rowByText(readRows(element), 'mailbox.example.com');
|
||||||
|
|
||||||
|
await click(mailboxRow.querySelector('.btn-remove-relay'));
|
||||||
|
|
||||||
|
assert.deepEqual(this.settings.nostrReadRelayExclusions, [
|
||||||
|
'wss://mailbox.example.com',
|
||||||
|
]);
|
||||||
|
assert.strictEqual(this.settings.nostrReadRelays, null);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('removing custom read relay updates custom list without exclusions', async function (assert) {
|
||||||
|
this.settings.update('nostrReadRelays', ['wss://custom.example.com']);
|
||||||
|
|
||||||
|
const element = await renderAndOpenDetails(this);
|
||||||
|
const customRow = rowByText(readRows(element), 'custom.example.com');
|
||||||
|
|
||||||
|
await click(customRow.querySelector('.btn-remove-relay'));
|
||||||
|
|
||||||
|
assert.strictEqual(this.settings.nostrReadRelays, null);
|
||||||
|
assert.strictEqual(this.settings.nostrReadRelayExclusions, null);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('adding read relay clears existing exclusion for same relay', async function (assert) {
|
||||||
|
this.settings.update('nostrReadRelayExclusions', [
|
||||||
|
'wss://mailbox.example.com',
|
||||||
|
]);
|
||||||
|
|
||||||
|
const element = await renderAndOpenDetails(this);
|
||||||
|
|
||||||
|
await fillIn('#new-read-relay', 'Mailbox.EXAMPLE.com/');
|
||||||
|
await click(element.querySelector('#new-read-relay').nextElementSibling);
|
||||||
|
|
||||||
|
assert.deepEqual(this.settings.nostrReadRelays, [
|
||||||
|
'wss://mailbox.example.com',
|
||||||
|
]);
|
||||||
|
assert.strictEqual(this.settings.nostrReadRelayExclusions, null);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('reset read relays clears additions and exclusions', async function (assert) {
|
||||||
|
this.settings.update('nostrReadRelays', ['wss://custom.example.com']);
|
||||||
|
this.settings.update('nostrReadRelayExclusions', [
|
||||||
|
'wss://mailbox.example.com',
|
||||||
|
]);
|
||||||
|
|
||||||
|
const element = await renderAndOpenDetails(this);
|
||||||
|
await click(element.querySelectorAll('.reset-relays')[0]);
|
||||||
|
|
||||||
|
assert.strictEqual(this.settings.nostrReadRelays, null);
|
||||||
|
assert.strictEqual(this.settings.nostrReadRelayExclusions, null);
|
||||||
|
|
||||||
|
const requiredRow = rowByText(readRows(element), 'nostr.kosmos.org');
|
||||||
|
assert.dom(requiredRow).exists();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('write relays are removable and mailbox delete stores exclusion', async function (assert) {
|
||||||
|
this.settings.update('nostrWriteRelays', [
|
||||||
|
'wss://custom-write.example.com',
|
||||||
|
]);
|
||||||
|
|
||||||
|
const element = await renderAndOpenDetails(this);
|
||||||
|
const rows = writeRows(element);
|
||||||
|
|
||||||
|
assert.true(
|
||||||
|
rows.every((row) => row.querySelector('.btn-remove-relay')),
|
||||||
|
'all write relays can be removed'
|
||||||
|
);
|
||||||
|
|
||||||
|
const mailboxRow = rowByText(rows, 'mailbox-write.example.com');
|
||||||
|
await click(mailboxRow.querySelector('.btn-remove-relay'));
|
||||||
|
|
||||||
|
assert.deepEqual(this.settings.nostrWriteRelayExclusions, [
|
||||||
|
'wss://mailbox-write.example.com',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -334,4 +334,54 @@ module('Integration | Component | place-details', function (hooks) {
|
|||||||
assert.dom(links[0]).hasText('+44 987 654 321');
|
assert.dom(links[0]).hasText('+44 987 654 321');
|
||||||
assert.dom(links[1]).hasText('+1 234-567 8900');
|
assert.dom(links[1]).hasText('+1 234-567 8900');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('it renders correct OpenStreetMap link for an OSM place', async function (assert) {
|
||||||
|
const place = {
|
||||||
|
title: 'OSM Place',
|
||||||
|
osmId: '12345',
|
||||||
|
osmType: 'node',
|
||||||
|
lat: 52.520008,
|
||||||
|
lon: 13.404954,
|
||||||
|
};
|
||||||
|
|
||||||
|
await render(<template><PlaceDetails @place={{place}} /></template>);
|
||||||
|
|
||||||
|
const osmLink = this.element.querySelector(
|
||||||
|
'.meta-info a[href^="https://www.openstreetmap.org/node/12345"]'
|
||||||
|
);
|
||||||
|
assert.ok(osmLink, 'OpenStreetMap link is rendered');
|
||||||
|
assert.strictEqual(
|
||||||
|
osmLink.getAttribute('href'),
|
||||||
|
'https://www.openstreetmap.org/node/12345'
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.dom('button.btn-link').hasText('Add a photo');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it renders correct search-based OpenStreetMap link for a custom saved place', async function (assert) {
|
||||||
|
class MockMapUi extends Service {
|
||||||
|
currentCenter = { lat: 52.5, lon: 13.4 };
|
||||||
|
currentZoom = 15.6;
|
||||||
|
}
|
||||||
|
this.owner.register('service:map-ui', MockMapUi);
|
||||||
|
|
||||||
|
const place = {
|
||||||
|
title: 'Custom Place',
|
||||||
|
lat: 52.520008,
|
||||||
|
lon: 13.404954,
|
||||||
|
};
|
||||||
|
|
||||||
|
await render(<template><PlaceDetails @place={{place}} /></template>);
|
||||||
|
|
||||||
|
const osmLink = this.element.querySelector(
|
||||||
|
'.meta-info a[href^="https://www.openstreetmap.org/search"]'
|
||||||
|
);
|
||||||
|
assert.ok(osmLink, 'OpenStreetMap search link is rendered');
|
||||||
|
assert.strictEqual(
|
||||||
|
osmLink.getAttribute('href'),
|
||||||
|
'https://www.openstreetmap.org/search?lat=52.520008&lon=13.404954&zoom=16#map=16/52.50000/13.40000'
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.dom('button.btn-link').doesNotExist();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
import { module, test } from 'qunit';
|
import { module, test } from 'qunit';
|
||||||
import { normalizeRelayUrl, parsePlacePhotos } from 'marco/utils/nostr';
|
import {
|
||||||
|
excludeRequiredRelays,
|
||||||
|
mergeRequiredRelays,
|
||||||
|
normalizeRelayUrl,
|
||||||
|
parsePlacePhotos,
|
||||||
|
uniqNormalizedRelays,
|
||||||
|
} from 'marco/utils/nostr';
|
||||||
|
|
||||||
module('Unit | Utility | nostr', function () {
|
module('Unit | Utility | nostr', function () {
|
||||||
test('normalizeRelayUrl normalizes protocol, case, and slashes', function (assert) {
|
test('normalizeRelayUrl normalizes protocol, case, and slashes', function (assert) {
|
||||||
@@ -141,4 +147,59 @@ module('Unit | Utility | nostr', function () {
|
|||||||
assert.strictEqual(photos[0].placeIdentifier, 'osm:node:123');
|
assert.strictEqual(photos[0].placeIdentifier, 'osm:node:123');
|
||||||
assert.strictEqual(photos[1].placeIdentifier, 'osm:node:456');
|
assert.strictEqual(photos[1].placeIdentifier, 'osm:node:456');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('uniqNormalizedRelays returns normalized unique relays', function (assert) {
|
||||||
|
const relays = uniqNormalizedRelays([
|
||||||
|
'Relay.example.com',
|
||||||
|
'wss://relay.example.com/',
|
||||||
|
'wss://other.example.com',
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert.deepEqual(relays, [
|
||||||
|
'wss://relay.example.com',
|
||||||
|
'wss://other.example.com',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('mergeRequiredRelays keeps required relays as-is and merges normalized custom relays', function (assert) {
|
||||||
|
const relays = mergeRequiredRelays(
|
||||||
|
['wss://required.example.com', 'required-2.example.com'],
|
||||||
|
['required-2.example.com/', 'wss://custom.example.com']
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.deepEqual(relays, [
|
||||||
|
'wss://required.example.com',
|
||||||
|
'required-2.example.com',
|
||||||
|
'wss://required-2.example.com',
|
||||||
|
'wss://custom.example.com',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('excludeRequiredRelays removes required relays from normalized custom list', function (assert) {
|
||||||
|
const relays = excludeRequiredRelays(
|
||||||
|
[
|
||||||
|
'wss://required.example.com',
|
||||||
|
'custom.example.com',
|
||||||
|
'ws://custom2.example.com',
|
||||||
|
],
|
||||||
|
['wss://required.example.com']
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.deepEqual(relays, [
|
||||||
|
'wss://custom.example.com',
|
||||||
|
'ws://custom2.example.com',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('excludeRequiredRelays trusts required list without normalizing it', function (assert) {
|
||||||
|
const relays = excludeRequiredRelays(
|
||||||
|
['Required.example.com', 'custom.example.com'],
|
||||||
|
['required.example.com']
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.deepEqual(relays, [
|
||||||
|
'wss://required.example.com',
|
||||||
|
'wss://custom.example.com',
|
||||||
|
]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { getIconNameForTags } from 'marco/utils/osm-icons';
|
import { getIconNameForTags, POI_ICON_RULES } from 'marco/utils/osm-icons';
|
||||||
|
import { getIcon } from 'marco/utils/icons';
|
||||||
import { module, test } from 'qunit';
|
import { module, test } from 'qunit';
|
||||||
|
|
||||||
module('Unit | Utility | osm-icons', function () {
|
module('Unit | Utility | osm-icons', function () {
|
||||||
@@ -36,4 +37,14 @@ module('Unit | Utility | osm-icons', function () {
|
|||||||
let result = getIconNameForTags({ foo: 'bar' });
|
let result = getIconNameForTags({ foo: 'bar' });
|
||||||
assert.strictEqual(result, null);
|
assert.strictEqual(result, null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('all icons used in POI_ICON_RULES exist in the icons utility', function (assert) {
|
||||||
|
for (let rule of POI_ICON_RULES) {
|
||||||
|
let icon = getIcon(rule.icon);
|
||||||
|
assert.ok(
|
||||||
|
icon,
|
||||||
|
`Icon "${rule.icon}" specified in POI_ICON_RULES should be imported and available in getIcon`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -89,6 +89,12 @@ module('Unit | Utility | osm', function (hooks) {
|
|||||||
assert.strictEqual(result, 'Building');
|
assert.strictEqual(result, 'Building');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('getPlaceType ignores generic "yes" values if a more specific tag is present', function (assert) {
|
||||||
|
const tags = { historic: 'yes', building: 'tower' };
|
||||||
|
const result = getPlaceType(tags);
|
||||||
|
assert.strictEqual(result, 'Tower');
|
||||||
|
});
|
||||||
|
|
||||||
test('getPlaceType prioritizes order (amenity > shop > building)', function (assert) {
|
test('getPlaceType prioritizes order (amenity > shop > building)', function (assert) {
|
||||||
// If something is both a shop and a building, it should be a shop
|
// If something is both a shop and a building, it should be a shop
|
||||||
const tags = { building: 'yes', shop: 'supermarket' };
|
const tags = { building: 'yes', shop: 'supermarket' };
|
||||||
|
|||||||
Reference in New Issue
Block a user