diff --git a/src/components/CitationCard/index.tsx b/src/components/CitationCard/index.tsx index 33b5ef9..4e5d74f 100644 --- a/src/components/CitationCard/index.tsx +++ b/src/components/CitationCard/index.tsx @@ -2,8 +2,8 @@ import { ExtendedKind } from '@/constants' import { Event } from 'nostr-tools' import { useMemo } from 'react' import { useTranslation } from 'react-i18next' -import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' -import { ExternalLink, Book, FileText, Bot } from 'lucide-react' +import { ExternalLink } from 'lucide-react' +import { nip19 } from 'nostr-tools' function getTagValue(event: Event, tagName: string): string { return event.tags.find(tag => tag[0] === tagName)?.[1] || '' @@ -13,9 +13,10 @@ interface CitationCardProps { event: Event className?: string displayType?: 'end' | 'foot' | 'foot-end' | 'inline' | 'quote' | 'prompt-end' | 'prompt-inline' + citationId?: string // The original citation ID (nevent/naddr) for Nostr references } -export default function CitationCard({ event, className, displayType = 'end' }: CitationCardProps) { +export default function CitationCard({ event, className, displayType = 'end', citationId }: CitationCardProps) { const { t } = useTranslation() const citationData = useMemo(() => { @@ -122,29 +123,252 @@ export default function CitationCard({ event, className, displayType = 'end' }: } } + const formatYear = (dateStr: string) => { + if (!dateStr) return '' + try { + const date = new Date(dateStr) + if (!isNaN(date.getTime())) { + return date.getFullYear().toString() + } + } catch { + // Fall through to regex extraction + } + // Try to extract year from string (YYYY format) + const yearMatch = dateStr.match(/\b(19|20)\d{2}\b/) + return yearMatch ? yearMatch[0] : '' + } + + // Format citation in academic style (NKBIP-03 format for Nostr references) + const formatAcademicCitation = () => { + if (citationData.type === 'internal') { + // NKBIP-03 format: [author]. Nostr: "[title]". [published on].\nnostr:[npub]\nnostr:[event identifier] + const parts: string[] = [] + + // Author + if (citationData.author) { + parts.push(citationData.author + '.') + } + + // Nostr: "[title]" + if (citationData.title) { + parts.push(`Nostr: "${citationData.title}".`) + } else if (citationData.summary) { + // Use summary if no title + const summaryText = citationData.summary.length > 100 + ? citationData.summary.substring(0, 100) + '...' + : citationData.summary + parts.push(`Nostr: "${summaryText}".`) + } else { + parts.push('Nostr:') + } + + // Published on date + if (citationData.publishedOn) { + const dateStr = formatDate(citationData.publishedOn) + if (dateStr) { + parts.push(dateStr + '.') + } + } + + // Nostr addresses on separate lines (NKBIP-03 format) + const nostrLines: string[] = [] + + // Extract npub from cTag (format: kind:pubkey:hex) + if (citationData.cTag) { + const cTagParts = citationData.cTag.split(':') + if (cTagParts.length >= 2) { + const pubkeyHex = cTagParts[1] + // Convert hex pubkey to npub + if (pubkeyHex && /^[0-9a-f]{64}$/i.test(pubkeyHex)) { + try { + const npub = nip19.npubEncode(pubkeyHex) + nostrLines.push(`nostr:${npub}`) + } catch (error) { + // If encoding fails, skip npub line + } + } else if (pubkeyHex.startsWith('npub') || pubkeyHex.startsWith('nprofile')) { + // Already a bech32 address + nostrLines.push(`nostr:${pubkeyHex}`) + } + } else if (citationData.cTag.startsWith('npub') || citationData.cTag.startsWith('nprofile')) { + // cTag is already a bech32 address + nostrLines.push(`nostr:${citationData.cTag}`) + } + } + + // Add citationId (event identifier) if it's a bech32 address (nevent/naddr) + if (citationId && (citationId.startsWith('nevent') || citationId.startsWith('naddr') || citationId.startsWith('note'))) { + nostrLines.push(`nostr:${citationId}`) + } + + // Join main parts and add nostr addresses on new lines + const mainText = parts.join(' ') + if (nostrLines.length > 0) { + return mainText + '\n' + nostrLines.join('\n') + } + return mainText + } else if (citationData.type === 'external') { + // APA format: Author. (Year). Title. Publisher. URL + const parts: string[] = [] + if (citationData.author) { + parts.push(citationData.author) + } + const year = formatYear(citationData.publishedOn || '') + if (year) { + parts.push(`(${year})`) + } + if (citationData.title) { + parts.push(citationData.title + '.') + } + if (citationData.publishedBy) { + parts.push(citationData.publishedBy + '.') + } + if (citationData.url) { + parts.push(citationData.url) + } + const accessedYear = formatYear(citationData.accessedOn) + if (accessedYear && accessedYear !== year) { + parts.push(`Retrieved ${formatDate(citationData.accessedOn)}`) + } + return parts.join(' ') + } else if (citationData.type === 'hardcopy') { + // APA format: Author. (Year). Title. In Editor (Ed.), Published In (Vol. X, pp. Y-Z). Publisher. + const parts: string[] = [] + if (citationData.author) { + parts.push(citationData.author) + } + const year = formatYear(citationData.publishedOn || '') + if (year) { + parts.push(`(${year})`) + } + if (citationData.chapterTitle) { + parts.push(citationData.chapterTitle + '.') + if (citationData.editor) { + parts.push(`In ${citationData.editor} (Ed.),`) + } + } else if (citationData.title) { + parts.push(citationData.title + '.') + } + if (citationData.publishedIn) { + const publishedInText = citationData.volume + ? `${citationData.publishedIn} (Vol. ${citationData.volume})` + : citationData.publishedIn + parts.push(publishedInText + '.') + } + if (citationData.pageRange) { + parts.push(`pp. ${citationData.pageRange}.`) + } + if (citationData.publishedBy) { + parts.push(citationData.publishedBy + '.') + } + if (citationData.doi) { + parts.push(`https://doi.org/${citationData.doi}`) + } + return parts.join(' ') + } else if (citationData.type === 'prompt') { + // APA format for AI: LLM. (Year). [Prompt interaction]. URL + const parts: string[] = [] + if (citationData.llm) { + parts.push(citationData.llm) + } + const year = formatYear(citationData.accessedOn) + if (year) { + parts.push(`(${year})`) + } + parts.push('[Prompt interaction].') + if (citationData.url) { + parts.push(citationData.url) + } + return parts.join(' ') + } + return '' + } + const renderCitationContent = () => { if (citationData.type === 'internal') { + // NKBIP-03 format: [author]. Nostr: "[title]". [published on].\nnostr:[npub]\nnostr:[event identifier] + const nostrAddresses: string[] = [] + + // Extract npub from cTag (format: kind:pubkey:hex) + if (citationData.cTag) { + const cTagParts = citationData.cTag.split(':') + if (cTagParts.length >= 2) { + const pubkeyHex = cTagParts[1] + // Convert hex pubkey to npub + if (pubkeyHex && /^[0-9a-f]{64}$/i.test(pubkeyHex)) { + try { + const npub = nip19.npubEncode(pubkeyHex) + nostrAddresses.push(`nostr:${npub}`) + } catch (error) { + // If encoding fails, skip npub line + } + } else if (pubkeyHex.startsWith('npub') || pubkeyHex.startsWith('nprofile')) { + // Already a bech32 address + nostrAddresses.push(`nostr:${pubkeyHex}`) + } + } else if (citationData.cTag.startsWith('npub') || citationData.cTag.startsWith('nprofile')) { + // cTag is already a bech32 address + nostrAddresses.push(`nostr:${citationData.cTag}`) + } + } + + // Add citationId (event identifier) if it's a bech32 address (nevent/naddr) + if (citationId && (citationId.startsWith('nevent') || citationId.startsWith('naddr') || citationId.startsWith('note'))) { + nostrAddresses.push(`nostr:${citationId}`) + } + return (
- {citationData.author && ( -
{citationData.author}
+ {/* Main citation line: Author. Nostr: "Title". Published on. */} +
+ {citationData.author && {citationData.author}} + {citationData.author && '. '} + Nostr: + {citationData.title ? ( + "{citationData.title}" + ) : citationData.summary ? ( + "{citationData.summary.length > 100 ? citationData.summary.substring(0, 100) + '...' : citationData.summary}" + ) : null} + {citationData.title || citationData.summary ? '. ' : ''} + {citationData.publishedOn && ( + {formatDate(citationData.publishedOn)} + )} + {citationData.publishedOn && '.'} +
+ + {/* Nostr addresses on separate lines */} + {nostrAddresses.map((addr, idx) => ( +
+ {addr} +
+ ))} + + {/* ALL additional fields */} + {citationData.accessedOn && ( +
+ {t('Accessed on')} {formatDate(citationData.accessedOn)} +
)} - {citationData.title && ( -
"{citationData.title}"
+ {citationData.location && ( +
+ {t('Location')}: {citationData.location} +
)} - {citationData.publishedOn && ( -
{formatDate(citationData.publishedOn)}
+ {citationData.geohash && ( +
+ {t('Geohash')}: {citationData.geohash} +
)} - {citationData.cTag && ( -
- nostr:{citationData.cTag} + {citationData.relayHint && ( +
+ {t('Relay')}: {citationData.relayHint}
)} - {citationData.summary && ( -
{citationData.summary}
+ {citationData.summary && citationData.title && ( +
{citationData.summary}
)} {event.content && ( -
+
{event.content}
)} @@ -181,62 +405,109 @@ export default function CitationCard({ event, className, displayType = 'end' }: {citationData.version && (
{t('Version')}: {citationData.version}
)} + {citationData.location && ( +
+ {t('Location')}: {citationData.location} +
+ )} + {citationData.geohash && ( +
+ {t('Geohash')}: {citationData.geohash} +
+ )} + {citationData.openTimestamp && ( +
+ {t('Open Timestamp')}: {citationData.openTimestamp} +
+ )} {citationData.summary && ( -
{citationData.summary}
+
{citationData.summary}
)} {event.content && ( -
+
{event.content}
)}
) } else if (citationData.type === 'hardcopy') { + // Display ALL hardcopy fields - show everything that exists + // Debug: Log all fields to see what we have + console.log('Hardcopy citation data:', { + author: citationData.author, + title: citationData.title, + chapterTitle: citationData.chapterTitle, + editor: citationData.editor, + publishedIn: citationData.publishedIn, + volume: citationData.volume, + publishedBy: citationData.publishedBy, + publishedOn: citationData.publishedOn, + pageRange: citationData.pageRange, + doi: citationData.doi, + accessedOn: citationData.accessedOn, + version: citationData.version, + location: citationData.location, + geohash: citationData.geohash, + summary: citationData.summary, + content: event.content, + allTags: event.tags + }) + return (
- {citationData.author && ( + {citationData.author && citationData.author.trim() !== '' && (
{citationData.author}
)} - {citationData.title && ( + {citationData.title && citationData.title.trim() !== '' && (
"{citationData.title}"
)} - {citationData.chapterTitle && ( + {citationData.chapterTitle && citationData.chapterTitle.trim() !== '' && (
{t('Chapter')}: {citationData.chapterTitle}
)} - {citationData.editor && ( + {citationData.editor && citationData.editor.trim() !== '' && (
{t('Edited by')} {citationData.editor}
)} - {citationData.publishedIn && ( + {citationData.publishedIn && citationData.publishedIn.trim() !== '' && (
{t('Published in')} {citationData.publishedIn} - {citationData.volume && `, ${t('Volume')} ${citationData.volume}`} + {citationData.volume && citationData.volume.trim() !== '' ? `, ${t('Volume')} ${citationData.volume}` : ''}
)} - {citationData.publishedBy && ( + {citationData.publishedBy && citationData.publishedBy.trim() !== '' && (
{citationData.publishedBy}
)} - {citationData.publishedOn && ( -
{formatDate(citationData.publishedOn)}
+ {citationData.publishedOn && citationData.publishedOn.trim() !== '' && ( +
{t('Published on')} {formatDate(citationData.publishedOn)}
)} - {citationData.pageRange && ( + {citationData.pageRange && citationData.pageRange.trim() !== '' && (
{t('Pages')}: {citationData.pageRange}
)} - {citationData.doi && ( + {citationData.doi && citationData.doi.trim() !== '' && (
DOI: {citationData.doi}
)} - {citationData.accessedOn && ( + {citationData.accessedOn && citationData.accessedOn.trim() !== '' && (
{t('Accessed on')} {formatDate(citationData.accessedOn)}
)} - {citationData.version && ( + {citationData.version && citationData.version.trim() !== '' && (
{t('Version')}: {citationData.version}
)} - {citationData.summary && ( -
{citationData.summary}
+ {citationData.location && citationData.location.trim() !== '' && ( +
+ {t('Location')}: {citationData.location} +
)} - {event.content && ( -
+ {citationData.geohash && citationData.geohash.trim() !== '' && ( +
+ {t('Geohash')}: {citationData.geohash} +
+ )} + {citationData.summary && citationData.summary.trim() !== '' && ( +
{citationData.summary}
+ )} + {event.content && event.content.trim() !== '' && ( +
{event.content}
)} @@ -263,10 +534,10 @@ export default function CitationCard({ event, className, displayType = 'end' }:
)} {citationData.summary && ( -
{citationData.summary}
+
{citationData.summary}
)} {event.content && ( -
+
{event.content}
)} @@ -277,42 +548,24 @@ export default function CitationCard({ event, className, displayType = 'end' }: return null } - const getIcon = () => { - switch (citationData.type) { - case 'internal': - return - case 'external': - return - case 'hardcopy': - return - case 'prompt': - return - default: - return - } - } - - const getTitle = () => { - switch (citationData.type) { - case 'internal': - return t('Internal Citation') - case 'external': - return t('External Citation') - case 'hardcopy': - return t('Hardcopy Citation') - case 'prompt': - return t('Prompt Citation') - default: - return t('Citation') - } - } - // For inline citations, render a compact version + // For inline citations, render a compact version in academic format if (displayType === 'inline' || displayType === 'prompt-inline') { - const inlineText = citationData.type === 'internal' && citationData.author && citationData.publishedOn - ? `(${citationData.author}, ${formatDate(citationData.publishedOn)})` - : citationData.type === 'prompt' && citationData.llm - ? `(${citationData.llm})` + // APA format: (Author, Year) + const author = citationData.type === 'internal' || citationData.type === 'external' || citationData.type === 'hardcopy' + ? citationData.author + : citationData.type === 'prompt' + ? citationData.llm + : '' + + const year = formatYear( + citationData.publishedOn || citationData.accessedOn || '' + ) + + const inlineText = author && year + ? `(${author}, ${year})` + : author + ? `(${author})` : `[${t('Citation')}]` return ( @@ -335,55 +588,59 @@ export default function CitationCard({ event, className, displayType = 'end' }: ) } - // For footnotes (foot-end), render a brief reference + // For footnotes (foot-end), render a brief academic reference if (displayType === 'foot-end') { + const academicText = formatAcademicCitation() + return ( +
+
+ {academicText || t('See reference')} +
+
+ ) + } + + // For footnotes (foot), render full citation information (same as endnotes) + if (displayType === 'foot') { + return ( +
+
+ {renderCitationContent()} +
+
+ ) + } + + // For endnotes and prompt-end, render full citation information (no card UI) + if (displayType === 'end' || displayType === 'prompt-end') { return (
-
- {citationData.type === 'internal' && citationData.author && citationData.publishedOn - ? `${citationData.author}, ${formatDate(citationData.publishedOn)}` - : citationData.type === 'external' && citationData.author - ? `${citationData.author}` - : citationData.type === 'hardcopy' && citationData.author - ? `${citationData.author}` - : citationData.type === 'prompt' && citationData.llm - ? `${citationData.llm}` - : t('See reference')} +
+ {renderCitationContent()}
) } - // For quotes, render with quote styling + // For quotes (block-level), render full citation information in block quote format if (displayType === 'quote') { return ( - - - - {getIcon()} - {getTitle()} - - - +
+
{renderCitationContent()} - - +
+
) } - // For endnotes, footnotes, and prompt-end, render full citation + // Default: render in academic format + const academicText = formatAcademicCitation() return ( - - - - {getIcon()} - {getTitle()} - - - - {renderCitationContent()} - - +
+
+ {academicText || t('Citation')} +
+
) } diff --git a/src/components/EmbeddedCitation/index.tsx b/src/components/EmbeddedCitation/index.tsx index 371ba44..84de6aa 100644 --- a/src/components/EmbeddedCitation/index.tsx +++ b/src/components/EmbeddedCitation/index.tsx @@ -55,6 +55,6 @@ export default function EmbeddedCitation({ citationId, displayType = 'end', clas ) } - return + return } diff --git a/src/components/Note/AsciidocArticle/AsciidocArticle.tsx b/src/components/Note/AsciidocArticle/AsciidocArticle.tsx index 64404d7..479bc01 100644 --- a/src/components/Note/AsciidocArticle/AsciidocArticle.tsx +++ b/src/components/Note/AsciidocArticle/AsciidocArticle.tsx @@ -1223,26 +1223,26 @@ export default function AsciidocArticle({ footnotesSection.appendChild(h3) const ol = document.createElement('ol') - ol.className = 'list-decimal list-inside space-y-2' + // Academic style: proper list formatting with aligned numbers + ol.className = 'list-decimal pl-6 space-y-3' + ol.style.listStylePosition = 'outside' footnotes.forEach((citation) => { const li = document.createElement('li') li.id = citation.id - li.className = 'text-sm' - - const span = document.createElement('span') - span.className = 'font-semibold' - span.textContent = `[${citation.index + 1}]: ` - li.appendChild(span) + li.className = 'text-sm pl-2' + li.style.display = 'list-item' const citationContainer = document.createElement('span') - citationContainer.className = 'inline-block mt-1' + citationContainer.className = 'inline' li.appendChild(citationContainer) const backLink = document.createElement('a') backLink.href = `#citation-ref-${citation.index}` - backLink.className = 'text-green-600 dark:text-green-400 hover:text-green-700 dark:hover:text-green-300 hover:underline text-xs ml-1' - backLink.textContent = '↩' + backLink.className = 'text-green-600 dark:text-green-400 hover:text-green-700 dark:hover:text-green-300 hover:underline text-xs ml-2 inline-flex items-center' + backLink.setAttribute('aria-label', 'Return to citation') + // Use hyperlink icon instead of emoji + backLink.innerHTML = '' backLink.addEventListener('click', (e) => { e.preventDefault() const refElement = document.getElementById(`citation-ref-${citation.index}`) @@ -1254,8 +1254,13 @@ export default function AsciidocArticle({ ol.appendChild(li) - // Render citation component - const citationRoot = createRoot(citationContainer) + // Render citation component - use a wrapper div to position backlink + const citationWrapperDiv = document.createElement('div') + citationWrapperDiv.className = 'inline' + citationWrapperDiv.style.display = 'inline' + citationContainer.appendChild(citationWrapperDiv) + + const citationRoot = createRoot(citationWrapperDiv) citationRoot.render( @@ -1266,7 +1271,22 @@ export default function AsciidocArticle({ ) - reactRootsRef.current.set(citationContainer, citationRoot) + reactRootsRef.current.set(citationWrapperDiv, citationRoot) + + // Insert backlink at end of first line after citation renders + setTimeout(() => { + const firstDiv = citationWrapperDiv.querySelector('div:first-child') as HTMLElement + if (firstDiv) { + firstDiv.style.display = 'inline' + firstDiv.style.position = 'relative' + backLink.style.position = 'absolute' + backLink.style.right = '0' + backLink.style.top = '0' + firstDiv.appendChild(backLink) + } else { + citationWrapperDiv.appendChild(backLink) + } + }, 100) }) footnotesSection.appendChild(ol) @@ -1298,26 +1318,29 @@ export default function AsciidocArticle({ referencesSection.appendChild(h3) const ol = document.createElement('ol') - ol.className = 'list-decimal list-inside space-y-2' + // Academic style: proper list formatting with aligned numbers + ol.className = 'list-decimal pl-6 space-y-3' + ol.style.listStylePosition = 'outside' endCitations.forEach((citation) => { const li = document.createElement('li') li.id = `citation-end-${citation.index}` - li.className = 'text-sm' + li.className = 'text-sm pl-2' + li.style.display = 'list-item' - const span = document.createElement('span') - span.className = 'font-semibold' - span.textContent = `[${citation.index + 1}]: ` - li.appendChild(span) + const citationWrapper = document.createElement('div') + citationWrapper.className = 'inline-block w-full relative' + li.appendChild(citationWrapper) const citationContainer = document.createElement('span') - citationContainer.className = 'inline-block mt-1' - li.appendChild(citationContainer) + citationContainer.className = 'inline' + citationWrapper.appendChild(citationContainer) const backLink = document.createElement('a') backLink.href = `#citation-ref-${citation.index}` - backLink.className = 'text-green-600 dark:text-green-400 hover:text-green-700 dark:hover:text-green-300 hover:underline text-xs ml-1' - backLink.textContent = '↩' + backLink.className = 'text-green-600 dark:text-green-400 hover:text-green-700 dark:hover:text-green-300 hover:underline text-xs ml-2 inline-flex items-center' + backLink.setAttribute('aria-label', 'Return to citation') + backLink.innerHTML = '' backLink.addEventListener('click', (e) => { e.preventDefault() const refElement = document.getElementById(`citation-ref-${citation.index}`) @@ -1325,7 +1348,6 @@ export default function AsciidocArticle({ refElement.scrollIntoView({ behavior: 'smooth', block: 'center' }) } }) - li.appendChild(backLink) ol.appendChild(li) @@ -1342,6 +1364,21 @@ export default function AsciidocArticle({ ) reactRootsRef.current.set(citationContainer, citationRoot) + + // Insert backlink at end of first line after citation renders + setTimeout(() => { + const firstDiv = citationContainer.querySelector('div:first-child') as HTMLElement + if (firstDiv) { + firstDiv.style.display = 'inline' + firstDiv.style.position = 'relative' + backLink.style.position = 'absolute' + backLink.style.right = '0' + backLink.style.top = '0' + firstDiv.appendChild(backLink) + } else { + citationWrapper.appendChild(backLink) + } + }, 100) }) referencesSection.appendChild(ol) @@ -1778,6 +1815,38 @@ export default function AsciidocArticle({ font-size: 0.83em; line-height: 0; } + /* Academic references section formatting */ + .asciidoc-content #references-section ol, + .asciidoc-content #footnotes-section ol { + list-style: decimal; + padding-left: 1.5rem; + list-style-position: outside; + } + .asciidoc-content #references-section li, + .asciidoc-content #footnotes-section li { + padding-left: 0.5rem; + margin-bottom: 0.5rem; + line-height: 1.6; + display: list-item; + } + /* Position backlink at end of first line */ + .asciidoc-content #references-section li > div > span > div:first-child, + .asciidoc-content #footnotes-section li > div > span > div:first-child { + position: relative; + display: inline-block; + width: 100%; + } + .asciidoc-content #references-section h3, + .asciidoc-content #footnotes-section h3 { + font-size: 1.125rem; + font-weight: 600; + margin-bottom: 1rem; + } + /* Blockquote spacing in citations */ + .asciidoc-content #references-section blockquote, + .asciidoc-content #footnotes-section blockquote { + padding-left: 1.5rem !important; + } `}
{/* Metadata */} diff --git a/src/components/Note/MarkdownArticle/MarkdownArticle.tsx b/src/components/Note/MarkdownArticle/MarkdownArticle.tsx index e0d2f7b..7aadd55 100644 --- a/src/components/Note/MarkdownArticle/MarkdownArticle.tsx +++ b/src/components/Note/MarkdownArticle/MarkdownArticle.tsx @@ -2509,33 +2509,36 @@ function parseMarkdownContent( wrappedParts.push(

Citations

-
    +
      {footCitations.map((citation, idx) => (
    1. - [{idx + 1}]:{' '} - - {' '} - { - e.preventDefault() - const refElement = document.getElementById(`citation-ref-${citation.id.replace('citation-', '')}`) - if (refElement) { - refElement.scrollIntoView({ behavior: 'smooth', block: 'center' }) - } - }} - > - ↩ - +
    2. ))}
    @@ -2549,33 +2552,37 @@ function parseMarkdownContent( wrappedParts.push(

    References

    -
      +
        {endCitations.map((citation, idx) => (
      1. - [{idx + 1}]:{' '} - - {' '} - { - e.preventDefault() - const refElement = document.getElementById(`citation-ref-${citation.id.replace('citation-', '')}`) - if (refElement) { - refElement.scrollIntoView({ behavior: 'smooth', block: 'center' }) - } - }} - > - ↩ - +
      2. ))}
      diff --git a/src/components/PostEditor/PostContent.tsx b/src/components/PostEditor/PostContent.tsx index ed6aade..3f632dc 100644 --- a/src/components/PostEditor/PostContent.tsx +++ b/src/components/PostEditor/PostContent.tsx @@ -580,14 +580,26 @@ export default function PostContent({ summary: citationSummary.trim() || undefined }) } else if (isCitationHardcopy) { - return createCitationHardcopyDraftEvent(cleanedText, { - accessedOn: citationAccessedOn.trim() || new Date().toISOString(), + // Convert date strings to ISO 8601 format if they exist + const formatDateToISO = (dateStr: string): string => { + if (!dateStr || !dateStr.trim()) return '' + // If already in ISO format, return as is + if (dateStr.includes('T')) return dateStr + // If in YYYY-MM-DD format, convert to ISO + if (dateStr.match(/^\d{4}-\d{2}-\d{2}$/)) { + return new Date(dateStr + 'T00:00:00Z').toISOString() + } + return dateStr + } + + const hardcopyOptions = { + accessedOn: formatDateToISO(citationAccessedOn.trim()) || new Date().toISOString(), title: citationTitle.trim() || undefined, author: citationAuthor.trim() || undefined, pageRange: citationHardcopyPageRange.trim() || undefined, chapterTitle: citationHardcopyChapterTitle.trim() || undefined, editor: citationHardcopyEditor.trim() || undefined, - publishedOn: citationPublishedOn.trim() || undefined, + publishedOn: citationPublishedOn.trim() ? formatDateToISO(citationPublishedOn.trim()) : undefined, publishedBy: citationPublishedBy.trim() || undefined, publishedIn: citationHardcopyPublishedIn.trim() || undefined, volume: citationHardcopyVolume.trim() || undefined, @@ -596,7 +608,12 @@ export default function PostContent({ location: citationLocation.trim() || undefined, geohash: citationGeohash.trim() || undefined, summary: citationSummary.trim() || undefined - }) + } + + // Debug: Log what we're passing to the function + console.log('Creating hardcopy citation with options:', hardcopyOptions) + + return createCitationHardcopyDraftEvent(cleanedText, hardcopyOptions) } else if (isCitationPrompt) { return createCitationPromptDraftEvent(cleanedText, { llm: citationPromptLlm.trim(), @@ -1641,13 +1658,14 @@ export default function PostContent({ {/* Citation metadata fields */} {(isCitationInternal || isCitationExternal || isCitationHardcopy || isCitationPrompt) && ( -
      +
      {isCitationInternal && t('Internal Citation Settings')} {isCitationExternal && t('External Citation Settings')} {isCitationHardcopy && t('Hardcopy Citation Settings')} {isCitationPrompt && t('Prompt Citation Settings')}
      +
      {/* Prompt Citation specific fields - shown first if prompt */} {isCitationPrompt && ( @@ -1939,8 +1957,8 @@ export default function PostContent({ />
      - {/* Summary field - different label for prompt citations */} -
      + {/* Summary field - different label for prompt citations - spans full width on desktop */} +
      @@ -1981,6 +1999,7 @@ export default function PostContent({
      )} +
      )}