refactor: use tabs instead of spaces

This commit is contained in:
Sonny
2024-10-07 01:33:59 +02:00
parent f425decf2c
commit eea9732100
197 changed files with 5206 additions and 5209 deletions

View File

@@ -11,27 +11,27 @@ import { appendLinkId } from '~/lib/navigation';
import { Link } from '~/types/app';
export default function LinkControls({ link }: { link: Link }) {
const theme = useTheme();
const { t } = useTranslation('common');
const theme = useTheme();
const { t } = useTranslation('common');
return (
<Dropdown
label={<BsThreeDotsVertical css={{ color: theme.colors.grey }} />}
css={{ backgroundColor: theme.colors.secondary }}
svgSize={18}
>
<FavoriteDropdownItem link={link} />
<DropdownItemLink
href={appendLinkId(route('link.edit-form').url, link.id)}
>
<GoPencil /> {t('link.edit')}
</DropdownItemLink>
<DropdownItemLink
href={appendLinkId(route('link.delete-form').url, link.id)}
danger
>
<IoTrashOutline /> {t('link.delete')}
</DropdownItemLink>
</Dropdown>
);
return (
<Dropdown
label={<BsThreeDotsVertical css={{ color: theme.colors.grey }} />}
css={{ backgroundColor: theme.colors.secondary }}
svgSize={18}
>
<FavoriteDropdownItem link={link} />
<DropdownItemLink
href={appendLinkId(route('link.edit-form').url, link.id)}
>
<GoPencil /> {t('link.edit')}
</DropdownItemLink>
<DropdownItemLink
href={appendLinkId(route('link.delete-form').url, link.id)}
danger
>
<IoTrashOutline /> {t('link.delete')}
</DropdownItemLink>
</Dropdown>
);
}

View File

@@ -7,76 +7,76 @@ import { rotate } from '~/styles/keyframes';
const IMG_LOAD_TIMEOUT = 7_500;
interface LinkFaviconProps {
url: string;
size?: number;
url: string;
size?: number;
}
const Favicon = styled.div({
position: 'relative',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
position: 'relative',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
});
const FaviconLoader = styled.div(({ theme }) => ({
position: 'absolute',
top: 0,
left: 0,
color: theme.colors.font,
backgroundColor: theme.colors.secondary,
position: 'absolute',
top: 0,
left: 0,
color: theme.colors.font,
backgroundColor: theme.colors.secondary,
'& > *': {
animation: `${rotate} 1s both reverse infinite linear`,
},
'& > *': {
animation: `${rotate} 1s both reverse infinite linear`,
},
}));
// The Favicon API should always return an image, so it's not really useful to keep the loader nor placeholder icon,
// but for slow connections and other random stuff, I'll keep this
export default function LinkFavicon({ url, size = 32 }: LinkFaviconProps) {
const imgRef = useRef<HTMLImageElement>(null);
const imgRef = useRef<HTMLImageElement>(null);
const [isFailed, setFailed] = useState<boolean>(false);
const [isLoading, setLoading] = useState<boolean>(true);
const [isFailed, setFailed] = useState<boolean>(false);
const [isLoading, setLoading] = useState<boolean>(true);
const setFallbackFavicon = () => setFailed(true);
const handleStopLoading = () => setLoading(false);
const setFallbackFavicon = () => setFailed(true);
const handleStopLoading = () => setLoading(false);
const handleErrorLoading = () => {
setFallbackFavicon();
handleStopLoading();
};
const handleErrorLoading = () => {
setFallbackFavicon();
handleStopLoading();
};
useEffect(() => {
// Ugly hack, onLoad cb not triggered on first load when SSR
if (imgRef.current?.complete) {
handleStopLoading();
return;
}
const id = setTimeout(() => handleErrorLoading(), IMG_LOAD_TIMEOUT);
return () => clearTimeout(id);
}, [isLoading]);
useEffect(() => {
// Ugly hack, onLoad cb not triggered on first load when SSR
if (imgRef.current?.complete) {
handleStopLoading();
return;
}
const id = setTimeout(() => handleErrorLoading(), IMG_LOAD_TIMEOUT);
return () => clearTimeout(id);
}, [isLoading]);
return (
<Favicon>
{!isFailed ? (
<img
src={`/favicon?url=${url}`}
onError={handleErrorLoading}
onLoad={handleStopLoading}
height={size}
width={size}
alt="icon"
ref={imgRef}
decoding="async"
/>
) : (
<TfiWorld size={size} />
)}
{isLoading && (
<FaviconLoader style={{ height: `${size}px`, width: `${size}px` }}>
<TbLoader3 size={size} />
</FaviconLoader>
)}
</Favicon>
);
return (
<Favicon>
{!isFailed ? (
<img
src={`/favicon?url=${url}`}
onError={handleErrorLoading}
onLoad={handleStopLoading}
height={size}
width={size}
alt="icon"
ref={imgRef}
decoding="async"
/>
) : (
<TfiWorld size={size} />
)}
{isLoading && (
<FaviconLoader style={{ height: `${size}px`, width: `${size}px` }}>
<TbLoader3 size={size} />
</FaviconLoader>
)}
</Favicon>
);
}

View File

@@ -6,121 +6,121 @@ import LinkFavicon from '~/components/dashboard/link/link_favicon';
import { Link } from '~/types/app';
const LinkWrapper = styled.li(({ theme }) => ({
userSelect: 'none',
cursor: 'pointer',
height: 'fit-content',
width: '100%',
color: theme.colors.primary,
backgroundColor: theme.colors.secondary,
padding: '0.75em 1em',
borderRadius: theme.border.radius,
userSelect: 'none',
cursor: 'pointer',
height: 'fit-content',
width: '100%',
color: theme.colors.primary,
backgroundColor: theme.colors.secondary,
padding: '0.75em 1em',
borderRadius: theme.border.radius,
'&:hover': {
outlineWidth: '1px',
outlineStyle: 'solid',
},
'&:hover': {
outlineWidth: '1px',
outlineStyle: 'solid',
},
}));
const LinkHeader = styled.div(({ theme }) => ({
display: 'flex',
gap: '1em',
alignItems: 'center',
display: 'flex',
gap: '1em',
alignItems: 'center',
'& > a': {
height: '100%',
maxWidth: 'calc(100% - 75px)', // TODO: fix this, it is ugly af :(
textDecoration: 'none',
display: 'flex',
flex: 1,
flexDirection: 'column',
transition: theme.transition.delay,
'& > a': {
height: '100%',
maxWidth: 'calc(100% - 75px)', // TODO: fix this, it is ugly af :(
textDecoration: 'none',
display: 'flex',
flex: 1,
flexDirection: 'column',
transition: theme.transition.delay,
'&, &:hover': {
border: 0,
},
},
'&, &:hover': {
border: 0,
},
},
}));
const LinkName = styled.div({
width: '100%',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
overflow: 'hidden',
width: '100%',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
overflow: 'hidden',
});
const LinkDescription = styled.div(({ theme }) => ({
marginTop: '0.5em',
color: theme.colors.font,
fontSize: '0.8em',
wordWrap: 'break-word',
marginTop: '0.5em',
color: theme.colors.font,
fontSize: '0.8em',
wordWrap: 'break-word',
}));
const LinkUrl = styled.span(({ theme }) => ({
width: '100%',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
overflow: 'hidden',
color: theme.colors.grey,
fontSize: '0.8em',
width: '100%',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
overflow: 'hidden',
color: theme.colors.grey,
fontSize: '0.8em',
}));
const StarIcon = styled(AiFillStar)(({ theme }) => ({
color: theme.colors.yellow,
color: theme.colors.yellow,
}));
const LinkUrlPathname = styled.span({
opacity: 0,
opacity: 0,
});
export default function LinkItem({
link,
showUserControls = false,
link,
showUserControls = false,
}: {
link: Link;
showUserControls: boolean;
link: Link;
showUserControls: boolean;
}) {
const { id, name, url, description, favorite } = link;
return (
<LinkWrapper key={id} title={name}>
<LinkHeader>
<LinkFavicon url={url} />
<ExternalLink href={url} className="reset">
<LinkName>
{name} {showUserControls && favorite && <StarIcon />}
</LinkName>
<LinkItemURL url={url} />
</ExternalLink>
{showUserControls && <LinkControls link={link} />}
</LinkHeader>
{description && <LinkDescription>{description}</LinkDescription>}
</LinkWrapper>
);
const { id, name, url, description, favorite } = link;
return (
<LinkWrapper key={id} title={name}>
<LinkHeader>
<LinkFavicon url={url} />
<ExternalLink href={url} className="reset">
<LinkName>
{name} {showUserControls && favorite && <StarIcon />}
</LinkName>
<LinkItemURL url={url} />
</ExternalLink>
{showUserControls && <LinkControls link={link} />}
</LinkHeader>
{description && <LinkDescription>{description}</LinkDescription>}
</LinkWrapper>
);
}
function LinkItemURL({ url }: { url: Link['url'] }) {
try {
const { origin, pathname, search } = new URL(url);
let text = '';
try {
const { origin, pathname, search } = new URL(url);
let text = '';
if (pathname !== '/') {
text += pathname;
}
if (pathname !== '/') {
text += pathname;
}
if (search !== '') {
if (text === '') {
text += '/';
}
text += search;
}
if (search !== '') {
if (text === '') {
text += '/';
}
text += search;
}
return (
<LinkUrl>
{origin}
<LinkUrlPathname>{text}</LinkUrlPathname>
</LinkUrl>
);
} catch (error) {
console.error('error', error);
return <LinkUrl>{url}</LinkUrl>;
}
return (
<LinkUrl>
{origin}
<LinkUrlPathname>{text}</LinkUrlPathname>
</LinkUrl>
);
} catch (error) {
console.error('error', error);
return <LinkUrl>{url}</LinkUrl>;
}
}

View File

@@ -5,34 +5,34 @@ import { sortByCreationDate } from '~/lib/array';
import { Link } from '~/types/app';
const LinkListStyle = styled.ul({
height: '100%',
width: '100%',
minWidth: 0,
display: 'flex',
flex: 1,
gap: '0.5em',
padding: '3px',
flexDirection: 'column',
overflowX: 'hidden',
overflowY: 'scroll',
height: '100%',
width: '100%',
minWidth: 0,
display: 'flex',
flex: 1,
gap: '0.5em',
padding: '3px',
flexDirection: 'column',
overflowX: 'hidden',
overflowY: 'scroll',
});
export default function LinkList({
links,
showControls = true,
links,
showControls = true,
}: {
links: Link[];
showControls?: boolean;
links: Link[];
showControls?: boolean;
}) {
if (links.length === 0) {
return <NoLink />;
}
if (links.length === 0) {
return <NoLink />;
}
return (
<LinkListStyle>
{sortByCreationDate(links).map((link) => (
<LinkItem link={link} key={link.id} showUserControls={showControls} />
))}
</LinkListStyle>
);
return (
<LinkListStyle>
{sortByCreationDate(links).map((link) => (
<LinkItem link={link} key={link.id} showUserControls={showControls} />
))}
</LinkListStyle>
);
}

View File

@@ -7,60 +7,60 @@ import { appendCollectionId } from '~/lib/navigation';
import { fadeIn } from '~/styles/keyframes';
const NoCollectionStyle = styled.div({
minWidth: 0,
display: 'flex',
flex: 1,
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'column',
animation: `${fadeIn} 0.3s both`,
minWidth: 0,
display: 'flex',
flex: 1,
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'column',
animation: `${fadeIn} 0.3s both`,
});
const Text = styled.p({
minWidth: 0,
width: '100%',
textAlign: 'center',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
overflow: 'hidden',
minWidth: 0,
width: '100%',
textAlign: 'center',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
overflow: 'hidden',
});
export function NoCollection() {
const { t } = useTranslation('home');
return (
<NoCollectionStyle>
<Text>{t('select-collection')}</Text>
<Link href={route('collection.create-form').url}>
{t('or-create-one')}
</Link>
</NoCollectionStyle>
);
const { t } = useTranslation('home');
return (
<NoCollectionStyle>
<Text>{t('select-collection')}</Text>
<Link href={route('collection.create-form').url}>
{t('or-create-one')}
</Link>
</NoCollectionStyle>
);
}
export function NoLink() {
const { t } = useTranslation('common');
const { activeCollection } = useActiveCollection();
return (
<NoCollectionStyle>
<Text
dangerouslySetInnerHTML={{
__html: t(
'home:no-link',
{ name: activeCollection?.name ?? '' } as any,
{
interpolation: { escapeValue: false },
}
),
}}
/>
<Link
href={appendCollectionId(
route('link.create-form').url,
activeCollection?.id
)}
>
{t('link.create')}
</Link>
</NoCollectionStyle>
);
const { t } = useTranslation('common');
const { activeCollection } = useActiveCollection();
return (
<NoCollectionStyle>
<Text
dangerouslySetInnerHTML={{
__html: t(
'home:no-link',
{ name: activeCollection?.name ?? '' } as any,
{
interpolation: { escapeValue: false },
}
),
}}
/>
<Link
href={appendCollectionId(
route('link.create-form').url,
activeCollection?.id
)}
>
{t('link.create')}
</Link>
</NoCollectionStyle>
);
}