diff --git a/app/routes/[sitemap.xml].tsx b/app/routes/[sitemap.xml].tsx index a3de8473..c25f47bd 100644 --- a/app/routes/[sitemap.xml].tsx +++ b/app/routes/[sitemap.xml].tsx @@ -1,11 +1,11 @@ import {LoaderArgs} from '@remix-run/cloudflare' -import {loadAllQuestions, QuestionStatus, Question} from '~/server-utils/stampy' +import {loadAllQuestions, QuestionStatus, Question, QuestionState} from '~/server-utils/stampy' export const loader = async ({request}: LoaderArgs) => { const origin = new URL(request.url).origin const formatQuestion = ({pageid, updatedAt}: Question) => ` - ${origin}/?state=${pageid} + ${origin}/?state=${pageid}${QuestionState.OPEN} ${updatedAt} 1.0 diff --git a/app/routes/questions/$question.tsx b/app/routes/questions/$question.tsx index 5934cfe2..667a3d57 100644 --- a/app/routes/questions/$question.tsx +++ b/app/routes/questions/$question.tsx @@ -273,7 +273,8 @@ function Contents({pageid, html, glossary}: {pageid: PageId; html: string; gloss entry && addPopup( e as HTMLSpanElement, - `
${entry.contents}

See more...` + `
${entry.contents}
` + + (entry.pageid ? `
See more...` : '') ) }) diff --git a/app/server-utils/stampy.ts b/app/server-utils/stampy.ts index 6bea7faa..4bf9be41 100644 --- a/app/server-utils/stampy.ts +++ b/app/server-utils/stampy.ts @@ -68,7 +68,7 @@ type Entity = { rowId: string tableUrl: string } -type CodaRow = { +type CodaRowCommon = { id: string type: string href: string @@ -77,6 +77,9 @@ type CodaRow = { createdAt: string updatedAt: string browserLink: string +} + +type AnswersRow = CodaRowCommon & { values: { 'Edit Answer': string Link: { @@ -91,13 +94,25 @@ type CodaRow = { 'Related IDs': '' | string[] Tags: '' | Entity[] 'Rich Text': string - + } +} +type TagsRow = CodaRowCommon & { + values: { 'Tag ID': number 'Internal?': boolean 'Questions tagged with this': Entity[] 'Main question': string | Entity | null } } +type GlossaryRow = CodaRowCommon & { + values: { + definition: string + phrase: string + aliases: string + 'UI ID': string + } +} +type CodaRow = AnswersRow | TagsRow | GlossaryRow type CodaResponse = { items: CodaRow[] nextPageLink: string | null @@ -113,6 +128,7 @@ const ALL_ANSWERS_TABLE = 'table-YvPEyAXl8a' // All answers const INCOMING_QUESTIONS_TABLE = 'grid-S_6SYj6Tjm' // Incoming questions const TAGS_TABLE = 'grid-4uOTjz1Rkz' const WRITES_TABLE = 'table-eEhx2YPsBE' +const GLOSSARY_TABLE = 'grid-_pSzs23jmw' const enc = encodeURIComponent const quote = (x: string) => encodeURIComponent(`"${x.replace(/"/g, '\\"')}"`) @@ -221,7 +237,7 @@ const head = (item: string | string[]) => { } const extractText = (markdown: string) => head(markdown)?.replace(/^```|```$/g, '') const extractLink = (markdown: string) => markdown?.replace(/^.*\(|\)/g, '') -const convertToQuestion = ({name, values, updatedAt} = {} as CodaRow): Question => ({ +const convertToQuestion = ({name, values, updatedAt} = {} as AnswersRow): Question => ({ title: name, pageid: extractText(values['UI ID']), text: renderText(extractText(values['UI ID']), values['Rich Text']), @@ -239,53 +255,50 @@ const convertToQuestion = ({name, values, updatedAt} = {} as CodaRow): Question }) export const loadQuestionDetail = withCache('questionDetail', async (question: string) => { - const rows = await getCodaRows( + const rows = (await getCodaRows( QUESTION_DETAILS_TABLE, // ids are now alphanumerical, so not possible to detect id by regex match for \d // let's detect ids by length, hopefully no one will make question name so short question.length <= 6 ? 'UI ID' : 'Name', question - ) + )) as AnswersRow[] return convertToQuestion(rows[0]) }) export const loadInitialQuestions = withCache('initialQuestions', async () => { - const rows = await getCodaRows(INITIAL_QUESTIONS_TABLE) + const rows = (await getCodaRows(INITIAL_QUESTIONS_TABLE)) as AnswersRow[] const data = rows.map(convertToQuestion) return data }) export const loadGlossary = withCache('loadGlossary', async () => { - const rows = await getCodaRows(ON_SITE_TABLE) - - const getContents = (q: Question): string => { - if (!q.text) return '' - - // The contents are HTML paragraphs with random stuff in them. This function - // should return the first paragraph - const contents = q.text.split('

')[0] - return contents && contents + '

' - } - - const gloss = rows - .map(convertToQuestion) - .filter((q) => q.tags.includes('Glossary')) - .map((q) => ({ - term: q.title.replace(/^What is ((a|an|the) )?('|")?(.*?)('|")?\?$/, '$4'), - pageid: q.pageid, - contents: getContents(q), - })) - return Object.fromEntries(gloss.map((e) => [e.term.toLowerCase(), e])) + const rows = (await getCodaRows(GLOSSARY_TABLE)) as GlossaryRow[] + return Object.fromEntries( + rows + .map(({values}) => { + const pageid = extractText(values['UI ID']) + const phrases = [values.phrase, ...values.aliases.split('\n')] + const item = { + pageid, + contents: renderText(pageid, extractText(values.definition)), + } + return phrases + .map((i) => extractText(i.toLowerCase())) + .filter(Boolean) + .map((phrase) => [phrase, {term: phrase, ...item}]) + }) + .flat() + ) }) export const loadOnSiteAnswers = withCache('onSiteAnswers', async () => { - const rows = await getCodaRows(ON_SITE_TABLE) + const rows = (await getCodaRows(ON_SITE_TABLE)) as AnswersRow[] const questions = rows.map(convertToQuestion) return {questions, nextPageLink: null} }) export const loadAllQuestions = withCache('allQuestions', async () => { - const rows = await getCodaRows(ALL_ANSWERS_TABLE) + const rows = (await getCodaRows(ALL_ANSWERS_TABLE)) as AnswersRow[] return rows.map(convertToQuestion) }) @@ -299,7 +312,7 @@ const extractMainQuestion = (question: string | Entity | null): string | null => return question } } -const toTag = (r: CodaRow, nameToId: Record): Tag => ({ +const toTag = (r: TagsRow, nameToId: Record): Tag => ({ rowId: r.id, tagId: r.values['Tag ID'], name: r.name, @@ -312,7 +325,7 @@ const toTag = (r: CodaRow, nameToId: Record): Tag => ({ }) export const loadTag = withCache('tag', async (tagName: string): Promise => { - const rows = await getCodaRows(TAGS_TABLE, 'Tag name', tagName) + const rows = (await getCodaRows(TAGS_TABLE, 'Tag name', tagName)) as TagsRow[] const questions = await loadAllQuestions('NEVER_RELOAD') const nameToId = Object.fromEntries( @@ -324,7 +337,7 @@ export const loadTag = withCache('tag', async (tagName: string): Promise => }) export const loadTags = withCache('tags', async (): Promise => { - const rows = await getCodaRows(TAGS_TABLE, 'Internal?', 'false') + const rows = (await getCodaRows(TAGS_TABLE, 'Internal?', 'false')) as TagsRow[] const questions = await loadAllQuestions('NEVER_RELOAD') const nameToId = Object.fromEntries( @@ -342,8 +355,8 @@ export const loadMoreAnswerDetails = withCache( ): Promise<{questions: Question[]; nextPageLink: string | null}> => { const url = nextPageLink || makeCodaRequest({table: ON_SITE_TABLE, limit: 10}) const result = await fetchRows(url) - const questions = result.items.map(convertToQuestion) - return {nextPageLink: result.nextPageLink, questions} + const items = result.items as AnswersRow[] + return {nextPageLink: result.nextPageLink, questions: items.map(convertToQuestion)} } )