feat: persist settings and enrich conversation context
This commit is contained in:
1 parent
a36723a325
commit
2c100d72b6
84 files changed
+2248
-644
No files matched your search
@@ -0,0 +1,72 @@
|
||||
import { useEffect, useRef, useState, type ReactNode } from 'react';
|
||||
|
||||
export interface OriginPost {
|
||||
title: string;
|
||||
url: string;
|
||||
imageUrl?: string | null;
|
||||
subreddit?: string;
|
||||
}
|
||||
|
||||
export function OriginPostCard({ post }: { post: OriginPost }) {
|
||||
return (
|
||||
<a aria-label={post.title} className="origin-post-card" href={post.url} rel="noreferrer" target="_blank">
|
||||
{post.imageUrl ? <img alt={`Source post: ${post.title}`} loading="lazy" src={post.imageUrl} /> : null}
|
||||
<span>
|
||||
<small>Started from {post.subreddit ? `r/${post.subreddit}` : 'Reddit'}</small>
|
||||
<strong>{post.title}</strong>
|
||||
<span>Open original post</span>
|
||||
</span>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
export function OriginPostButton({ children, post }: { children: ReactNode; post: OriginPost }) {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const triggerRef = useRef<HTMLButtonElement>(null);
|
||||
const closeRef = useRef<HTMLButtonElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen) return;
|
||||
closeRef.current?.focus();
|
||||
const closeOnEscape = (event: KeyboardEvent) => {
|
||||
if (event.key === 'Escape') setIsOpen(false);
|
||||
};
|
||||
document.addEventListener('keydown', closeOnEscape);
|
||||
return () => document.removeEventListener('keydown', closeOnEscape);
|
||||
}, [isOpen]);
|
||||
|
||||
function close() {
|
||||
setIsOpen(false);
|
||||
queueMicrotask(() => triggerRef.current?.focus());
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<button className="origin-post-trigger" onClick={() => setIsOpen(true)} ref={triggerRef} type="button">
|
||||
{children}
|
||||
</button>
|
||||
{isOpen ? (
|
||||
<div
|
||||
aria-labelledby="origin-post-dialog-title"
|
||||
aria-modal="true"
|
||||
className="origin-post-backdrop"
|
||||
onMouseDown={(event) => { if (event.target === event.currentTarget) close(); }}
|
||||
role="dialog"
|
||||
>
|
||||
<article className="origin-post-dialog">
|
||||
<div className="origin-post-dialog-heading">
|
||||
<span>Conversation origin</span>
|
||||
<button aria-label="Close source post" onClick={close} ref={closeRef} type="button">×</button>
|
||||
</div>
|
||||
{post.imageUrl ? <img alt={`Source post: ${post.title}`} loading="lazy" src={post.imageUrl} /> : null}
|
||||
<div className="origin-post-dialog-copy">
|
||||
{post.subreddit ? <small>r/{post.subreddit}</small> : null}
|
||||
<h2 id="origin-post-dialog-title">{post.title}</h2>
|
||||
<a className="button primary" href={post.url} rel="noreferrer" target="_blank">Open Reddit post</a>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Fragment } from 'react';
|
||||
import { RedditUsernameLink } from './reddit-username-link.js';
|
||||
|
||||
const USER_MENTION = /(u\/[A-Za-z0-9_-]+|@?t2_[A-Za-z0-9]+(?::reddit\.com)?)/gi;
|
||||
|
||||
export function RedditUserText({ children }: { children: string }) {
|
||||
return children.split(USER_MENTION).map((part, index) => {
|
||||
if (/^u\/(?!t2_)[A-Za-z0-9_-]+$/i.test(part)) {
|
||||
return <RedditUsernameLink key={`${part}-${index}`} username={part} />;
|
||||
}
|
||||
if (/^(?:@?t2_)/i.test(part) || /^u\/t2_/i.test(part)) {
|
||||
return <Fragment key={`${part}-${index}`}>Reddit user</Fragment>;
|
||||
}
|
||||
return <Fragment key={`${part}-${index}`}>{part}</Fragment>;
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
export interface RedditUsernameLinkProps {
|
||||
username: string;
|
||||
showPrefix?: boolean;
|
||||
}
|
||||
|
||||
export function normalizeRedditUsername(username: string): string {
|
||||
return username.trim().replace(/^\/?u\//i, '');
|
||||
}
|
||||
|
||||
export function isDisplayableRedditUsername(username: string): boolean {
|
||||
const normalized = normalizeRedditUsername(username);
|
||||
return normalized.length > 0
|
||||
&& !/^(?:me|you|unknown|unknown reddit user)$/i.test(normalized)
|
||||
&& !/^t2_/i.test(normalized)
|
||||
&& !/^@t2_/i.test(normalized);
|
||||
}
|
||||
|
||||
export function redditUsernameLabel(username: string, showPrefix = true): string {
|
||||
if (!isDisplayableRedditUsername(username)) return 'Unknown Reddit user';
|
||||
const normalized = normalizeRedditUsername(username);
|
||||
return showPrefix ? `u/${normalized}` : normalized;
|
||||
}
|
||||
|
||||
export function RedditUsernameLink({ username, showPrefix = true }: RedditUsernameLinkProps) {
|
||||
const normalizedUsername = normalizeRedditUsername(username);
|
||||
const label = redditUsernameLabel(username, showPrefix);
|
||||
|
||||
if (!isDisplayableRedditUsername(username)) {
|
||||
return <span className="reddit-username-link is-unresolved">{label}</span>;
|
||||
}
|
||||
|
||||
return (
|
||||
<a
|
||||
className="reddit-username-link"
|
||||
href={`https://www.reddit.com/u/${encodeURIComponent(normalizedUsername)}`}
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
{label}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user