Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Separate /tags/display/$tag from /tags/$tag #380

Merged
merged 6 commits into from
Feb 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions app/components/ArticlesDropdown/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,16 @@ export const ArticlesDropdown = ({toc, categories}: ArticlesDropdownProps) => (
<Link
key={rowId}
className="articles-dropdown-teal-entry"
to={`/tags/${name}`}
to={`/tags/display/${name}`}
text={name}
/>
))}

<Button action="/tags" className="dropdown-button bordered grey dropdown-button-label">
Browse all categories
<Button
action={`/tags/display/${categories[0].name}`}
className="dropdown-button bordered grey dropdown-button-label"
>
Browse all categories
</Button>
</div>
</div>
Expand Down
14 changes: 5 additions & 9 deletions app/components/CategoriesNav/Menu.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import {useState} from 'react'
import {SearchInput} from '../SearchInput/Input'
import {Tag as TagType} from '~/server-utils/stampy'
import './menu.css'
Expand All @@ -11,7 +10,7 @@ interface CategoriesNavProps {
/**
* Selected article
*/
active: number
active: TagType
/**
* Callback function to handle click on article
*/
Expand All @@ -23,12 +22,9 @@ interface CategoriesNavProps {
}

export const CategoriesNav = ({categories, active, onChange, onClick}: CategoriesNavProps) => {
const [selected, setSelected] = useState(active || 0)

const handleClick = (index: number) => {
setSelected(index)
const handleClick = (newTag: TagType) => {
if (onClick) {
onClick(index)
onClick(newTag)
}
}

Expand All @@ -44,9 +40,9 @@ export const CategoriesNav = ({categories, active, onChange, onClick}: Categorie
key={`category-${category.tagId}`}
className={[
'category-autoLayoutHorizontal',
selected == category.tagId ? ['active'].join(' ') : '',
active.tagId == category.tagId ? ['active'].join(' ') : '',
].join(' ')}
onClick={() => handleClick(category.tagId)}
onClick={() => handleClick(category)}
>
<div className={'category-title'}>
{category.name} ({category.questions.length})
Expand Down
2 changes: 1 addition & 1 deletion app/routes/questions.$questionId.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import AutoHeight from 'react-auto-height'
import type {Question, Glossary, PageId, Banner as BannerType, Tag} from '~/server-utils/stampy'
import type useQuestionStateInUrl from '~/hooks/useQuestionStateInUrl'
import {Edit, Link as LinkIcon} from '~/components/icons-generated'
import {Tags} from '~/routes/tags.$tag'
import {Tags} from '~/routes/tags.single.$tag'
import CopyLink from '~/components/copyLink'
import {Action, ActionType} from '~/routes/questions.actions'
import {reloadInBackgroundIfNeeded} from '~/server-utils/kv-cache'
Expand Down
164 changes: 27 additions & 137 deletions app/routes/tags.$tag.tsx
Original file line number Diff line number Diff line change
@@ -1,39 +1,28 @@
import {useState, useEffect, ReactNode} from 'react'
import {useState, useEffect} from 'react'
import {LoaderFunction} from '@remix-run/cloudflare'
import {reloadInBackgroundIfNeeded} from '~/server-utils/kv-cache'
import {Tag as TagType, QuestionState, RelatedQuestions, loadTags} from '~/server-utils/stampy'
import Dialog from '~/components/dialog'
import {Tag as TagType, loadTags} from '~/server-utils/stampy'
import Footer from '~/components/Footer'
import Header from '~/components/Header'
import useToC from '~/hooks/useToC'
import {useLoaderData} from '@remix-run/react'
import {useLoaderData, useNavigate} from '@remix-run/react'
import {ListTable} from '~/components/Table/ListTable'
import {CategoriesNav} from '~/components/CategoriesNav/Menu'

type Props = {
tags: string[]
selectQuestion: (pageid: string, title: string) => void
[k: string]: unknown
}

export const loader = async ({request, params}: Parameters<LoaderFunction>[0]) => {
const {tag} = params
if (!tag) {
const {tag: tagFromUrl} = params
if (!tagFromUrl) {
throw Error('missing tag name')
}

try {
const tags = await loadTags(request)
return {tag, ...tags}
} catch (error: unknown) {
console.error(`error fetching tag "${tag}":`, error)
return {
error: error?.toString(),
timestamp: new Date().toISOString(),
data: new Array<TagType>(),
tag,
}
const tags = await loadTags(request)
const currentTag = tags.data.find((tagData) => tagData.name === tagFromUrl)
if (currentTag === undefined) {
throw new Response(null, {
status: 404,
statusText: 'Unable to find requested tag',
})
}
return {...tags, currentTag}
}

export const sortFuncs = {
Expand All @@ -42,121 +31,20 @@ export const sortFuncs = {
'by number of questions': (a: TagType, b: TagType) => b.questions.length - a.questions.length,
}

export async function fetchTag(tagName: string): Promise<TagType | never[]> {
const url = `/tags/${encodeURIComponent(tagName)}`
return fetch(url).then(async (response) => {
const json: Awaited<ReturnType<typeof loader>> = await response.json()
if ('error' in json) console.error(json.error)
const {data, timestamp} = json
const currentTagData = data.filter((tagData) => tagData.name === tagName)[0]

reloadInBackgroundIfNeeded(url, timestamp)

return currentTagData
})
}

export function Tag({
name,
questions: tqs,
showCount,
}: {
name: string
questions?: RelatedQuestions
showCount?: boolean
}) {
const [questions, setQuestions] = useState(tqs)
const pageIds = questions?.map((q) => q.pageid).join(QuestionState.COLLAPSED)

useEffect(() => {
const fetcher = async () => {
if (!questions) {
const tag = (await fetchTag(name)) as TagType
if (tag) setQuestions(tag.questions)
}
}
fetcher()
}, [questions, name])

return (
<a className="tag" href={`/?state=${pageIds}${QuestionState.COLLAPSED}`} key={name}>
<span className="tag-name">{name}</span>
{showCount && <span className="tag-stat">({questions?.length})</span>}
</a>
)
}

export function TagQuestions({
tag,
selectQuestion,
clearTag,
}: {
tag: TagType
selectQuestion: (pageid: string, title: string) => void
clearTag: () => void
}) {
const TagDialog = ({children}: {children: ReactNode | ReactNode[]}) => (
<Dialog onClose={() => clearTag()}>
<div className="dialog-title">
<div className="dialog-title-header">
Select a question from the <b>{tag.name}</b> tag...
</div>
</div>
{children}
</Dialog>
)

if (!tag.rowId) {
return (
<TagDialog>
<div className="loader"></div>
</TagDialog>
)
}

return (
<TagDialog>
<div className="tag-questions">
{tag.questions.map((question) => (
<div key={tag.rowId + '-' + question.pageid}>
<button
className="question-tag"
onClick={() => {
selectQuestion(question.pageid, question.title)
clearTag()
}}
>
{question.title}
</button>
</div>
))}
</div>
</TagDialog>
)
}

export function Tags({tags}: Props) {
return (
<div className="tags-container">
<div>{tags && tags.map((name) => <Tag key={name} name={name} />)}</div>
</div>
)
}

export default function App() {
const {tag, data} = useLoaderData<ReturnType<typeof loader>>()
const {currentTag, data} = useLoaderData<ReturnType<typeof loader>>()
const [selectedTag, setSelectedTag] = useState<TagType | null>(null)
const [tagsFilter, setTagsFilter] = useState<string>('')
const {toc} = useToC()
const navigate = useNavigate()

const [sortBy] = useState<keyof typeof sortFuncs>('alphabetically')

const currentTagData = data.filter((tagData) => tagData.name === tag)[0]
useEffect(() => {
if (selectedTag === null) {
setSelectedTag(data.filter((tag) => tag.questions.length > 0)[0])
if (selectedTag !== currentTag) {
setSelectedTag(currentTag)
}
}, [data, selectedTag])
}, [selectedTag, data, currentTag])
if (selectedTag === null) {
return null
}
Expand All @@ -175,22 +63,24 @@ export default function App() {

// {title: "AI Safety", id: 1},
}
active={Number(selectedTag)}
onClick={setSelectedTag}
active={selectedTag}
onClick={(selectedTag) => {
navigate(`../${selectedTag.name}`, {relative: 'path'})
}}
onChange={setTagsFilter}
/>

{selectedTag === null ? null : (
<div>
<h1 style={{marginTop: '0px'}}>{currentTagData.name}</h1>
{currentTagData.questions.length === 0 ? (
<h1 style={{marginTop: '0px'}}>{selectedTag.name}</h1>
{selectedTag.questions.length === 0 ? (
<div className={'no-questions'}>No questions found</div>
) : (
<p>
{currentTagData.questions.length} pages tagged {`"${currentTagData.name}"`}
{selectedTag.questions.length} pages tagged {`"${selectedTag.name}"`}
</p>
)}
{selectedTag && <ListTable elements={currentTagData.questions} />}
{selectedTag && <ListTable elements={selectedTag.questions} />}
</div>
)}
</div>
Expand All @@ -203,4 +93,4 @@ export default function App() {
<Footer />
</>
)
}
}
2 changes: 1 addition & 1 deletion app/routes/tags._index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ export const loader = async ({request}: Parameters<LoaderFunction>[0]) => {
const tags = await loadTags(request)
const {data = []} = tags ?? {}
const defaultTag = data[0]
throw redirect(`${defaultTag.name}`)
throw redirect(`${encodeURIComponent(defaultTag.name)}`)
}
Loading
Loading