Multi-line rendering for multi-value tags
E.g. opening hours, multiple phone numbers, ...
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import Component from '@glimmer/component';
|
import Component from '@glimmer/component';
|
||||||
import { fn } from '@ember/helper';
|
import { fn } from '@ember/helper';
|
||||||
import { on } from '@ember/modifier';
|
import { on } from '@ember/modifier';
|
||||||
|
import { htmlSafe } from '@ember/template';
|
||||||
import { humanizeOsmTag } from '../utils/format-text';
|
import { humanizeOsmTag } from '../utils/format-text';
|
||||||
import { getLocalizedName, getPlaceType } from '../utils/osm';
|
import { getLocalizedName, getPlaceType } from '../utils/osm';
|
||||||
import Icon from '../components/icon';
|
import Icon from '../components/icon';
|
||||||
@@ -95,21 +96,55 @@ export default class PlaceDetails extends Component {
|
|||||||
return parts.join(', ');
|
return parts.join(', ');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
formatMultiLine(val, type) {
|
||||||
|
if (!val) return null;
|
||||||
|
const parts = val.split(';').map((s) => s.trim()).filter(Boolean);
|
||||||
|
if (parts.length === 0) return null;
|
||||||
|
|
||||||
|
if (type === 'phone') {
|
||||||
|
return htmlSafe(
|
||||||
|
parts.map((p) => `<a href="tel:${p}">${p}</a>`).join('<br>')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === 'url') {
|
||||||
|
return htmlSafe(
|
||||||
|
parts
|
||||||
|
.map(
|
||||||
|
(url) =>
|
||||||
|
`<a href="${url}" target="_blank" rel="noopener noreferrer">${this.getDomain(
|
||||||
|
url
|
||||||
|
)}</a>`
|
||||||
|
)
|
||||||
|
.join('<br>')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return htmlSafe(parts.join('<br>'));
|
||||||
|
}
|
||||||
|
|
||||||
get phone() {
|
get phone() {
|
||||||
return this.tags.phone || this.tags['contact:phone'];
|
const val = this.tags.phone || this.tags['contact:phone'];
|
||||||
|
return this.formatMultiLine(val, 'phone');
|
||||||
}
|
}
|
||||||
|
|
||||||
get website() {
|
get website() {
|
||||||
return this.place.url || this.tags.website || this.tags['contact:website'];
|
const val = this.place.url || this.tags.website || this.tags['contact:website'];
|
||||||
|
return this.formatMultiLine(val, 'url');
|
||||||
}
|
}
|
||||||
|
|
||||||
get websiteDomain() {
|
getDomain(urlStr) {
|
||||||
const url = new URL(this.website);
|
try {
|
||||||
return url.hostname;
|
const url = new URL(urlStr);
|
||||||
|
return url.hostname;
|
||||||
|
} catch {
|
||||||
|
return urlStr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get openingHours() {
|
get openingHours() {
|
||||||
return this.tags.opening_hours;
|
const val = this.tags.opening_hours;
|
||||||
|
return this.formatMultiLine(val);
|
||||||
}
|
}
|
||||||
|
|
||||||
get cuisine() {
|
get cuisine() {
|
||||||
@@ -121,7 +156,9 @@ export default class PlaceDetails extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get wikipedia() {
|
get wikipedia() {
|
||||||
return this.tags.wikipedia;
|
const val = this.tags.wikipedia;
|
||||||
|
if (!val) return null;
|
||||||
|
return val.split(';').map((s) => s.trim()).filter(Boolean)[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
get geoLink() {
|
get geoLink() {
|
||||||
@@ -215,7 +252,7 @@ export default class PlaceDetails extends Component {
|
|||||||
<div class="meta-info">
|
<div class="meta-info">
|
||||||
|
|
||||||
{{#if this.cuisine}}
|
{{#if this.cuisine}}
|
||||||
<p>
|
<p class="cuisine-info">
|
||||||
<strong>Cuisine:</strong>
|
<strong>Cuisine:</strong>
|
||||||
{{this.cuisine}}
|
{{this.cuisine}}
|
||||||
</p>
|
</p>
|
||||||
@@ -224,25 +261,27 @@ export default class PlaceDetails extends Component {
|
|||||||
{{#if this.openingHours}}
|
{{#if this.openingHours}}
|
||||||
<p class="content-with-icon">
|
<p class="content-with-icon">
|
||||||
<Icon @name="clock" @title="Opening hours" />
|
<Icon @name="clock" @title="Opening hours" />
|
||||||
<span>{{this.openingHours}}</span>
|
<span>
|
||||||
|
{{this.openingHours}}
|
||||||
|
</span>
|
||||||
</p>
|
</p>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
{{#if this.phone}}
|
{{#if this.phone}}
|
||||||
<p class="content-with-icon">
|
<p class="content-with-icon">
|
||||||
<Icon @name="phone" @title="Phone" />
|
<Icon @name="phone" @title="Phone" />
|
||||||
<span><a href="tel:{{this.phone}}">{{this.phone}}</a></span>
|
<span>
|
||||||
|
{{this.phone}}
|
||||||
|
</span>
|
||||||
</p>
|
</p>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
{{#if this.website}}
|
{{#if this.website}}
|
||||||
<p class="content-with-icon">
|
<p class="content-with-icon">
|
||||||
<Icon @name="globe" @title="Website" />
|
<Icon @name="globe" @title="Website" />
|
||||||
<span><a
|
<span>
|
||||||
href={{this.website}}
|
{{this.website}}
|
||||||
target="_blank"
|
</span>
|
||||||
rel="noopener noreferrer"
|
|
||||||
>{{this.websiteDomain}}</a></span>
|
|
||||||
</p>
|
</p>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
|
|||||||
@@ -617,10 +617,14 @@ span.icon {
|
|||||||
.content-with-icon {
|
.content-with-icon {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: flex-start;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.content-with-icon .icon {
|
||||||
|
margin-top: 0.15rem;
|
||||||
|
}
|
||||||
|
|
||||||
/* Selected Pin Animation */
|
/* Selected Pin Animation */
|
||||||
.selected-pin-container {
|
.selected-pin-container {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|||||||
Reference in New Issue
Block a user