Merge pull request 'Add operator info to place details' (#111) from feature/operator into master
CI / Lint (push) Successful in 1m3s
CI / Test (push) Successful in 1m22s

Reviewed-on: #111
This commit was merged in pull request #111.
This commit is contained in:
2026-09-24 17:07:24 +00:00
3 changed files with 213 additions and 0 deletions
+103
View File
@@ -316,6 +316,70 @@ export default class PlaceDetails extends Component {
return this.formatMultiLine(val);
}
get operatorNames() {
const raw = this.tags.operator;
if (!raw) return null;
const values = raw
.split(';')
.map((value) => value.trim())
.filter(
(value) =>
value && value.toLowerCase() !== 'yes' && value.toLowerCase() !== 'no'
);
if (values.length === 0) return null;
return this.formatMultiLine(values.join(';'));
}
get operatorWebsite() {
const val = this.tags['operator:website'];
return val ? val.trim() : null;
}
get operatorWebsiteLabel() {
return this.operatorWebsite ? this.getDomain(this.operatorWebsite) : null;
}
get operatorWikidata() {
const val = this.tags['operator:wikidata'];
if (!val) return null;
return (
val
.split(';')
.map((v) => v.trim())
.filter(Boolean)[0] || null
);
}
get operatorWikipedia() {
const val = this.tags['operator:wikipedia'];
if (!val) return null;
return (
val
.split(';')
.map((v) => v.trim())
.filter(Boolean)[0] || null
);
}
get operatorWikipediaUrl() {
const val = this.operatorWikipedia;
if (!val) return null;
const match = val.match(/^([a-z-]+):(.+)$/i);
if (match) {
return `https://${match[1]}.wikipedia.org/wiki/${match[2]}`;
}
return `https://wikipedia.org/wiki/${val}`;
}
get showOperator() {
return !!(
this.operatorNames ||
this.operatorWebsite ||
this.operatorWikidata ||
this.operatorWikipedia
);
}
get cuisine() {
if (!this.tags.cuisine) return null;
return this.tags.cuisine
@@ -490,6 +554,45 @@ export default class PlaceDetails extends Component {
</p>
{{/if}}
{{#if this.showOperator}}
<p class="content-with-icon">
<Icon @name="briefcase" @title="Operator" />
<span>
{{this.operatorNames}}
{{#if this.operatorWebsite}}
<br />
<a
href={{this.operatorWebsite}}
target="_blank"
rel="noopener noreferrer"
>
{{this.operatorWebsiteLabel}}
</a>
{{/if}}
{{#if this.operatorWikidata}}
<br />
<a
href="https://www.wikidata.org/wiki/{{this.operatorWikidata}}"
target="_blank"
rel="noopener noreferrer"
>
Wikidata
</a>
{{/if}}
{{#if this.operatorWikipedia}}
<br />
<a
href={{this.operatorWikipediaUrl}}
target="_blank"
rel="noopener noreferrer"
>
Wikipedia
</a>
{{/if}}
</span>
</p>
{{/if}}
{{#if this.phone}}
<p class="content-with-icon">
<Icon @name="phone" @title="Phone" />
+2
View File
@@ -5,6 +5,7 @@
import activity from 'feather-icons/dist/icons/activity.svg?raw';
import arrowLeft from 'feather-icons/dist/icons/arrow-left.svg?raw';
import bookmark from 'feather-icons/dist/icons/bookmark.svg?raw';
import briefcase from 'feather-icons/dist/icons/briefcase.svg?raw';
import featherCamera from 'feather-icons/dist/icons/camera.svg?raw';
import checkSquare from 'feather-icons/dist/icons/check-square.svg?raw';
import chevronLeft from 'feather-icons/dist/icons/chevron-left.svg?raw';
@@ -182,6 +183,7 @@ const ICONS = {
'boxing-glove-up': boxingGloveUp,
'burger-and-drink-cup-with-straw': burgerAndDrinkCupWithStraw,
bridge,
briefcase,
bus,
camera,
'feather-camera': featherCamera,
@@ -457,4 +457,112 @@ module('Integration | Component | place-details', function (hooks) {
assert.dom('.place-payment-methods').doesNotExist();
});
test('it renders the operator name with a briefcase icon', async function (assert) {
const place = {
title: 'Operated Cafe',
osmTags: {
operator: 'Le Méridien',
},
};
await render(<template><PlaceDetails @place={{place}} /></template>);
const operatorBlock = this.element.querySelector(
'.meta-info .content-with-icon span.icon[title="Operator"]'
);
assert.ok(operatorBlock, 'Operator block is rendered');
const row = operatorBlock.closest('.content-with-icon');
assert.dom(row).includesText('Le Méridien');
});
test('it splits semicolon-separated operators onto separate lines', async function (assert) {
const place = {
title: 'Multi Operator Stop',
osmTags: {
operator: 'De Lijn;TEC',
},
};
await render(<template><PlaceDetails @place={{place}} /></template>);
const row = this.element
.querySelector('span.icon[title="Operator"]')
.closest('.content-with-icon');
assert.dom(row).includesText('De Lijn');
assert.dom(row).includesText('TEC');
assert.true(
row.querySelector('span:not(.icon)').innerHTML.includes('<br>'),
'Operators are separated by a line break'
);
});
test('it suppresses invalid operator values of yes and no', async function (assert) {
const place = {
title: 'Invalid Operator',
osmTags: {
operator: 'yes',
},
};
await render(<template><PlaceDetails @place={{place}} /></template>);
assert
.dom('span.icon[title="Operator"]')
.doesNotExist('Operator block is hidden for operator=yes');
});
test('it renders operator wikidata and website links', async function (assert) {
const place = {
title: 'Linked Operator',
osmTags: {
operator: 'Flixbus',
'operator:wikidata': 'Q15712278',
'operator:website': 'https://www.flixbus.com/',
'operator:wikipedia': 'en:FlixBus',
},
};
await render(<template><PlaceDetails @place={{place}} /></template>);
const row = this.element
.querySelector('span.icon[title="Operator"]')
.closest('.content-with-icon');
const wikidataLink = row.querySelector(
'a[href="https://www.wikidata.org/wiki/Q15712278"]'
);
assert.ok(wikidataLink, 'Wikidata link is rendered');
assert.dom(wikidataLink).hasText('Wikidata');
const websiteLink = row.querySelector('a[href="https://www.flixbus.com/"]');
assert.ok(websiteLink, 'Operator website link is rendered');
assert.dom(websiteLink).hasText('www.flixbus.com');
const wikipediaLink = row.querySelector(
'a[href="https://en.wikipedia.org/wiki/FlixBus"]'
);
assert.ok(wikipediaLink, 'Operator Wikipedia link is rendered');
assert.dom(wikipediaLink).hasText('Wikipedia');
});
test('it renders the operator block when only operator subkeys are present', async function (assert) {
const place = {
title: 'Subkey Only',
osmTags: {
'operator:wikidata': 'Q42',
},
};
await render(<template><PlaceDetails @place={{place}} /></template>);
assert
.dom('span.icon[title="Operator"]')
.exists('Operator block is rendered from subkeys alone');
assert
.dom('a[href="https://www.wikidata.org/wiki/Q42"]')
.exists('Wikidata link is rendered');
});
});