dragoman 0.3.12

Server for scholarly metadata in commonmeta format stored in a SQLite database.
<script>
  import Icon from '@iconify/svelte'
  import orcidSvg from './assets/orcid.svg'
  import rorSvg from './assets/ror.svg'
  import { _ } from '@sveltia/i18n'
  import { Button } from '$lib/components/ui/button/index.js'
  import { renderContent, formatDate, typeLabel, languageLabel, getCCIcons } from '$lib/bib-utils.js'

  let {
    entry,
    meta,
    index,
    bibLocale,
    copiedCiteId,
    addedId,
    isSaved = false,
    dragId,
    dropPosition,
    ondragstart,
    ondragover,
    ondragleave,
    ondrop,
    ondragend,
    oncopy,
    ondelete,
    onadd,
    dragHandleActive = $bindable(false),
  } = $props()

  function rorPath(url) {
    return url?.replace('https://ror.org/', '/') ?? url
  }
  function orcidPath(url) {
    return url?.replace('https://orcid.org/', '/') ?? url
  }
</script>

<li
  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 !== undefined && index !== null}
    <span class="text-xs text-muted-foreground w-5 shrink-0 pt-0.5 text-right">{index + 1}.</span>
  {/if}

  <!-- Structured metadata -->
  <div class="flex-1 min-w-0 space-y-0.5">
    {#if meta}
      {#if meta.title}
        <p class="text-lg font-semibold leading-snug">{@html renderContent(meta.title)}</p>
      {/if}
      {@const formattedDate = formatDate(meta.datePublished, bibLocale)}
      {@const typeLbl = typeLabel(meta.type, bibLocale)}
      {@const langLbl = languageLabel(meta.language, bibLocale)}
      {@const line2 = [
        typeLbl,
        meta.version ? `${_('bibliography.version')} ${meta.version}` : '',
        formattedDate ? `${_('bibliography.published')} ${formattedDate}` : '',
        meta.containerTitle ? `${_('bibliography.in')} ${meta.containerTitle}` : '',
        langLbl ? `${_('bibliography.in')} ${langLbl}` : '',
      ].filter(Boolean).join(' ')}
      {#if line2}
        <p class="text-sm text-muted-foreground">{line2}</p>
      {/if}
      {#if meta.authorList.length > 0}
        <p class="text-sm text-muted-foreground leading-relaxed">
          {#each meta.authorList as author, ai}
            {#if ai > 0}, {/if}{#if author.isEllipsis}<span class="select-none">…</span>{:else}<span class="inline-flex items-baseline gap-px">{#if author.orcid}<a href={orcidPath(author.orcid)} class="hover:underline">{author.name}</a>{:else}{author.name}{/if}{#if author.affIndices.length > 0}<sup class="text-[0.65em] leading-none ml-px">{author.affIndices.join(',')}</sup>{/if}{#if author.orcid}<img src={orcidSvg} width="14" height="14" alt="ORCID" class="ml-0.5 inline-block align-middle opacity-70 shrink-0" />{/if}</span>{/if}
          {/each}
        </p>
        {#if meta.affiliations.length > 0}
          <p class="text-sm text-muted-foreground/60">
            {#each meta.affiliations as aff, i}
              {#if i > 0} · {/if}<sup class="text-[0.65em] leading-none">{i + 1}</sup> {#if aff.ror}<a href={rorPath(aff.ror)} class="hover:underline">{aff.name}</a><img src={rorSvg} width="14" height="14" alt="ROR" class="ml-0.5 inline-block align-middle opacity-70 shrink-0" />{:else}{aff.name}{/if}
            {/each}
          </p>
        {/if}
      {/if}
      {#if meta.funders.length > 0}
        <p class="text-sm text-muted-foreground/60">
          {_('bibliography.funded_by')}
          {#each meta.funders as f, i}{#if i > 0}, {/if}{#if f.funder_id?.startsWith('https://ror.org/')}<a href={rorPath(f.funder_id)} class="hover:underline inline-flex items-center gap-0.5">{f.funder_name || f.funder_id}<img src={rorSvg} width="14" height="14" alt="ROR" class="inline-block align-middle opacity-70 shrink-0" /></a>{:else if f.funder_id?.startsWith('http')}{f.funder_name || f.funder_id}{:else}{f.funder_name || f.funder_id}{/if}{#if f.award_title || f.award_number}{@const awardLabel = f.award_title ? (f.award_number ? `${f.award_title} (${f.award_number})` : f.award_title) : f.award_number}: {#if f.award_id?.startsWith('http')}<a href={f.award_id} target="_blank" rel="noreferrer" class="hover:underline">{awardLabel}</a>{:else}{awardLabel}{/if}{/if}{/each}
        </p>
      {/if}
      {#if meta.description}
        <p class="text-sm text-muted-foreground line-clamp-3 mt-1 mb-1">{@html renderContent(meta.description)}</p>
      {/if}
      {#if meta.referenceCount > 0 || meta.citationCount > 0}
        {@const refPart  = meta.referenceCount > 0 ? `${meta.referenceCount} ${meta.referenceCount === 1 ? _('bibliography.reference') : _('bibliography.references')}` : ''}
        {@const citePart = meta.citationCount  > 0 ? `${meta.citationCount}  ${meta.citationCount  === 1 ? _('bibliography.citation')  : _('bibliography.citations')}` : ''}
        <p class="text-sm text-muted-foreground/60">{[refPart, citePart].filter(Boolean).join(` ${_('bibliography.and')} `)}</p>
      {/if}
      {#if meta.licenseTitle || meta.licenseId}
        {@const ccIcons = getCCIcons(meta.licenseId)}
        <p class="text-sm text-muted-foreground/60 flex flex-wrap items-center gap-0.5">
          {_('bibliography.license')} {meta.licenseTitle}{#if meta.licenseId}{meta.licenseTitle ? ' ' : ''}({#if meta.licenseUrl}<a href={meta.licenseUrl} target="_blank" rel="noreferrer" class="hover:font-semibold hover:underline">{meta.licenseId}</a>{:else}{meta.licenseId}{/if}){/if}.{#if ccIcons.length > 0}&nbsp;{#each ccIcons as icon}<Icon {icon} width="16" height="16" />{/each}{/if}
        </p>
      {/if}
      {#if meta.subjects.length > 0}
        <div class="flex flex-wrap gap-1 my-1">
          {#each meta.subjects.slice(0, 5) as subject}
            <span class="inline-flex items-center rounded-full border border-border px-2.5 py-0.5 text-xs font-medium text-muted-foreground">{subject}</span>
          {/each}
        </div>
      {/if}
      {#if meta.id}
        {@const doiPath = meta.id.startsWith('https://doi.org/') ? meta.id.replace('https://doi.org/', '/') : null}
        <a href={doiPath ?? meta.id} {...(doiPath ? {} : { target: '_blank', rel: 'noreferrer' })}
          class="text-sm text-primary hover:underline flex items-center gap-1.5 pt-0.5"
        >{#if meta.id.startsWith('https://doi.org/')}<Icon icon="academicons:doi" width="16" height="16" color="#fab608" />{/if}{meta.id}</a>
      {/if}
    {/if}
  </div>

  <!-- Per-entry icon buttons -->
  <div class="flex flex-col gap-0.5 shrink-0">
    {#if meta?.id}
      <a href={meta.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 external link"
      >
        <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}
    {#if onadd && (!isSaved || addedId === meta?.id)}
      <Button type="button" variant="ghost" size="icon"
        onclick={() => onadd(meta?.id ?? '', entry.data)}
        aria-label="Add to saved"
        class="h-7 w-7 text-muted-foreground hover:text-foreground"
      >
        {#if addedId === meta?.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}
    <Button type="button" variant="ghost" size="icon"
      onclick={oncopy}
      aria-label={_('bibliography.copy_citation')}
      class="h-7 w-7 text-muted-foreground hover:text-foreground"
    >
      {#if copiedCiteId === entry.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="M15.666 3.888A2.25 2.25 0 0 0 13.5 2.25h-3c-1.03 0-1.9.693-2.166 1.638m7.332 0c.055.194.084.4.084.612v0a.75.75 0 0 1-.75.75H9a.75.75 0 0 1-.75-.75v0c0-.212.03-.418.084-.612m7.332 0c.646.049 1.288.11 1.927.184 1.1.128 1.907 1.077 1.907 2.185V19.5a2.25 2.25 0 0 1-2.25 2.25H6.75A2.25 2.25 0 0 1 4.5 19.5V6.257c0-1.108.806-2.057 1.907-2.185a48.208 48.208 0 0 1 1.927-.184" />
        </svg>
      {/if}
    </Button>
    {#if ondelete}
      <Button type="button" variant="ghost" size="icon"
        onclick={ondelete}
        aria-label={_('bibliography.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>
</li>