add outbound links to place suggestions
This commit is contained in:
@@ -24,6 +24,18 @@
|
|||||||
rating?: number;
|
rating?: number;
|
||||||
latitude?: number | string;
|
latitude?: number | string;
|
||||||
longitude?: number | string;
|
longitude?: number | string;
|
||||||
|
link?: string | null;
|
||||||
|
preferred_link?: string | null;
|
||||||
|
url?: string | null;
|
||||||
|
website?: string | null;
|
||||||
|
map_link?: string | null;
|
||||||
|
links?: {
|
||||||
|
link?: string | null;
|
||||||
|
preferred?: string | null;
|
||||||
|
official?: string | null;
|
||||||
|
website?: string | null;
|
||||||
|
map?: string | null;
|
||||||
|
} | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
type Conversation = {
|
type Conversation = {
|
||||||
@@ -661,6 +673,43 @@
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeHttpUrl(value: unknown): string | null {
|
||||||
|
if (typeof value !== 'string') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const candidate = value.trim();
|
||||||
|
if (!candidate) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const parsed = new URL(candidate);
|
||||||
|
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsed.toString();
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPreferredPlaceLink(place: PlaceResult): string | null {
|
||||||
|
return (
|
||||||
|
normalizeHttpUrl(place.link) ||
|
||||||
|
normalizeHttpUrl(place.preferred_link) ||
|
||||||
|
normalizeHttpUrl(place.url) ||
|
||||||
|
normalizeHttpUrl(place.website) ||
|
||||||
|
normalizeHttpUrl(place.map_link) ||
|
||||||
|
normalizeHttpUrl(place.links?.link) ||
|
||||||
|
normalizeHttpUrl(place.links?.preferred) ||
|
||||||
|
normalizeHttpUrl(place.links?.official) ||
|
||||||
|
normalizeHttpUrl(place.links?.website) ||
|
||||||
|
normalizeHttpUrl(place.links?.map)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function hasPlaceCoordinates(place: PlaceResult): boolean {
|
function hasPlaceCoordinates(place: PlaceResult): boolean {
|
||||||
return parseCoordinate(place.latitude) !== null && parseCoordinate(place.longitude) !== null;
|
return parseCoordinate(place.latitude) !== null && parseCoordinate(place.longitude) !== null;
|
||||||
}
|
}
|
||||||
@@ -737,6 +786,7 @@
|
|||||||
location: place.address || place.name,
|
location: place.address || place.name,
|
||||||
latitude,
|
latitude,
|
||||||
longitude,
|
longitude,
|
||||||
|
link: getPreferredPlaceLink(place),
|
||||||
collections: [collectionId],
|
collections: [collectionId],
|
||||||
is_public: false
|
is_public: false
|
||||||
})
|
})
|
||||||
@@ -1149,6 +1199,7 @@
|
|||||||
{#if hasPlaceResults(result)}
|
{#if hasPlaceResults(result)}
|
||||||
<div class="grid gap-2">
|
<div class="grid gap-2">
|
||||||
{#each getPlaceResults(result) as place}
|
{#each getPlaceResults(result) as place}
|
||||||
|
{@const placeLink = getPreferredPlaceLink(place)}
|
||||||
<div class="card card-compact bg-base-200 p-3">
|
<div class="card card-compact bg-base-200 p-3">
|
||||||
<h4 class="font-semibold">{place.name}</h4>
|
<h4 class="font-semibold">{place.name}</h4>
|
||||||
{#if place.address}
|
{#if place.address}
|
||||||
@@ -1160,6 +1211,18 @@
|
|||||||
<span>{place.rating}</span>
|
<span>{place.rating}</span>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if placeLink}
|
||||||
|
<div class="mt-2">
|
||||||
|
<a
|
||||||
|
href={placeLink}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
class="btn btn-ghost btn-xs"
|
||||||
|
>
|
||||||
|
↗ {$t('adventures.external_link')}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
{#if collectionId}
|
{#if collectionId}
|
||||||
{@const isDuplicate = mergedLocationNames.has(
|
{@const isDuplicate = mergedLocationNames.has(
|
||||||
normalizeLocationName(place.name)
|
normalizeLocationName(place.name)
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
price_level?: string | null;
|
price_level?: string | null;
|
||||||
latitude?: number | string | null;
|
latitude?: number | string | null;
|
||||||
longitude?: number | string | null;
|
longitude?: number | string | null;
|
||||||
|
link?: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
@@ -166,6 +167,40 @@
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeHttpUrl(value: unknown): string | null {
|
||||||
|
if (typeof value !== 'string') return null;
|
||||||
|
const candidate = value.trim();
|
||||||
|
if (!candidate) return null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const parsed = new URL(candidate);
|
||||||
|
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return parsed.toString();
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPreferredSuggestionLink(item: Record<string, unknown>): string | null {
|
||||||
|
const links = asRecord(item.links);
|
||||||
|
return (
|
||||||
|
normalizeHttpUrl(item.link) ||
|
||||||
|
normalizeHttpUrl(item.preferred_link) ||
|
||||||
|
normalizeHttpUrl(item.url) ||
|
||||||
|
normalizeHttpUrl(item.website) ||
|
||||||
|
normalizeHttpUrl(item.map_link) ||
|
||||||
|
(links
|
||||||
|
? normalizeHttpUrl(links.link) ||
|
||||||
|
normalizeHttpUrl(links.preferred) ||
|
||||||
|
normalizeHttpUrl(links.official) ||
|
||||||
|
normalizeHttpUrl(links.website) ||
|
||||||
|
normalizeHttpUrl(links.map)
|
||||||
|
: null)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function normalizeSuggestionItem(value: unknown): SuggestionItem | null {
|
function normalizeSuggestionItem(value: unknown): SuggestionItem | null {
|
||||||
const item = asRecord(value);
|
const item = asRecord(value);
|
||||||
if (!item) return null;
|
if (!item) return null;
|
||||||
@@ -191,6 +226,7 @@
|
|||||||
const rating = normalizeRating(item.rating ?? item.score);
|
const rating = normalizeRating(item.rating ?? item.score);
|
||||||
const latitude = normalizeCoordinate(item.latitude ?? item.lat);
|
const latitude = normalizeCoordinate(item.latitude ?? item.lat);
|
||||||
const longitude = normalizeCoordinate(item.longitude ?? item.lon ?? item.lng);
|
const longitude = normalizeCoordinate(item.longitude ?? item.lon ?? item.lng);
|
||||||
|
const link = getPreferredSuggestionLink(item);
|
||||||
|
|
||||||
const finalName = name || location;
|
const finalName = name || location;
|
||||||
if (!finalName) return null;
|
if (!finalName) return null;
|
||||||
@@ -204,7 +240,8 @@
|
|||||||
rating,
|
rating,
|
||||||
price_level: priceLevel || null,
|
price_level: priceLevel || null,
|
||||||
latitude,
|
latitude,
|
||||||
longitude
|
longitude,
|
||||||
|
link
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,6 +262,7 @@
|
|||||||
const rating = normalizeRating(suggestion.rating);
|
const rating = normalizeRating(suggestion.rating);
|
||||||
const latitude = normalizeCoordinate(suggestion.latitude);
|
const latitude = normalizeCoordinate(suggestion.latitude);
|
||||||
const longitude = normalizeCoordinate(suggestion.longitude);
|
const longitude = normalizeCoordinate(suggestion.longitude);
|
||||||
|
const link = normalizeHttpUrl(suggestion.link);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name,
|
name,
|
||||||
@@ -233,6 +271,7 @@
|
|||||||
rating,
|
rating,
|
||||||
latitude,
|
latitude,
|
||||||
longitude,
|
longitude,
|
||||||
|
link,
|
||||||
collections: [collection.id],
|
collections: [collection.id],
|
||||||
is_public: Boolean(collection?.is_public)
|
is_public: Boolean(collection?.is_public)
|
||||||
};
|
};
|
||||||
@@ -508,7 +547,17 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card-actions justify-end mt-3">
|
<div class="card-actions justify-between items-center mt-3">
|
||||||
|
{#if suggestion.link}
|
||||||
|
<a
|
||||||
|
href={suggestion.link}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
class="btn btn-ghost btn-xs"
|
||||||
|
>
|
||||||
|
↗ {$t('adventures.external_link')}
|
||||||
|
</a>
|
||||||
|
{/if}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="btn btn-primary btn-sm"
|
class="btn btn-primary btn-sm"
|
||||||
|
|||||||
Reference in New Issue
Block a user