From a6ca362876ab7343828d527d6ddd491ea3a19e32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Tue, 24 Feb 2026 14:50:39 +0400 Subject: [PATCH] Multi-line rendering for multi-value tags E.g. opening hours, multiple phone numbers, ... --- app/components/place-details.gjs | 69 +++++++++++++++++++++++++------- app/styles/app.css | 6 ++- 2 files changed, 59 insertions(+), 16 deletions(-) diff --git a/app/components/place-details.gjs b/app/components/place-details.gjs index bbffd6b..727fcd7 100644 --- a/app/components/place-details.gjs +++ b/app/components/place-details.gjs @@ -1,6 +1,7 @@ import Component from '@glimmer/component'; import { fn } from '@ember/helper'; import { on } from '@ember/modifier'; +import { htmlSafe } from '@ember/template'; import { humanizeOsmTag } from '../utils/format-text'; import { getLocalizedName, getPlaceType } from '../utils/osm'; import Icon from '../components/icon'; @@ -95,21 +96,55 @@ export default class PlaceDetails extends Component { 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) => `${p}`).join('
') + ); + } + + if (type === 'url') { + return htmlSafe( + parts + .map( + (url) => + `${this.getDomain( + url + )}` + ) + .join('
') + ); + } + + return htmlSafe(parts.join('
')); + } + 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() { - 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() { - const url = new URL(this.website); - return url.hostname; + getDomain(urlStr) { + try { + const url = new URL(urlStr); + return url.hostname; + } catch { + return urlStr; + } } get openingHours() { - return this.tags.opening_hours; + const val = this.tags.opening_hours; + return this.formatMultiLine(val); } get cuisine() { @@ -121,7 +156,9 @@ export default class PlaceDetails extends Component { } 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() { @@ -215,7 +252,7 @@ export default class PlaceDetails extends Component {
{{#if this.cuisine}} -

+

Cuisine: {{this.cuisine}}

@@ -224,25 +261,27 @@ export default class PlaceDetails extends Component { {{#if this.openingHours}}

- {{this.openingHours}} + + {{this.openingHours}} +

{{/if}} {{#if this.phone}}

- {{this.phone}} + + {{this.phone}} +

{{/if}} {{#if this.website}}

- {{this.websiteDomain}} + + {{this.website}} +

{{/if}} diff --git a/app/styles/app.css b/app/styles/app.css index 2b85cb1..9c248a3 100644 --- a/app/styles/app.css +++ b/app/styles/app.css @@ -617,10 +617,14 @@ span.icon { .content-with-icon { display: flex; flex-direction: row; - align-items: center; + align-items: flex-start; gap: 0.5rem; } +.content-with-icon .icon { + margin-top: 0.15rem; +} + /* Selected Pin Animation */ .selected-pin-container { position: absolute;