mirror of
https://github.com/aykhans/slash-e.git
synced 2025-04-16 12:23:12 +00:00
feat(web): use favicon provider
This commit is contained in:
parent
8649e562dc
commit
5264dc9d8a
36
frontend/web/src/components/LinkFavicon.tsx
Normal file
36
frontend/web/src/components/LinkFavicon.tsx
Normal file
@ -0,0 +1,36 @@
|
||||
import { useState } from "react";
|
||||
import { useWorkspaceStore } from "@/stores";
|
||||
import Icon from "./Icon";
|
||||
|
||||
interface Props {
|
||||
url: string;
|
||||
}
|
||||
|
||||
const getFaviconUrlWithProvider = (url: string, provider: string) => {
|
||||
try {
|
||||
const searchParams = new URLSearchParams();
|
||||
searchParams.set("domain", new URL(url).hostname);
|
||||
return new URL(`?${searchParams.toString()}`, provider).toString();
|
||||
} catch (error) {
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
const LinkFavicon = (props: Props) => {
|
||||
const { url } = props;
|
||||
const workspaceStore = useWorkspaceStore();
|
||||
const faviconProvider = workspaceStore.profile.faviconProvider || "https://www.google.com/s2/favicons";
|
||||
const [faviconUrl, setFaviconUrl] = useState<string>(getFaviconUrlWithProvider(url, faviconProvider));
|
||||
|
||||
const handleImgError = () => {
|
||||
setFaviconUrl("");
|
||||
};
|
||||
|
||||
return faviconUrl ? (
|
||||
<img className="w-full h-auto rounded" src={faviconUrl} decoding="async" loading="lazy" onError={handleImgError} />
|
||||
) : (
|
||||
<Icon.CircleSlash className="w-full h-auto text-gray-400" strokeWidth={1.5} />
|
||||
);
|
||||
};
|
||||
|
||||
export default LinkFavicon;
|
@ -8,8 +8,9 @@ import { Link } from "react-router-dom";
|
||||
import { useUserStore, useViewStore } from "@/stores";
|
||||
import { Shortcut } from "@/types/proto/api/v1/shortcut_service";
|
||||
import { convertVisibilityFromPb } from "@/utils/visibility";
|
||||
import { absolutifyLink, getFaviconWithGoogleS2 } from "../helpers/utils";
|
||||
import { absolutifyLink } from "../helpers/utils";
|
||||
import Icon from "./Icon";
|
||||
import LinkFavicon from "./LinkFavicon";
|
||||
import ShortcutActionsDropdown from "./ShortcutActionsDropdown";
|
||||
import VisibilityIcon from "./VisibilityIcon";
|
||||
|
||||
@ -24,7 +25,6 @@ const ShortcutCard = (props: Props) => {
|
||||
const viewStore = useViewStore();
|
||||
const creator = userStore.getUserById(shortcut.creatorId);
|
||||
const shortcutLink = absolutifyLink(`/s/${shortcut.name}`);
|
||||
const favicon = getFaviconWithGoogleS2(shortcut.link);
|
||||
|
||||
useEffect(() => {
|
||||
userStore.getOrFetchUserById(shortcut.creatorId);
|
||||
@ -48,11 +48,7 @@ const ShortcutCard = (props: Props) => {
|
||||
to={`/shortcut/${shortcut.id}`}
|
||||
unstable_viewTransition
|
||||
>
|
||||
{favicon ? (
|
||||
<img className="w-full h-auto rounded" src={favicon} decoding="async" loading="lazy" />
|
||||
) : (
|
||||
<Icon.CircleSlash className="w-full h-auto text-gray-400" />
|
||||
)}
|
||||
<LinkFavicon url={shortcut.link} />
|
||||
</Link>
|
||||
<div className="ml-1 w-[calc(100%-24px)] flex flex-col justify-start items-start">
|
||||
<div className="w-full flex flex-row justify-start items-center">
|
||||
|
@ -1,17 +1,15 @@
|
||||
import { Divider } from "@mui/joy";
|
||||
import classNames from "classnames";
|
||||
import { Link } from "react-router-dom";
|
||||
import { getFaviconWithGoogleS2 } from "@/helpers/utils";
|
||||
import { Shortcut } from "@/types/proto/api/v1/shortcut_service";
|
||||
import Icon from "./Icon";
|
||||
import LinkFavicon from "./LinkFavicon";
|
||||
|
||||
interface Props {
|
||||
shortcut: Shortcut;
|
||||
}
|
||||
|
||||
const ShortcutFrame = ({ shortcut }: Props) => {
|
||||
const favicon = getFaviconWithGoogleS2(shortcut.link);
|
||||
|
||||
return (
|
||||
<div className="w-full h-full flex flex-col justify-center items-center p-8">
|
||||
<Link
|
||||
@ -20,11 +18,7 @@ const ShortcutFrame = ({ shortcut }: Props) => {
|
||||
target="_blank"
|
||||
>
|
||||
<div className={classNames("w-12 h-12 flex justify-center items-center overflow-clip rounded-lg shrink-0")}>
|
||||
{favicon ? (
|
||||
<img className="w-full h-auto" src={favicon} decoding="async" loading="lazy" />
|
||||
) : (
|
||||
<Icon.Globe2Icon className="w-full h-auto opacity-70" strokeWidth={1} />
|
||||
)}
|
||||
<LinkFavicon url={shortcut.link} />
|
||||
</div>
|
||||
<p className="text-lg font-medium leading-8 mt-2 truncate">{shortcut.title || shortcut.name}</p>
|
||||
<p className="text-gray-500 truncate">{shortcut.description}</p>
|
||||
|
@ -1,8 +1,8 @@
|
||||
import classNames from "classnames";
|
||||
import { Link } from "react-router-dom";
|
||||
import { Shortcut } from "@/types/proto/api/v1/shortcut_service";
|
||||
import { getFaviconWithGoogleS2 } from "../helpers/utils";
|
||||
import Icon from "./Icon";
|
||||
import LinkFavicon from "./LinkFavicon";
|
||||
import ShortcutActionsDropdown from "./ShortcutActionsDropdown";
|
||||
|
||||
interface Props {
|
||||
@ -15,7 +15,6 @@ interface Props {
|
||||
|
||||
const ShortcutView = (props: Props) => {
|
||||
const { shortcut, className, showActions, alwaysShowLink, onClick } = props;
|
||||
const favicon = getFaviconWithGoogleS2(shortcut.link);
|
||||
|
||||
return (
|
||||
<div
|
||||
@ -26,11 +25,7 @@ const ShortcutView = (props: Props) => {
|
||||
onClick={onClick}
|
||||
>
|
||||
<div className={classNames("w-5 h-5 flex justify-center items-center overflow-clip shrink-0")}>
|
||||
{favicon ? (
|
||||
<img className="w-full h-auto rounded" src={favicon} decoding="async" loading="lazy" />
|
||||
) : (
|
||||
<Icon.CircleSlash className="w-full h-auto text-gray-400" />
|
||||
)}
|
||||
<LinkFavicon url={shortcut.link} />
|
||||
</div>
|
||||
<div className="ml-2 w-full truncate">
|
||||
{shortcut.title ? (
|
||||
|
@ -1,9 +1,3 @@
|
||||
import { isNull, isUndefined } from "lodash-es";
|
||||
|
||||
export const isNullorUndefined = (value: any) => {
|
||||
return isNull(value) || isUndefined(value);
|
||||
};
|
||||
|
||||
export const absolutifyLink = (rel: string): string => {
|
||||
const anchor = document.createElement("a");
|
||||
anchor.setAttribute("href", rel);
|
||||
@ -15,19 +9,6 @@ export const isURL = (str: string): boolean => {
|
||||
return urlRegex.test(str);
|
||||
};
|
||||
|
||||
export const releaseGuard = () => {
|
||||
return import.meta.env.MODE === "development";
|
||||
};
|
||||
|
||||
export const getFaviconWithGoogleS2 = (url: string) => {
|
||||
try {
|
||||
const urlObject = new URL(url);
|
||||
return `https://www.google.com/s2/favicons?sz=128&domain=${urlObject.hostname}`;
|
||||
} catch (error) {
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
export const generateRandomString = () => {
|
||||
const characters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||
let randomString = "";
|
||||
|
@ -5,20 +5,21 @@ import { useEffect, useState } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { showCommonDialog } from "@/components/Alert";
|
||||
import AnalyticsView from "@/components/AnalyticsView";
|
||||
import CreateShortcutDrawer from "@/components/CreateShortcutDrawer";
|
||||
import GenerateQRCodeDialog from "@/components/GenerateQRCodeDialog";
|
||||
import Icon from "@/components/Icon";
|
||||
import LinkFavicon from "@/components/LinkFavicon";
|
||||
import VisibilityIcon from "@/components/VisibilityIcon";
|
||||
import Dropdown from "@/components/common/Dropdown";
|
||||
import { absolutifyLink } from "@/helpers/utils";
|
||||
import useLoading from "@/hooks/useLoading";
|
||||
import useNavigateTo from "@/hooks/useNavigateTo";
|
||||
import { useUserStore, useShortcutStore } from "@/stores";
|
||||
import { Shortcut } from "@/types/proto/api/v1/shortcut_service";
|
||||
import { Role } from "@/types/proto/api/v1/user_service";
|
||||
import { convertVisibilityFromPb } from "@/utils/visibility";
|
||||
import { showCommonDialog } from "../components/Alert";
|
||||
import AnalyticsView from "../components/AnalyticsView";
|
||||
import CreateShortcutDrawer from "../components/CreateShortcutDrawer";
|
||||
import GenerateQRCodeDialog from "../components/GenerateQRCodeDialog";
|
||||
import Icon from "../components/Icon";
|
||||
import VisibilityIcon from "../components/VisibilityIcon";
|
||||
import Dropdown from "../components/common/Dropdown";
|
||||
import { absolutifyLink, getFaviconWithGoogleS2 } from "../helpers/utils";
|
||||
|
||||
interface State {
|
||||
showEditDrawer: boolean;
|
||||
@ -41,7 +42,6 @@ const ShortcutDetail = () => {
|
||||
const creator = userStore.getUserById(shortcut.creatorId);
|
||||
const havePermission = currentUser.role === Role.ADMIN || shortcut.creatorId === currentUser.id;
|
||||
const shortcutLink = absolutifyLink(`/s/${shortcut.name}`);
|
||||
const favicon = getFaviconWithGoogleS2(shortcut.link);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
@ -78,11 +78,7 @@ const ShortcutDetail = () => {
|
||||
<>
|
||||
<div className="mx-auto max-w-8xl w-full px-4 sm:px-6 md:px-12 pt-4 pb-6 flex flex-col justify-start items-start">
|
||||
<div className="mt-4 sm:mt-8 w-12 h-12 flex justify-center items-center overflow-clip">
|
||||
{favicon ? (
|
||||
<img className="w-full h-auto rounded-lg" src={favicon} decoding="async" loading="lazy" />
|
||||
) : (
|
||||
<Icon.CircleSlash className="w-full h-auto text-gray-400" strokeWidth={1} />
|
||||
)}
|
||||
<LinkFavicon url={shortcut.link} />
|
||||
</div>
|
||||
<a
|
||||
className={classNames(
|
||||
|
Loading…
x
Reference in New Issue
Block a user