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

onlyInitial url parameter for iframe embeds #314

Merged
merged 3 commits into from
Sep 15, 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
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ Contributions are welcome, the code is released under the MIT License. If you'd
- state - controls which questions are displayed as collapsed / open / related, e.g. [aisafety.info/?state=6568\_](https://aisafety.info/?state=6568_)
- q (string) - search query for sharing direct link to search results (and not just link to 1 question), e.g. [aisafety.info/?q=alignment&limit=10](https://aisafety.info/?q=alignment&limit=10)
- limit (number, default 5) - how many results to show
- embed - show site without header/footer for embedding on other sites, se [embed-example.html](/public/embed-example.html)
- embed - show site without header/footer for embedding on other sites, see [embed-example.html](https://aisafety.info/embed-example.html)
- placeholder (string) - override `<input placeholder=...>` of the search box
- theme (light|dark) - override CSS theme (if not provided, the embedded site will use `preferred-color-scheme` system setting)
- showInitial - also show initial questions, not just search bar
- showInitial - show initial questions as well as the search bar
Aprillion marked this conversation as resolved.
Show resolved Hide resolved
- onlyInitial - show only initial questions without the search bar
- showDetails - open question details (answers) directly instead of just links to aisafety.info
- more (disabled|infini|button|buttonInfini) - debug versions of load more / infinite scroll, e.g. `aisafety.info/?more=infini`

Expand Down
2 changes: 1 addition & 1 deletion app/components/layouts.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {useOutletContext, Link} from '@remix-run/react'
import {useOutletContext} from '@remix-run/react'
import logoFunSvg from '../assets/stampy-logo.svg'
import logoMinSvg from '../assets/stampy-logo-min.svg'
import {Share, Users, Code, Tag} from './icons-generated'
Expand Down
2 changes: 1 addition & 1 deletion app/components/search.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {useState, useEffect, useRef, MutableRefObject, FocusEvent, useCallback} from 'react'
import {useState, useEffect, useRef, MutableRefObject, FocusEvent} from 'react'
import debounce from 'lodash/debounce'
import {AddQuestion} from '~/routes/questions/add'
import {Action, ActionType} from '~/routes/questions/actions'
Expand Down
1 change: 0 additions & 1 deletion app/hooks/useQuestionStateInUrl.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
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, Glossary} from '~/server-utils/stampy'
import {fetchAllQuestionsOnSite} from '~/routes/questions/allQuestionsOnSite'
Expand Down
8 changes: 5 additions & 3 deletions app/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ export const loader = async ({request}: Parameters<LoaderFunction>[0]) => {
const minLogo = isDomainWithFunLogo ? !!isFunLogoForcedOff : !isFunLogoForcedOn

const embed = !!request.url.match(/embed/)
const showSearch = !request.url.match(/onlyInitial/)

const question = await fetchQuestion(request).catch((e) => {
console.error('\n\nUnexpected error in loader\n', e)
Expand All @@ -90,6 +91,7 @@ export const loader = async ({request}: Parameters<LoaderFunction>[0]) => {
url: request.url,
minLogo,
embed,
showSearch,
}
}

Expand Down Expand Up @@ -131,12 +133,12 @@ export function ErrorBoundary({error}: {error: Error}) {
}

type Loader = Awaited<ReturnType<typeof loader>>
export type Context = Pick<Loader, 'minLogo' | 'embed'>
export type Context = Pick<Loader, 'minLogo' | 'embed' | 'showSearch'>

export default function App() {
const {minLogo, embed} = useLoaderData<Loader>()
const {minLogo, embed, showSearch} = useLoaderData<Loader>()
const {savedTheme} = useTheme()
const context: Context = {minLogo, embed}
const context: Context = {minLogo, embed, showSearch}

useEffect(() => {
if (embed) {
Expand Down
39 changes: 22 additions & 17 deletions app/routes/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,10 @@ import type {Context} from '~/root'
const empty: Awaited<ReturnType<typeof loadInitialQuestions>> = {data: [], timestamp: ''}
export const loader = async ({request}: Parameters<LoaderFunction>[0]) => {
const showInitialFromUrl = !!request.url.match(/showInitial/)
const onlyInitialFromUrl = !!request.url.match(/onlyInitial/)
const embedFromUrl = !!request.url.match(/embed/)
const queryFromUrl = !!request.url.match(/[?&]q=/)
const fetchInitial = showInitialFromUrl || (!embedFromUrl && !queryFromUrl)
const fetchInitial = showInitialFromUrl || onlyInitialFromUrl || (!embedFromUrl && !queryFromUrl)
if (!fetchInitial) return {initialQuestionsData: empty}

try {
Expand Down Expand Up @@ -108,7 +109,7 @@ const Bottom = ({
}

export default function App() {
const {minLogo, embed} = useOutletContext<Context>()
const {minLogo, embed, showSearch} = useOutletContext<Context>()
const {initialQuestionsData} = useLoaderData<ReturnType<typeof loader>>()
const {data: initialQuestions = [], timestamp} = initialQuestionsData ?? {}

Expand Down Expand Up @@ -193,21 +194,25 @@ export default function App() {
<>
<Header />
<main onClick={handleSpecialLinks}>
<Search
onSiteAnswersRef={onSiteAnswersRef}
openQuestionTitles={openQuestionTitles}
onSelect={selectQuestion}
embedWithoutDetails={embedWithoutDetails}
queryFromUrl={queryFromUrl}
limitFromUrl={limitFromUrl}
removeQueryFromUrl={removeQueryFromUrl}
/>

{/* Add an extra, draggable div here, so that questions can be moved to the top of the list */}
<div draggable onDragOver={handleDragOver({pageid: TOP})}>
&nbsp;
</div>
<DragPlaceholder pageid={TOP} />
{showSearch && (
<>
<Search
onSiteAnswersRef={onSiteAnswersRef}
openQuestionTitles={openQuestionTitles}
onSelect={selectQuestion}
embedWithoutDetails={embedWithoutDetails}
queryFromUrl={queryFromUrl}
limitFromUrl={limitFromUrl}
removeQueryFromUrl={removeQueryFromUrl}
/>

{/* Add an extra, draggable div here, so that questions can be moved to the top of the list */}
<div draggable onDragOver={handleDragOver({pageid: TOP})}>
&nbsp;
</div>
<DragPlaceholder pageid={TOP} />
</>
)}
<div className="articles-container">
{questions.map((question) => (
<ErrorBoundary title={question.title} key={question.pageid}>
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,10 @@
"postinstall": "remix setup cloudflare-workers",
"generate-icons": "npx @svgr/cli --out-dir app/components/icons-generated -- app/assets/icons",
"eslint": "eslint --ignore-pattern .gitignore \"**/*.ts*\"",
"prettier": "prettier --check --ignore-path .gitignore \"**/*.{ts*,js,css}\"",
"prettier": "prettier --check --ignore-path .gitignore \"**/*.{ts*,js,css,md,html}\"",
"lint": "tsc && npm run prettier && npm run eslint",
"eslint:fix": "eslint --fix --ignore-pattern .gitignore \"**/*.ts*\"",
"prettier:fix": "prettier --write --ignore-path .gitignore \"**/*.{ts*,js,css,md}\"",
"prettier:fix": "prettier --write --ignore-path .gitignore \"**/*.{ts*,js,css,md,html}\"",
"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 --sourcemap",
"dev:remix": "cross-env NODE_ENV=development remix watch",
Expand Down
71 changes: 55 additions & 16 deletions public/embed-example.html
Original file line number Diff line number Diff line change
@@ -1,23 +1,62 @@
<!DOCTYPE html>
<div>Some content before embedding aisafety.info search...</div>

<iframe
src="/?embed&showInitial&showDetails&placeholder=Search the AI Safety FAQ&theme=light"
width="100%"
height="315px"
frameborder="0"
id="ai-safety-search-iframe"
style="transition: height 0.3s; overflow: hidden;"
></iframe>
<main>
<h3>Search bar, using links, light theme</h3>
<iframe
src="/?embed&placeholder=Search the AI Safety FAQ&theme=light"
id="ai-safety-search-iframe1"
height="65px"
></iframe>

<div>Some content after...</div>
<h3>Only initial questions, using links, system default theme</h3>
<iframe
src="/?embed&onlyInitial&placeholder=Search the AI Safety FAQ"
id="ai-safety-search-iframe2"
height="250px"
></iframe>

<h3>Both search and initial questions, opening inline, expanding iframe height, dark theme</h3>
<iframe
src="/?embed&showInitial&showDetails&placeholder=Search the AI Safety FAQ&theme=dark"
id="ai-safety-search-iframe3"
height="315px"
></iframe>

<script>
'use strict'
const iframes = [...document.querySelectorAll('iframe')]
window.addEventListener('message', function (event) {
if (event.data.type === 'aisafety.info__height') {
const iframe = iframes.find((iframe) => iframe.src === event.source.location.href)
if (iframe) {
iframe.style.height = event.data.height + 'px'
}
}
})
</script>

<style>
body {
font-family: sans-serif;
overflow: scroll;
}

iframe {
transition: height 0.3s;
overflow: hidden;
width: 100%;
border: none;
}
</style>
</main>

<h3>Source code:</h3>
<pre id="source"></pre>
<script>
'use strict'
const iframe = document.getElementById('ai-safety-search-iframe')
window.addEventListener('message', function (event) {
if (event.data.type === 'aisafety.info__height') {
iframe.style.height = event.data.height + 'px'
}
})
const source = document.getElementById('source')
const main = document.querySelector('main').innerHTML
source.textContent = main
.replace(/&amp;/g, '&')
.replace(/src="\//g, 'src="https://aisafety.info/')
</script>