mirror of
https://github.com/aykhans/slash-e.git
synced 2025-04-20 22:07:15 +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 { useUserStore, useViewStore } from "@/stores";
|
||||||
import { Shortcut } from "@/types/proto/api/v1/shortcut_service";
|
import { Shortcut } from "@/types/proto/api/v1/shortcut_service";
|
||||||
import { convertVisibilityFromPb } from "@/utils/visibility";
|
import { convertVisibilityFromPb } from "@/utils/visibility";
|
||||||
import { absolutifyLink, getFaviconWithGoogleS2 } from "../helpers/utils";
|
import { absolutifyLink } from "../helpers/utils";
|
||||||
import Icon from "./Icon";
|
import Icon from "./Icon";
|
||||||
|
import LinkFavicon from "./LinkFavicon";
|
||||||
import ShortcutActionsDropdown from "./ShortcutActionsDropdown";
|
import ShortcutActionsDropdown from "./ShortcutActionsDropdown";
|
||||||
import VisibilityIcon from "./VisibilityIcon";
|
import VisibilityIcon from "./VisibilityIcon";
|
||||||
|
|
||||||
@ -24,7 +25,6 @@ const ShortcutCard = (props: Props) => {
|
|||||||
const viewStore = useViewStore();
|
const viewStore = useViewStore();
|
||||||
const creator = userStore.getUserById(shortcut.creatorId);
|
const creator = userStore.getUserById(shortcut.creatorId);
|
||||||
const shortcutLink = absolutifyLink(`/s/${shortcut.name}`);
|
const shortcutLink = absolutifyLink(`/s/${shortcut.name}`);
|
||||||
const favicon = getFaviconWithGoogleS2(shortcut.link);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
userStore.getOrFetchUserById(shortcut.creatorId);
|
userStore.getOrFetchUserById(shortcut.creatorId);
|
||||||
@ -48,11 +48,7 @@ const ShortcutCard = (props: Props) => {
|
|||||||
to={`/shortcut/${shortcut.id}`}
|
to={`/shortcut/${shortcut.id}`}
|
||||||
unstable_viewTransition
|
unstable_viewTransition
|
||||||
>
|
>
|
||||||
{favicon ? (
|
<LinkFavicon url={shortcut.link} />
|
||||||
<img className="w-full h-auto rounded" src={favicon} decoding="async" loading="lazy" />
|
|
||||||
) : (
|
|
||||||
<Icon.CircleSlash className="w-full h-auto text-gray-400" />
|
|
||||||
)}
|
|
||||||
</Link>
|
</Link>
|
||||||
<div className="ml-1 w-[calc(100%-24px)] flex flex-col justify-start items-start">
|
<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">
|
<div className="w-full flex flex-row justify-start items-center">
|
||||||
|
@ -1,17 +1,15 @@
|
|||||||
import { Divider } from "@mui/joy";
|
import { Divider } from "@mui/joy";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { getFaviconWithGoogleS2 } from "@/helpers/utils";
|
|
||||||
import { Shortcut } from "@/types/proto/api/v1/shortcut_service";
|
import { Shortcut } from "@/types/proto/api/v1/shortcut_service";
|
||||||
import Icon from "./Icon";
|
import Icon from "./Icon";
|
||||||
|
import LinkFavicon from "./LinkFavicon";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
shortcut: Shortcut;
|
shortcut: Shortcut;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ShortcutFrame = ({ shortcut }: Props) => {
|
const ShortcutFrame = ({ shortcut }: Props) => {
|
||||||
const favicon = getFaviconWithGoogleS2(shortcut.link);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full h-full flex flex-col justify-center items-center p-8">
|
<div className="w-full h-full flex flex-col justify-center items-center p-8">
|
||||||
<Link
|
<Link
|
||||||
@ -20,11 +18,7 @@ const ShortcutFrame = ({ shortcut }: Props) => {
|
|||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
<div className={classNames("w-12 h-12 flex justify-center items-center overflow-clip rounded-lg shrink-0")}>
|
<div className={classNames("w-12 h-12 flex justify-center items-center overflow-clip rounded-lg shrink-0")}>
|
||||||
{favicon ? (
|
<LinkFavicon url={shortcut.link} />
|
||||||
<img className="w-full h-auto" src={favicon} decoding="async" loading="lazy" />
|
|
||||||
) : (
|
|
||||||
<Icon.Globe2Icon className="w-full h-auto opacity-70" strokeWidth={1} />
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<p className="text-lg font-medium leading-8 mt-2 truncate">{shortcut.title || shortcut.name}</p>
|
<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>
|
<p className="text-gray-500 truncate">{shortcut.description}</p>
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { Shortcut } from "@/types/proto/api/v1/shortcut_service";
|
import { Shortcut } from "@/types/proto/api/v1/shortcut_service";
|
||||||
import { getFaviconWithGoogleS2 } from "../helpers/utils";
|
|
||||||
import Icon from "./Icon";
|
import Icon from "./Icon";
|
||||||
|
import LinkFavicon from "./LinkFavicon";
|
||||||
import ShortcutActionsDropdown from "./ShortcutActionsDropdown";
|
import ShortcutActionsDropdown from "./ShortcutActionsDropdown";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@ -15,7 +15,6 @@ interface Props {
|
|||||||
|
|
||||||
const ShortcutView = (props: Props) => {
|
const ShortcutView = (props: Props) => {
|
||||||
const { shortcut, className, showActions, alwaysShowLink, onClick } = props;
|
const { shortcut, className, showActions, alwaysShowLink, onClick } = props;
|
||||||
const favicon = getFaviconWithGoogleS2(shortcut.link);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@ -26,11 +25,7 @@ const ShortcutView = (props: Props) => {
|
|||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
>
|
>
|
||||||
<div className={classNames("w-5 h-5 flex justify-center items-center overflow-clip shrink-0")}>
|
<div className={classNames("w-5 h-5 flex justify-center items-center overflow-clip shrink-0")}>
|
||||||
{favicon ? (
|
<LinkFavicon url={shortcut.link} />
|
||||||
<img className="w-full h-auto rounded" src={favicon} decoding="async" loading="lazy" />
|
|
||||||
) : (
|
|
||||||
<Icon.CircleSlash className="w-full h-auto text-gray-400" />
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-2 w-full truncate">
|
<div className="ml-2 w-full truncate">
|
||||||
{shortcut.title ? (
|
{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 => {
|
export const absolutifyLink = (rel: string): string => {
|
||||||
const anchor = document.createElement("a");
|
const anchor = document.createElement("a");
|
||||||
anchor.setAttribute("href", rel);
|
anchor.setAttribute("href", rel);
|
||||||
@ -15,19 +9,6 @@ export const isURL = (str: string): boolean => {
|
|||||||
return urlRegex.test(str);
|
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 = () => {
|
export const generateRandomString = () => {
|
||||||
const characters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
const characters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||||
let randomString = "";
|
let randomString = "";
|
||||||
|
@ -5,20 +5,21 @@ import { useEffect, useState } from "react";
|
|||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useParams } from "react-router-dom";
|
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 useLoading from "@/hooks/useLoading";
|
||||||
import useNavigateTo from "@/hooks/useNavigateTo";
|
import useNavigateTo from "@/hooks/useNavigateTo";
|
||||||
import { useUserStore, useShortcutStore } from "@/stores";
|
import { useUserStore, useShortcutStore } from "@/stores";
|
||||||
import { Shortcut } from "@/types/proto/api/v1/shortcut_service";
|
import { Shortcut } from "@/types/proto/api/v1/shortcut_service";
|
||||||
import { Role } from "@/types/proto/api/v1/user_service";
|
import { Role } from "@/types/proto/api/v1/user_service";
|
||||||
import { convertVisibilityFromPb } from "@/utils/visibility";
|
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 {
|
interface State {
|
||||||
showEditDrawer: boolean;
|
showEditDrawer: boolean;
|
||||||
@ -41,7 +42,6 @@ const ShortcutDetail = () => {
|
|||||||
const creator = userStore.getUserById(shortcut.creatorId);
|
const creator = userStore.getUserById(shortcut.creatorId);
|
||||||
const havePermission = currentUser.role === Role.ADMIN || shortcut.creatorId === currentUser.id;
|
const havePermission = currentUser.role === Role.ADMIN || shortcut.creatorId === currentUser.id;
|
||||||
const shortcutLink = absolutifyLink(`/s/${shortcut.name}`);
|
const shortcutLink = absolutifyLink(`/s/${shortcut.name}`);
|
||||||
const favicon = getFaviconWithGoogleS2(shortcut.link);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
(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="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">
|
<div className="mt-4 sm:mt-8 w-12 h-12 flex justify-center items-center overflow-clip">
|
||||||
{favicon ? (
|
<LinkFavicon url={shortcut.link} />
|
||||||
<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} />
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<a
|
<a
|
||||||
className={classNames(
|
className={classNames(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user