enhance(embed): add ability to pin color mode (#7186)
* enhance(embed): add ability to pin color mode * fix: Move color mode dropdown to the root section * auto -> system * style tweaks * default to light theme * try and fix eslint * fix dropdown styles on other browsers * rm unnecessary eslintrc change * more explicit color mode select * make light explicit --------- Co-authored-by: kakkokari-gtyih <67428053+kakkokari-gtyih@users.noreply.github.com>
This commit is contained in:
+1
-1
@@ -92,9 +92,9 @@ module.exports = {
|
||||
'*.lock',
|
||||
'.husky',
|
||||
'patches',
|
||||
'bskyweb',
|
||||
'*.html',
|
||||
'bskyweb',
|
||||
'bskyembed',
|
||||
'src/locale/locales/_build/',
|
||||
'src/locale/locales/**/*.js',
|
||||
],
|
||||
|
||||
@@ -68,6 +68,7 @@ function scan(node = document) {
|
||||
if (ref_url.startsWith('http')) {
|
||||
searchParams.set('ref_url', encodeURIComponent(ref_url))
|
||||
}
|
||||
searchParams.set('colorMode', embed.dataset.blueskyColorMode || 'system')
|
||||
|
||||
const iframe = document.createElement('iframe')
|
||||
iframe.setAttribute('data-bluesky-id', id)
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
export type ColorModeValues = 'system' | 'light' | 'dark'
|
||||
|
||||
export function assertColorModeValues(value: string): value is ColorModeValues {
|
||||
return ['system', 'light', 'dark'].includes(value)
|
||||
}
|
||||
|
||||
export function applyTheme(theme: 'light' | 'dark') {
|
||||
document.documentElement.classList.remove('light', 'dark')
|
||||
document.documentElement.classList.add(theme)
|
||||
}
|
||||
|
||||
export function initColorMode() {
|
||||
export function initSystemColorMode() {
|
||||
applyTheme(
|
||||
window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||
? 'dark'
|
||||
|
||||
@@ -9,3 +9,14 @@
|
||||
:root {
|
||||
color-scheme: light dark;
|
||||
}
|
||||
|
||||
select {
|
||||
background-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' height='14px' width='14px' fill='none' viewBox='0 0 24 24'><path fill='black' fill-rule='evenodd' d='M3.293 8.293a1 1 0 0 1 1.414 0L12 15.586l7.293-7.293a1 1 0 1 1 1.414 1.414l-8 8a1 1 0 0 1-1.414 0l-8-8a1 1 0 0 1 0-1.414Z' clip-rule='evenodd'/></svg>");
|
||||
background-repeat: no-repeat;
|
||||
background-position: calc(100% - 0.75rem) center;
|
||||
padding-right: 2rem;
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
background-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' height='14px' width='14px' fill='none' viewBox='0 0 24 24'><path fill='white' fill-rule='evenodd' d='M3.293 8.293a1 1 0 0 1 1.414 0L12 15.586l7.293-7.293a1 1 0 1 1 1.414 1.414l-8 8a1 1 0 0 1-1.414 0l-8-8a1 1 0 0 1 0-1.414Z' clip-rule='evenodd'/></svg>");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
import '../index.css'
|
||||
|
||||
import {AppBskyFeedDefs, AppBskyFeedPost, AtUri, BskyAgent} from '@atproto/api'
|
||||
import {AppBskyFeedDefs, AppBskyFeedPost, AtpAgent, AtUri} from '@atproto/api'
|
||||
import {h, render} from 'preact'
|
||||
import {useEffect, useMemo, useRef, useState} from 'preact/hooks'
|
||||
|
||||
import arrowBottom from '../../assets/arrowBottom_stroke2_corner0_rounded.svg'
|
||||
import logo from '../../assets/logo.svg'
|
||||
import {initColorMode} from '../color-mode'
|
||||
import {
|
||||
assertColorModeValues,
|
||||
ColorModeValues,
|
||||
initSystemColorMode,
|
||||
} from '../color-mode'
|
||||
import {Container} from '../components/container'
|
||||
import {Link} from '../components/link'
|
||||
import {Post} from '../components/post'
|
||||
@@ -22,9 +26,9 @@ export const EMBED_SCRIPT = `${EMBED_SERVICE}/static/embed.js`
|
||||
const root = document.getElementById('app')
|
||||
if (!root) throw new Error('No root element')
|
||||
|
||||
initColorMode()
|
||||
initSystemColorMode()
|
||||
|
||||
const agent = new BskyAgent({
|
||||
const agent = new AtpAgent({
|
||||
service: 'https://public.api.bsky.app',
|
||||
})
|
||||
|
||||
@@ -32,6 +36,7 @@ render(<LandingPage />, root)
|
||||
|
||||
function LandingPage() {
|
||||
const [uri, setUri] = useState('')
|
||||
const [colorMode, setColorMode] = useState<ColorModeValues>('system')
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [thread, setThread] = useState<AppBskyFeedDefs.ThreadViewPost | null>(
|
||||
@@ -120,24 +125,50 @@ function LandingPage() {
|
||||
|
||||
<h1 className="text-4xl font-bold text-center">Embed a Bluesky Post</h1>
|
||||
|
||||
<input
|
||||
type="text"
|
||||
value={uri}
|
||||
onInput={e => setUri(e.currentTarget.value)}
|
||||
className="border rounded-lg py-3 w-full max-w-[600px] px-4 dark:bg-dimmedBg dark:border-slate-500"
|
||||
placeholder={DEFAULT_POST}
|
||||
/>
|
||||
<div className="flex flex-col w-full max-w-[600px] gap-6">
|
||||
<input
|
||||
type="text"
|
||||
value={uri}
|
||||
onInput={e => setUri(e.currentTarget.value)}
|
||||
className="border rounded-lg py-3 px-4 dark:bg-dimmedBg dark:border-slate-500"
|
||||
placeholder={DEFAULT_POST}
|
||||
/>
|
||||
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<label className="text-sm font-medium" for="colorModeSelect">
|
||||
Theme
|
||||
</label>
|
||||
<select
|
||||
value={colorMode}
|
||||
onChange={e => {
|
||||
const value = e.currentTarget.value
|
||||
if (assertColorModeValues(value)) {
|
||||
setColorMode(value)
|
||||
}
|
||||
}}
|
||||
id="colorModeSelect"
|
||||
className="appearance-none bg-white border w-full rounded-lg text-sm px-3 py-2 dark:bg-dimmedBg dark:border-slate-500">
|
||||
<option value="system">System</option>
|
||||
<option value="light">Light</option>
|
||||
<option value="dark">Dark</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<img src={arrowBottom} className="w-6 dark:invert" />
|
||||
|
||||
{loading ? (
|
||||
<div className="w-full max-w-[600px]">
|
||||
<div className={`${colorMode} w-full max-w-[600px]`}>
|
||||
<Skeleton />
|
||||
</div>
|
||||
) : (
|
||||
<div className="w-full max-w-[600px] gap-8 flex flex-col">
|
||||
{!error && thread && uri && <Snippet thread={thread} />}
|
||||
{!error && thread && <Post thread={thread} key={thread.post.uri} />}
|
||||
{!error && thread && uri && (
|
||||
<Snippet thread={thread} colorMode={colorMode} />
|
||||
)}
|
||||
<div className={colorMode}>
|
||||
{!error && thread && <Post thread={thread} key={thread.post.uri} />}
|
||||
</div>
|
||||
{error && (
|
||||
<div className="w-full border border-red-500 bg-red-500/10 px-4 py-3 rounded-lg">
|
||||
<p className="text-red-500 text-center">{error}</p>
|
||||
@@ -168,7 +199,13 @@ function Skeleton() {
|
||||
)
|
||||
}
|
||||
|
||||
function Snippet({thread}: {thread: AppBskyFeedDefs.ThreadViewPost}) {
|
||||
function Snippet({
|
||||
thread,
|
||||
colorMode,
|
||||
}: {
|
||||
thread: AppBskyFeedDefs.ThreadViewPost
|
||||
colorMode: ColorModeValues
|
||||
}) {
|
||||
const ref = useRef<HTMLInputElement>(null)
|
||||
const [copied, setCopied] = useState(false)
|
||||
|
||||
@@ -204,9 +241,11 @@ function Snippet({thread}: {thread: AppBskyFeedDefs.ThreadViewPost}) {
|
||||
// x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x
|
||||
return `<blockquote class="bluesky-embed" data-bluesky-uri="${escapeHtml(
|
||||
thread.post.uri,
|
||||
)}" data-bluesky-cid="${escapeHtml(thread.post.cid)}"><p lang="${escapeHtml(
|
||||
lang,
|
||||
)}">${escapeHtml(record.text)}${
|
||||
)}" data-bluesky-cid="${escapeHtml(
|
||||
thread.post.cid,
|
||||
)}" data-bluesky-embed-color-mode="${escapeHtml(
|
||||
colorMode,
|
||||
)}"><p lang="${escapeHtml(lang)}">${escapeHtml(record.text)}${
|
||||
record.embed
|
||||
? `<br><br><a href="${escapeHtml(href)}">[image or embed]</a>`
|
||||
: ''
|
||||
@@ -217,7 +256,7 @@ function Snippet({thread}: {thread: AppBskyFeedDefs.ThreadViewPost}) {
|
||||
)}</a>) <a href="${escapeHtml(href)}">${escapeHtml(
|
||||
niceDate(thread.post.indexedAt),
|
||||
)}</a></blockquote><script async src="${EMBED_SCRIPT}" charset="utf-8"></script>`
|
||||
}, [thread])
|
||||
}, [thread, colorMode])
|
||||
|
||||
return (
|
||||
<div className="flex gap-2 w-full">
|
||||
|
||||
@@ -4,7 +4,7 @@ import {AppBskyFeedDefs, AtpAgent} from '@atproto/api'
|
||||
import {h, render} from 'preact'
|
||||
|
||||
import logo from '../../assets/logo.svg'
|
||||
import {initColorMode} from '../color-mode'
|
||||
import {applyTheme, initSystemColorMode} from '../color-mode'
|
||||
import {Container} from '../components/container'
|
||||
import {Link} from '../components/link'
|
||||
import {Post} from '../components/post'
|
||||
@@ -22,7 +22,23 @@ if (!uri) {
|
||||
throw new Error('No uri in path')
|
||||
}
|
||||
|
||||
initColorMode()
|
||||
const query = new URLSearchParams(window.location.search)
|
||||
|
||||
// theme - default to light mode
|
||||
const colorMode = query.get('colorMode')
|
||||
|
||||
switch (colorMode) {
|
||||
case 'dark':
|
||||
applyTheme('dark')
|
||||
break
|
||||
case 'system':
|
||||
initSystemColorMode()
|
||||
break
|
||||
case 'light':
|
||||
default:
|
||||
applyTheme('light')
|
||||
break
|
||||
}
|
||||
|
||||
agent
|
||||
.getPostThread({
|
||||
|
||||
Reference in New Issue
Block a user