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",
},
],
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 { cIf, cJoin } from "helpers/className";
@ -20,27 +20,23 @@ export const ChroniclePreview = ({
title,
isActive,
}: Props): JSX.Element => (
<Link href={url}>
<div
className={cJoin(
`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
hover:bg-mid hover:shadow-inner-sm hover:shadow-shade
hover:outline-[transparent] hover:active:shadow-inner hover:active:shadow-shade`,
cIf(
isActive,
"bg-mid shadow-inner-sm shadow-shade outline-[transparent]"
)
)}
>
<div className="text-right">
<p>{date.year}</p>
<p className="text-sm text-dark">
{prettyMonthDay(date.month, date.day)}
</p>
</div>
<p className="text-lg leading-tight">{title}</p>
<Link
href={url}
className={cJoin(
`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
hover:bg-mid hover:shadow-inner-sm hover:shadow-shade
hover:outline-[transparent] hover:active:shadow-inner hover:active:shadow-shade`,
cIf(isActive, "bg-mid shadow-inner-sm shadow-shade outline-[transparent]")
)}
>
<div className="text-right">
<p>{date.year}</p>
<p className="text-sm text-dark">
{prettyMonthDay(date.month, date.day)}
</p>
</div>
<p className="text-lg leading-tight">{title}</p>
</Link>
);

View File

@ -1,5 +1,5 @@
import { useRouter } from "next/router";
import React, { MouseEventHandler } from "react";
import { Link } from "./Link";
import { Ico, Icon } from "components/Ico";
import { cIf, cJoin } from "helpers/className";
import { ConditionalWrapper, Wrapper } from "helpers/component";
@ -17,8 +17,7 @@ interface Props {
active?: boolean;
icon?: Icon;
text?: string | null | undefined;
locale?: string;
target?: "_blank";
alwaysNewTab?: boolean;
onClick?: MouseEventHandler<HTMLDivElement>;
draggable?: boolean;
badgeNumber?: number;
@ -35,70 +34,56 @@ export const Button = ({
className,
icon,
text,
target,
href,
locale,
alwaysNewTab = false,
badgeNumber,
size = "normal",
}: Props): JSX.Element => {
const router = useRouter();
return (
<ConditionalWrapper
isWrapping={isDefined(target)}
wrapperProps={{ href: href }}
wrapper={LinkWrapper}
>
}: Props): JSX.Element => (
<ConditionalWrapper
isWrapping={isDefinedAndNotEmpty(href)}
wrapperProps={{ href: href ?? "", alwaysNewTab }}
wrapper={LinkWrapper}
>
<div className="relative">
<div
className="relative"
onClick={() => {
if (!isDefined(target) && (isDefined(href) || isDefined(locale))) {
router.push(href ?? router.asPath, href, {
locale: locale,
});
}
}}
>
<div
draggable={draggable}
id={id}
onClick={onClick}
className={cJoin(
`group grid cursor-pointer select-none grid-flow-col place-content-center
draggable={draggable}
id={id}
onClick={onClick}
className={cJoin(
`group grid cursor-pointer select-none grid-flow-col place-content-center
place-items-center gap-2 rounded-full border-[1px] border-dark py-3 px-4
leading-none text-dark transition-all`,
cIf(
active,
"!border-black bg-black !text-light drop-shadow-black-lg",
`hover:bg-dark hover:text-light hover:drop-shadow-shade-lg active:hover:!border-black
cIf(
active,
"!border-black bg-black !text-light drop-shadow-black-lg",
`hover:bg-dark hover:text-light hover:drop-shadow-shade-lg active:hover:!border-black
active:hover:bg-black active:hover:!text-light active:hover:drop-shadow-black-lg`
),
cIf(size === "small", "px-3 py-1 text-xs"),
className
)}
>
{isDefined(badgeNumber) && (
<div
className={cJoin(
`absolute -top-3 -right-2 grid h-8 w-8 place-items-center
),
cIf(size === "small", "px-3 py-1 text-xs"),
className
)}
>
{isDefined(badgeNumber) && (
<div
className={cJoin(
`absolute -top-3 -right-2 grid h-8 w-8 place-items-center
rounded-full bg-dark font-bold text-light transition-opacity group-hover:opacity-0`,
cIf(size === "small", "-top-2 -right-2 h-5 w-5")
)}
>
<p className="-translate-y-[0.05em]">{badgeNumber}</p>
</div>
)}
{isDefinedAndNotEmpty(icon) && (
<Ico className="[font-size:150%] [line-height:0.66]" icon={icon} />
)}
{isDefinedAndNotEmpty(text) && (
<p className="-translate-y-[0.05em] text-center">{text}</p>
)}
</div>
cIf(size === "small", "-top-2 -right-2 h-5 w-5")
)}
>
<p className="-translate-y-[0.05em]">{badgeNumber}</p>
</div>
)}
{isDefinedAndNotEmpty(icon) && (
<Ico className="[font-size:150%] [line-height:0.66]" icon={icon} />
)}
{isDefinedAndNotEmpty(text) && (
<p className="-translate-y-[0.05em] text-center">{text}</p>
)}
</div>
</ConditionalWrapper>
);
};
</div>
</ConditionalWrapper>
);
/*
*
@ -106,11 +91,16 @@ export const Button = ({
*/
interface LinkWrapperProps {
href?: string;
href: string;
alwaysNewTab: boolean;
}
const LinkWrapper = ({ children, href }: LinkWrapperProps & Wrapper) => (
<a href={href} target="_blank" rel="noreferrer">
const LinkWrapper = ({
children,
alwaysNewTab,
href,
}: LinkWrapperProps & Wrapper) => (
<Link href={href} alwaysNewTab={alwaysNewTab}>
{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 { cJoin, cIf } from "helpers/className";
import { isDefinedAndNotEmpty } from "helpers/others";
import { Link } from "components/Inputs/Link";
/*
*
@ -51,17 +52,9 @@ export const NavOption = ({
className="text-left"
disabled={!reduced}
>
<div
onClick={(event) => {
if (onClick) onClick(event);
if (url) {
if (url.startsWith("#")) {
router.replace(url);
} else {
router.push(url);
}
}
}}
<Link
href={url}
onClick={onClick}
className={cJoin(
`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
@ -84,7 +77,7 @@ export const NavOption = ({
)}
</div>
)}
</div>
</Link>
</ToolTip>
);
};

View File

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

View File

@ -1,9 +1,9 @@
import Link from "next/link";
import { useMemo } from "react";
import { useRouter } from "next/router";
import { Chip } from "./Chip";
import { Ico, Icon } from "./Ico";
import { Img } from "./Img";
import { Link } from "./Inputs/Link";
import { useAppLayout } from "contexts/AppLayoutContext";
import {
DatePickerFragment,
@ -131,168 +131,167 @@ export const PreviewCard = ({
);
return (
<Link href={href} passHref>
<div
className="group grid cursor-pointer items-end text-left transition-transform
drop-shadow-shade-xl hover:scale-[1.02]"
>
{stackNumber > 0 && (
<>
<div
className={cJoin(
`absolute inset-0 scale-[.85] overflow-hidden bg-light brightness-[0.8] sepia-[0.5]
transition-[top_transform] group-hover:-top-9`,
cIf(thumbnailRounded, "rounded-md")
)}
>
{thumbnail && (
<Img
className="opacity-30"
image={thumbnail}
quality={ImageQuality.Medium}
/>
)}
</div>
<div
className={cJoin(
`absolute inset-0 overflow-hidden bg-light brightness-[0.9] sepia-[0.2]
transition-[top_transform] group-hover:-top-4 group-hover:scale-[.94]`,
cIf(thumbnailRounded, "rounded-md")
)}
>
{thumbnail && (
<Img
className="opacity-70"
image={thumbnail}
quality={ImageQuality.Medium}
/>
)}
</div>
</>
)}
{thumbnail ? (
<Link
href={href}
className="group grid cursor-pointer items-end text-left transition-transform
drop-shadow-shade-xl hover:scale-[1.02]"
>
{stackNumber > 0 && (
<>
<div
className="relative"
style={{
aspectRatio: thumbnailForceAspectRatio
? thumbnailAspectRatio
: "unset",
}}
>
<Img
className={cJoin(
cIf(
thumbnailRounded,
cIf(
keepInfoVisible,
"rounded-t-md",
"rounded-md notHoverable:rounded-b-none"
)
),
cIf(thumbnailForceAspectRatio, "h-full w-full object-cover")
)}
image={thumbnail}
quality={ImageQuality.Medium}
/>
{stackNumber > 0 && (
<div
className="absolute right-2 top-2 rounded-full bg-black
bg-opacity-60 px-2 text-light"
>
{stackNumber}
</div>
)}
{hoverlay && hoverlay.__typename === "Video" && (
<>
<div
className="absolute inset-0 grid place-content-center bg-shade bg-opacity-0
text-light transition-colors drop-shadow-shade-lg group-hover:bg-opacity-50"
>
<Ico
icon={Icon.PlayCircleOutline}
className="!text-6xl opacity-0 transition-opacity group-hover:opacity-100"
/>
</div>
<div
className="absolute right-2 bottom-2 rounded-full bg-black bg-opacity-60 px-2
text-light"
>
{prettyDuration(hoverlay.duration)}
</div>
</>
)}
</div>
) : (
<div
style={{ aspectRatio: thumbnailAspectRatio }}
className={cJoin(
"relative w-full bg-light",
cIf(
keepInfoVisible,
"rounded-t-md",
"rounded-md notHoverable:rounded-b-none"
)
`absolute inset-0 scale-[.85] overflow-hidden bg-light brightness-[0.8] sepia-[0.5]
transition-[top_transform] group-hover:-top-9`,
cIf(thumbnailRounded, "rounded-md")
)}
>
{stackNumber > 0 && (
<div
className="absolute right-2 top-2 rounded-full bg-black
bg-opacity-60 px-2 text-light"
>
{stackNumber}
</div>
{thumbnail && (
<Img
className="opacity-30"
image={thumbnail}
quality={ImageQuality.Medium}
/>
)}
</div>
)}
<div
className={cJoin(
`absolute inset-0 overflow-hidden bg-light brightness-[0.9] sepia-[0.2]
transition-[top_transform] group-hover:-top-4 group-hover:scale-[.94]`,
cIf(thumbnailRounded, "rounded-md")
)}
>
{thumbnail && (
<Img
className="opacity-70"
image={thumbnail}
quality={ImageQuality.Medium}
/>
)}
</div>
</>
)}
{thumbnail ? (
<div
className="relative"
style={{
aspectRatio: thumbnailForceAspectRatio
? thumbnailAspectRatio
: "unset",
}}
>
<Img
className={cJoin(
cIf(
thumbnailRounded,
cIf(
keepInfoVisible,
"rounded-t-md",
"rounded-md notHoverable:rounded-b-none"
)
),
cIf(thumbnailForceAspectRatio, "h-full w-full object-cover")
)}
image={thumbnail}
quality={ImageQuality.Medium}
/>
{stackNumber > 0 && (
<div
className="absolute right-2 top-2 rounded-full bg-black
bg-opacity-60 px-2 text-light"
>
{stackNumber}
</div>
)}
{hoverlay && hoverlay.__typename === "Video" && (
<>
<div
className="absolute inset-0 grid place-content-center bg-shade bg-opacity-0
text-light transition-colors drop-shadow-shade-lg group-hover:bg-opacity-50"
>
<Ico
icon={Icon.PlayCircleOutline}
className="!text-6xl opacity-0 transition-opacity group-hover:opacity-100"
/>
</div>
<div
className="absolute right-2 bottom-2 rounded-full bg-black bg-opacity-60 px-2
text-light"
>
{prettyDuration(hoverlay.duration)}
</div>
</>
)}
</div>
) : (
<div
style={{ aspectRatio: thumbnailAspectRatio }}
className={cJoin(
"z-20 grid gap-2 p-4 transition-opacity linearbg-obi",
"relative w-full bg-light",
cIf(
!keepInfoVisible && isHoverable,
`-inset-x-0.5 bottom-2 opacity-0 [border-radius:10%_10%_10%_10%_/_1%_1%_3%_3%]
group-hover:opacity-100 hoverable:absolute hoverable:drop-shadow-shade-lg
notHoverable:rounded-b-md notHoverable:opacity-100`,
"[border-radius:0%_0%_10%_10%_/_0%_0%_3%_3%]"
keepInfoVisible,
"rounded-t-md",
"rounded-md notHoverable:rounded-b-none"
)
)}
>
{metadata?.position === "Top" && metadataJSX}
{topChips && topChips.length > 0 && (
<div className="grid grid-flow-col place-content-start gap-1 overflow-hidden">
{topChips.map((text, index) => (
<Chip key={index} text={text} />
))}
</div>
)}
<div className="my-1">
{pre_title && (
<p className="mb-1 break-words leading-none">{pre_title}</p>
)}
{title && (
<p className="break-words font-headers text-lg font-bold leading-none">
{title}
</p>
)}
{subtitle && <p className="break-words leading-none">{subtitle}</p>}
</div>
{description && <p>{description}</p>}
{bottomChips && bottomChips.length > 0 && (
{stackNumber > 0 && (
<div
className="grid grid-flow-col place-content-start gap-1 overflow-x-scroll
[scrollbar-width:none] webkit-scrollbar:h-0"
className="absolute right-2 top-2 rounded-full bg-black
bg-opacity-60 px-2 text-light"
>
{bottomChips.map((text, index) => (
<Chip key={index} className="text-sm" text={text} />
))}
{stackNumber}
</div>
)}
{metadata?.position === "Bottom" && metadataJSX}
{infoAppend}
</div>
)}
<div
className={cJoin(
"z-20 grid gap-2 p-4 transition-opacity linearbg-obi",
cIf(
!keepInfoVisible && isHoverable,
`-inset-x-0.5 bottom-2 opacity-0 [border-radius:10%_10%_10%_10%_/_1%_1%_3%_3%]
group-hover:opacity-100 hoverable:absolute hoverable:drop-shadow-shade-lg
notHoverable:rounded-b-md notHoverable:opacity-100`,
"[border-radius:0%_0%_10%_10%_/_0%_0%_3%_3%]"
)
)}
>
{metadata?.position === "Top" && metadataJSX}
{topChips && topChips.length > 0 && (
<div className="grid grid-flow-col place-content-start gap-1 overflow-hidden">
{topChips.map((text, index) => (
<Chip key={index} text={text} />
))}
</div>
)}
<div className="my-1">
{pre_title && (
<p className="mb-1 break-words leading-none">{pre_title}</p>
)}
{title && (
<p className="break-words font-headers text-lg font-bold leading-none">
{title}
</p>
)}
{subtitle && <p className="break-words leading-none">{subtitle}</p>}
</div>
{description && <p>{description}</p>}
{bottomChips && bottomChips.length > 0 && (
<div
className="grid grid-flow-col place-content-start gap-1 overflow-x-scroll
[scrollbar-width:none] webkit-scrollbar:h-0"
>
{bottomChips.map((text, index) => (
<Chip key={index} className="text-sm" text={text} />
))}
</div>
)}
{metadata?.position === "Bottom" && metadataJSX}
{infoAppend}
</div>
</Link>
);

View File

@ -1,6 +1,6 @@
import Link from "next/link";
import { Chip } from "./Chip";
import { Img } from "./Img";
import { Link } from "./Inputs/Link";
import { UploadImageFragment } from "graphql/generated";
import { ImageQuality } from "helpers/img";
@ -32,47 +32,44 @@ export const PreviewLine = ({
bottomChips,
thumbnailAspectRatio,
}: Props): JSX.Element => (
<Link href={href} passHref>
<div
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]"
>
{thumbnail ? (
<div className="aspect-[3/2] h-full">
<Img
className="h-full object-cover"
image={thumbnail}
quality={ImageQuality.Medium}
/>
</div>
) : (
<div style={{ aspectRatio: thumbnailAspectRatio }}></div>
)}
<div className="grid gap-2">
{topChips && topChips.length > 0 && (
<div className="grid grid-flow-col place-content-start gap-1 overflow-hidden">
{topChips.map((text, index) => (
<Chip key={index} text={text} />
))}
</div>
)}
<div className="my-1 flex flex-col">
{pre_title && <p className="mb-1 leading-none">{pre_title}</p>}
{title && (
<p className="font-headers text-lg font-bold leading-none">
{title}
</p>
)}
{subtitle && <p className="leading-none">{subtitle}</p>}
</div>
{bottomChips && bottomChips.length > 0 && (
<div className="grid grid-flow-col place-content-start gap-1 overflow-hidden">
{bottomChips.map((text, index) => (
<Chip key={index} className="text-sm" text={text} />
))}
</div>
)}
<Link
href={href}
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]"
>
{thumbnail ? (
<div className="aspect-[3/2] h-full">
<Img
className="h-full object-cover"
image={thumbnail}
quality={ImageQuality.Medium}
/>
</div>
) : (
<div style={{ aspectRatio: thumbnailAspectRatio }}></div>
)}
<div className="grid gap-2">
{topChips && topChips.length > 0 && (
<div className="grid grid-flow-col place-content-start gap-1 overflow-hidden">
{topChips.map((text, index) => (
<Chip key={index} text={text} />
))}
</div>
)}
<div className="my-1 flex flex-col">
{pre_title && <p className="mb-1 leading-none">{pre_title}</p>}
{title && (
<p className="font-headers text-lg font-bold leading-none">{title}</p>
)}
{subtitle && <p className="leading-none">{subtitle}</p>}
</div>
{bottomChips && bottomChips.length > 0 && (
<div className="grid grid-flow-col place-content-start gap-1 overflow-hidden">
{bottomChips.map((text, index) => (
<Chip key={index} className="text-sm" text={text} />
))}
</div>
)}
</div>
</Link>
);

View File

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

View File

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

View File

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

View File

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