mirror of
https://github.com/aljazceru/rabbit.git
synced 2025-12-17 14:04:21 +01:00
feat: support initialHidden in PreviewedLink
This commit is contained in:
@@ -1,14 +1,15 @@
|
||||
import { Component, JSX, Switch, Match, createEffect, Show } from 'solid-js';
|
||||
import { Component, JSX, Switch, Match, createSignal, createEffect, Show } from 'solid-js';
|
||||
|
||||
import LazyLoad from '@/components/utils/LazyLoad';
|
||||
import SafeLink from '@/components/utils/SafeLink';
|
||||
import useConfig from '@/core/useConfig';
|
||||
import { useOgp } from '@/utils/ogp';
|
||||
import { useTranslation } from '@/i18n/useTranslation';
|
||||
import { useOgp, isOgpUrl } from '@/utils/ogp';
|
||||
import { isTwitterUrl, parseYouTubeVideoUrl } from '@/utils/url';
|
||||
|
||||
type PreviewdLinkProps = {
|
||||
class?: string;
|
||||
href: string;
|
||||
url: string;
|
||||
initialHidden: boolean;
|
||||
children?: JSX.Element;
|
||||
};
|
||||
|
||||
@@ -29,13 +30,13 @@ const youtubeUrl = (videoId: string): string => {
|
||||
return iframeUrl.href;
|
||||
};
|
||||
|
||||
const TwitterEmbed: Component<{ class?: string; href: string }> = (props) => {
|
||||
const TwitterEmbed: Component<{ url: string }> = (props) => {
|
||||
let twitterRef: HTMLQuoteElement | undefined;
|
||||
|
||||
const { getColorTheme } = useConfig();
|
||||
|
||||
createEffect(() => {
|
||||
if (isTwitterUrl(props.href)) {
|
||||
if (isTwitterUrl(props.url)) {
|
||||
window.twttr?.widgets?.load(twitterRef);
|
||||
}
|
||||
});
|
||||
@@ -51,12 +52,12 @@ const TwitterEmbed: Component<{ class?: string; href: string }> = (props) => {
|
||||
return (
|
||||
<blockquote ref={twitterRef} class="twitter-tweet" data-theme={dataTheme()}>
|
||||
<a
|
||||
class={props.class}
|
||||
href={twitterUrl(props.href)}
|
||||
class="text-link underline"
|
||||
href={twitterUrl(props.url)}
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
>
|
||||
{twitterUrl(props.href)}
|
||||
{twitterUrl(props.url)}
|
||||
</a>
|
||||
</blockquote>
|
||||
);
|
||||
@@ -68,7 +69,7 @@ const OgpEmbed: Component<{ class?: string; url: string }> = (props) => {
|
||||
}));
|
||||
|
||||
return (
|
||||
<Show when={ogp()} fallback={<SafeLink class={props.class} href={props.url} />} keyed>
|
||||
<Show when={ogp()} fallback={<SafeLink class="text-link underline" href={props.url} />} keyed>
|
||||
{(ogpProps) => (
|
||||
<SafeLink href={props.url}>
|
||||
<div class="my-2 rounded-lg border border-border transition-colors hover:bg-bg-tertiary">
|
||||
@@ -89,39 +90,75 @@ const OgpEmbed: Component<{ class?: string; url: string }> = (props) => {
|
||||
);
|
||||
};
|
||||
|
||||
type ClickToShowProps = {
|
||||
initialHidden: boolean;
|
||||
url: string;
|
||||
children: JSX.Element;
|
||||
};
|
||||
|
||||
const ClickToShow: Component<ClickToShowProps> = (props) => {
|
||||
const i18n = useTranslation();
|
||||
const [hidden, setHidden] = createSignal(props.initialHidden);
|
||||
|
||||
return (
|
||||
<Show
|
||||
when={!hidden()}
|
||||
fallback={
|
||||
<div>
|
||||
<button
|
||||
class="flex flex-col items-center rounded bg-bg-tertiary p-3 text-xs text-fg-secondary hover:shadow"
|
||||
onClick={() => setHidden(false)}
|
||||
>
|
||||
{i18n()('post.showPreview')}
|
||||
</button>
|
||||
<SafeLink class="text-link underline" href={props.url} />
|
||||
</div>
|
||||
}
|
||||
>
|
||||
{props.children}
|
||||
</Show>
|
||||
);
|
||||
};
|
||||
|
||||
const PreviewedLink: Component<PreviewdLinkProps> = (props) => {
|
||||
const { config } = useConfig();
|
||||
|
||||
return (
|
||||
<Switch fallback={<SafeLink class={props.class} href={props.href} />}>
|
||||
<Match when={config().embedding.twitter && isTwitterUrl(props.href)}>
|
||||
<TwitterEmbed class={props.class} href={props.href} />
|
||||
<Switch fallback={<SafeLink class="text-link underline" href={props.url} />}>
|
||||
<Match when={config().embedding.twitter && isTwitterUrl(props.url)}>
|
||||
<ClickToShow url={props.url} initialHidden={props.initialHidden}>
|
||||
<TwitterEmbed url={props.url} />
|
||||
</ClickToShow>
|
||||
</Match>
|
||||
<Match when={config().embedding.youtube && parseYouTubeVideoUrl(props.href)} keyed>
|
||||
<Match when={config().embedding.youtube && parseYouTubeVideoUrl(props.url)} keyed>
|
||||
{({ videoId }) => (
|
||||
<LazyLoad
|
||||
fallback={
|
||||
<div class="aspect-video max-w-full">
|
||||
<SafeLink href={props.href} />
|
||||
</div>
|
||||
}
|
||||
>
|
||||
{() => (
|
||||
<div class="my-2 aspect-video w-full">
|
||||
<iframe
|
||||
loading="lazy"
|
||||
title="YouTube"
|
||||
class="my-2 h-full w-full"
|
||||
src={youtubeUrl(videoId)}
|
||||
allowfullscreen
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</LazyLoad>
|
||||
<ClickToShow url={props.url} initialHidden={props.initialHidden}>
|
||||
<LazyLoad
|
||||
fallback={
|
||||
<div class="aspect-video max-w-full">
|
||||
<SafeLink href={props.url} />
|
||||
</div>
|
||||
}
|
||||
>
|
||||
{() => (
|
||||
<div class="my-2 aspect-video w-full">
|
||||
<iframe
|
||||
loading="lazy"
|
||||
title="YouTube"
|
||||
class="my-2 h-full w-full"
|
||||
src={youtubeUrl(videoId)}
|
||||
allowfullscreen
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</LazyLoad>
|
||||
</ClickToShow>
|
||||
)}
|
||||
</Match>
|
||||
<Match when={config().embedding.ogp}>
|
||||
<LazyLoad>{() => <OgpEmbed class={props.class} url={props.href} />}</LazyLoad>
|
||||
<Match when={config().embedding.ogp && isOgpUrl(props.url)}>
|
||||
<ClickToShow url={props.url} initialHidden={props.initialHidden}>
|
||||
<LazyLoad>{() => <OgpEmbed url={props.url} />}</LazyLoad>
|
||||
</ClickToShow>
|
||||
</Match>
|
||||
</Switch>
|
||||
);
|
||||
|
||||
@@ -64,7 +64,7 @@ const TextNoteContentDisplay = (props: TextNoteContentDisplayProps) => {
|
||||
</button>
|
||||
);
|
||||
}
|
||||
return <PreviewedLink class="text-link underline" href={item.content} />;
|
||||
return <PreviewedLink url={item.content} initialHidden={initialHidden()} />;
|
||||
}
|
||||
if (item.type === 'TagReferenceResolved') {
|
||||
if (item.reference == null) {
|
||||
|
||||
@@ -108,6 +108,7 @@ export default {
|
||||
failedToDelete: 'Failed to delete',
|
||||
showImage: 'Show image',
|
||||
showVideo: 'Show video',
|
||||
showPreview: 'Show preview',
|
||||
showOverflow: 'Read more',
|
||||
hideOverflow: 'Hide',
|
||||
download: 'Download',
|
||||
|
||||
@@ -104,6 +104,7 @@ export default {
|
||||
failedToDelete: 'すべてのリレーで削除に失敗しました',
|
||||
showImage: '画像を表示する',
|
||||
showVideo: '動画を表示する',
|
||||
showPreview: 'プレビューを表示する',
|
||||
showOverflow: '続きを読む',
|
||||
hideOverflow: '隠す',
|
||||
download: 'ダウンロード',
|
||||
|
||||
@@ -39,13 +39,16 @@ export const parseOgp = (text: string): OgpContent | null => {
|
||||
return parseOgpFromDOM(doc);
|
||||
};
|
||||
|
||||
export const fetchOgpContent = async (urlString: string): Promise<OgpContent | null> => {
|
||||
export const isOgpUrl = (urlString: string): boolean => {
|
||||
const allowList = ['www3.nhk.or.jp'];
|
||||
|
||||
const url = new URL(urlString);
|
||||
if (!allowList.includes(url.host)) return null;
|
||||
return allowList.includes(url.host);
|
||||
};
|
||||
|
||||
const res = await fetch(url, { headers: { Accept: 'text/html' } });
|
||||
export const fetchOgpContent = async (urlString: string): Promise<OgpContent | null> => {
|
||||
if (!isOgpUrl(urlString)) return null;
|
||||
|
||||
const res = await fetch(urlString, { headers: { Accept: 'text/html' } });
|
||||
const text = await res.text();
|
||||
return parseOgp(text);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user