Skip to content

Commit

Permalink
Merge pull request #300 from StampyAI/banners
Browse files Browse the repository at this point in the history
Add basic banner functionality
  • Loading branch information
mruwnik authored Aug 8, 2023
2 parents dc2fdaf + 616d221 commit 2f9cd81
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 30 deletions.
12 changes: 4 additions & 8 deletions app/hooks/useQuestionStateInUrl.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,7 @@
import {useState, useRef, useEffect, useMemo, useCallback} from 'react'
import type {MouseEvent} from 'react'
import {useSearchParams, useTransition} from '@remix-run/react'
import {
Question,
QuestionState,
RelatedQuestions,
PageId,
QuestionStatus,
Glossary,
} from '~/server-utils/stampy'
import {Question, QuestionState, RelatedQuestions, PageId, Glossary} from '~/server-utils/stampy'
import {fetchAllQuestionsOnSite} from '~/routes/questions/allQuestionsOnSite'
import {fetchGlossary} from '~/routes/questions/glossary'
import {
Expand All @@ -32,6 +25,7 @@ function updateQuestionMap(question: Question, map: Map<PageId, Question>): Map<
answerEditLink: null,
relatedQuestions: [],
tags: [],
banners: [],
})
}
return map
Expand Down Expand Up @@ -94,6 +88,7 @@ export default function useQuestionStateInUrl(minLogo: boolean, initialQuestions
relatedQuestions: [],
questionState,
tags: [],
banners: [],
...questionMap.get(pageid),
}))
}, [stateString, initialCollapsedState, questionMap])
Expand Down Expand Up @@ -255,6 +250,7 @@ export default function useQuestionStateInUrl(minLogo: boolean, initialQuestions
answerEditLink: null,
relatedQuestions: [],
tags: [],
banners: [],
}
onLazyLoadQuestion(tmpQuestion)
toggleQuestion(tmpQuestion, {moveToTop: true})
Expand Down
15 changes: 15 additions & 0 deletions app/root.css
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,21 @@ tr:nth-child(even) {
margin: 0;
}

.answer .banner {
padding: 1px 10px 5px 10px;
margin: 10px;
}
.answer .banner h3 {
display: flex;
align-items: center;
}
.answer .banner h3 img {
width: 2em;
}
.answer .banner h3 .title {
padding-left: 10px;
}

.question-footer {
display: grid;
grid-template-columns: auto auto;
Expand Down
46 changes: 28 additions & 18 deletions app/routes/questions/$question.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,8 @@
import type {LoaderArgs} from '@remix-run/cloudflare'
import {
GlossaryEntry,
loadQuestionDetail,
QuestionState,
QuestionStatus,
} from '~/server-utils/stampy'
import {GlossaryEntry, loadQuestionDetail, QuestionState} from '~/server-utils/stampy'
import {useRef, useEffect, useState} from 'react'
import AutoHeight from 'react-auto-height'
import type {Question, Glossary, PageId} from '~/server-utils/stampy'
import type {Question, Glossary, PageId, Banner as BannerType} 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'
Expand All @@ -34,6 +29,7 @@ export const loader = async ({request, params}: LoaderArgs) => {
answerEditLink: null,
relatedQuestions: [],
tags: [],
banners: [],
}
return {
error: error?.toString(),
Expand Down Expand Up @@ -70,17 +66,7 @@ export function Question({
glossary: Glossary
selectQuestion: (pageid: string, title: string) => void
} & JSX.IntrinsicElements['div']) {
const {
pageid,
title: codaTitle,
status: codaStatus,
text,
answerEditLink,
questionState,
tags,
} = questionProps
const title =
codaStatus && codaStatus !== QuestionStatus.LIVE_ON_SITE ? `WIP - ${codaTitle}` : codaTitle
const {pageid, title, text, answerEditLink, questionState, tags, banners} = questionProps
const isLoading = useRef(false)

const isExpanded = questionState === QuestionState.OPEN
Expand Down Expand Up @@ -133,6 +119,7 @@ export function Question({
</h2>
<AutoHeight>
<div className="answer" draggable="false">
<div className="banners">{banners && banners.map(Banner)}</div>
{isExpanded && (
<>
<Contents pageid={pageid} html={html} glossary={glossary} />
Expand Down Expand Up @@ -166,6 +153,29 @@ export function Question({
)
}

const Banner = ({title, text, icon, backgroundColour, textColour}: BannerType) => {
return (
<div
className="banner"
style={{
backgroundColor: backgroundColour || 'inherit',
color: textColour || 'inherit',
}}
>
<h3>
<img src={icon?.url} alt={icon?.name} />
<span className="title">{title}</span>
</h3>
<div
className="banner-contents"
dangerouslySetInnerHTML={{
__html: text,
}}
></div>
</div>
)
}

/*
* Recursively go through the child nodes of the provided node, and replace all text nodes
* with the result of calling `textProcessor(textNode)`
Expand Down
52 changes: 48 additions & 4 deletions app/server-utils/stampy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@ export enum QuestionStatus {
LIVE_ON_SITE = 'Live on site',
UNKNOWN = 'Unknown',
}
export type Banner = {
title: string
text: string
icon: Record<string, string>
textColour: string
backgroundColour: string
}
export type GlossaryEntry = {
term: string
pageid: PageId
Expand All @@ -49,6 +56,7 @@ export type Question = {
relatedQuestions: RelatedQuestions
questionState?: QuestionState
tags: string[]
banners: Banner[]
status?: QuestionStatus
updatedAt?: string
}
Expand Down Expand Up @@ -93,6 +101,7 @@ type AnswersRow = CodaRowCommon & {
'Related Answers': '' | Entity[]
'Related IDs': '' | string[]
Tags: '' | Entity[]
Banners: '' | Entity[]
'Rich Text': string
}
}
Expand All @@ -112,7 +121,16 @@ type GlossaryRow = CodaRowCommon & {
'UI ID': string
}
}
type CodaRow = AnswersRow | TagsRow | GlossaryRow
type BannersRow = CodaRowCommon & {
values: {
Title: string
Icon: any[]
Text: string
'Background colour': string
'Text colour': string
}
}
type CodaRow = AnswersRow | TagsRow | GlossaryRow | BannersRow
type CodaResponse = {
items: CodaRow[]
nextPageLink: string | null
Expand All @@ -129,10 +147,12 @@ 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 BANNERS_TABLE = 'grid-3WgZ9_NkvO'

const enc = encodeURIComponent
const quote = (x: string) => encodeURIComponent(`"${x.replace(/"/g, '\\"')}"`)
let allTags = {} as Record<string, Tag>
let allBanners = {} as Record<string, Banner>

const sendToCoda = async (
url: string,
Expand Down Expand Up @@ -238,14 +258,19 @@ const head = (item: string | string[]) => {
}
const extractText = (markdown: string) => head(markdown)?.replace(/^```|```$/g, '')
const extractLink = (markdown: string) => markdown?.replace(/^.*\(|\)/g, '')
const extractJoined = (values: Entity[], mapper: Record<string, any>) =>
values
.map((e) => e.name)
.filter((name) => Object.prototype.hasOwnProperty.call(mapper, name))
.map((name) => mapper[name])

const convertToQuestion = ({name, values, updatedAt} = {} as AnswersRow): Question => ({
title: name,
pageid: extractText(values['UI ID']),
text: renderText(extractText(values['UI ID']), values['Rich Text']),
answerEditLink: extractLink(values['Edit Answer']).replace(/\?.*$/, ''),
tags: ((values['Tags'] || []) as Entity[])
.map((e) => e.name)
.filter((name) => Object.prototype.hasOwnProperty.call(allTags, name)),
tags: extractJoined(values['Tags'] || [], allTags).map((t) => t.name),
banners: extractJoined(values['Banners'] || [], allBanners),
relatedQuestions:
values['Related Answers'] && values['Related IDs']
? values['Related Answers'].map(({name}, i) => ({
Expand All @@ -263,6 +288,10 @@ export const loadQuestionDetail = withCache('questionDetail', async (question: s
const {data} = await loadTags('NEVER_RELOAD')
allTags = Object.fromEntries(data.map((r) => [r.name, r])) as Record<string, Tag>
}
if (Object.keys(allBanners).length === 0) {
const {data} = await loadBanners('NEVER_RELOAD')
allBanners = data
}
const rows = (await getCodaRows(
QUESTION_DETAILS_TABLE,
// ids are now alphanumerical, so not possible to detect id by regex match for \d
Expand Down Expand Up @@ -299,6 +328,21 @@ export const loadGlossary = withCache('loadGlossary', async () => {
)
})

export const loadBanners = withCache('loadBanners', async (): Promise<Record<string, Banner>> => {
const rows = (await getCodaRows(BANNERS_TABLE)) as BannersRow[]
return Object.fromEntries(
rows
.map(({values}) => ({
title: extractText(values.Title),
text: renderText('', values.Text) || '',
icon: values.Icon[0],
backgroundColour: extractText(values['Background colour']),
textColour: extractText(values['Text colour']),
}))
.map((item) => [item.title, item])
)
})

export const loadOnSiteAnswers = withCache('onSiteAnswers', async () => {
const rows = (await getCodaRows(ON_SITE_TABLE)) as AnswersRow[]
return rows.map(convertToQuestion)
Expand Down

0 comments on commit 2f9cd81

Please sign in to comment.