Merge pull request #5 from Team-1280/better-documents

This commit is contained in:
Youwen Wu 2024-02-14 20:55:46 -08:00 committed by GitHub
commit 27fc474a62
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 450 additions and 101 deletions

27
.github/workflows/style.yml vendored Normal file
View file

@ -0,0 +1,27 @@
name: npm run format
on: [push, workflow_dispatch]
jobs:
style:
runs-on: ubuntu-latest
permissions:
# Give the default GITHUB_TOKEN write permission to commit and push the
# added or changed files to the repository.
contents: write
steps:
- uses: actions/checkout@v4
- name: Use Node.js
uses: actions/setup-node@v3
with:
node-version: '20.x'
- run: npm install
- run: npm run format
- uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: "style: format"

1
.prettierignore Normal file
View file

@ -0,0 +1 @@
*.yml

168
package-lock.json generated
View file

@ -521,6 +521,32 @@
"node": ">=6.9.0" "node": ">=6.9.0"
} }
}, },
"node_modules/@cspotcode/source-map-support": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
"integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
"dev": true,
"optional": true,
"peer": true,
"dependencies": {
"@jridgewell/trace-mapping": "0.3.9"
},
"engines": {
"node": ">=12"
}
},
"node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": {
"version": "0.3.9",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
"integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
"dev": true,
"optional": true,
"peer": true,
"dependencies": {
"@jridgewell/resolve-uri": "^3.0.3",
"@jridgewell/sourcemap-codec": "^1.4.10"
}
},
"node_modules/@eslint-community/eslint-utils": { "node_modules/@eslint-community/eslint-utils": {
"version": "4.4.0", "version": "4.4.0",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
@ -971,6 +997,38 @@
"react": "^18.0.0" "react": "^18.0.0"
} }
}, },
"node_modules/@tsconfig/node10": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz",
"integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==",
"dev": true,
"optional": true,
"peer": true
},
"node_modules/@tsconfig/node12": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
"integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
"dev": true,
"optional": true,
"peer": true
},
"node_modules/@tsconfig/node14": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
"integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
"dev": true,
"optional": true,
"peer": true
},
"node_modules/@tsconfig/node16": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz",
"integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
"dev": true,
"optional": true,
"peer": true
},
"node_modules/@types/estree": { "node_modules/@types/estree": {
"version": "1.0.5", "version": "1.0.5",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
@ -1182,6 +1240,17 @@
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
} }
}, },
"node_modules/acorn-walk": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz",
"integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==",
"dev": true,
"optional": true,
"peer": true,
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/ajv": { "node_modules/ajv": {
"version": "6.12.6", "version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
@ -1729,6 +1798,14 @@
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="
}, },
"node_modules/create-require": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
"dev": true,
"optional": true,
"peer": true
},
"node_modules/cross-spawn": { "node_modules/cross-spawn": {
"version": "7.0.3", "version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
@ -3568,6 +3645,14 @@
"node": "14 || >=16.14" "node": "14 || >=16.14"
} }
}, },
"node_modules/make-error": {
"version": "1.3.6",
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
"integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
"dev": true,
"optional": true,
"peer": true
},
"node_modules/merge2": { "node_modules/merge2": {
"version": "1.4.1", "version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
@ -5029,6 +5114,70 @@
"integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
"dev": true "dev": true
}, },
"node_modules/ts-node": {
"version": "10.9.1",
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz",
"integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==",
"dev": true,
"optional": true,
"peer": true,
"dependencies": {
"@cspotcode/source-map-support": "^0.8.0",
"@tsconfig/node10": "^1.0.7",
"@tsconfig/node12": "^1.0.7",
"@tsconfig/node14": "^1.0.0",
"@tsconfig/node16": "^1.0.2",
"acorn": "^8.4.1",
"acorn-walk": "^8.1.1",
"arg": "^4.1.0",
"create-require": "^1.1.0",
"diff": "^4.0.1",
"make-error": "^1.1.1",
"v8-compile-cache-lib": "^3.0.1",
"yn": "3.1.1"
},
"bin": {
"ts-node": "dist/bin.js",
"ts-node-cwd": "dist/bin-cwd.js",
"ts-node-esm": "dist/bin-esm.js",
"ts-node-script": "dist/bin-script.js",
"ts-node-transpile-only": "dist/bin-transpile.js",
"ts-script": "dist/bin-script-deprecated.js"
},
"peerDependencies": {
"@swc/core": ">=1.2.50",
"@swc/wasm": ">=1.2.50",
"@types/node": "*",
"typescript": ">=2.7"
},
"peerDependenciesMeta": {
"@swc/core": {
"optional": true
},
"@swc/wasm": {
"optional": true
}
}
},
"node_modules/ts-node/node_modules/arg": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
"dev": true,
"optional": true,
"peer": true
},
"node_modules/ts-node/node_modules/diff": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
"dev": true,
"optional": true,
"peer": true,
"engines": {
"node": ">=0.3.1"
}
},
"node_modules/tsconfig-paths": { "node_modules/tsconfig-paths": {
"version": "3.15.0", "version": "3.15.0",
"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz",
@ -5243,6 +5392,14 @@
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"dev": true "dev": true
}, },
"node_modules/v8-compile-cache-lib": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
"integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
"dev": true,
"optional": true,
"peer": true
},
"node_modules/webpack-sources": { "node_modules/webpack-sources": {
"version": "3.2.3", "version": "3.2.3",
"resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz",
@ -5462,6 +5619,17 @@
"node": ">= 14" "node": ">= 14"
} }
}, },
"node_modules/yn": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
"integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
"dev": true,
"optional": true,
"peer": true,
"engines": {
"node": ">=6"
}
},
"node_modules/yocto-queue": { "node_modules/yocto-queue": {
"version": "0.1.0", "version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",

View file

@ -28,10 +28,7 @@ export default function Page() {
<li> <li>
You can contribute to our website development or add your You can contribute to our website development or add your
documents and user account to eeXiv on our{' '} documents and user account to eeXiv on our{' '}
<Link <Link href='https://github.com/team-1280/eexiv' target='_blank'>
href='https://github.com/team-1280/eexiv'
target='_blank'
>
GitHub repository GitHub repository
</Link> </Link>
. .

View file

@ -178,10 +178,10 @@ export default function AuthorDisplay({
))} ))}
</div> </div>
<Bio /> <Bio />
<hr className='mx-auto w-full h-1 border-0 bg-slate-200 my-2 rounded-md' />
<br />
{authorsDocuments.length > 0 && ( {authorsDocuments.length > 0 && (
<> <>
<hr className='mx-auto w-full h-1 border-0 bg-slate-200 my-2 rounded-md' />
<br />
<h1 className='text-3xl md:my-6 my-4 font-serif'> <h1 className='text-3xl md:my-6 my-4 font-serif'>
Published documents Published documents
</h1> </h1>

View file

@ -31,14 +31,21 @@ export default function News() {
eeXiv is currently under active development! There may be major eeXiv is currently under active development! There may be major
updates, breaking changes, or weird bugs. Report bugs, suggest new updates, breaking changes, or weird bugs. Report bugs, suggest new
features, or give us feedback at{' '} features, or give us feedback at{' '}
<a href='https://github.com/team-1280/eexiv/issues' target='_blank' className='text-blue-300'> <a
href='https://github.com/team-1280/eexiv/issues'
target='_blank'
className='text-blue-300'
>
our issue tracker. our issue tracker.
</a> </a>
</li> </li>
<li key={4}> <li key={4}>
Want to upload your documents or just make yourself a profile on Want to upload your documents or just make yourself a profile on
eeXiv? Check our <Link href='/about' className='text-blue-300'>about page</Link> for more eeXiv? Check our{' '}
information! <Link href='/about' className='text-blue-300'>
about page
</Link>{' '}
for more information!
</li> </li>
</ul> </ul>
</div> </div>

View file

@ -1,31 +1,3 @@
/*
documents db schema
documents {
slug: {
manifest: {
title: string
authors: string[]
date: unix epoch integer[] -> if multiple revisions, put the earlier dates first
type: presentation | report | whitepaper | other
latest: integer >= 1 -> the latest revision of the document (earliest = 1)
keywords: string[]
topics: string[]
references: string[],
code: url[]
},
abstract: string,
file: pdf | docx | pptx | targz | other, named file[rev].[ext]
(eg. revision 1 = file1.pdf, revision 2 = file2.pdf, etc)
the "latest" should be the latest revision
citation: a string that can be used to cite the document
reviewers: an array of reviewers, following the reviewer format. if you specify a local
profile username, it will link to the author's profile, and take priority over the link
status: draft | under review | reviewed | published no review
note: published no review should be used for documents where peer review
is not appropriate or unnecessary
}
}
*/
export type FileType = 'pdf' | 'docx' | 'pptx' | 'tar.gz' | 'other' export type FileType = 'pdf' | 'docx' | 'pptx' | 'tar.gz' | 'other'
export type DocumentType = export type DocumentType =
| 'presentation' | 'presentation'
@ -42,11 +14,13 @@ export type reviewer = {
profile?: string profile?: string
url?: string url?: string
} }
export type DocumentStatus = export type DocumentStatus =
| 'draft' | 'draft'
| 'under review' | 'under review'
| 'reviewed' | 'reviewed'
| 'published no review' | 'published no review'
export interface Document { export interface Document {
manifest: DocumentManifest manifest: DocumentManifest
abstract: string abstract: string

View file

@ -83,16 +83,15 @@ export const loadAllAuthors = (): Promise<{ [key: string]: Author }> => {
worker.postMessage('LOAD') worker.postMessage('LOAD')
} else { } else {
reject( return
new Error(
'Web Workers are not supported in this environment. Please avoid using a prehistoric browser.'
)
)
} }
}) })
} }
export const loadAuthor = (id: string): Promise<Author> => { export const loadAuthors = (
authorIds: string[]
): Promise<{ [key: string]: Author }> => {
'use client'
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (typeof Worker !== 'undefined') { if (typeof Worker !== 'undefined') {
const worker = new Worker( const worker = new Worker(
@ -101,12 +100,10 @@ export const loadAuthor = (id: string): Promise<Author> => {
) )
worker.onmessage = (e: MessageEvent<{ [key: string]: Author }>) => { worker.onmessage = (e: MessageEvent<{ [key: string]: Author }>) => {
const data = e.data if (typeof e.data === 'object' && Object.keys(e.data).length > 0) {
const author: Author | undefined = data[id] resolve(e.data)
if (!author) {
return reject(new Error('404'))
} else { } else {
resolve(author) reject(new Error('404'))
} }
worker.terminate() worker.terminate()
} }
@ -116,7 +113,7 @@ export const loadAuthor = (id: string): Promise<Author> => {
worker.terminate() worker.terminate()
} }
worker.postMessage('LOAD') worker.postMessage(authorIds)
} else { } else {
reject( reject(
new Error( new Error(

View file

@ -1,7 +1,30 @@
import { authors } from '../data' import { authors, Author } from '../data'
export function getAuthorsById(authorIds: string[]): { [key: string]: Author } {
const result: { [key: string]: Author } = {}
// Iterate through the array of author IDs
for (const id of authorIds) {
const author = authors[id] // Retrieve the author entry by ID
if (author) {
result[id] = author // If the author exists, add it to the result object
}
}
return result
}
const checkIsStringArray = (data: unknown): data is string[] => {
if (Array.isArray(data)) {
return data.every((d) => typeof d === 'string')
}
return false
}
onmessage = (e) => { onmessage = (e) => {
if (e.data === 'LOAD') { let authorIds: string[] = []
self.postMessage(authors) checkIsStringArray(e.data) && (authorIds = e.data as string[])
} let results = getAuthorsById(authorIds)
postMessage(results)
} }

View file

@ -1,8 +0,0 @@
export default function Page() {
return (
<h1>
This is a placeholder for a document browser that will be implemented in
the future. In the meantime, you can view specific documents by searching.
</h1>
)
}

View file

@ -1,4 +1,4 @@
import { DocumentType, documents } from '@/app/db/data' 'use client'
import { Zilla_Slab } from 'next/font/google' import { Zilla_Slab } from 'next/font/google'
import { epoch2datestring } from '@/app/utils/epoch2datestring' import { epoch2datestring } from '@/app/utils/epoch2datestring'
import { import {
@ -9,14 +9,29 @@ import {
Reviewers, Reviewers,
} from '@/app/components/DataDisplay' } from '@/app/components/DataDisplay'
import { ItemBadge, Status } from '@/app/components/Badges' import { ItemBadge, Status } from '@/app/components/Badges'
import Link from 'next/link'
import { notFound } from 'next/navigation' import { notFound } from 'next/navigation'
import VersionChooser from './VersionChooser'
import crypto from 'crypto'
import { Suspense } from 'react'
import { loadDocument } from '@/app/db/loaders'
import { useSuspenseQuery } from '@tanstack/react-query'
const zillaSlab = Zilla_Slab({ subsets: ['latin'], weight: ['500'] }) const zillaSlab = Zilla_Slab({ subsets: ['latin'], weight: ['500'] })
export default function DocumentViewer({ slug }: Readonly<{ slug: string }>) { const DocumentViewer = ({ slug }: Readonly<{ slug: string }>) => {
const { manifest, abstract, file, citation } = documents[slug] const { data, error } = useSuspenseQuery({
if (!manifest) return notFound() queryKey: [slug],
queryFn: () => {
const data = loadDocument(slug)
return data
},
})
if (error) throw error
let doc = data
// const doc = documents[slug]
const { manifest, abstract, citation } = doc
// if (!manifest) return notFound()
const { const {
title, title,
authors, authors,
@ -30,6 +45,13 @@ export default function DocumentViewer({ slug }: Readonly<{ slug: string }>) {
status, status,
} = manifest } = manifest
// git style hash
const hash = crypto
.createHash('sha256')
.update(slug)
.digest('hex')
.substring(0, 7)
return ( return (
<div className='max-w-4xl lg:max-w-6xl mx-auto'> <div className='max-w-4xl lg:max-w-6xl mx-auto'>
<h1 <h1
@ -51,7 +73,7 @@ export default function DocumentViewer({ slug }: Readonly<{ slug: string }>) {
</span> </span>
</p> </p>
<div className='flex flex-wrap gap-2'> <div className='flex flex-wrap gap-2'>
<ItemBadge itemName={type as DocumentType} /> <ItemBadge itemName={type} />
<Status statusName={status} /> <Status statusName={status} />
<span className='border-gray-200 border-2 rounded px-2 py-1.5 mr-2 shadow-sm shadow-slate-300'> <span className='border-gray-200 border-2 rounded px-2 py-1.5 mr-2 shadow-sm shadow-slate-300'>
Revision {latest} Revision {latest}
@ -76,27 +98,21 @@ export default function DocumentViewer({ slug }: Readonly<{ slug: string }>) {
</p> </p>
<p className='my-2'> <p className='my-2'>
<span className='font-bold'>Cite as: </span> <span className='font-bold'>Cite as: </span>
{citation ? <>{citation}</> : <>eeXiv:{slug}</>} {citation ? <>{citation}</> : <>eeXiv:{hash}</>}
</p> </p>
<Link <Suspense
href={`/download/${slug}/file${latest}${file === 'other' ? '' : `.${file}`}`} fallback={
download={`${slug}-rev-${latest}${file === 'other' ? '' : `.${file}`}`} <div className='max-w-sm animate-pulse flex flex-wrap gap-2'>
target='_blank' <div className='rounded-sm h-10 bg-gray-300 w-3 flex-grow basis-1 mt-2 mb-2'></div>
> <div className='rounded-sm h-10 bg-gray-300 w-3 flex-grow basis-1.5 mt-2 mb-2'></div>
<button className='button-default'> <div className='rounded-sm h-10 bg-gray-300 w-1 flex-grow basis-1 mt-2 mb-2'></div>
Download{' '} </div>
{(() => {
switch (file) {
case 'other':
return <></>
case 'tar.gz':
return 'Tarball'
default:
return file.toUpperCase()
} }
})()} >
</button> <VersionChooser doc={doc} slug={slug} />
</Link> </Suspense>
</div> </div>
) )
} }
export default DocumentViewer

View file

@ -0,0 +1,89 @@
'use client'
import { useState } from 'react'
import { Document } from '@/app/db/data'
import Link from 'next/link'
import { loadAuthors } from '@/app/db/loaders'
import { useSuspenseQuery } from '@tanstack/react-query'
import { epoch2date } from '@/app/utils/epoch2datestring'
const VersionChooser = ({
doc,
slug,
}: Readonly<{ doc: Document; slug: string }>) => {
const { data, error } = useSuspenseQuery({
queryKey: ['authors_all'],
queryFn: () => {
const data = loadAuthors(doc.manifest.authors)
return data
},
})
if (error) throw error
const { file } = doc
const { authors, latest } = doc.manifest
const date = epoch2date(doc.manifest.dates[doc.manifest.dates.length - 1])
const fileEnding = file === 'other' ? '' : `.${file}`
const [selectedRevision, setSelectedRevision] = useState<number>(latest) // Initialize the selected revision with the latest revision
return (
<div>
<Link
href={`/download/${slug}/file${selectedRevision}${fileEnding}`}
target='_blank'
>
<button className='button-default'>
Download{' '}
{(() => {
switch (file) {
case 'other':
return <></>
case 'tar.gz':
return 'Tarball'
}
})()}
</button>
</Link>
<button
className='ml-2 h-10 px-2.5 bg-slate-300 rounded-md'
onClick={() => {
const bibtex = `@article{
author={${authors
.map((a: string, i) => {
const author = data[a].name.first + ' ' + data[a].name.last
if (i === 0) return author
else if (i === authors.length - 1) return ` and ${author}`
else return `, ${author}`
})
.join('')}},
title={${doc.manifest.title}},
journal={eeXiv journal},
year={${date.getFullYear()}},
month={${date.toLocaleString('default', { month: 'short' })}},
url={${window.location.href}}
}`
navigator.clipboard.writeText(bibtex)
}}
>
Export BibTeX
</button>
<select
className='ml-2 h-10 px-2.5 bg-slate-300 rounded-md'
value={`v${selectedRevision}`}
onChange={(e) => {
setSelectedRevision(parseInt(e.target.value.replace(/\D/g, ''), 10))
}}
>
{Array.from({ length: latest }, (_, index) => index + 1).map(
(version) => (
<option key={version} value={`v${version}`} className='p-2.5'>
{version == latest ? 'Latest' : `v${version}`}
</option>
)
)}
</select>
</div>
)
}
export default VersionChooser

View file

@ -0,0 +1,31 @@
.skeleton-box {
display: inline-block;
height: 1em;
position: relative;
overflow: hidden;
background-color: #dddbdd;
&::after {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
transform: translateX(-100%);
background-image: linear-gradient(
90deg,
rgba(#fff, 0) 0,
rgba(#fff, 0.2) 20%,
rgba(#fff, 0.5) 60%,
rgba(#fff, 0)
);
animation: shimmer 5s infinite;
content: '';
}
@keyframes shimmer {
100% {
transform: translateX(100%);
}
}
}

View file

@ -1,13 +1,18 @@
'use client'
import DocumentViewer from './DocumentViewer' import DocumentViewer from './DocumentViewer'
import { documents } from '@/app/db/data' import ErrorBoundary from '@/app/utils/ErrorBoundary'
export function generateStaticParams() { // export function generateStaticParams() {
const docsList = Object.keys(documents) // const docsList = Object.keys(documents)
return docsList.map((doc) => ({ doc })) // return docsList.map((doc) => ({ doc }))
} // }
export default function Page({ export default function Page({
params, params,
}: Readonly<{ params: { slug: string } }>) { }: Readonly<{ params: { slug: string } }>) {
return <DocumentViewer slug={params.slug} /> return (
<ErrorBoundary>
<DocumentViewer slug={params.slug} />
</ErrorBoundary>
)
} }

View file

@ -20,6 +20,11 @@ a:hover {
text-decoration: underline; text-decoration: underline;
} }
option {
@apply font-sans;
@apply p-4;
}
.button-default { .button-default {
@apply bg-blue-600 text-slate-100 hover:bg-blue-400 font-semibold rounded py-2 px-4 my-2 shadow-sm shadow-slate-400; @apply bg-blue-600 text-slate-100 hover:bg-blue-400 font-semibold rounded py-2 px-4 my-2 shadow-sm shadow-slate-400;
} }

View file

@ -1,7 +1,9 @@
export default function Page() { export default function Page() {
return ( return (
<div className='content text-slate-800'> <div className='content text-slate-800'>
<h1 className='text-3xl text-slate-800 mt-4 mb-2 font-serif'>Accessibility</h1> <h1 className='text-3xl text-slate-800 mt-4 mb-2 font-serif'>
Accessibility
</h1>
<p> <p>
If you encounter any accessibility-related issues related to your use of If you encounter any accessibility-related issues related to your use of
our site, it is likely because of our jank code architecture. our site, it is likely because of our jank code architecture.

View file

@ -2,7 +2,9 @@ import Link from 'next/link'
export default function Page() { export default function Page() {
return ( return (
<div className='content text-slate-800'> <div className='content text-slate-800'>
<h1 className='text-3xl text-slate-800 mt-4 mb-2 font-serif'>Copyright</h1> <h1 className='text-3xl text-slate-800 mt-4 mb-2 font-serif'>
Copyright
</h1>
<p> <p>
All content on this site is licensed under the{' '} All content on this site is licensed under the{' '}
<a href='https://creativecommons.org/licenses/by-sa/4.0/'> <a href='https://creativecommons.org/licenses/by-sa/4.0/'>

View file

@ -1,7 +1,9 @@
export default function Page() { export default function Page() {
return ( return (
<div className='content text-slate-800'> <div className='content text-slate-800'>
<h1 className='text-3xl text-slate-800 mt-4 mb-2 font-serif'>Privacy Policy</h1> <h1 className='text-3xl text-slate-800 mt-4 mb-2 font-serif'>
Privacy Policy
</h1>
<p> <p>
{`User privacy is important to us. Just kidding. We don't collect any {`User privacy is important to us. Just kidding. We don't collect any
personal information. We only use it to help us improve eeXiv. Your personal information. We only use it to help us improve eeXiv. Your

View file

@ -3,12 +3,15 @@ import Link from 'next/link'
export default function Page() { export default function Page() {
return ( return (
<div className='content text-slate-800'> <div className='content text-slate-800'>
<h1 className='text-3xl text-slate-800 mt-4 mb-2 font-serif'>Get status notifications</h1> <h1 className='text-3xl text-slate-800 mt-4 mb-2 font-serif'>
Get status notifications
</h1>
<p> <p>
We currently do not have the technical support to implement mailing We currently do not have the technical support to implement mailing
lists in eeXiv. Check <Link href='/status'>status</Link> frequently for updates. lists in eeXiv. Check <Link href='/status'>status</Link> frequently for
The best way to stay connected with the status of the eeXiv project is updates. The best way to stay connected with the status of the eeXiv
to watch it on <a href='https://github.com/Team-1280/eeXiv'>GitHub</a>. project is to watch it on{' '}
<a href='https://github.com/Team-1280/eeXiv'>GitHub</a>.
</p> </p>
</div> </div>
) )

View file

@ -6,7 +6,9 @@ export default function Page() {
return ( return (
<div className='content text-slate-800'> <div className='content text-slate-800'>
<h1 className='text-3xl text-slate-800 mt-4 mb-4 font-serif'>Status</h1> <h1 className='text-3xl text-slate-800 mt-4 mb-4 font-serif'>Status</h1>
<p className={`${zillaSlab.className} p-6 rounded-lg bg-green-600 text-xl text-white text-center`}> <p
className={`${zillaSlab.className} p-6 rounded-lg bg-green-600 text-xl text-white text-center`}
>
eeXiv is <strong>online</strong>. All systems{' '} eeXiv is <strong>online</strong>. All systems{' '}
<strong>operational</strong>. <strong>operational</strong>.
</p> </p>

View file

@ -1,7 +1,9 @@
export default function Page() { export default function Page() {
return ( return (
<div className='content text-slate-800'> <div className='content text-slate-800'>
<h1 className='text-3xl text-slate-800 mt-4 mb-2 font-serif'>Subscribe</h1> <h1 className='text-3xl text-slate-800 mt-4 mb-2 font-serif'>
Subscribe
</h1>
<p> <p>
We currently do not have the technical support to implement mailing We currently do not have the technical support to implement mailing
lists in eeXiv. Check back later for updates. The best way to stay lists in eeXiv. Check back later for updates. The best way to stay

View file

@ -11,3 +11,7 @@ export function epoch2datestring(epoch: number): string {
return formattedDate return formattedDate
} }
export function epoch2date(epoch: number): Date {
return new Date(epoch * 1000)
}