Merge branch 'main' of github.com:Team-1280/eeXiv

This commit is contained in:
Youwen Wu 2024-02-15 02:43:49 -08:00
commit bd6b331338
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_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": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
@ -971,6 +997,38 @@
"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": {
"version": "1.0.5",
"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"
}
},
"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": {
"version": "6.12.6",
"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",
"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": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
@ -3568,6 +3645,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": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
@ -5029,6 +5114,70 @@
"integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
"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": {
"version": "3.15.0",
"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz",
@ -5243,6 +5392,14 @@
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"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": {
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz",
@ -5462,6 +5619,17 @@
"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": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",

View file

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

View file

@ -178,10 +178,10 @@ export default function AuthorDisplay({
))}
</div>
<Bio />
<hr className='mx-auto w-full h-1 border-0 bg-slate-200 my-2 rounded-md' />
<br />
{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'>
Published documents
</h1>

View file

@ -31,14 +31,21 @@ export default function News() {
eeXiv is currently under active development! There may be major
updates, breaking changes, or weird bugs. Report bugs, suggest new
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.
</a>
</li>
<li key={4}>
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
information!
eeXiv? Check our{' '}
<Link href='/about' className='text-blue-300'>
about page
</Link>{' '}
for more information!
</li>
</ul>
</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 DocumentType =
| 'presentation'
@ -42,11 +14,13 @@ export type reviewer = {
profile?: string
url?: string
}
export type DocumentStatus =
| 'draft'
| 'under review'
| 'reviewed'
| 'published no review'
export interface Document {
manifest: DocumentManifest
abstract: string

View file

@ -83,16 +83,15 @@ export const loadAllAuthors = (): Promise<{ [key: string]: Author }> => {
worker.postMessage('LOAD')
} else {
reject(
new Error(
'Web Workers are not supported in this environment. Please avoid using a prehistoric browser.'
)
)
return
}
})
}
export const loadAuthor = (id: string): Promise<Author> => {
export const loadAuthors = (
authorIds: string[]
): Promise<{ [key: string]: Author }> => {
'use client'
return new Promise((resolve, reject) => {
if (typeof Worker !== 'undefined') {
const worker = new Worker(
@ -101,12 +100,10 @@ export const loadAuthor = (id: string): Promise<Author> => {
)
worker.onmessage = (e: MessageEvent<{ [key: string]: Author }>) => {
const data = e.data
const author: Author | undefined = data[id]
if (!author) {
return reject(new Error('404'))
if (typeof e.data === 'object' && Object.keys(e.data).length > 0) {
resolve(e.data)
} else {
resolve(author)
reject(new Error('404'))
}
worker.terminate()
}
@ -116,7 +113,7 @@ export const loadAuthor = (id: string): Promise<Author> => {
worker.terminate()
}
worker.postMessage('LOAD')
worker.postMessage(authorIds)
} else {
reject(
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) => {
if (e.data === 'LOAD') {
self.postMessage(authors)
}
let authorIds: string[] = []
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 { epoch2datestring } from '@/app/utils/epoch2datestring'
import {
@ -9,14 +9,29 @@ import {
Reviewers,
} from '@/app/components/DataDisplay'
import { ItemBadge, Status } from '@/app/components/Badges'
import Link from 'next/link'
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'] })
export default function DocumentViewer({ slug }: Readonly<{ slug: string }>) {
const { manifest, abstract, file, citation } = documents[slug]
if (!manifest) return notFound()
const DocumentViewer = ({ slug }: Readonly<{ slug: string }>) => {
const { data, error } = useSuspenseQuery({
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 {
title,
authors,
@ -30,6 +45,13 @@ export default function DocumentViewer({ slug }: Readonly<{ slug: string }>) {
status,
} = manifest
// git style hash
const hash = crypto
.createHash('sha256')
.update(slug)
.digest('hex')
.substring(0, 7)
return (
<div className='max-w-4xl lg:max-w-6xl mx-auto'>
<h1
@ -51,7 +73,7 @@ export default function DocumentViewer({ slug }: Readonly<{ slug: string }>) {
</span>
</p>
<div className='flex flex-wrap gap-2'>
<ItemBadge itemName={type as DocumentType} />
<ItemBadge itemName={type} />
<Status statusName={status} />
<span className='border-gray-200 border-2 rounded px-2 py-1.5 mr-2 shadow-sm shadow-slate-300'>
Revision {latest}
@ -76,27 +98,21 @@ export default function DocumentViewer({ slug }: Readonly<{ slug: string }>) {
</p>
<p className='my-2'>
<span className='font-bold'>Cite as: </span>
{citation ? <>{citation}</> : <>eeXiv:{slug}</>}
{citation ? <>{citation}</> : <>eeXiv:{hash}</>}
</p>
<Link
href={`/download/${slug}/file${latest}${file === 'other' ? '' : `.${file}`}`}
download={`${slug}-rev-${latest}${file === 'other' ? '' : `.${file}`}`}
target='_blank'
<Suspense
fallback={
<div className='max-w-sm animate-pulse flex flex-wrap gap-2'>
<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>
<div className='rounded-sm h-10 bg-gray-300 w-1 flex-grow basis-1 mt-2 mb-2'></div>
</div>
}
>
<button className='button-default'>
Download{' '}
{(() => {
switch (file) {
case 'other':
return <></>
case 'tar.gz':
return 'Tarball'
default:
return file.toUpperCase()
}
})()}
</button>
</Link>
<VersionChooser doc={doc} slug={slug} />
</Suspense>
</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 { documents } from '@/app/db/data'
import ErrorBoundary from '@/app/utils/ErrorBoundary'
export function generateStaticParams() {
const docsList = Object.keys(documents)
return docsList.map((doc) => ({ doc }))
}
// export function generateStaticParams() {
// const docsList = Object.keys(documents)
// return docsList.map((doc) => ({ doc }))
// }
export default function Page({
params,
}: 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;
}
option {
@apply font-sans;
@apply p-4;
}
.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;
}

View file

@ -1,7 +1,9 @@
export default function Page() {
return (
<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>
If you encounter any accessibility-related issues related to your use of
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() {
return (
<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>
All content on this site is licensed under the{' '}
<a href='https://creativecommons.org/licenses/by-sa/4.0/'>

View file

@ -1,7 +1,9 @@
export default function Page() {
return (
<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>
{`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

View file

@ -3,12 +3,15 @@ import Link from 'next/link'
export default function Page() {
return (
<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>
We currently do not have the technical support to implement mailing
lists in eeXiv. Check <Link href='/status'>status</Link> frequently for updates.
The best way to stay connected with the status of the eeXiv project is
to watch it on <a href='https://github.com/Team-1280/eeXiv'>GitHub</a>.
lists in eeXiv. Check <Link href='/status'>status</Link> frequently for
updates. The best way to stay connected with the status of the eeXiv
project is to watch it on{' '}
<a href='https://github.com/Team-1280/eeXiv'>GitHub</a>.
</p>
</div>
)

View file

@ -6,7 +6,9 @@ export default function Page() {
return (
<div className='content text-slate-800'>
<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{' '}
<strong>operational</strong>.
</p>

View file

@ -1,7 +1,9 @@
export default function Page() {
return (
<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>
We currently do not have the technical support to implement mailing
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
}
export function epoch2date(epoch: number): Date {
return new Date(epoch * 1000)
}