mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
feat(post): add links and media info, including embed
This commit is contained in:
@@ -5,13 +5,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.boardNavDesktop a {
|
.boardNavDesktop a {
|
||||||
color: var(--link-internal-text-color);
|
color: var(--button-desktop-text-color);
|
||||||
text-decoration: var(--link-internal-text-decoration);
|
text-decoration: var(--button-desktop-text-color-decoration);
|
||||||
}
|
}
|
||||||
|
|
||||||
.boardNavDesktop a:hover {
|
.boardNavDesktop a:hover {
|
||||||
color: var(--link-internal-text-color-hover);
|
color: var(--button-desktop-text-color-hover);
|
||||||
text-decoration: var(--link-internal-text-decoration-hover);
|
text-decoration: var(--button-desktop-text-color-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
.navTopRight {
|
.navTopRight {
|
||||||
@@ -58,8 +58,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.pageJump a {
|
.pageJump a {
|
||||||
color: var(--link-internal-text-color);
|
color: var(--homepage-box-link-text-color);
|
||||||
text-decoration: var(--link-internal-text-decoration);
|
text-decoration: var(--homepage-box-link-text-decoration);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 640px) {
|
@media (max-width: 640px) {
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
.audioEmbed {
|
||||||
|
height: 240px !important;
|
||||||
|
width: 700px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.audioEmbed {
|
||||||
|
max-height: 250px !important;
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.instagramEmbed {
|
||||||
|
height: 420px !important;
|
||||||
|
width: 360px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.instagramEmbed {
|
||||||
|
max-height: 420px !important;
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.redditEmbed {
|
||||||
|
width: 500px !important;
|
||||||
|
height: 520px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.redditEmbed {
|
||||||
|
width: 100% !important;
|
||||||
|
height: 520px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tiktokEmbed {
|
||||||
|
width: 400px !important;
|
||||||
|
height: 780px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.tiktokEmbed {
|
||||||
|
width: 100% !important;
|
||||||
|
height: 70vh !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.videoEmbed {
|
||||||
|
height: 450px !important;
|
||||||
|
width: 800px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.videoEmbed {
|
||||||
|
max-height: 250px !important;
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.xEmbed {
|
||||||
|
height: 580px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.xEmbed {
|
||||||
|
width: 100% !important;
|
||||||
|
height: 50vh !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,273 @@
|
|||||||
|
import styles from './embed.module.css';
|
||||||
|
|
||||||
|
interface EmbedProps {
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Embed = ({ url }: EmbedProps) => {
|
||||||
|
const parsedUrl = new URL(url);
|
||||||
|
|
||||||
|
if (youtubeHosts.has(parsedUrl.host)) {
|
||||||
|
return <YoutubeEmbed parsedUrl={parsedUrl} />;
|
||||||
|
}
|
||||||
|
if (xHosts.has(parsedUrl.host)) {
|
||||||
|
return <XEmbed parsedUrl={parsedUrl} />;
|
||||||
|
}
|
||||||
|
if (redditHosts.has(parsedUrl.host)) {
|
||||||
|
return <RedditEmbed parsedUrl={parsedUrl} />;
|
||||||
|
}
|
||||||
|
if (twitchHosts.has(parsedUrl.host)) {
|
||||||
|
return <TwitchEmbed parsedUrl={parsedUrl} />;
|
||||||
|
}
|
||||||
|
if (tiktokHosts.has(parsedUrl.host)) {
|
||||||
|
return <TiktokEmbed parsedUrl={parsedUrl} />;
|
||||||
|
}
|
||||||
|
if (instagramHosts.has(parsedUrl.host)) {
|
||||||
|
return <InstagramEmbed parsedUrl={parsedUrl} />;
|
||||||
|
}
|
||||||
|
if (odyseeHosts.has(parsedUrl.host)) {
|
||||||
|
return <OdyseeEmbed parsedUrl={parsedUrl} />;
|
||||||
|
}
|
||||||
|
if (bitchuteHosts.has(parsedUrl.host)) {
|
||||||
|
return <BitchuteEmbed parsedUrl={parsedUrl} />;
|
||||||
|
}
|
||||||
|
if (streamableHosts.has(parsedUrl.host)) {
|
||||||
|
return <StreamableEmbed parsedUrl={parsedUrl} />;
|
||||||
|
}
|
||||||
|
if (spotifyHosts.has(parsedUrl.host)) {
|
||||||
|
return <SpotifyEmbed parsedUrl={parsedUrl} />;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
interface EmbedComponentProps {
|
||||||
|
parsedUrl: URL;
|
||||||
|
}
|
||||||
|
|
||||||
|
const youtubeHosts = new Set<string>(['youtube.com', 'www.youtube.com', 'youtu.be', 'www.youtu.be', 'm.youtube.com']);
|
||||||
|
|
||||||
|
const YoutubeEmbed = ({ parsedUrl }: EmbedComponentProps) => {
|
||||||
|
let youtubeId;
|
||||||
|
if (parsedUrl.host.endsWith('youtu.be')) {
|
||||||
|
youtubeId = parsedUrl.pathname.replaceAll('/', '');
|
||||||
|
} else {
|
||||||
|
youtubeId = parsedUrl.searchParams.get('v');
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<iframe
|
||||||
|
className={styles.videoEmbed}
|
||||||
|
height='100%'
|
||||||
|
width='100%'
|
||||||
|
allow='accelerometer; encrypted-media; gyroscope; picture-in-picture; web-share'
|
||||||
|
allowFullScreen
|
||||||
|
title={parsedUrl.href}
|
||||||
|
src={`https://www.youtube-nocookie.com/embed/${youtubeId}`}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const xHosts = new Set<string>(['twitter.com', 'www.twitter.com', 'x.com', 'www.x.com']);
|
||||||
|
|
||||||
|
const XEmbed = ({ parsedUrl }: EmbedComponentProps) => {
|
||||||
|
return (
|
||||||
|
<iframe
|
||||||
|
className={styles.xEmbed}
|
||||||
|
height='100%'
|
||||||
|
width='100%'
|
||||||
|
referrerPolicy='no-referrer'
|
||||||
|
allow='accelerometer; encrypted-media; gyroscope; picture-in-picture; web-share'
|
||||||
|
title={parsedUrl.href}
|
||||||
|
srcDoc={`
|
||||||
|
<blockquote class="twitter-tweet" data-theme="dark">
|
||||||
|
<a href="${parsedUrl.href.replace('x.com', 'twitter.com')}"></a>
|
||||||
|
</blockquote>
|
||||||
|
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
|
||||||
|
`}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const redditHosts = new Set<string>(['reddit.com', 'www.reddit.com', 'old.reddit.com']);
|
||||||
|
|
||||||
|
const RedditEmbed = ({ parsedUrl }: EmbedComponentProps) => {
|
||||||
|
return (
|
||||||
|
<iframe
|
||||||
|
className={styles.redditEmbed}
|
||||||
|
height='100%'
|
||||||
|
width='100%'
|
||||||
|
referrerPolicy='no-referrer'
|
||||||
|
allow='accelerometer; encrypted-media; gyroscope; picture-in-picture; web-share'
|
||||||
|
title={parsedUrl.href}
|
||||||
|
srcDoc={`
|
||||||
|
<style>
|
||||||
|
/* fix reddit iframe being centered */
|
||||||
|
iframe {
|
||||||
|
margin: 0!important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<blockquote class="reddit-embed-bq" style="height:240px" data-embed-theme="dark">
|
||||||
|
<a href="${parsedUrl.href}"></a>
|
||||||
|
</blockquote>
|
||||||
|
<script async src="https://embed.reddit.com/widgets.js" charset="UTF-8"></script>
|
||||||
|
`}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const twitchHosts = new Set<string>(['twitch.tv', 'www.twitch.tv']);
|
||||||
|
|
||||||
|
const TwitchEmbed = ({ parsedUrl }: EmbedComponentProps) => {
|
||||||
|
let iframeUrl;
|
||||||
|
if (parsedUrl.pathname.startsWith('/videos/')) {
|
||||||
|
const videoId = parsedUrl.pathname.replace('/videos/', '');
|
||||||
|
iframeUrl = `https://player.twitch.tv/?video=${videoId}&parent=${window.location.hostname}`;
|
||||||
|
} else {
|
||||||
|
const channel = parsedUrl.pathname.replaceAll('/', '');
|
||||||
|
iframeUrl = `https://player.twitch.tv/?channel=${channel}&parent=${window.location.hostname}`;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<iframe
|
||||||
|
className={styles.videoEmbed}
|
||||||
|
height='100%'
|
||||||
|
width='100%'
|
||||||
|
referrerPolicy='no-referrer'
|
||||||
|
allow='accelerometer; encrypted-media; gyroscope; picture-in-picture; web-share'
|
||||||
|
allowFullScreen
|
||||||
|
title={parsedUrl.href}
|
||||||
|
src={iframeUrl}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const tiktokHosts = new Set<string>(['tiktok.com', 'www.tiktok.com']);
|
||||||
|
|
||||||
|
const TiktokEmbed = ({ parsedUrl }: EmbedComponentProps) => {
|
||||||
|
const videoId = parsedUrl.pathname.replace(/.+\/video\//, '').replaceAll('/', '');
|
||||||
|
return (
|
||||||
|
<iframe
|
||||||
|
className={styles.tiktokEmbed}
|
||||||
|
height='100%'
|
||||||
|
width='100%'
|
||||||
|
referrerPolicy='no-referrer'
|
||||||
|
allow='accelerometer; encrypted-media; gyroscope; picture-in-picture; web-share'
|
||||||
|
title={parsedUrl.href}
|
||||||
|
srcDoc={`
|
||||||
|
<blockquote class="tiktok-embed" data-video-id="${videoId}">
|
||||||
|
<a></a>
|
||||||
|
</blockquote>
|
||||||
|
<script async src="https://www.tiktok.com/embed.js"></script>
|
||||||
|
`}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const instagramHosts = new Set<string>(['instagram.com', 'www.instagram.com']);
|
||||||
|
|
||||||
|
const InstagramEmbed = ({ parsedUrl }: EmbedComponentProps) => {
|
||||||
|
const pathNames = parsedUrl.pathname.replace(/\/+$/, '').split('/');
|
||||||
|
const id = pathNames[pathNames.length - 1];
|
||||||
|
return (
|
||||||
|
<iframe
|
||||||
|
className={styles.instagramEmbed}
|
||||||
|
height='100%'
|
||||||
|
width='100%'
|
||||||
|
referrerPolicy='no-referrer'
|
||||||
|
allow='accelerometer; encrypted-media; gyroscope; picture-in-picture; web-share'
|
||||||
|
title={parsedUrl.href}
|
||||||
|
srcDoc={`
|
||||||
|
<blockquote class="instagram-media">
|
||||||
|
<a href="https://www.instagram.com/p/${id}/"></a>
|
||||||
|
</blockquote>
|
||||||
|
<script async src="//www.instagram.com/embed.js"></script>
|
||||||
|
`}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const odyseeHosts = new Set<string>(['odysee.com', 'www.odysee.com']);
|
||||||
|
|
||||||
|
const OdyseeEmbed = ({ parsedUrl }: EmbedComponentProps) => {
|
||||||
|
const iframeUrl = `https://odysee.com/$/embed${parsedUrl.pathname}`;
|
||||||
|
return (
|
||||||
|
<iframe
|
||||||
|
className={styles.videoEmbed}
|
||||||
|
height='100%'
|
||||||
|
width='100%'
|
||||||
|
referrerPolicy='no-referrer'
|
||||||
|
allow='accelerometer; encrypted-media; gyroscope; picture-in-picture; web-share'
|
||||||
|
allowFullScreen
|
||||||
|
title={parsedUrl.href}
|
||||||
|
src={iframeUrl}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const bitchuteHosts = new Set<string>(['bitchute.com', 'www.bitchute.com']);
|
||||||
|
|
||||||
|
const BitchuteEmbed = ({ parsedUrl }: EmbedComponentProps) => {
|
||||||
|
const videoId = parsedUrl.pathname.replace(/\/video\//, '').replaceAll('/', '');
|
||||||
|
return (
|
||||||
|
<iframe
|
||||||
|
className={styles.videoEmbed}
|
||||||
|
height='100%'
|
||||||
|
width='100%'
|
||||||
|
referrerPolicy='no-referrer'
|
||||||
|
allow='accelerometer; encrypted-media; gyroscope; picture-in-picture; web-share'
|
||||||
|
allowFullScreen
|
||||||
|
title={parsedUrl.href}
|
||||||
|
src={`https://www.bitchute.com/embed/${videoId}/`}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const streamableHosts = new Set<string>(['streamable.com', 'www.streamable.com']);
|
||||||
|
|
||||||
|
const StreamableEmbed = ({ parsedUrl }: EmbedComponentProps) => {
|
||||||
|
const videoId = parsedUrl.pathname.replaceAll('/', '');
|
||||||
|
return (
|
||||||
|
<iframe
|
||||||
|
className={styles.videoEmbed}
|
||||||
|
height='100%'
|
||||||
|
width='100%'
|
||||||
|
referrerPolicy='no-referrer'
|
||||||
|
allow='accelerometer; encrypted-media; gyroscope; picture-in-picture; web-share'
|
||||||
|
allowFullScreen
|
||||||
|
title={parsedUrl.href}
|
||||||
|
src={`https://streamable.com/e/${videoId}`}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const spotifyHosts = new Set<string>(['spotify.com', 'www.spotify.com', 'open.spotify.com']);
|
||||||
|
|
||||||
|
const SpotifyEmbed = ({ parsedUrl }: EmbedComponentProps) => {
|
||||||
|
const iframeUrl = `https://open.spotify.com/embed${parsedUrl.pathname}?theme=0`;
|
||||||
|
return (
|
||||||
|
<iframe
|
||||||
|
className={styles.audioEmbed}
|
||||||
|
height='100%'
|
||||||
|
width='100%'
|
||||||
|
referrerPolicy='no-referrer'
|
||||||
|
allow='accelerometer; encrypted-media; gyroscope; picture-in-picture; web-share'
|
||||||
|
allowFullScreen
|
||||||
|
title={parsedUrl.href}
|
||||||
|
src={iframeUrl}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const canEmbedHosts = new Set<string>([
|
||||||
|
...youtubeHosts,
|
||||||
|
...xHosts,
|
||||||
|
...redditHosts,
|
||||||
|
...twitchHosts,
|
||||||
|
...tiktokHosts,
|
||||||
|
...instagramHosts,
|
||||||
|
...odyseeHosts,
|
||||||
|
...bitchuteHosts,
|
||||||
|
...streamableHosts,
|
||||||
|
...spotifyHosts,
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const canEmbed = (parsedUrl: URL): boolean => canEmbedHosts.has(parsedUrl.host);
|
||||||
|
|
||||||
|
export default Embed;
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { default, canEmbed } from './embed';
|
||||||
@@ -1,3 +1,13 @@
|
|||||||
.thread {
|
.thread {
|
||||||
clear: both;
|
clear: both;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.link a {
|
||||||
|
color: var(--post-link-text-color);
|
||||||
|
text-decoration: var(--post-link-text-decoration);
|
||||||
|
}
|
||||||
|
|
||||||
|
.link a:hover {
|
||||||
|
color: var(--post-link-text-color-hover);
|
||||||
|
text-decoration: var(--post-link-text-decoration-hover);
|
||||||
|
}
|
||||||
@@ -1,13 +1,30 @@
|
|||||||
import { Comment } from '@plebbit/plebbit-react-hooks';
|
import { Comment } from '@plebbit/plebbit-react-hooks';
|
||||||
import styles from './post.module.css';
|
import styles from './post.module.css';
|
||||||
|
import useReplies from '../../hooks/use-replies';
|
||||||
|
import { getLinkMediaInfoMemoized } from '../../lib/utils/media-utils';
|
||||||
|
|
||||||
const Post = ({ post }: Comment) => {
|
const Post = ({ post }: Comment) => {
|
||||||
const { content, link, title } = post;
|
const { content, link, title } = post;
|
||||||
|
const replies = useReplies(post);
|
||||||
|
const linkMediaInfo = getLinkMediaInfoMemoized(link);
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className={styles.thread}>
|
||||||
<hr />
|
<hr />
|
||||||
<h1>{post.title}</h1>
|
<div className={styles.postContainer}>
|
||||||
<p>{post.content}</p>
|
<div className={styles.postDesktop}>
|
||||||
|
{linkMediaInfo?.url && (
|
||||||
|
<div className={styles.link}>
|
||||||
|
Link:{' '}
|
||||||
|
<a href={linkMediaInfo?.url} target='_blank' rel='noopener noreferrer'>
|
||||||
|
{linkMediaInfo.url.length > 30 ? linkMediaInfo?.url.slice(0, 30) + '...' : linkMediaInfo?.url}
|
||||||
|
</a>{' '}
|
||||||
|
({linkMediaInfo?.type})
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className={styles.postInfo}></div>
|
||||||
|
<div className={styles.postMessage}></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { useMemo, useCallback } from 'react';
|
||||||
|
import { Comment, useAccountComments } from '@plebbit/plebbit-react-hooks';
|
||||||
|
|
||||||
|
const useRepliesAndAccountReplies = (comment: Comment) => {
|
||||||
|
// filter only the parent cid
|
||||||
|
const filter = useCallback((accountComment: Comment) => accountComment.parentCid === (comment?.cid || 'n/a'), [comment?.cid]);
|
||||||
|
const { accountComments } = useAccountComments({ filter });
|
||||||
|
|
||||||
|
// the account's replies have a delay before getting published, so get them locally from accountComments instead
|
||||||
|
const accountRepliesNotYetPublished = useMemo(() => {
|
||||||
|
const replies = comment?.replies?.pages?.topAll?.comments || [];
|
||||||
|
const replyCids = new Set(replies.map((reply: Comment) => reply?.cid));
|
||||||
|
// filter out the account comments already in comment.replies, so they don't appear twice
|
||||||
|
return accountComments.filter((accountReply) => !replyCids.has(accountReply?.cid));
|
||||||
|
}, [comment?.replies?.pages?.topAll?.comments, accountComments]);
|
||||||
|
|
||||||
|
const repliesAndNotYetPublishedReplies = useMemo(() => {
|
||||||
|
return [
|
||||||
|
// put the author's unpublished replies at the top, latest first (reverse)
|
||||||
|
...accountRepliesNotYetPublished.reverse(),
|
||||||
|
// put the published replies after,
|
||||||
|
...(comment?.replies?.pages?.topAll?.comments || []),
|
||||||
|
];
|
||||||
|
}, [comment?.replies?.pages?.topAll?.comments, accountRepliesNotYetPublished]);
|
||||||
|
|
||||||
|
return repliesAndNotYetPublishedReplies;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useRepliesAndAccountReplies;
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
import { Comment } from '@plebbit/plebbit-react-hooks';
|
||||||
|
import extName from 'ext-name';
|
||||||
|
import { canEmbed } from '../../components/embed';
|
||||||
|
import memoize from 'memoizee';
|
||||||
|
|
||||||
|
export interface CommentMediaInfo {
|
||||||
|
url: string;
|
||||||
|
type: string;
|
||||||
|
thumbnail?: string;
|
||||||
|
patternThumbnailUrl?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getHasThumbnail = (commentMediaInfo: CommentMediaInfo | undefined, link: string | undefined): boolean => {
|
||||||
|
const iframeThumbnail = commentMediaInfo?.patternThumbnailUrl || commentMediaInfo?.thumbnail;
|
||||||
|
return link &&
|
||||||
|
commentMediaInfo &&
|
||||||
|
(commentMediaInfo.type === 'image' ||
|
||||||
|
commentMediaInfo.type === 'video' ||
|
||||||
|
(commentMediaInfo.type === 'webpage' && commentMediaInfo.thumbnail) ||
|
||||||
|
(commentMediaInfo.type === 'iframe' && iframeThumbnail))
|
||||||
|
? true
|
||||||
|
: false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getCommentMediaInfo = (comment: Comment) => {
|
||||||
|
if (!comment?.thumbnailUrl && !comment?.link) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (comment?.link) {
|
||||||
|
let mime: string | undefined;
|
||||||
|
try {
|
||||||
|
mime = extName(new URL(comment?.link).pathname.toLowerCase().replace('/', ''))[0]?.mime;
|
||||||
|
} catch (e) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = new URL(comment.link);
|
||||||
|
const host = url.hostname;
|
||||||
|
let patternThumbnailUrl;
|
||||||
|
|
||||||
|
if (['youtube.com', 'www.youtube.com', 'youtu.be', 'www.youtu.be', 'm.youtube.com'].includes(host)) {
|
||||||
|
const videoId = host === 'youtu.be' ? url.pathname.slice(1) : url.searchParams.get('v');
|
||||||
|
patternThumbnailUrl = `https://img.youtube.com/vi/${videoId}/0.jpg`;
|
||||||
|
} else if (host.includes('streamable.com')) {
|
||||||
|
const videoId = url.pathname.split('/')[1];
|
||||||
|
patternThumbnailUrl = `https://cdn-cf-east.streamable.com/image/${videoId}.jpg`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (canEmbed(url)) {
|
||||||
|
return {
|
||||||
|
url: comment.link,
|
||||||
|
type: 'iframe',
|
||||||
|
thumbnail: comment.thumbnailUrl,
|
||||||
|
patternThumbnailUrl,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mime?.startsWith('image')) {
|
||||||
|
return { url: comment.link, type: 'image' };
|
||||||
|
}
|
||||||
|
if (mime?.startsWith('video')) {
|
||||||
|
return { url: comment.link, type: 'video', thumbnail: comment.thumbnailUrl };
|
||||||
|
}
|
||||||
|
if (mime?.startsWith('audio')) {
|
||||||
|
return { url: comment.link, type: 'audio' };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (comment?.thumbnailUrl && comment?.thumbnailUrl !== comment?.link) {
|
||||||
|
return { url: comment.link, type: 'webpage', thumbnail: comment.thumbnailUrl };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (comment?.link) {
|
||||||
|
return { url: comment.link, type: 'webpage' };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getCommentMediaInfoMemoized = memoize(getCommentMediaInfo, { max: 1000 });
|
||||||
|
|
||||||
|
const getLinkMediaInfo = (link: string) => {
|
||||||
|
let mime: string | undefined;
|
||||||
|
try {
|
||||||
|
mime = extName(new URL(link).pathname.toLowerCase().replace('/', ''))[0]?.mime;
|
||||||
|
} catch (e) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = new URL(link);
|
||||||
|
const host = url.hostname;
|
||||||
|
let patternThumbnailUrl;
|
||||||
|
|
||||||
|
if (['youtube.com', 'www.youtube.com', 'youtu.be', 'www.youtu.be', 'm.youtube.com'].includes(host)) {
|
||||||
|
const videoId = host === 'youtu.be' ? url.pathname.slice(1) : url.searchParams.get('v');
|
||||||
|
patternThumbnailUrl = `https://img.youtube.com/vi/${videoId}/0.jpg`;
|
||||||
|
} else if (host.includes('streamable.com')) {
|
||||||
|
const videoId = url.pathname.split('/')[1];
|
||||||
|
patternThumbnailUrl = `https://cdn-cf-east.streamable.com/image/${videoId}.jpg`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (canEmbed(url)) {
|
||||||
|
return {
|
||||||
|
url: link,
|
||||||
|
type: 'iframe',
|
||||||
|
patternThumbnailUrl,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mime?.startsWith('image')) {
|
||||||
|
return { url: link, type: 'image' };
|
||||||
|
}
|
||||||
|
if (mime?.startsWith('video')) {
|
||||||
|
return { url: link, type: 'video' };
|
||||||
|
}
|
||||||
|
if (mime?.startsWith('audio')) {
|
||||||
|
return { url: link, type: 'audio' };
|
||||||
|
}
|
||||||
|
return { url: link, type: 'webpage' };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getLinkMediaInfoMemoized = memoize(getLinkMediaInfo, { max: 1000 });
|
||||||
+36
-38
@@ -46,17 +46,21 @@
|
|||||||
--homepage-box-bar-text-color: #800;
|
--homepage-box-bar-text-color: #800;
|
||||||
--homepage-box-bar-text-color-hover: #c63;
|
--homepage-box-bar-text-color-hover: #c63;
|
||||||
--homepage-box-bar-background-color: #fca;
|
--homepage-box-bar-background-color: #fca;
|
||||||
|
--homepage-infobox-link-text-color: #00f;
|
||||||
|
--homepage-infobox-link-text-decoration: underline;
|
||||||
|
--homepage-box-link-text-color: #800;
|
||||||
|
--homepage-box-link-text-color-hover: #e00;
|
||||||
|
--homepage-box-link-text-decoration: none;
|
||||||
|
--homepage-box-link-text-decoration-hover: underline;
|
||||||
|
|
||||||
/* footer */
|
/* footer */
|
||||||
--footer-item-background-color-desktop: #fed;
|
--footer-item-background-color-desktop: #fed;
|
||||||
|
|
||||||
/* links */
|
/* links */
|
||||||
--link-external-text-color: #00f;
|
--post-link-text-color: #00e;
|
||||||
--link-external-text-decoration: underline;
|
--post-link-text-decoration: underline;
|
||||||
--link-internal-text-color: #800;
|
--post-link-text-color-hover: red;
|
||||||
--link-internal-text-color-hover: #e00;
|
--post-link-text-decoration-hover: underline;
|
||||||
--link-internal-text-decoration: none;
|
|
||||||
--link-internal-text-decoration-hover: underline;
|
|
||||||
|
|
||||||
/* post form */
|
/* post form */
|
||||||
--post-form-toggle-font-size: 22px;
|
--post-form-toggle-font-size: 22px;
|
||||||
@@ -96,6 +100,12 @@
|
|||||||
--homepage-box-bar-text-color: #000;
|
--homepage-box-bar-text-color: #000;
|
||||||
--homepage-box-bar-text-color-hover: #fff;
|
--homepage-box-bar-text-color-hover: #fff;
|
||||||
--homepage-box-bar-background-color: #98e;
|
--homepage-box-bar-background-color: #98e;
|
||||||
|
--homepage-infobox-link-text-color: #00f;
|
||||||
|
--homepage-infobox-link-text-decoration: underline;
|
||||||
|
--homepage-box-link-text-color: #34345c;
|
||||||
|
--homepage-box-link-text-color-hover: #d00;
|
||||||
|
--homepage-box-link-text-decoration: none;
|
||||||
|
--homepage-box-link-text-decoration-hover: underline;
|
||||||
|
|
||||||
/* desktop buttons */
|
/* desktop buttons */
|
||||||
--button-desktop-text-color: #34345c;
|
--button-desktop-text-color: #34345c;
|
||||||
@@ -117,12 +127,10 @@
|
|||||||
--hr-border-top: 1px solid #b7c5d9;
|
--hr-border-top: 1px solid #b7c5d9;
|
||||||
|
|
||||||
/* links */
|
/* links */
|
||||||
--link-external-text-color: #00f;
|
--post-link-text-color: #34345c;
|
||||||
--link-external-text-decoration: underline;
|
--post-link-text-decoration: underline;
|
||||||
--link-internal-text-color: #34345c;
|
--post-link-text-color-hover: #d00;
|
||||||
--link-internal-text-color-hover: #d00;
|
--post-link-text-decoration-hover: underline;
|
||||||
--link-internal-text-decoration: none;
|
|
||||||
--link-internal-text-decoration-hover: underline;
|
|
||||||
|
|
||||||
/* post form */
|
/* post form */
|
||||||
--post-form-toggle-font-size: 22px;
|
--post-form-toggle-font-size: 22px;
|
||||||
@@ -137,16 +145,6 @@
|
|||||||
--body-font-family: times new roman, serif;
|
--body-font-family: times new roman, serif;
|
||||||
--body-font-size: 12pt;
|
--body-font-size: 12pt;
|
||||||
|
|
||||||
/* homepage */
|
|
||||||
--homepage-infobox-text-color: #000;
|
|
||||||
--homepage-infobox-bar-text-color: #fff;
|
|
||||||
--homepage-infobox-bar-background-color: #800;
|
|
||||||
--homepage-box-background-color: #fff;
|
|
||||||
--homepage-box-border-color: #800;
|
|
||||||
--homepage-box-bar-text-color: #800;
|
|
||||||
--homepage-box-bar-text-color-hover: #c63;
|
|
||||||
--homepage-box-bar-background-color: #fca;
|
|
||||||
|
|
||||||
/* board nav */
|
/* board nav */
|
||||||
--boardnav-font-size: 11pt;
|
--boardnav-font-size: 11pt;
|
||||||
--boardnav-separator-color: none;
|
--boardnav-separator-color: none;
|
||||||
@@ -176,12 +174,10 @@
|
|||||||
--footer-item-background-color-desktop: #fed;
|
--footer-item-background-color-desktop: #fed;
|
||||||
|
|
||||||
/* links */
|
/* links */
|
||||||
--link-external-text-color: #00f;
|
--post-link-text-color: #00e;
|
||||||
--link-external-text-decoration: underline;
|
--post-link-text-decoration: underline;
|
||||||
--link-internal-text-color: #00e;
|
--post-link-text-color-hover: red;
|
||||||
--link-internal-text-color-hover: #e00;
|
--post-link-text-decoration-hover: underline;
|
||||||
--link-internal-text-decoration: underline;
|
|
||||||
--link-internal-text-decoration-hover: underline;
|
|
||||||
|
|
||||||
/* post form */
|
/* post form */
|
||||||
--post-form-toggle-font-size: 22px;
|
--post-form-toggle-font-size: 22px;
|
||||||
@@ -239,12 +235,10 @@
|
|||||||
--footer-item-background-color-desktop: #eef2ff;
|
--footer-item-background-color-desktop: #eef2ff;
|
||||||
|
|
||||||
/* links */
|
/* links */
|
||||||
--link-external-text-color: #00f;
|
--post-link-text-color: #34345c;
|
||||||
--link-external-text-decoration: underline;
|
--post-link-text-decoration: underline;
|
||||||
--link-internal-text-color: #34345c;
|
--post-link-text-color-hover: #d00;
|
||||||
--link-internal-text-color-hover: #d00;
|
--post-link-text-decoration-hover: underline;
|
||||||
--link-internal-text-decoration: underline;
|
|
||||||
--link-internal-text-decoration-hover: underline;
|
|
||||||
|
|
||||||
/* post form */
|
/* post form */
|
||||||
--post-form-toggle-font-size: 22px;
|
--post-form-toggle-font-size: 22px;
|
||||||
@@ -300,8 +294,10 @@
|
|||||||
--hr-height: 0px;
|
--hr-height: 0px;
|
||||||
|
|
||||||
/* links */
|
/* links */
|
||||||
--link-internal-text-color: #81a2be;
|
--post-link-text-color: #81a2be;
|
||||||
--link-internal-text-color-hover: #5f89ac;
|
--post-link-text-decoration: underline;
|
||||||
|
--post-link-text-color-hover: #5f89ac;
|
||||||
|
--post-link-text-decoration-hover: underline;
|
||||||
|
|
||||||
/* post form */
|
/* post form */
|
||||||
--post-form-toggle-font-size: 22px;
|
--post-form-toggle-font-size: 22px;
|
||||||
@@ -352,8 +348,10 @@
|
|||||||
--hr-height: 0px;
|
--hr-height: 0px;
|
||||||
|
|
||||||
/* links */
|
/* links */
|
||||||
--link-internal-text-color: #f60;
|
--post-link-text-color: #f60;
|
||||||
--link-internal-text-color-hover: #f30;
|
--post-link-text-decoration: underline;
|
||||||
|
--post-link-text-color-hover: #f30;
|
||||||
|
--post-link-text-decoration-hover: underline;
|
||||||
|
|
||||||
/* post form */
|
/* post form */
|
||||||
--post-form-toggle-font-size: 22px;
|
--post-form-toggle-font-size: 22px;
|
||||||
|
|||||||
@@ -72,7 +72,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.boxContent a, .boxContent a:visited{
|
.boxContent a, .boxContent a:visited{
|
||||||
color: var(--link-external-text-color);
|
color: var(--homepage-infobox-link-text-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.searchBar {
|
.searchBar {
|
||||||
@@ -116,13 +116,13 @@
|
|||||||
|
|
||||||
.list a {
|
.list a {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
color: var(--link-internal-text-color);
|
color: var(--homepage-box-link-text-color);
|
||||||
text-decoration: var(--link-internal-text-decoration);
|
text-decoration: var(--homepage-box-link-text-decoration);
|
||||||
}
|
}
|
||||||
|
|
||||||
.list a:hover {
|
.list a:hover {
|
||||||
color: var(--link-internal-text-color-hover);
|
color: var(--homepage-box-link-text-color-hover);
|
||||||
text-decoration: var(--link-internal-text-decoration-hover);
|
text-decoration: var(--homepage-box-link-text-decoration-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
.boardsBoxContent {
|
.boardsBoxContent {
|
||||||
@@ -175,7 +175,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.footer a {
|
.footer a {
|
||||||
color: var(--link-external-text-color);
|
color: var(--homepage-infobox-link-text-color);
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user