8000 feat: add grouped links component and related configs by NightWatcher314 · Pull Request #999 · linkwarden/linkwarden · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

feat: add grouped links component and related configs #999

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions components/Divider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default function Divider() {
return (
<div className="flex items-center py-2">
<div className="flex-1 border-t border-base-content/20"></div>
</div>
);
}
68 changes: 68 additions & 0 deletions components/LinkViews/GroupedLinks.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { GroupBy, LinkIncludingShortenedCollectionAndTags, Sort, SortToGroupMap } from "@/types/global";
import { useMemo } from "react";
import { useTranslation } from "next-i18next";

function getGroupKeyFromLink(link: LinkIncludingShortenedCollectionAndTags, groupBy: GroupBy) {
switch (groupBy) {
case GroupBy.Date: {
const date = new Date(link.createdAt as string);
const year = date.getFullYear();
const month = date.getMonth() + 1;
const day = date.getDate();
return `${year}.${month}.${day}`;
}
case GroupBy.Name:
return link.name.charAt(0).toUpperCase();
case GroupBy.Description:
return link.description ? link.description.charAt(0).toUpperCase() : '#';
default:
return '';
}
}

export default function GroupedLinks({
links,
sortBy,
enableGrouping,
renderLinks
}: {
links?: LinkIncludingShortenedCollectionAndTags[];
sortBy: Sort;
enableGrouping: boolean;
renderLinks: (groupLinks: LinkIncludingShortenedCollectionAndTags[]) => React.ReactNode;
}) {
const { t } = useTranslation();
const groupedLinks = useMemo(() => {
if (!links || !enableGrouping) {
return { "": links || [] };
}

const groupBy = SortToGroupMap[sortBy] || GroupBy.None;
if (groupBy === GroupBy.None) {
return { "": links };
}

return links.reduce((groups: { [key: string]: LinkIncludingShortenedCollectionAndTags[] }, link) => {
const groupKey = getGroupKeyFromLink(link, groupBy);

if (!groups[groupKey]) {
groups[groupKey] = [];
}
groups[groupKey].push(link);
return groups;
}, {});
}, [links, sortBy, enableGrouping]);

return (
<div className="flex flex-col gap-8">
{Object.entries(groupedLinks).map(([groupName, groupLinks]) => (
<div key={groupName} className="space-y-4">
{groupName && enableGrouping && (
<h2 className="text-xl font-semibold text-neutral">{groupName}</h2>
)}
{renderLinks(groupLinks)}
</div>
))}
</div>
);
}
7 changes: 3 additions & 4 deletions components/LinkViews/LinkComponents/LinkList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,8 @@ export default function LinkCardCompact({ link, editMode }: Props) {
return (
<>
<div
className={`${selectedStyle} rounded-md border relative group items-center flex ${
!isPWA() ? "hover:bg-base-300 px-2 py-1" : "py-1"
} duration-200`}
className={`${selectedStyle} rounded-md border relative group items-center flex ${!isPWA() ? "hover:bg-base-300 px-2 py-1" : "py-1"
} duration-200`}
=>
selectable
? handleCheckboxClick(link)
Expand All @@ -129,7 +128,7 @@ export default function LinkCardCompact({ link, editMode }: Props) {
<div className="w-[calc(100%-56px)] ml-2">
{show.name && (
<div className="flex gap-1 mr-20">
<p className="truncate text-primary">
<p className="text-primary">
{unescapeString(link.name)}
</p>
{show.preserved_formats &&
Expand Down
83 changes: 48 additions & 35 deletions components/LinkViews/Links.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import LinkCard from "@/components/LinkViews/LinkComponents/LinkCard";
import {
LinkIncludingShortenedCollectionAndTags,
ViewMode,
Sort,
} from "@/types/global";
import { useEffect, useState } from "react";
import { useInView } from "react-intersection-observer";
Expand All @@ -12,6 +13,7 @@ import tailwindConfig from "../../tailwind.config.js";
import { useMemo } from "react";
import LinkList from "@/components/LinkViews/LinkComponents/LinkList";
import useLocalSettingsStore from "@/store/localSettings";
import GroupedLinks from "@/components/LinkViews/GroupedLinks";

export function CardView({
links,
Expand Down Expand Up @@ -288,48 +290,59 @@ export default function Links({
useData?: any;
}) {
const { ref, inView } = useInView();
const { settings } = useLocalSettingsStore();

useEffect(() => {
if (inView && useData?.fetchNextPage && useData?.hasNextPage) {
useData.fetchNextPage();
}
}, [useData, inView]);

if (layout === ViewMode.List) {
return (
<ListView
links={links}
editMode={editMode}
isLoading={useData?.isLoading}
placeholders={placeholderCountToArray(placeholderCount)}
hasNextPage={useData?.hasNextPage}
placeHolderRef={ref}
/>
);
} else if (layout === ViewMode.Masonry) {
return (
<MasonryView
links={links}
editMode={editMode}
isLoading={useData?.isLoading}
placeholders={placeholderCountToArray(placeholderCount)}
hasNextPage={useData?.hasNextPage}
placeHolderRef={ref}
/>
);
} else {
// Default to card view
return (
<CardView
links={links}
editMode={editMode}
isLoading={useData?.isLoading}
placeholders={placeholderCountToArray(placeholderCount)}
hasNextPage={useData?.hasNextPage}
placeHolderRef={ref}
/>
);
}
const renderView = (groupLinks: LinkIncludingShortenedCollectionAndTags[]) => {
if (layout === ViewMode.List) {
return (
<ListView
links={groupLinks}
editMode={editMode}
isLoading={useData?.isLoading}
placeholders={placeholderCountToArray(placeholderCount)}
hasNextPage={useData?.hasNextPage}
placeHolderRef={ref}
/>
);
} else if (layout === ViewMode.Masonry) {
return (
<MasonryView
links={groupLinks}
editMode={editMode}
isLoading={useData?.isLoading}
placeholders={placeholderCountToArray(placeholderCount)}
hasNextPage={useData?.hasNextPage}
placeHolderRef={ref}
/>
);
} else {
return (
<CardView
links={groupLinks}
editMode={editMode}
isLoading={useData?.isLoading}
placeholders={placeholderCountToArray(placeholderCount)}
hasNextPage={useData?.hasNextPage}
placeHolderRef={ref}
/>
);
}
};

return (
<GroupedLinks
links={links}
sortBy={settings.sortBy || Sort.DateNewestFirst}
enableGrouping={settings.enableGrouping}
renderLinks={renderView}
/>
);
}

const placeholderCountToArray = (num?: number) =>
Expand Down
18 changes: 17 additions & 1 deletion components/SortDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ type Props = {
};

export default function SortDropdown({ sortBy, setSort, t }: Props) {
const { updateSettings } = useLocalSettingsStore();
const { settings, updateSettings } = useLocalSettingsStore();
const queryClient = useQueryClient();

useEffect(() => {
Expand All @@ -31,6 +31,7 @@ export default function SortDropdown({ sortBy, setSort, t }: Props) {
<i className="bi-chevron-expand text-neutral text-2xl"></i>
</div>
<ul className="dropdown-content z-[30] menu shadow bg-base-200 border border-neutral-content rounded-xl mt-1">

<li>
<label
className="label cursor-pointer flex justify-start"
Expand Down Expand Up @@ -153,6 +154,21 @@ export default function SortDropdown({ sortBy, setSort, t }: Props) {
</span>
</label>
</li>
<div className="divider m-0"></div>
<li>
<label className="label cursor-pointer flex justify-start">
<input
type="checkbox"
className="toggle toggle-primary toggle-sm"
checked={settings.enableGrouping}
=> {
updateSettings({ enableGrouping: e.target.checked });
}}
/>
<span className="truncate label-text whitespace-nowrap">{t("enable_grouping")}</span>
</label>
</li>

</ul>
</div>
);
Expand Down
3 changes: 2 additions & 1 deletion public/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -458,5 +458,6 @@
"not_found_404": "404 - Not Found",
"collection_publicly_shared": "This collection is being shared publicly.",
"search_for_links": "Search for Links",
"search_query_invalid_symbol": "The search query should not contain '%'."
"search_query_invalid_symbol": "The search query should not contain '%'.",
"enable_grouping": "Enable Grouping"
}
3 changes: 2 additions & 1 deletion public/locales/zh/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -370,5 +370,6 @@
"demo_title": "仅限演示",
"demo_desc": "这只是 Linkwarden 的演示实例,禁止上传文件。",
"demo_desc_2": "如果你想尝试完整版,你可以注册免费试用:",
"demo_button": "以演示用户登录"
"demo_button": "以演示用户登录",
"enable_grouping": "启用分组"
}
3 changes: 3 additions & 0 deletions store/localSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ type LocalSettings = {
};
columns: number;
sortBy?: Sort;
enableGrouping: boolean;
};

type LocalSettingsStore = {
Expand All @@ -44,6 +45,7 @@ const useLocalSettingsStore = create<LocalSettingsStore>((set) => ({
},
columns: 0,
sortBy: Sort.DateNewestFirst,
enableGrouping: true,
},
updateSettings: (newSettings) => {
const { theme, viewMode, color, sortBy, show, columns } = newSettings;
Expand Down Expand Up @@ -126,6 +128,7 @@ const useLocalSettingsStore = create<LocalSettingsStore>((set) => ({
show,
columns,
sortBy: useLocalSettingsStore.getState().settings.sortBy,
enableGrouping: true,
},
});

Expand Down
17 changes: 17 additions & 0 deletions types/global.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,3 +158,20 @@ export enum TokenExpiry {
threeMonths,
never,
}

export enum GroupBy {
None,
Date,
Name,
Description
}

// 将Sort和GroupBy关联起来
export const SortToGroupMap: { [key in Sort]?: GroupBy } = {
[Sort.DateNewestFirst]: GroupBy.Date,
[Sort.DateOldestFirst]: GroupBy.Date,
[Sort.NameAZ]: GroupBy.Name,
[Sort.NameZA]: GroupBy.Name,
[Sort.DescriptionAZ]: GroupBy.Description,
[Sort.DescriptionZA]: GroupBy.Description,
};
Loading
0