chore: Link preview style changes
This commit is contained in:
parent
664d4bf976
commit
3d8269b674
@ -12,9 +12,8 @@ const Avatar = ({ user, ...rest }: { user?: UserMetadata; onClick?: () => void }
|
||||
|
||||
useEffect(() => {
|
||||
if (user?.picture) {
|
||||
proxy(user.picture, 120)
|
||||
.then(a => setUrl(a))
|
||||
.catch(console.warn);
|
||||
const url = proxy(user.picture, 120);
|
||||
setUrl(url);
|
||||
}
|
||||
}, [user]);
|
||||
|
||||
|
@ -92,14 +92,7 @@ export default function HyperText({ link, creator }: { link: string; creator: st
|
||||
return <MagnetLink magnet={parsed} />;
|
||||
}
|
||||
} else {
|
||||
return (
|
||||
<>
|
||||
<a href={a} onClick={e => e.stopPropagation()} target="_blank" rel="noreferrer" className="ext">
|
||||
{a}
|
||||
</a>
|
||||
<LinkPreview url={a} />
|
||||
</>
|
||||
);
|
||||
return <LinkPreview url={a} />;
|
||||
}
|
||||
} catch {
|
||||
// Ignore the error.
|
||||
|
28
packages/app/src/Element/LinkPreview.css
Normal file
28
packages/app/src/Element/LinkPreview.css
Normal file
@ -0,0 +1,28 @@
|
||||
.link-preview-container {
|
||||
border: 1px solid var(--gray);
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.link-preview-container:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.link-preview-title {
|
||||
padding: 0 10px 10px 10px;
|
||||
}
|
||||
|
||||
.link-preview-title > small {
|
||||
color: var(--font-secondary-color);
|
||||
font-size: small;
|
||||
}
|
||||
|
||||
.link-preview-image {
|
||||
margin: 0 0 15px 0 !important;
|
||||
border-radius: 0 !important;
|
||||
background-image: var(--img-url);
|
||||
min-height: 250px;
|
||||
max-height: 500px;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
}
|
@ -1,21 +1,14 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import "./LinkPreview.css";
|
||||
import { CSSProperties, useEffect, useState } from "react";
|
||||
|
||||
import { ApiHost } from "Const";
|
||||
import Spinner from "Icons/Spinner";
|
||||
import { ProxyImg } from "Element/ProxyImg";
|
||||
|
||||
interface LinkPreviewData {
|
||||
title?: string;
|
||||
description?: string;
|
||||
image?: string;
|
||||
}
|
||||
import SnortApi, { LinkPreviewData } from "SnortApi";
|
||||
import useImgProxy from "Hooks/useImgProxy";
|
||||
|
||||
async function fetchUrlPreviewInfo(url: string) {
|
||||
const api = new SnortApi();
|
||||
try {
|
||||
const res = await fetch(`${ApiHost}/api/v1/preview?url=${encodeURIComponent(url)}`);
|
||||
if (res.ok) {
|
||||
return (await res.json()) as LinkPreviewData;
|
||||
}
|
||||
return await api.linkPreview(url);
|
||||
} catch (e) {
|
||||
console.warn(`Failed to load link preview`, url);
|
||||
}
|
||||
@ -23,11 +16,12 @@ async function fetchUrlPreviewInfo(url: string) {
|
||||
|
||||
const LinkPreview = ({ url }: { url: string }) => {
|
||||
const [preview, setPreview] = useState<LinkPreviewData | null>();
|
||||
const { proxy } = useImgProxy();
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const data = await fetchUrlPreviewInfo(url);
|
||||
if (data && data.title) {
|
||||
if (data && data.image) {
|
||||
setPreview(data);
|
||||
} else {
|
||||
setPreview(null);
|
||||
@ -35,19 +29,27 @@ const LinkPreview = ({ url }: { url: string }) => {
|
||||
})();
|
||||
}, [url]);
|
||||
|
||||
if (preview === null) return null;
|
||||
if (preview === null)
|
||||
return (
|
||||
<a href={url} onClick={e => e.stopPropagation()} target="_blank" rel="noreferrer" className="ext">
|
||||
{url}
|
||||
</a>
|
||||
);
|
||||
|
||||
const backgroundImage = preview?.image ? `url(${proxy(preview?.image)})` : "";
|
||||
const style = { "--img-url": backgroundImage } as CSSProperties;
|
||||
|
||||
return (
|
||||
<div className="link-preview-container">
|
||||
{preview && (
|
||||
<a href={url} onClick={e => e.stopPropagation()} target="_blank" rel="noreferrer" className="ext">
|
||||
{preview?.image && <ProxyImg src={preview?.image} className="link-preview-image" />}
|
||||
{preview?.image && <div className="link-preview-image" style={style} />}
|
||||
<p className="link-preview-title">
|
||||
{preview?.title}
|
||||
{preview?.description && (
|
||||
<>
|
||||
<br />
|
||||
<small>{preview?.description}</small>
|
||||
<small>{preview.description.slice(0, 160)}</small>
|
||||
</>
|
||||
)}
|
||||
</p>
|
||||
|
@ -263,26 +263,3 @@
|
||||
.close-menu-container {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.link-preview-container {
|
||||
border: 1px solid var(--gray);
|
||||
border-radius: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.link-preview-container:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.link-preview-title {
|
||||
padding: 0 10px 10px 10px;
|
||||
}
|
||||
|
||||
.link-preview-title > small {
|
||||
color: var(--font-secondary-color);
|
||||
font-size: small;
|
||||
}
|
||||
|
||||
.link-preview-image {
|
||||
margin: 0 0 15px 0 !important;
|
||||
}
|
||||
|
@ -12,9 +12,8 @@ export const ProxyImg = (props: ProxyImgProps) => {
|
||||
|
||||
useEffect(() => {
|
||||
if (src) {
|
||||
proxy(src, size)
|
||||
.then(a => setUrl(a))
|
||||
.catch(console.warn);
|
||||
const url = proxy(src, size);
|
||||
setUrl(url);
|
||||
}
|
||||
}, [src]);
|
||||
|
||||
|
@ -17,8 +17,8 @@ export default function useImgProxy() {
|
||||
return s.replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
|
||||
}
|
||||
|
||||
async function signUrl(u: string) {
|
||||
const result = await hmacSha256(
|
||||
function signUrl(u: string) {
|
||||
const result = hmacSha256(
|
||||
secp.utils.hexToBytes(unwrap(settings).key),
|
||||
secp.utils.hexToBytes(unwrap(settings).salt),
|
||||
te.encode(u)
|
||||
@ -27,13 +27,13 @@ export default function useImgProxy() {
|
||||
}
|
||||
|
||||
return {
|
||||
proxy: async (url: string, resize?: number) => {
|
||||
proxy: (url: string, resize?: number) => {
|
||||
if (!settings) return url;
|
||||
const opt = resize ? `rs:fit:${resize}:${resize}/dpr:${window.devicePixelRatio}` : "";
|
||||
const urlBytes = te.encode(url);
|
||||
const urlEncoded = urlSafe(base64.encode(urlBytes, 0, urlBytes.byteLength));
|
||||
const path = `/${opt}/${urlEncoded}`;
|
||||
const sig = await signUrl(path);
|
||||
const sig = signUrl(path);
|
||||
return `${new URL(settings.url).toString()}${sig}${path}`;
|
||||
},
|
||||
};
|
||||
|
@ -85,7 +85,8 @@ export default function LoginPage() {
|
||||
|
||||
useEffect(() => {
|
||||
const ret = unwrap(Artwork.at(Artwork.length * Math.random()));
|
||||
proxy(ret.link).then(a => setArt({ ...ret, link: a }));
|
||||
const url = proxy(ret.link);
|
||||
setArt({ ...ret, link: url });
|
||||
}, []);
|
||||
|
||||
async function doLogin() {
|
||||
|
@ -41,6 +41,12 @@ export class SubscriptionError extends Error {
|
||||
}
|
||||
}
|
||||
|
||||
export interface LinkPreviewData {
|
||||
title?: string;
|
||||
description?: string;
|
||||
image?: string;
|
||||
}
|
||||
|
||||
export default class SnortApi {
|
||||
#url: string;
|
||||
#publisher?: EventPublisher;
|
||||
@ -74,6 +80,10 @@ export default class SnortApi {
|
||||
return this.#getJsonAuthd<Array<Subscription>>("api/v1/subscription");
|
||||
}
|
||||
|
||||
linkPreview(url: string) {
|
||||
return this.#getJson<LinkPreviewData>(`api/v1/preview?url=${encodeURIComponent(url)}`);
|
||||
}
|
||||
|
||||
async #getJsonAuthd<T>(
|
||||
path: string,
|
||||
method?: "GET" | string,
|
||||
|
@ -473,12 +473,8 @@ export function findTag(e: TaggedRawEvent, tag: string) {
|
||||
return maybeTag && maybeTag[1];
|
||||
}
|
||||
|
||||
export async function hmacSha256(key: Uint8Array, ...messages: Uint8Array[]) {
|
||||
if (window.crypto.subtle) {
|
||||
return await secp.utils.hmacSha256(key, ...messages);
|
||||
} else {
|
||||
return hmac(hash, key, secp.utils.concatBytes(...messages));
|
||||
}
|
||||
export function hmacSha256(key: Uint8Array, ...messages: Uint8Array[]) {
|
||||
return hmac(hash, key, secp.utils.concatBytes(...messages));
|
||||
}
|
||||
|
||||
export function getRelayName(url: string) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user