add support for tags

This commit is contained in:
Athou
2022-10-25 10:18:50 +02:00
parent d7c6f8eb52
commit f838f877fa
14 changed files with 182 additions and 18 deletions

View File

@@ -1,10 +1,12 @@
import { t } from "@lingui/macro"
import { Checkbox, Group, Popover } from "@mantine/core"
import { markEntriesUpToEntry, markEntry, starEntry } from "app/slices/entries"
import { Checkbox, Group, MultiSelect, Popover } from "@mantine/core"
import { Constants } from "app/constants"
import { markEntriesUpToEntry, markEntry, starEntry, tagEntry } from "app/slices/entries"
import { useAppDispatch, useAppSelector } from "app/store"
import { Entry } from "app/types"
import { ActionButton } from "components/ActionButtton"
import { TbArrowBarToDown, TbExternalLink, TbShare, TbStar, TbStarOff } from "react-icons/tb"
import { useEffect, useState } from "react"
import { TbArrowBarToDown, TbExternalLink, TbShare, TbStar, TbStarOff, TbTag } from "react-icons/tb"
import { ShareButtons } from "./ShareButtons"
interface FeedEntryFooterProps {
@@ -12,13 +14,30 @@ interface FeedEntryFooterProps {
}
export function FeedEntryFooter(props: FeedEntryFooterProps) {
const [scrollPosition, setScrollPosition] = useState(0)
const sharingSettings = useAppSelector(state => state.user.settings?.sharingSettings)
const tags = useAppSelector(state => state.user.tags)
const dispatch = useAppDispatch()
const showSharingButtons =
sharingSettings && (Object.values(sharingSettings) as Array<typeof sharingSettings[keyof typeof sharingSettings]>).some(v => v)
const readStatusCheckboxClicked = () => dispatch(markEntry({ entry: props.entry, read: !props.entry.read }))
const onTagsChange = (values: string[]) =>
dispatch(
tagEntry({
entryId: +props.entry.id,
tags: values,
})
)
useEffect(() => {
const scrollArea = document.getElementById(Constants.dom.mainScrollAreaId)
const listener = () => setScrollPosition(scrollArea ? scrollArea.scrollTop : 0)
scrollArea?.addEventListener("scroll", listener)
return () => scrollArea?.removeEventListener("scroll", listener)
}, [])
return (
<Group position="apart">
@@ -41,7 +60,7 @@ export function FeedEntryFooter(props: FeedEntryFooterProps) {
/>
{showSharingButtons && (
<Popover withArrow withinPortal shadow="md">
<Popover withArrow withinPortal shadow="md" positionDependencies={[scrollPosition]}>
<Popover.Target>
<ActionButton icon={<TbShare size={18} />} label={t`Share`} />
</Popover.Target>
@@ -51,6 +70,25 @@ export function FeedEntryFooter(props: FeedEntryFooterProps) {
</Popover>
)}
{tags && (
<Popover withArrow withinPortal shadow="md" positionDependencies={[scrollPosition]}>
<Popover.Target>
<ActionButton icon={<TbTag size={18} />} label={t`Tags`} />
</Popover.Target>
<Popover.Dropdown>
<MultiSelect
data={tags}
placeholder="Tags"
searchable
creatable
getCreateLabel={query => t`Create tag: ${query}`}
value={props.entry.tags}
onChange={onTagsChange}
/>
</Popover.Dropdown>
</Popover>
)}
<a href={props.entry.url} target="_blank" rel="noreferrer">
<ActionButton icon={<TbExternalLink size={18} />} label={t`Open link`} />
</a>

View File

@@ -1,7 +1,14 @@
import { t } from "@lingui/macro"
import { Box, Stack } from "@mantine/core"
import { Constants } from "app/constants"
import { redirectToCategory, redirectToCategoryDetails, redirectToFeed, redirectToFeedDetails } from "app/slices/redirect"
import {
redirectToCategory,
redirectToCategoryDetails,
redirectToFeed,
redirectToFeedDetails,
redirectToTag,
redirectToTagDetails,
} from "app/slices/redirect"
import { collapseTreeCategory } from "app/slices/tree"
import { useAppDispatch, useAppSelector } from "app/store"
import { Category, Subscription } from "app/types"
@@ -9,19 +16,21 @@ import { categoryUnreadCount, flattenCategoryTree } from "app/utils"
import { Loader } from "components/Loader"
import { OnDesktop } from "components/responsive/OnDesktop"
import React from "react"
import { FaChevronDown, FaChevronRight, FaInbox, FaStar } from "react-icons/fa"
import { TbChevronDown, TbChevronRight, TbInbox, TbStar, TbTag } from "react-icons/tb"
import { TreeNode } from "./TreeNode"
import { TreeSearch } from "./TreeSearch"
const allIcon = <FaInbox size={14} />
const starredIcon = <FaStar size={14} />
const expandedIcon = <FaChevronDown size={14} />
const collapsedIcon = <FaChevronRight size={14} />
const allIcon = <TbInbox size={16} />
const starredIcon = <TbStar size={16} />
const tagIcon = <TbTag size={16} />
const expandedIcon = <TbChevronDown size={16} />
const collapsedIcon = <TbChevronRight size={16} />
const errorThreshold = 9
export function Tree() {
const root = useAppSelector(state => state.tree.rootCategory)
const source = useAppSelector(state => state.entries.source)
const tags = useAppSelector(state => state.user.tags)
const showRead = useAppSelector(state => state.user.settings?.showRead)
const dispatch = useAppDispatch()
@@ -46,6 +55,10 @@ export function Tree() {
})
)
}
const tagClicked = (e: React.MouseEvent, id: string) => {
if (e.detail === 2) dispatch(redirectToTagDetails(id))
else dispatch(redirectToTag(id))
}
const allCategoryNode = () => (
<TreeNode
@@ -114,6 +127,20 @@ export function Tree() {
)
}
const tagNode = (tag: string) => (
<TreeNode
id={tag}
name={tag}
icon={tagIcon}
unread={0}
selected={source.type === "tag" && source.id === tag}
level={0}
hasError={false}
onClick={tagClicked}
key={tag}
/>
)
const recursiveCategoryNode = (category: Category, level = 0) => (
<React.Fragment key={`recursiveCategoryNode-${category.id}`}>
{categoryNode(category, level)}
@@ -134,6 +161,7 @@ export function Tree() {
{starredCategoryNode()}
{root.children.map(c => recursiveCategoryNode(c))}
{root.feeds.map(f => feedNode(f))}
{tags?.map(tag => tagNode(tag))}
</Box>
</Stack>
)