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

bugfixes #271

Merged
merged 6 commits into from
Jul 6, 2023
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
15 changes: 8 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Contributions are welcome, the code is released under the MIT License. If you'd
- [Install Node.js and npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm)
- [Create Cloudflare account](https://dash.cloudflare.com/)
- [Install Wrangler CLI](https://developers.cloudflare.com/workers/wrangler/install-and-update/#install-wrangler-locally)
and [authenticate the CLI](https://developers.cloudflare.com/workers/wrangler/install-and-update/#install-wrangler-locally)
and [authenticate the CLI](https://developers.cloudflare.com/workers/wrangler/install-and-update/#install-wrangler-locally)

2. Clone the [Repo](https://github.com/StampyAI/stampy-ui)

Expand All @@ -31,23 +31,24 @@ Contributions are welcome, the code is released under the MIT License. If you'd
4. Setup in [Coda.io](https://coda.io/account)

- **4.1 (Required)** Setup read access to the API view in Coda
> *Note*:

> _Note_:
> Content in Coda comes from parsing Google Docs (so that site visitors can make suggestions that
> will be reviewed by our editors). The parser lives in the
> [GDocsRelatedThings](https://github.com/StampyAI/GDocsRelatedThings/#readme) repo.

- When logged in to Coda, `Generate API token` in your Account settigns
- Add restrictions: `Doc or table`, `Read only`, for the doc with url `https://coda.io/d/_dfau7sl2hmG`
(you need access to the doc of course, which you can request on the Discord)
(you need access to the doc of course, which you can request on the Discord)
- Replace the value for `{CODA_TOKEN}` in `wrangler.toml`

- **4.2 (Optional)** Setup write access to the API write view in Coda

> Note: This step is only needed for incrementing counters (helpful, etc.). There isn't a test environment, so any changes there will also effect the live site, so think twice before using them.
> Note: This step is only needed for incrementing counters (helpful, etc.). There isn't a test environment, so any changes there will also effect the live site, so think twice before using them.

- When logged in to Coda, `Generate API token` in your Account settings
- Add restrictions: `Doc or table`, `Read and Write`, for the table with url `https://coda.io/d/_dfau7sl2hmG#_tutable-eEhx2YPsBE`
- Replace the value for `{CODA_WRITES_TOKEN}` in `wrangler.toml`
- When logged in to Coda, `Generate API token` in your Account settings
- Add restrictions: `Doc or table`, `Read and Write`, for the table with url `https://coda.io/d/_dfau7sl2hmG#_tutable-eEhx2YPsBE`
- Replace the value for `{CODA_WRITES_TOKEN}` in `wrangler.toml`

- **4.3 (Optional)** Setup write access to the "Incoming questions" table in Coda

Expand Down
15 changes: 10 additions & 5 deletions app/hooks/useQuestionStateInUrl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,9 +124,12 @@ export default function useQuestionStateInUrl(minLogo: boolean, initialQuestions
}

const moveToTop = (currentState: string, {pageid}: Question) => {
window.scrollTo({
top: 0,
behavior: 'smooth',
setTimeout(() => {
// scroll to top after the state is updated
window.scrollTo({
top: 0,
behavior: 'smooth',
})
})
return moveQuestionToTop(currentState, pageid)
}
Expand Down Expand Up @@ -196,7 +199,7 @@ export default function useQuestionStateInUrl(minLogo: boolean, initialQuestions
* are on the page
*/
const toggleQuestion = useCallback(
(questionProps: Question, options?: {moveToTop?: boolean}) => {
(questionProps: Question, options?: {moveToTop?: boolean; onlyRelated?: boolean}) => {
const {pageid, relatedQuestions} = questionProps
let currentState = stateString ?? initialCollapsedState

Expand All @@ -212,7 +215,9 @@ export default function useQuestionStateInUrl(minLogo: boolean, initialQuestions
}

const newRelatedQuestions = unshownRelatedQuestions(questions, questionProps)
const newState = insertIntoState(currentState, pageid, newRelatedQuestions)
const newState = insertIntoState(currentState, pageid, newRelatedQuestions, {
toggle: !options?.onlyRelated,
})

updateStateString(newState)
},
Expand Down
2 changes: 1 addition & 1 deletion app/root.css
Original file line number Diff line number Diff line change
Expand Up @@ -660,7 +660,7 @@ a.see-more.visible:after {
content: 'See less';
}

a[target="_blank"]:not(.icon-link):after {
a[target='_blank']:not(.icon-link):after {
content: ' ';
display: inline-block;
vertical-align: top;
Expand Down
48 changes: 25 additions & 23 deletions app/routes/questions/$question.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import type {LoaderArgs} from '@remix-run/cloudflare'
import {GlossaryEntry, loadQuestionDetail, QuestionStatus} from '~/server-utils/stampy'
import {
GlossaryEntry,
loadQuestionDetail,
QuestionState,
QuestionStatus,
} 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'
Expand Down Expand Up @@ -77,35 +82,29 @@ export function Question({
const title =
codaStatus && codaStatus !== QuestionStatus.LIVE_ON_SITE ? `WIP - ${codaTitle}` : codaTitle
const isLoading = useRef(false)
const refreshOnToggleAfterLoading = useRef(false)

const isExpanded = questionState === QuestionState.OPEN
const isRelated = questionState === QuestionState.RELATED
const clsExpanded = isExpanded ? 'expanded' : isRelated ? 'related' : 'collapsed'

const [isLinkHovered, setLinkHovered] = useState(false)
const clsLinkHovered = isLinkHovered ? 'link-hovered' : ''
const cls = `${clsExpanded} ${clsLinkHovered}`

useEffect(() => {
if (text == null && !isLoading.current) {
isLoading.current = true
// This is where the actual contents of the question get fetched from the backend
fetchQuestion(pageid).then((newQuestionProps) => {
if (!newQuestionProps) return
onLazyLoadQuestion(newQuestionProps)
if (refreshOnToggleAfterLoading.current) {
onToggle(newQuestionProps)
refreshOnToggleAfterLoading.current = false
}
if (isExpanded) onToggle(newQuestionProps, {onlyRelated: true})
})
}
}, [pageid, text, onLazyLoadQuestion, onToggle])

const isExpanded = questionState === '_'
const isRelated = questionState === 'r'
const clsExpanded = isExpanded ? 'expanded' : isRelated ? 'related' : 'collapsed'

const [isLinkHovered, setLinkHovered] = useState(false)
const clsLinkHovered = isLinkHovered ? 'link-hovered' : ''
const cls = `${clsExpanded} ${clsLinkHovered}`
}, [pageid, text, onLazyLoadQuestion, onToggle, isExpanded])

const handleToggle = () => {
onToggle(questionProps)
if (text == null) {
refreshOnToggleAfterLoading.current = true
}
}

let html
Expand Down Expand Up @@ -183,9 +182,11 @@ const updateTextNodes = (el: Node, textProcessor: (node: Node) => Node) => {
function Contents({pageid, html, glossary}: {pageid: PageId; html: string; glossary: Glossary}) {
const elementRef = useRef<HTMLDivElement>(null)

const footnoteHTML = (el: HTMLDivElement, e: HTMLAnchorElement): string => {
const footnoteHTML = (el: HTMLDivElement, e: HTMLAnchorElement): string | null => {
const id = e.getAttribute('href') || ''
const footnote = el.querySelector(id) as HTMLLabelElement
const footnote = el.querySelector(id)

if (!footnote) return null

const elem = document.createElement('div')
elem.innerHTML = footnote.innerHTML
Expand Down Expand Up @@ -282,9 +283,10 @@ function Contents({pageid, html, glossary}: {pageid: PageId; html: string; gloss
updateTextNodes(el, insertGlossary)

// In theory this could be extended to all links
el.querySelectorAll('.footnote-ref > a').forEach((e) =>
addPopup(e as HTMLAnchorElement, footnoteHTML(el, e as HTMLAnchorElement))
)
el.querySelectorAll('.footnote-ref > a').forEach((e) => {
const footnote = footnoteHTML(el, e as HTMLAnchorElement)
if (footnote) addPopup(e as HTMLAnchorElement, footnote)
})
}, [html, glossary, pageid])

return (
Expand Down
41 changes: 40 additions & 1 deletion app/server-utils/parsing-utils.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {urlToIframe, externalLinksOnNewTab} from './parsing-utils'
import {urlToIframe, uniqueFootnotes, externalLinksOnNewTab} from './parsing-utils'

describe('urlToIframe', () => {
describe('should not convert a markdown link', () => {
Expand Down Expand Up @@ -38,6 +38,45 @@ describe('urlToIframe', () => {
})
})

describe('uniqueFootnotes', () => {
test('no link', () => {
expect(uniqueFootnotes('gg', '7757')).toBe('gg')
})

test('multiple footnotes, one of them repeated', () => {
expect(
uniqueFootnotes(
`x<a href="#fn1" id="fnref1">[1]</a>
y<a href="#fn1" id="fnref1:1">[1:1]</a>
z<a href="#fn2" id="fnref2">[2]</a>
---
<ol>
<li id="fn1" class="footnote-item">
gg <a href="#fnref1" class="footnote-backref">x</a> <a href="#fnref1:1" class="footnote-backref">y</a>
</li>
<li id="fn2" class="footnote-item">
gg <a href="#fnref2" class="footnote-backref">x</a>
</li>
</ol>`,
'7757'
)
).toBe(
`x<a href="#fn1-7757" id="fnref1-7757">[1]</a>
y<a href="#fn1-7757" id="fnref1:1-7757">[1:1]</a>
z<a href="#fn2-7757" id="fnref2-7757">[2]</a>
---
<ol>
<li id="fn1-7757" class="footnote-item">
gg <a href="#fnref1-7757" class="footnote-backref">x</a> <a href="#fnref1:1-7757" class="footnote-backref">y</a>
</li>
<li id="fn2-7757" class="footnote-item">
gg <a href="#fnref2-7757" class="footnote-backref">x</a>
</li>
</ol>`
)
})
})

describe('externalLinksOnNewTab', () => {
test('no link', () => {
expect(externalLinksOnNewTab('gg')).toBe('gg')
Expand Down
4 changes: 2 additions & 2 deletions app/server-utils/parsing-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,11 @@ export const uniqueFootnotes = (html: string, pageid: string): string => {
// Make sure the footnotes point to unique ids. This is very ugly and would be better handled
// with a proper parser, but should do the trick so is fine? Maybe?
html = html.replace(
/<a href="#(fn\d+)" id="(fnref\d+)">/g,
/<a href="#(fn\d+)" id="(fnref[\d:]+)">/g,
`<a href="#$1-${pageid}" id="$2-${pageid}">`
)
html = html.replace(
/<a href="#(fnref?\d+)" class="footnote-backref">/g,
/<a href="#(fnref?[\d:]+)" class="footnote-backref">/g,
`<a href="#$1-${pageid}" class="footnote-backref">`
)
html = html.replace(
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
"eslint:fix": "eslint --fix --ignore-pattern .gitignore \"**/*.ts*\"",
"prettier:fix": "prettier --write --ignore-path .gitignore \"**/*.{ts*,js,css,md}\"",
"lint:fix": "tsc && npm run prettier:fix && npm run eslint:fix",
"build": "rimraf public/build && npm run generate-icons && cross-env NODE_ENV=production remix build",
"build": "rimraf public/build && npm run generate-icons && cross-env NODE_ENV=production remix build --sourcemap",
"dev:remix": "cross-env NODE_ENV=development remix watch",
"dev:miniflare": "cross-env NODE_ENV=development miniflare ./build/index.js --watch",
"start": "npm run generate-icons && cross-env NODE_ENV=development remix build && run-p dev:*",
Expand Down