Introduce SubPopover (#1620)
* Introduce SubPopover * add truncation to sub description popover --------- Co-authored-by: Keyan <34140557+huumn@users.noreply.github.com> Co-authored-by: k00b <k00b@stacker.news>
This commit is contained in:
parent
bd5db1b62e
commit
55d1f2c952
@ -27,6 +27,7 @@ import { useRetryCreateItem } from './use-item-submit'
|
|||||||
import { useToast } from './toast'
|
import { useToast } from './toast'
|
||||||
import { useShowModal } from './modal'
|
import { useShowModal } from './modal'
|
||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
|
import SubPopover from './sub-popover'
|
||||||
|
|
||||||
export default function ItemInfo ({
|
export default function ItemInfo ({
|
||||||
item, full, commentsText = 'comments',
|
item, full, commentsText = 'comments',
|
||||||
@ -134,9 +135,11 @@ export default function ItemInfo ({
|
|||||||
</>}
|
</>}
|
||||||
</span>
|
</span>
|
||||||
{item.subName &&
|
{item.subName &&
|
||||||
<Link href={`/~${item.subName}`}>
|
<SubPopover sub={item.subName}>
|
||||||
{' '}<Badge className={styles.newComment} bg={null}>{item.subName}</Badge>
|
<Link href={`/~${item.subName}`}>
|
||||||
</Link>}
|
{' '}<Badge className={styles.newComment} bg={null}>{item.subName}</Badge>
|
||||||
|
</Link>
|
||||||
|
</SubPopover>}
|
||||||
{sub?.nsfw &&
|
{sub?.nsfw &&
|
||||||
<Badge className={styles.newComment} bg={null}>nsfw</Badge>}
|
<Badge className={styles.newComment} bg={null}>nsfw</Badge>}
|
||||||
{(item.outlawed && !item.mine &&
|
{(item.outlawed && !item.mine &&
|
||||||
|
@ -12,6 +12,7 @@ import Badges from './badge'
|
|||||||
import { MEDIA_URL } from '@/lib/constants'
|
import { MEDIA_URL } from '@/lib/constants'
|
||||||
import { abbrNum } from '@/lib/format'
|
import { abbrNum } from '@/lib/format'
|
||||||
import { Badge } from 'react-bootstrap'
|
import { Badge } from 'react-bootstrap'
|
||||||
|
import SubPopover from './sub-popover'
|
||||||
|
|
||||||
export default function ItemJob ({ item, toc, rank, children }) {
|
export default function ItemJob ({ item, toc, rank, children }) {
|
||||||
const isEmail = string().email().isValidSync(item.url)
|
const isEmail = string().email().isValidSync(item.url)
|
||||||
@ -62,9 +63,11 @@ export default function ItemJob ({ item, toc, rank, children }) {
|
|||||||
</Link>
|
</Link>
|
||||||
</span>
|
</span>
|
||||||
{item.subName &&
|
{item.subName &&
|
||||||
<Link href={`/~${item.subName}`}>
|
<SubPopover sub={item.subName}>
|
||||||
{' '}<Badge className={styles.newComment} bg={null}>{item.subName}</Badge>
|
<Link href={`/~${item.subName}`}>
|
||||||
</Link>}
|
{' '}<Badge className={styles.newComment} bg={null}>{item.subName}</Badge>
|
||||||
|
</Link>
|
||||||
|
</SubPopover>}
|
||||||
{item.status === 'STOPPED' &&
|
{item.status === 'STOPPED' &&
|
||||||
<>{' '}<Badge bg='info' className={styles.badge}>stopped</Badge></>}
|
<>{' '}<Badge bg='info' className={styles.badge}>stopped</Badge></>}
|
||||||
{item.mine && !item.deletedAt &&
|
{item.mine && !item.deletedAt &&
|
||||||
|
29
components/sub-popover.js
Normal file
29
components/sub-popover.js
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { SUB_FULL } from '@/fragments/subs'
|
||||||
|
import errorStyles from '@/styles/error.module.css'
|
||||||
|
import { useLazyQuery } from '@apollo/client'
|
||||||
|
import classNames from 'classnames'
|
||||||
|
import HoverablePopover from './hoverable-popover'
|
||||||
|
import { TerritoryInfo, TerritoryInfoSkeleton } from './territory-header'
|
||||||
|
import { truncateString } from '@/lib/format'
|
||||||
|
|
||||||
|
export default function SubPopover ({ sub, children }) {
|
||||||
|
const [getSub, { loading, data }] = useLazyQuery(
|
||||||
|
SUB_FULL,
|
||||||
|
{
|
||||||
|
variables: { sub },
|
||||||
|
fetchPolicy: 'cache-first'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<HoverablePopover
|
||||||
|
onShow={getSub}
|
||||||
|
trigger={children}
|
||||||
|
body={!data || loading
|
||||||
|
? <TerritoryInfoSkeleton />
|
||||||
|
: !data.sub
|
||||||
|
? <h1 className={classNames(errorStyles.status, errorStyles.describe)}>SUB NOT FOUND</h1>
|
||||||
|
: <TerritoryInfo sub={{ ...data.sub, desc: truncateString(data.sub.desc, 280) }} />}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
@ -31,6 +31,17 @@ export function TerritoryDetails ({ sub, children }) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function TerritoryInfoSkeleton ({ children, className }) {
|
||||||
|
return (
|
||||||
|
<div className={`${styles.item} ${styles.skeleton} ${className}`}>
|
||||||
|
<div className={styles.hunk}>
|
||||||
|
<div className={`${styles.name} clouds text-reset`} />
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export function TerritoryInfo ({ sub }) {
|
export function TerritoryInfo ({ sub }) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -12,6 +12,7 @@ import { useRouter } from 'next/router'
|
|||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import { UNKNOWN_LINK_REL } from '@/lib/constants'
|
import { UNKNOWN_LINK_REL } from '@/lib/constants'
|
||||||
import isEqual from 'lodash/isEqual'
|
import isEqual from 'lodash/isEqual'
|
||||||
|
import SubPopover from './sub-popover'
|
||||||
import UserPopover from './user-popover'
|
import UserPopover from './user-popover'
|
||||||
import ItemPopover from './item-popover'
|
import ItemPopover from './item-popover'
|
||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
@ -183,8 +184,12 @@ function Mention ({ children, node, href, name, id }) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function Sub ({ children, node, href, ...props }) {
|
function Sub ({ children, node, href, name, ...props }) {
|
||||||
return <Link href={href}>{children}</Link>
|
return (
|
||||||
|
<SubPopover sub={name}>
|
||||||
|
<Link href={href}>{children}</Link>
|
||||||
|
</SubPopover>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function Item ({ children, node, href, id }) {
|
function Item ({ children, node, href, id }) {
|
||||||
|
@ -204,3 +204,49 @@ export const toPositive = (x) => {
|
|||||||
if (typeof x === 'bigint') return toPositiveBigInt(x)
|
if (typeof x === 'bigint') return toPositiveBigInt(x)
|
||||||
return toPositiveNumber(x)
|
return toPositiveNumber(x)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Truncates a string intelligently, trying to keep natural breaks
|
||||||
|
* @param {string} str - The string to truncate
|
||||||
|
* @param {number} maxLength - Maximum length of the result
|
||||||
|
* @param {string} [suffix='...'] - String to append when truncated
|
||||||
|
* @returns {string} Truncated string
|
||||||
|
*/
|
||||||
|
export const truncateString = (str, maxLength, suffix = ' ...') => {
|
||||||
|
if (!str || str.length <= maxLength) return str
|
||||||
|
|
||||||
|
const effectiveLength = maxLength - suffix.length
|
||||||
|
|
||||||
|
// Split into paragraphs and accumulate until we exceed the limit
|
||||||
|
const paragraphs = str.split(/\n\n+/)
|
||||||
|
let result = ''
|
||||||
|
for (const paragraph of paragraphs) {
|
||||||
|
if ((result + paragraph).length > effectiveLength) {
|
||||||
|
// If this is the first paragraph and it's too long,
|
||||||
|
// fall back to sentence/word breaking
|
||||||
|
if (!result) {
|
||||||
|
// Try to break at sentence
|
||||||
|
const sentenceBreak = paragraph.slice(0, effectiveLength).match(/[.!?]\s+[A-Z]/g)
|
||||||
|
if (sentenceBreak) {
|
||||||
|
const lastBreak = paragraph.lastIndexOf(sentenceBreak[sentenceBreak.length - 1], effectiveLength)
|
||||||
|
if (lastBreak > effectiveLength / 2) {
|
||||||
|
return paragraph.slice(0, lastBreak + 1) + suffix
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to break at word
|
||||||
|
const wordBreak = paragraph.lastIndexOf(' ', effectiveLength)
|
||||||
|
if (wordBreak > 0) {
|
||||||
|
return paragraph.slice(0, wordBreak) + suffix
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall back to character break
|
||||||
|
return paragraph.slice(0, effectiveLength) + suffix
|
||||||
|
}
|
||||||
|
return result.trim() + suffix
|
||||||
|
}
|
||||||
|
result += (result ? '\n\n' : '') + paragraph
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user