improvements to the pwa
This commit is contained in:
parent
834d25a99e
commit
d4f59d7f32
|
@ -9,6 +9,7 @@ import useAccountStore from "@/store/account";
|
|||
import EditCollectionModal from "./ModalContent/EditCollectionModal";
|
||||
import EditCollectionSharingModal from "./ModalContent/EditCollectionSharingModal";
|
||||
import DeleteCollectionModal from "./ModalContent/DeleteCollectionModal";
|
||||
import { dropdownTriggerer } from "@/lib/client/utils";
|
||||
|
||||
type Props = {
|
||||
collection: CollectionIncludingMembersAndLinkCount;
|
||||
|
@ -70,6 +71,7 @@ export default function CollectionCard({ collection, className }: Props) {
|
|||
<div
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
onMouseDown={dropdownTriggerer}
|
||||
className="btn btn-ghost btn-sm btn-square text-neutral"
|
||||
>
|
||||
<i className="bi-three-dots text-xl" title="More"></i>
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { dropdownTriggerer } from "@/lib/client/utils";
|
||||
import React from "react";
|
||||
|
||||
type Props = {
|
||||
|
@ -20,6 +21,7 @@ export default function FilterSearchDropdown({
|
|||
<div
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
onMouseDown={dropdownTriggerer}
|
||||
className="btn btn-sm btn-square btn-ghost"
|
||||
>
|
||||
<i className="bi-funnel text-neutral text-2xl"></i>
|
||||
|
|
|
@ -126,17 +126,12 @@ export default function LinkGrid({ link, count, className }: Props) {
|
|||
{unescapeString(link.name || link.description) || link.url}
|
||||
</p>
|
||||
|
||||
<Link
|
||||
href={link.url || ""}
|
||||
target="_blank"
|
||||
title={link.url || ""}
|
||||
className="w-fit"
|
||||
>
|
||||
<div title={link.url || ""} className="w-fit">
|
||||
<div className="flex gap-1 item-center select-none text-neutral mt-1">
|
||||
<i className="bi-link-45deg text-lg mt-[0.15rem] leading-none"></i>
|
||||
<p className="text-sm truncate">{shortendURL}</p>
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr className="divider mt-2 mb-1 last:hidden border-t border-neutral-content h-[1px]" />
|
||||
|
|
|
@ -10,6 +10,7 @@ import PreservedFormatsModal from "@/components/ModalContent/PreservedFormatsMod
|
|||
import useLinkStore from "@/store/links";
|
||||
import { toast } from "react-hot-toast";
|
||||
import useAccountStore from "@/store/account";
|
||||
import { dropdownTriggerer } from "@/lib/client/utils";
|
||||
|
||||
type Props = {
|
||||
link: LinkIncludingShortenedCollectionAndTags;
|
||||
|
@ -71,6 +72,7 @@ export default function LinkActions({
|
|||
<div
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
onMouseDown={dropdownTriggerer}
|
||||
className="btn btn-ghost btn-sm btn-square text-neutral"
|
||||
>
|
||||
<i title="More" className="bi-three-dots text-xl" />
|
||||
|
|
|
@ -9,6 +9,7 @@ import usePermissions from "@/hooks/usePermissions";
|
|||
import ProfilePhoto from "../ProfilePhoto";
|
||||
import addMemberToCollection from "@/lib/client/addMemberToCollection";
|
||||
import Modal from "../Modal";
|
||||
import { dropdownTriggerer } from "@/lib/client/utils";
|
||||
|
||||
type Props = {
|
||||
onClose: Function;
|
||||
|
@ -264,6 +265,7 @@ export default function EditCollectionSharingModal({
|
|||
<div
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
onMouseDown={dropdownTriggerer}
|
||||
className="btn btn-sm btn-primary font-normal"
|
||||
>
|
||||
{roleLabel}
|
||||
|
|
|
@ -13,6 +13,8 @@ import NewLinkModal from "./ModalContent/NewLinkModal";
|
|||
import NewCollectionModal from "./ModalContent/NewCollectionModal";
|
||||
import Link from "next/link";
|
||||
import UploadFileModal from "./ModalContent/UploadFileModal";
|
||||
import { dropdownTriggerer } from "@/lib/client/utils";
|
||||
import NewButtonMobile from "./NewButtonMobile";
|
||||
|
||||
export default function Navbar() {
|
||||
const { settings, updateSettings } = useLocalSettingsStore();
|
||||
|
@ -35,14 +37,12 @@ export default function Navbar() {
|
|||
|
||||
useEffect(() => {
|
||||
setSidebar(false);
|
||||
}, [width]);
|
||||
|
||||
useEffect(() => {
|
||||
setSidebar(false);
|
||||
}, [router]);
|
||||
document.body.style.overflow = "auto";
|
||||
}, [width, router]);
|
||||
|
||||
const toggleSidebar = () => {
|
||||
setSidebar(!sidebar);
|
||||
setSidebar(false);
|
||||
document.body.style.overflow = "auto";
|
||||
};
|
||||
|
||||
const [newLinkModal, setNewLinkModal] = useState(false);
|
||||
|
@ -52,7 +52,10 @@ export default function Navbar() {
|
|||
return (
|
||||
<div className="flex justify-between gap-2 items-center pl-3 pr-4 py-2 border-solid border-b-neutral-content border-b">
|
||||
<div
|
||||
onClick={toggleSidebar}
|
||||
onClick={() => {
|
||||
setSidebar(true);
|
||||
document.body.style.overflow = "hidden";
|
||||
}}
|
||||
className="text-neutral btn btn-square btn-sm btn-ghost lg:hidden"
|
||||
>
|
||||
<i className="bi-list text-2xl leading-none"></i>
|
||||
|
@ -61,11 +64,12 @@ export default function Navbar() {
|
|||
<div className="flex items-center gap-2">
|
||||
<ToggleDarkMode className="hidden sm:inline-grid" />
|
||||
|
||||
<div className="dropdown dropdown-end">
|
||||
<div className="dropdown dropdown-end sm:inline-block hidden">
|
||||
<div className="tooltip tooltip-bottom" data-tip="Create New...">
|
||||
<div
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
onMouseDown={dropdownTriggerer}
|
||||
className="flex min-w-[3.4rem] items-center btn btn-accent dark:border-violet-400 text-white btn-sm max-h-[2rem] px-2 relative"
|
||||
>
|
||||
<span>
|
||||
|
@ -117,7 +121,12 @@ export default function Navbar() {
|
|||
</div>
|
||||
|
||||
<div className="dropdown dropdown-end">
|
||||
<div tabIndex={0} role="button" className="btn btn-circle btn-ghost">
|
||||
<div
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
onMouseDown={dropdownTriggerer}
|
||||
className="btn btn-circle btn-ghost"
|
||||
>
|
||||
<ProfilePhoto
|
||||
src={account.image ? account.image : undefined}
|
||||
priority={true}
|
||||
|
@ -134,7 +143,7 @@ export default function Navbar() {
|
|||
Settings
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<li className="block sm:hidden">
|
||||
<div
|
||||
onClick={() => {
|
||||
(document?.activeElement as HTMLElement)?.blur();
|
||||
|
@ -161,6 +170,9 @@ export default function Navbar() {
|
|||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<NewButtonMobile />
|
||||
|
||||
{sidebar ? (
|
||||
<div className="fixed top-0 bottom-0 right-0 left-0 bg-black bg-opacity-10 backdrop-blur-sm flex items-center fade-in z-40">
|
||||
<ClickAwayHandler className="h-full" onClickOutside={toggleSidebar}>
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
import { dropdownTriggerer } from "@/lib/client/utils";
|
||||
import React from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import NewLinkModal from "./ModalContent/NewLinkModal";
|
||||
import NewCollectionModal from "./ModalContent/NewCollectionModal";
|
||||
import UploadFileModal from "./ModalContent/UploadFileModal";
|
||||
|
||||
type Props = {};
|
||||
|
||||
export default function NewButtonMobile({}: Props) {
|
||||
const [hasScrolled, setHasScrolled] = useState(false);
|
||||
const [newLinkModal, setNewLinkModal] = useState(false);
|
||||
const [newCollectionModal, setNewCollectionModal] = useState(false);
|
||||
const [uploadFileModal, setUploadFileModal] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const handleScroll = () => {
|
||||
if (window.scrollY > 0) {
|
||||
setHasScrolled(true);
|
||||
} else {
|
||||
setHasScrolled(false);
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener("scroll", handleScroll);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("scroll", handleScroll);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="dropdown dropdown-end dropdown-top sm:hidden fixed bottom-10 right-10 z-30">
|
||||
<div
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
onMouseDown={dropdownTriggerer}
|
||||
className={`flex items-center btn btn-accent dark:border-violet-400 text-white btn-circle btn-lg px-2 relative ${
|
||||
hasScrolled ? "opacity-50" : ""
|
||||
}`}
|
||||
>
|
||||
<span>
|
||||
<i className="bi-plus text-5xl pointer-events-none"></i>
|
||||
</span>
|
||||
</div>
|
||||
<ul className="dropdown-content z-[1] menu shadow bg-base-200 border border-neutral-content rounded-box w-40 mb-1">
|
||||
<li>
|
||||
<div
|
||||
onClick={() => {
|
||||
(document?.activeElement as HTMLElement)?.blur();
|
||||
setNewLinkModal(true);
|
||||
}}
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
>
|
||||
New Link
|
||||
</div>
|
||||
</li>
|
||||
{/* <li>
|
||||
<div
|
||||
onClick={() => {
|
||||
(document?.activeElement as HTMLElement)?.blur();
|
||||
setUploadFileModal(true);
|
||||
}}
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
>
|
||||
Upload File
|
||||
</div>
|
||||
</li> */}
|
||||
<li>
|
||||
<div
|
||||
onClick={() => {
|
||||
(document?.activeElement as HTMLElement)?.blur();
|
||||
setNewCollectionModal(true);
|
||||
}}
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
>
|
||||
New Collection
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
{newLinkModal ? (
|
||||
<NewLinkModal onClose={() => setNewLinkModal(false)} />
|
||||
) : undefined}
|
||||
{newCollectionModal ? (
|
||||
<NewCollectionModal onClose={() => setNewCollectionModal(false)} />
|
||||
) : undefined}
|
||||
{uploadFileModal ? (
|
||||
<UploadFileModal onClose={() => setUploadFileModal(false)} />
|
||||
) : undefined}
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -44,7 +44,7 @@ export default function Sidebar({ className }: { className?: string }) {
|
|||
return (
|
||||
<div
|
||||
id="sidebar"
|
||||
className={`bg-base-200 h-full w-72 lg:w-80 overflow-y-auto border-solid border border-base-200 border-r-neutral-content p-2 z-20 ${
|
||||
className={`bg-base-200 h-full w-80 overflow-y-auto border-solid border border-base-200 border-r-neutral-content p-2 z-20 ${
|
||||
className || ""
|
||||
}`}
|
||||
>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import React, { Dispatch, SetStateAction } from "react";
|
||||
import { Sort } from "@/types/global";
|
||||
import { dropdownTriggerer } from "@/lib/client/utils";
|
||||
|
||||
type Props = {
|
||||
sortBy: Sort;
|
||||
|
@ -12,6 +13,7 @@ export default function SortDropdown({ sortBy, setSort }: Props) {
|
|||
<div
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
onMouseDown={dropdownTriggerer}
|
||||
className="btn btn-sm btn-square btn-ghost"
|
||||
>
|
||||
<i className="bi-chevron-expand text-neutral text-2xl"></i>
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
export function isPWA() {
|
||||
return (
|
||||
window.matchMedia("(display-mode: standalone)").matches ||
|
||||
(window.navigator as any).standalone ||
|
||||
document.referrer.includes("android-app://")
|
||||
);
|
||||
}
|
||||
|
||||
export function dropdownTriggerer(e: any) {
|
||||
let targetEl = e.currentTarget;
|
||||
if (targetEl && targetEl.matches(":focus")) {
|
||||
setTimeout(function () {
|
||||
targetEl.blur();
|
||||
}, 0);
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import React from "react";
|
||||
import React, { useEffect } from "react";
|
||||
import "@/styles/globals.css";
|
||||
import "bootstrap-icons/font/bootstrap-icons.css";
|
||||
import { SessionProvider } from "next-auth/react";
|
||||
|
@ -7,6 +7,7 @@ import Head from "next/head";
|
|||
import AuthRedirect from "@/layouts/AuthRedirect";
|
||||
import { Toaster } from "react-hot-toast";
|
||||
import { Session } from "next-auth";
|
||||
import { isPWA } from "@/lib/client/utils";
|
||||
|
||||
export default function App({
|
||||
Component,
|
||||
|
@ -14,6 +15,15 @@ export default function App({
|
|||
}: AppProps<{
|
||||
session: Session;
|
||||
}>) {
|
||||
useEffect(() => {
|
||||
if (isPWA()) {
|
||||
const meta = document.createElement("meta");
|
||||
meta.name = "viewport";
|
||||
meta.content = "width=device-width, initial-scale=1, maximum-scale=1";
|
||||
document.getElementsByTagName("head")[0].appendChild(meta);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<SessionProvider
|
||||
session={pageProps.session}
|
||||
|
|
|
@ -23,6 +23,7 @@ import ViewDropdown from "@/components/ViewDropdown";
|
|||
import CardView from "@/components/LinkViews/Layouts/CardView";
|
||||
// import GridView from "@/components/LinkViews/Layouts/GridView";
|
||||
import ListView from "@/components/LinkViews/Layouts/ListView";
|
||||
import { dropdownTriggerer } from "@/lib/client/utils";
|
||||
|
||||
export default function Index() {
|
||||
const { settings } = useLocalSettingsStore();
|
||||
|
@ -125,6 +126,7 @@ export default function Index() {
|
|||
<div
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
onMouseDown={dropdownTriggerer}
|
||||
className="btn btn-ghost btn-sm btn-square text-neutral"
|
||||
>
|
||||
<i className="bi-three-dots text-xl" title="More"></i>
|
||||
|
|
|
@ -11,7 +11,6 @@ import PageHeader from "@/components/PageHeader";
|
|||
|
||||
export default function Collections() {
|
||||
const { collections } = useCollectionStore();
|
||||
const [expandDropdown, setExpandDropdown] = useState(false);
|
||||
const [sortBy, setSortBy] = useState<Sort>(Sort.DateNewestFirst);
|
||||
const [sortedCollections, setSortedCollections] = useState(collections);
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ import PageHeader from "@/components/PageHeader";
|
|||
import CardView from "@/components/LinkViews/Layouts/CardView";
|
||||
import ListView from "@/components/LinkViews/Layouts/ListView";
|
||||
import ViewDropdown from "@/components/ViewDropdown";
|
||||
import { dropdownTriggerer } from "@/lib/client/utils";
|
||||
// import GridView from "@/components/LinkViews/Layouts/GridView";
|
||||
|
||||
export default function Dashboard() {
|
||||
|
@ -200,6 +201,7 @@ export default function Dashboard() {
|
|||
<div
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
onMouseDown={dropdownTriggerer}
|
||||
className="inline-flex items-center gap-2 text-sm btn btn-outline btn-neutral"
|
||||
id="import-dropdown"
|
||||
>
|
||||
|
|
|
@ -25,8 +25,6 @@ export default function Search() {
|
|||
tags: true,
|
||||
});
|
||||
|
||||
const [filterDropdown, setFilterDropdown] = useState(false);
|
||||
|
||||
const [viewMode, setViewMode] = useState<string>(
|
||||
localStorage.getItem("viewMode") || ViewMode.Card
|
||||
);
|
||||
|
|
|
@ -11,6 +11,7 @@ import React from "react";
|
|||
import { MigrationFormat, MigrationRequest } from "@/types/global";
|
||||
import Link from "next/link";
|
||||
import Checkbox from "@/components/Checkbox";
|
||||
import { dropdownTriggerer } from "@/lib/client/utils";
|
||||
|
||||
export default function Account() {
|
||||
const emailEnabled = process.env.NEXT_PUBLIC_EMAIL_PROVIDER;
|
||||
|
@ -245,6 +246,7 @@ export default function Account() {
|
|||
<div
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
onMouseDown={dropdownTriggerer}
|
||||
className="flex gap-2 text-sm btn btn-outline btn-neutral group"
|
||||
id="import-dropdown"
|
||||
>
|
||||
|
|
|
@ -11,6 +11,7 @@ import ViewDropdown from "@/components/ViewDropdown";
|
|||
import CardView from "@/components/LinkViews/Layouts/CardView";
|
||||
// import GridView from "@/components/LinkViews/Layouts/GridView";
|
||||
import ListView from "@/components/LinkViews/Layouts/ListView";
|
||||
import { dropdownTriggerer } from "@/lib/client/utils";
|
||||
|
||||
export default function Index() {
|
||||
const router = useRouter();
|
||||
|
@ -153,6 +154,7 @@ export default function Index() {
|
|||
<div
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
onMouseDown={dropdownTriggerer}
|
||||
className="btn btn-ghost btn-sm btn-square text-neutral"
|
||||
>
|
||||
<i
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"name":"Linkwarden","short_name":"Linkwarden","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
|
||||
{"name":"Linkwarden","short_name":"Linkwarden","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#000000","background_color":"#000000","display":"standalone"}
|
Ŝarĝante…
Reference in New Issue