Allow middle click to open new tab

This commit is contained in:
DrMint 2022-08-08 22:12:18 +02:00
parent c2c434eb80
commit 82c605086b
14 changed files with 341 additions and 308 deletions

View File

@ -24,5 +24,14 @@ module.exports = {
hreflang: "ja", hreflang: "ja",
}, },
], ],
exclude: ["/en/*", "/fr/*", "/ja/*", "/es/*", "/pt-br/*", "/404", "/500", "/dev/*"], exclude: [
"/en/*",
"/fr/*",
"/ja/*",
"/es/*",
"/pt-br/*",
"/404",
"/500",
"/dev/*",
],
}; };

View File

@ -1,10 +0,0 @@
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
<link rel="manifest" href="/site.webmanifest" />
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#9c6644" />
<meta name="apple-mobile-web-app-title" content="Accord's Library" />
<meta name="application-name" content="Accord's Library" />
<meta name="msapplication-TileColor" content="#feecd6" />
<meta name="msapplication-TileImage" content="/mstile-144x144.png" />
<meta name="theme-color" content="#feecd6" />

View File

@ -1,4 +1,4 @@
import Link from "next/link"; import { Link } from "components/Inputs/Link";
import { DatePickerFragment } from "graphql/generated"; import { DatePickerFragment } from "graphql/generated";
import { cIf, cJoin } from "helpers/className"; import { cIf, cJoin } from "helpers/className";
@ -20,17 +20,14 @@ export const ChroniclePreview = ({
title, title,
isActive, isActive,
}: Props): JSX.Element => ( }: Props): JSX.Element => (
<Link href={url}> <Link
<div href={url}
className={cJoin( className={cJoin(
`flex w-full cursor-pointer gap-4 rounded-2xl py-4 px-5 `flex w-full cursor-pointer gap-4 rounded-2xl py-4 px-5
text-left align-top outline outline-2 outline-offset-[-2px] outline-mid transition-all text-left align-top outline outline-2 outline-offset-[-2px] outline-mid transition-all
hover:bg-mid hover:shadow-inner-sm hover:shadow-shade hover:bg-mid hover:shadow-inner-sm hover:shadow-shade
hover:outline-[transparent] hover:active:shadow-inner hover:active:shadow-shade`, hover:outline-[transparent] hover:active:shadow-inner hover:active:shadow-shade`,
cIf( cIf(isActive, "bg-mid shadow-inner-sm shadow-shade outline-[transparent]")
isActive,
"bg-mid shadow-inner-sm shadow-shade outline-[transparent]"
)
)} )}
> >
<div className="text-right"> <div className="text-right">
@ -40,7 +37,6 @@ export const ChroniclePreview = ({
</p> </p>
</div> </div>
<p className="text-lg leading-tight">{title}</p> <p className="text-lg leading-tight">{title}</p>
</div>
</Link> </Link>
); );

View File

@ -1,5 +1,5 @@
import { useRouter } from "next/router";
import React, { MouseEventHandler } from "react"; import React, { MouseEventHandler } from "react";
import { Link } from "./Link";
import { Ico, Icon } from "components/Ico"; import { Ico, Icon } from "components/Ico";
import { cIf, cJoin } from "helpers/className"; import { cIf, cJoin } from "helpers/className";
import { ConditionalWrapper, Wrapper } from "helpers/component"; import { ConditionalWrapper, Wrapper } from "helpers/component";
@ -17,8 +17,7 @@ interface Props {
active?: boolean; active?: boolean;
icon?: Icon; icon?: Icon;
text?: string | null | undefined; text?: string | null | undefined;
locale?: string; alwaysNewTab?: boolean;
target?: "_blank";
onClick?: MouseEventHandler<HTMLDivElement>; onClick?: MouseEventHandler<HTMLDivElement>;
draggable?: boolean; draggable?: boolean;
badgeNumber?: number; badgeNumber?: number;
@ -35,30 +34,17 @@ export const Button = ({
className, className,
icon, icon,
text, text,
target,
href, href,
locale, alwaysNewTab = false,
badgeNumber, badgeNumber,
size = "normal", size = "normal",
}: Props): JSX.Element => { }: Props): JSX.Element => (
const router = useRouter();
return (
<ConditionalWrapper <ConditionalWrapper
isWrapping={isDefined(target)} isWrapping={isDefinedAndNotEmpty(href)}
wrapperProps={{ href: href }} wrapperProps={{ href: href ?? "", alwaysNewTab }}
wrapper={LinkWrapper} wrapper={LinkWrapper}
> >
<div <div className="relative">
className="relative"
onClick={() => {
if (!isDefined(target) && (isDefined(href) || isDefined(locale))) {
router.push(href ?? router.asPath, href, {
locale: locale,
});
}
}}
>
<div <div
draggable={draggable} draggable={draggable}
id={id} id={id}
@ -98,7 +84,6 @@ export const Button = ({
</div> </div>
</ConditionalWrapper> </ConditionalWrapper>
); );
};
/* /*
* *
@ -106,11 +91,16 @@ export const Button = ({
*/ */
interface LinkWrapperProps { interface LinkWrapperProps {
href?: string; href: string;
alwaysNewTab: boolean;
} }
const LinkWrapper = ({ children, href }: LinkWrapperProps & Wrapper) => ( const LinkWrapper = ({
<a href={href} target="_blank" rel="noreferrer"> children,
alwaysNewTab,
href,
}: LinkWrapperProps & Wrapper) => (
<Link href={href} alwaysNewTab={alwaysNewTab}>
{children} {children}
</a> </Link>
); );

View File

@ -0,0 +1,62 @@
import router from "next/router";
import { MouseEventHandler, useState } from "react";
import { isDefined } from "helpers/others";
interface Props {
href: string;
className?: string;
allowNewTab?: boolean;
alwaysNewTab?: boolean;
children: React.ReactNode;
onClick?: MouseEventHandler<HTMLDivElement>;
}
export const Link = ({
href,
allowNewTab = true,
alwaysNewTab = false,
children,
className,
onClick,
}: Props): JSX.Element => {
const [isValidClick, setIsValidClick] = useState(false);
return (
<div
className={className}
onMouseLeave={() => setIsValidClick(false)}
onContextMenu={(event) => event.preventDefault()}
onMouseDown={(event) => {
event.preventDefault();
setIsValidClick(true);
}}
onMouseUp={(event) => {
if (isDefined(onClick)) {
onClick(event);
} else if (isValidClick && href) {
if (event.button !== MouseButton.Right) {
if (alwaysNewTab) {
window.open(href, "_blank");
} else if (event.button === MouseButton.Left) {
if (href.startsWith("#")) {
router.replace(href);
} else {
router.push(href);
}
} else if (allowNewTab) {
window.open(href, "_blank");
}
}
}
}}
>
{children}
</div>
);
};
enum MouseButton {
Left = 0,
Middle = 1,
Right = 2,
}

View File

@ -4,6 +4,7 @@ import { Ico, Icon } from "components/Ico";
import { ToolTip } from "components/ToolTip"; import { ToolTip } from "components/ToolTip";
import { cJoin, cIf } from "helpers/className"; import { cJoin, cIf } from "helpers/className";
import { isDefinedAndNotEmpty } from "helpers/others"; import { isDefinedAndNotEmpty } from "helpers/others";
import { Link } from "components/Inputs/Link";
/* /*
* *
@ -51,17 +52,9 @@ export const NavOption = ({
className="text-left" className="text-left"
disabled={!reduced} disabled={!reduced}
> >
<div <Link
onClick={(event) => { href={url}
if (onClick) onClick(event); onClick={onClick}
if (url) {
if (url.startsWith("#")) {
router.replace(url);
} else {
router.push(url);
}
}
}}
className={cJoin( className={cJoin(
`relative grid w-full cursor-pointer auto-cols-fr grid-flow-col grid-cols-[auto] `relative grid w-full cursor-pointer auto-cols-fr grid-flow-col grid-cols-[auto]
justify-center gap-x-5 rounded-2xl p-4 transition-all hover:bg-mid hover:shadow-inner-sm justify-center gap-x-5 rounded-2xl p-4 transition-all hover:bg-mid hover:shadow-inner-sm
@ -84,7 +77,7 @@ export const NavOption = ({
)} )}
</div> </div>
)} )}
</div> </Link>
</ToolTip> </ToolTip>
); );
}; };

View File

@ -1,5 +1,4 @@
import Markdown from "markdown-to-jsx"; import Markdown from "markdown-to-jsx";
import Link from "next/link";
import { HorizontalLine } from "components/HorizontalLine"; import { HorizontalLine } from "components/HorizontalLine";
import { Button } from "components/Inputs/Button"; import { Button } from "components/Inputs/Button";
import { NavOption } from "components/PanelComponents/NavOption"; import { NavOption } from "components/PanelComponents/NavOption";
@ -11,6 +10,7 @@ import { useMediaDesktop } from "hooks/useMediaQuery";
import { Icon } from "components/Ico"; import { Icon } from "components/Ico";
import { cIf, cJoin } from "helpers/className"; import { cIf, cJoin } from "helpers/className";
import { isDefinedAndNotEmpty } from "helpers/others"; import { isDefinedAndNotEmpty } from "helpers/others";
import { Link } from "components/Inputs/Link";
/* /*
* *
@ -54,7 +54,7 @@ export const MainPanel = ({ langui }: Props): JSX.Element => {
<div> <div>
<div className="grid place-items-center"> <div className="grid place-items-center">
<Link href="/" passHref> <Link href="/" className="flex w-full justify-center">
<div <div
className={cJoin( className={cJoin(
`mb-4 aspect-square cursor-pointer bg-black transition-colors `mb-4 aspect-square cursor-pointer bg-black transition-colors

View File

@ -1,4 +1,4 @@
import { Dispatch, SetStateAction, useEffect } from "react"; import { useEffect } from "react";
import Hotkeys from "react-hot-keys"; import Hotkeys from "react-hot-keys";
import { useAppLayout } from "contexts/AppLayoutContext"; import { useAppLayout } from "contexts/AppLayoutContext";
import { cIf, cJoin } from "helpers/className"; import { cIf, cJoin } from "helpers/className";

View File

@ -1,9 +1,9 @@
import Link from "next/link";
import { useMemo } from "react"; import { useMemo } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { Chip } from "./Chip"; import { Chip } from "./Chip";
import { Ico, Icon } from "./Ico"; import { Ico, Icon } from "./Ico";
import { Img } from "./Img"; import { Img } from "./Img";
import { Link } from "./Inputs/Link";
import { useAppLayout } from "contexts/AppLayoutContext"; import { useAppLayout } from "contexts/AppLayoutContext";
import { import {
DatePickerFragment, DatePickerFragment,
@ -131,8 +131,8 @@ export const PreviewCard = ({
); );
return ( return (
<Link href={href} passHref> <Link
<div href={href}
className="group grid cursor-pointer items-end text-left transition-transform className="group grid cursor-pointer items-end text-left transition-transform
drop-shadow-shade-xl hover:scale-[1.02]" drop-shadow-shade-xl hover:scale-[1.02]"
> >
@ -293,7 +293,6 @@ export const PreviewCard = ({
{infoAppend} {infoAppend}
</div> </div>
</div>
</Link> </Link>
); );
}; };

View File

@ -1,6 +1,6 @@
import Link from "next/link";
import { Chip } from "./Chip"; import { Chip } from "./Chip";
import { Img } from "./Img"; import { Img } from "./Img";
import { Link } from "./Inputs/Link";
import { UploadImageFragment } from "graphql/generated"; import { UploadImageFragment } from "graphql/generated";
import { ImageQuality } from "helpers/img"; import { ImageQuality } from "helpers/img";
@ -32,8 +32,8 @@ export const PreviewLine = ({
bottomChips, bottomChips,
thumbnailAspectRatio, thumbnailAspectRatio,
}: Props): JSX.Element => ( }: Props): JSX.Element => (
<Link href={href} passHref> <Link
<div href={href}
className="flex h-36 w-full cursor-pointer flex-row place-items-center gap-4 overflow-hidden className="flex h-36 w-full cursor-pointer flex-row place-items-center gap-4 overflow-hidden
rounded-md bg-light pr-4 transition-transform drop-shadow-shade-xl hover:scale-[1.02]" rounded-md bg-light pr-4 transition-transform drop-shadow-shade-xl hover:scale-[1.02]"
> >
@ -59,9 +59,7 @@ export const PreviewLine = ({
<div className="my-1 flex flex-col"> <div className="my-1 flex flex-col">
{pre_title && <p className="mb-1 leading-none">{pre_title}</p>} {pre_title && <p className="mb-1 leading-none">{pre_title}</p>}
{title && ( {title && (
<p className="font-headers text-lg font-bold leading-none"> <p className="font-headers text-lg font-bold leading-none">{title}</p>
{title}
</p>
)} )}
{subtitle && <p className="leading-none">{subtitle}</p>} {subtitle && <p className="leading-none">{subtitle}</p>}
</div> </div>
@ -73,6 +71,5 @@ export const PreviewLine = ({
</div> </div>
)} )}
</div> </div>
</div>
</Link> </Link>
); );

View File

@ -1,5 +1,4 @@
import { useCallback } from "react"; import { useCallback } from "react";
import Link from "next/link";
import { Chip } from "components/Chip"; import { Chip } from "components/Chip";
import { ToolTip } from "components/ToolTip"; import { ToolTip } from "components/ToolTip";
import { AppStaticProps } from "graphql/getAppStaticProps"; import { AppStaticProps } from "graphql/getAppStaticProps";
@ -87,12 +86,10 @@ const DefinitionCard = ({
<p>{selectedTranslation?.definition}</p> <p>{selectedTranslation?.definition}</p>
{source?.url && source.name && ( {source?.url && source.name && (
<Link href={source.url}>
<div className="mt-3 flex place-items-center gap-2 mobile:flex-col mobile:text-center"> <div className="mt-3 flex place-items-center gap-2 mobile:flex-col mobile:text-center">
<p>{langui.source}: </p> <p>{langui.source}: </p>
<Button size="small" text={source.name} /> <Button href={source.url} size="small" text={source.name} />
</div> </div>
</Link>
)} )}
</> </>
); );

View File

@ -53,15 +53,15 @@ const CheckupContents = ({ contents, ...otherProps }: Props): JSX.Element => {
> >
<Button <Button
href={line.frontendUrl} href={line.frontendUrl}
target="_blank"
className="w-4 text-xs" className="w-4 text-xs"
text="F" text="F"
alwaysNewTab
/> />
<Button <Button
href={line.backendUrl} href={line.backendUrl}
target="_blank"
className="w-4 text-xs" className="w-4 text-xs"
text="B" text="B"
alwaysNewTab
/> />
<p>{line.subitems.join(" -> ")}</p> <p>{line.subitems.join(" -> ")}</p>
<p>{line.name}</p> <p>{line.name}</p>

View File

@ -58,15 +58,15 @@ const CheckupLibraryItems = ({
> >
<Button <Button
href={line.frontendUrl} href={line.frontendUrl}
target="_blank"
className="w-4 text-xs" className="w-4 text-xs"
text="F" text="F"
alwaysNewTab
/> />
<Button <Button
href={line.backendUrl} href={line.backendUrl}
target="_blank"
className="w-4 text-xs" className="w-4 text-xs"
text="B" text="B"
alwaysNewTab
/> />
<p>{line.subitems.join(" -> ")}</p> <p>{line.subitems.join(" -> ")}</p>
<p>{line.name}</p> <p>{line.name}</p>

View File

@ -230,8 +230,8 @@ const LibrarySlug = ({
<Fragment key={index}> <Fragment key={index}>
<Button <Button
href={url.url} href={url.url}
target={"_blank"}
text={prettyURL(url.url)} text={prettyURL(url.url)}
alwaysNewTab
/> />
</Fragment> </Fragment>
) )