From e01cb2ce6f8fd50f03662dba2c3115355324e492 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Wed, 11 Mar 2026 15:02:47 +0400 Subject: [PATCH 1/3] Add Facebook and Instagram links --- app/components/icon.gjs | 4 ++ app/components/place-details.gjs | 39 ++++++++++++++++ app/utils/social-links.js | 52 +++++++++++++++++++++ tests/unit/utils/social-links-test.js | 66 +++++++++++++++++++++++++++ 4 files changed, 161 insertions(+) create mode 100644 app/utils/social-links.js create mode 100644 tests/unit/utils/social-links-test.js diff --git a/app/components/icon.gjs b/app/components/icon.gjs index b00dc5f..d043420 100644 --- a/app/components/icon.gjs +++ b/app/components/icon.gjs @@ -6,8 +6,10 @@ import activity from 'feather-icons/dist/icons/activity.svg?raw'; import bookmark from 'feather-icons/dist/icons/bookmark.svg?raw'; import clock from 'feather-icons/dist/icons/clock.svg?raw'; import edit from 'feather-icons/dist/icons/edit.svg?raw'; +import facebook from 'feather-icons/dist/icons/facebook.svg?raw'; import globe from 'feather-icons/dist/icons/globe.svg?raw'; import home from 'feather-icons/dist/icons/home.svg?raw'; +import instagram from 'feather-icons/dist/icons/instagram.svg?raw'; import logIn from 'feather-icons/dist/icons/log-in.svg?raw'; import logOut from 'feather-icons/dist/icons/log-out.svg?raw'; import map from 'feather-icons/dist/icons/map.svg?raw'; @@ -31,8 +33,10 @@ const ICONS = { bookmark, clock, edit, + facebook, globe, home, + instagram, 'log-in': logIn, 'log-out': logOut, map, diff --git a/app/components/place-details.gjs b/app/components/place-details.gjs index 2e92ae0..9685066 100644 --- a/app/components/place-details.gjs +++ b/app/components/place-details.gjs @@ -4,6 +4,7 @@ import { on } from '@ember/modifier'; import { htmlSafe } from '@ember/template'; import { humanizeOsmTag } from '../utils/format-text'; import { getLocalizedName, getPlaceType } from '../utils/osm'; +import { getSocialInfo } from '../utils/social-links'; import Icon from '../components/icon'; import PlaceEditForm from './place-edit-form'; @@ -159,6 +160,14 @@ export default class PlaceDetails extends Component { .join(', '); } + get facebook() { + return getSocialInfo(this.tags, 'facebook'); + } + + get instagram() { + return getSocialInfo(this.tags, 'instagram'); + } + get wikipedia() { const val = this.tags.wikipedia; if (!val) return null; @@ -292,6 +301,36 @@ export default class PlaceDetails extends Component {

{{/if}} + {{#if this.facebook}} +

+ + + + {{this.facebook.username}} + + +

+ {{/if}} + + {{#if this.instagram}} +

+ + + + {{this.instagram.username}} + + +

+ {{/if}} + {{#if this.wikipedia}}

diff --git a/app/utils/social-links.js b/app/utils/social-links.js new file mode 100644 index 0000000..6137c8d --- /dev/null +++ b/app/utils/social-links.js @@ -0,0 +1,52 @@ +// Helper to get value from multiple keys +const get = (tags, ...keys) => { + for (const k of keys) { + if (tags[k]) return tags[k]; + } + return null; +}; + +export function getSocialInfo(tags, platform) { + if (!tags) return null; + + const key = platform; + const domain = `${platform}.com`; + const val = get(tags, `contact:${key}`, key); + + if (!val) return null; + + // Check if it's a full URL + if (val.startsWith('http')) { + try { + const url = new URL(val); + + // Handle Facebook profile.php?id=... + if ( + platform === 'facebook' && + url.pathname === '/profile.php' && + url.searchParams.has('id') + ) { + return { + url: val, + username: url.searchParams.get('id'), + }; + } + + // Clean up pathname to get username + let username = url.pathname.replace(/^\/|\/$/g, ''); + return { + url: val, + username: username || val, // Fallback to full URL if path is empty + }; + } catch { + return { url: val, username: val }; + } + } + + // Assume it's a username + const username = val.replace(/^@/, ''); // Remove leading @ + return { + url: `https://${domain}/${username}`, + username: username, + }; +} diff --git a/tests/unit/utils/social-links-test.js b/tests/unit/utils/social-links-test.js new file mode 100644 index 0000000..22ffa1a --- /dev/null +++ b/tests/unit/utils/social-links-test.js @@ -0,0 +1,66 @@ +import { getSocialInfo } from 'marco/utils/social-links'; +import { module, test } from 'qunit'; + +module('Unit | Utility | social-links', function () { + test('it returns null if tags are missing', function (assert) { + let result = getSocialInfo({}, 'facebook'); + assert.strictEqual(result, null); + }); + + test('it returns null if specific platform tags are missing', function (assert) { + let result = getSocialInfo({ twitter: 'foo' }, 'facebook'); + assert.strictEqual(result, null); + }); + + test('it handles simple usernames', function (assert) { + let result = getSocialInfo({ facebook: 'foo' }, 'facebook'); + assert.deepEqual(result, { + url: 'https://facebook.com/foo', + username: 'foo', + }); + + result = getSocialInfo({ 'contact:instagram': '@bar' }, 'instagram'); + assert.deepEqual(result, { + url: 'https://instagram.com/bar', + username: 'bar', + }); + }); + + test('it handles full URLs', function (assert) { + let result = getSocialInfo( + { facebook: 'https://www.facebook.com/foo' }, + 'facebook' + ); + assert.deepEqual(result, { + url: 'https://www.facebook.com/foo', + username: 'foo', + }); + }); + + test('it handles Facebook profile.php URLs', function (assert) { + let result = getSocialInfo( + { facebook: 'https://www.facebook.com/profile.php?id=12345' }, + 'facebook' + ); + assert.deepEqual(result, { + url: 'https://www.facebook.com/profile.php?id=12345', + username: '12345', + }); + }); + + test('it falls back gracefully for malformed URLs', function (assert) { + let result = getSocialInfo({ facebook: 'http://' }, 'facebook'); + assert.deepEqual(result, { + url: 'http://', + username: 'http://', + }); + }); + + test('it prioritizes contact:tag over tag', function (assert) { + let result = getSocialInfo( + { 'contact:facebook': 'priority', facebook: 'fallback' }, + 'facebook' + ); + assert.strictEqual(result.username, 'priority'); + }); +}); From ffcb8219b026585cb98065f9fcb6a922e1fbfc0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Wed, 11 Mar 2026 15:22:34 +0400 Subject: [PATCH 2/3] Add email links --- app/components/icon.gjs | 2 ++ app/components/place-details.gjs | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/app/components/icon.gjs b/app/components/icon.gjs index d043420..6b5c90f 100644 --- a/app/components/icon.gjs +++ b/app/components/icon.gjs @@ -12,6 +12,7 @@ import home from 'feather-icons/dist/icons/home.svg?raw'; import instagram from 'feather-icons/dist/icons/instagram.svg?raw'; import logIn from 'feather-icons/dist/icons/log-in.svg?raw'; import logOut from 'feather-icons/dist/icons/log-out.svg?raw'; +import mail from 'feather-icons/dist/icons/mail.svg?raw'; import map from 'feather-icons/dist/icons/map.svg?raw'; import mapPin from 'feather-icons/dist/icons/map-pin.svg?raw'; import menu from 'feather-icons/dist/icons/menu.svg?raw'; @@ -39,6 +40,7 @@ const ICONS = { instagram, 'log-in': logIn, 'log-out': logOut, + mail, map, 'map-pin': mapPin, menu, diff --git a/app/components/place-details.gjs b/app/components/place-details.gjs index 9685066..db23a2f 100644 --- a/app/components/place-details.gjs +++ b/app/components/place-details.gjs @@ -111,6 +111,12 @@ export default class PlaceDetails extends Component { ); } + if (type === 'email') { + return htmlSafe( + parts.map((p) => `${p}`).join('
') + ); + } + if (type === 'url') { return htmlSafe( parts @@ -132,6 +138,11 @@ export default class PlaceDetails extends Component { return this.formatMultiLine(val, 'phone'); } + get email() { + const val = this.tags.email || this.tags['contact:email']; + return this.formatMultiLine(val, 'email'); + } + get website() { const val = this.place.url || this.tags.website || this.tags['contact:website']; @@ -301,6 +312,15 @@ export default class PlaceDetails extends Component {

{{/if}} + {{#if this.email}} +

+ + + {{this.email}} + +

+ {{/if}} + {{#if this.facebook}}

From b07640375ae3239f999c8cde9c8784a821406a57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Wed, 11 Mar 2026 16:07:37 +0400 Subject: [PATCH 3/3] Add some white space to place details bottom --- app/styles/app.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/styles/app.css b/app/styles/app.css index 18b46b9..7a4c311 100644 --- a/app/styles/app.css +++ b/app/styles/app.css @@ -427,6 +427,10 @@ body { justify-content: center; } +.place-details { + padding-bottom: 2rem; +} + .place-details h3 { font-size: 1.2rem; margin-top: 0;