<script>
import rorSvg from './assets/ror.svg'
import { _ } from '@sveltia/i18n'
import { Button } from '$lib/components/ui/button/index.js'
let {
entry, org, bibLocale = 'en-US', addedId, index, worksCount = 0,
dragId, dropPosition, ondragstart, ondragover, ondragleave, ondrop, ondragend,
onadd, ondelete,
dragHandleActive = $bindable(false),
} = $props()
function capitalize(s) {
return s ? s.charAt(0).toUpperCase() + s.slice(1) : s
}
function rorPath(url) {
return url?.replace('https://ror.org/', '/') ?? url
}
function countryName(code) {
if (!code) return ''
try {
return new Intl.DisplayNames([bibLocale], { type: 'region' }).of(code) || code
} catch { return code }
}
function regionName(code) {
if (!code) return ''
try {
return new Intl.DisplayNames([bibLocale], { type: 'region' }).of(code) || ''
} catch { return '' }
}
function relI18nKey(type) {
if (type === 'IsPartOf') return 'org.rel_parent'
if (type === 'HasPart') return 'org.rel_child'
return 'org.rel_related'
}
// Derived display data
let websiteUrls = $derived(org?.urls?.filter(u => u.name !== 'wikipedia') ?? [])
let fundrefId = $derived(org?.identifiers?.find(id => id.identifier_type === 'FundRef')?.identifier ?? null)
let isniId = $derived(org?.identifiers?.find(id => id.identifier_type === 'ISNI')?.identifier ?? null)
let wikidataId = $derived(org?.identifiers?.find(id => id.identifier_type === 'Wikidata')?.identifier ?? null)
let relGroups = $derived.by(() => {
const groups = new Map()
for (const rel of (org?.relations ?? [])) {
if (!groups.has(rel.type)) groups.set(rel.type, [])
groups.get(rel.type).push({ id: rel.id, name: rel.name })
}
return [...groups.entries()]
})
let hasLeftCol = $derived(
org?.country || org?.location ||
websiteUrls.length > 0 || fundrefId || isniId || wikidataId
)
</script>
{#if org}
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div
draggable={dragHandleActive}
onpointerdown={() => { dragHandleActive = false }}
ondragstart={e => { if (!dragHandleActive) { e.preventDefault(); return } ondragstart?.(e) }}
ondragover={ondragover}
ondragleave={ondragleave}
ondrop={ondrop}
ondragend={() => { dragHandleActive = false; ondragend?.() }}
class="flex items-start gap-3 px-5 py-4 bg-background transition-opacity
{dragId === entry.id ? 'opacity-30' : ''}
{dropPosition?.id === entry.id && dropPosition.before ? 'border-t-2 border-primary' : ''}
{dropPosition?.id === entry.id && !dropPosition.before ? 'border-b-2 border-primary' : ''}"
>
{#if index !== null && index !== undefined}
<span class="text-xs text-muted-foreground w-5 shrink-0 pt-0.5 text-right">{index + 1}.</span>
{/if}
<div class="flex-1 min-w-0">
<p class="text-lg font-semibold leading-snug">
{org.name}
</p>
<div class="mt-2 space-y-3">
{#if org.additional_names?.length > 0}
<dl class="text-sm">
<div>
<dt class="font-semibold text-foreground">{_('org.additional_names')}</dt>
<dd class="text-muted-foreground">{org.additional_names.join(' · ')}</dd>
</div>
</dl>
{/if}
<div class="flex gap-6">
<!-- Left column: details -->
{#if hasLeftCol}
<dl class="flex-1 min-w-0 space-y-3 text-sm">
{#if org.country || org.location}
<div>
<dt class="font-semibold text-foreground">{_('org.location')}</dt>
<dd class="text-muted-foreground">
{#if org.location?.city}
{@const loc = org.location}
{@const region = regionName(loc.region_code) || loc.region_name || ''}
{@const country = countryName(loc.country_code) || countryName(org.country)}
{@const displayPlace = [loc.city, region, country].filter(Boolean).join(', ')}
{#if loc.id}
<a href={loc.id} target="_blank" rel="noreferrer" class="hover:underline">{displayPlace}</a>
{:else}
{displayPlace}
{/if}
{:else if org.location?.place}
{@const parts = org.location.place.split(', ')}
{@const displayPlace = [...parts.slice(0, -1), countryName(org.country) || parts[parts.length - 1]].join(', ')}
{#if org.location.id}
<a href={org.location.id} target="_blank" rel="noreferrer" class="hover:underline">{displayPlace}</a>
{:else}
{displayPlace}
{/if}
{:else}
{countryName(org.country)}
{/if}
</dd>
</div>
{/if}
{#if websiteUrls.length > 0}
<div>
<dt class="font-semibold text-foreground">{_('org.website')}</dt>
<dd class="text-muted-foreground space-y-0.5">
{#each websiteUrls as u}
<div><a href={u.url} target="_blank" rel="noreferrer" class="hover:underline">{u.url}</a></div>
{/each}
</dd>
</div>
{/if}
{#if isniId}
<div>
<dt class="font-semibold text-foreground">{_('org.isni')}</dt>
<dd class="text-muted-foreground">
<a href="https://isni.org/isni/{isniId.replace(/[\s-]/g, '')}" target="_blank" rel="noreferrer" class="hover:underline">{isniId}</a>
</dd>
</div>
{/if}
{#if wikidataId}
<div>
<dt class="font-semibold text-foreground">{_('org.wikidata')}</dt>
<dd class="text-muted-foreground">
<a href="https://www.wikidata.org/wiki/{wikidataId}" target="_blank" rel="noreferrer" class="hover:underline">{wikidataId}</a>
</dd>
</div>
{/if}
{#if fundrefId}
<div>
<dt class="font-semibold text-foreground">Crossref Funder ID</dt>
<dd class="text-muted-foreground">
<a href="https://api.crossref.org/funders/{fundrefId}" target="_blank" rel="noreferrer" class="hover:underline">{fundrefId}</a>
</dd>
</div>
{/if}
</dl>
{/if}
<!-- Right column: relations grouped by type -->
{#if relGroups.length > 0}
<dl class="w-96 shrink-0 space-y-3 text-sm">
<div>
<dt class="font-semibold text-foreground">{_('org.relations')}</dt>
<dd class="text-muted-foreground mt-1 space-y-3">
{#each relGroups as [type, ids]}
<div>
<p class="text-xs text-muted-foreground/60">{_(relI18nKey(type), { values: { count: ids.length } })}</p>
<div class="space-y-1.5 mt-1">
{#each ids as rel}
<div><a href={rorPath(rel.id)} class="text-primary hover:underline inline-flex items-center gap-1.5 leading-snug"><img src={rorSvg} width="14" height="14" alt="ROR" class="shrink-0" />{rel.name || rel.id}</a></div>
{/each}
</div>
</div>
{/each}
</dd>
</div>
</dl>
{/if}
</div>
</div>
{#if org.types.length > 0}
<dl class="mt-3 mb-1 text-sm">
<div>
<dt class="font-semibold text-foreground">{_('org.types')}</dt>
<dd class="text-muted-foreground">{org.types.map(t => _(`org.type.${t}`, { default: capitalize(t) })).join(' · ')}</dd>
</div>
</dl>
{/if}
{#if worksCount > 0}
<p class="text-sm text-muted-foreground/60 mt-2">
<a href={rorPath(org.id)} class="hover:underline">{_('org.works', { values: { count: worksCount } })}</a>
</p>
{/if}
<a href={rorPath(org.id)}
class="text-sm text-primary hover:underline inline-flex items-center gap-1.5 pt-0.5"
><img src={rorSvg} width="14" height="14" alt="ROR" class="shrink-0" />{org.id}</a>
</div>
<div class="flex flex-col gap-0.5 shrink-0">
<a href={org.id} target="_blank" rel="noreferrer"
class="inline-flex items-center justify-center h-7 w-7 rounded-md text-muted-foreground hover:text-foreground hover:bg-accent transition-colors"
aria-label="Open ROR page"
>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" class="w-4 h-4">
<path stroke-linecap="round" stroke-linejoin="round" d="M13.5 6H5.25A2.25 2.25 0 0 0 3 8.25v10.5A2.25 2.25 0 0 0 5.25 21h10.5A2.25 2.25 0 0 0 18 18.75V10.5m-10.5 6L21 3m0 0h-5.25M21 3v5.25" />
</svg>
</a>
{#if onadd}
<Button type="button" variant="ghost" size="icon"
onclick={() => onadd(org.id, entry.data)}
aria-label="Add to saved"
class="h-7 w-7 text-muted-foreground hover:text-foreground"
>
{#if addedId === org.id}
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" class="w-4 h-4">
<path stroke-linecap="round" stroke-linejoin="round" d="m4.5 12.75 6 6 9-13.5" />
</svg>
{:else}
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" class="w-4 h-4">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
</svg>
{/if}
</Button>
{/if}
{#if websiteUrls.length > 0}
<a href={websiteUrls[0].url} target="_blank" rel="noreferrer"
class="inline-flex items-center justify-center h-7 w-7 rounded-md text-muted-foreground hover:text-foreground hover:bg-accent transition-colors"
aria-label="Open website"
>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" class="w-4 h-4">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 21a9.004 9.004 0 0 0 8.716-6.747M12 21a9.004 9.004 0 0 1-8.716-6.747M12 21c2.485 0 4.5-4.03 4.5-9S14.485 3 12 3m0 18c-2.485 0-4.5-4.03-4.5-9S9.515 3 12 3m0 0a8.997 8.997 0 0 1 7.843 4.582M12 3a8.997 8.997 0 0 0-7.843 4.582m15.686 0A11.953 11.953 0 0 1 12 10.5c-2.998 0-5.74-1.1-7.843-2.918m15.686 0A8.959 8.959 0 0 1 21 12c0 .778-.099 1.533-.284 2.253m0 0A17.919 17.919 0 0 1 12 16.5c-3.162 0-6.133-.815-8.716-2.247m0 0A9.015 9.015 0 0 1 3 12c0-1.605.42-3.113 1.157-4.418" />
</svg>
</a>
{/if}
{#if ondelete}
<Button type="button" variant="ghost" size="icon"
onclick={ondelete}
aria-label="Delete entry"
class="h-7 w-7 text-muted-foreground hover:text-destructive hover:bg-destructive/10"
>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" class="w-4 h-4">
<path stroke-linecap="round" stroke-linejoin="round" d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" />
</svg>
</Button>
{/if}
</div>
</div>
{/if}