diff --git a/src/app.css b/src/app.css index 8b575c63..954fa9e3 100644 --- a/src/app.css +++ b/src/app.css @@ -1077,8 +1077,10 @@ body:has(.status-deck) .media-post-link { max-width: 90vw; overflow: hidden; } -.szh-menu__item--focusable { - background-color: transparent; +.szh-menu[aria-label='Submenu'] { + background-color: var(--bg-blur-color); + backdrop-filter: blur(4px); + box-shadow: 0 3px 24px -3px var(--drop-shadow-color); } .szh-menu__header { margin: -8px 0 8px; @@ -1098,7 +1100,7 @@ body:has(.status-deck) .media-post-link { display: flex; gap: 8px; align-items: center; - line-height: 1; + line-height: 1.1; padding: 8px 16px !important; transition: all 0.1s ease-in-out; text-decoration: none; @@ -1106,6 +1108,9 @@ body:has(.status-deck) .media-post-link { overflow: hidden; text-overflow: ellipsis; } +.szh-menu .szh-menu__item--focusable { + background-color: transparent; +} .szh-menu .szh-menu__item span { white-space: nowrap; overflow: hidden; @@ -1186,6 +1191,15 @@ body:has(.status-deck) .media-post-link { opacity: 1; } +.szh-menu .menu-wrap { + display: flex; + flex-wrap: wrap; +} +.szh-menu .menu-wrap > * { + flex-grow: 1; + flex-basis: 50%; +} + /* GLASS MENU */ .glass-menu { diff --git a/src/components/account-info.css b/src/components/account-info.css index 8184aac5..f14780bb 100644 --- a/src/components/account-info.css +++ b/src/components/account-info.css @@ -158,6 +158,9 @@ .account-container .actions button { align-self: flex-end; } +.account-container .actions .buttons { + display: flex; +} .account-container .profile-metadata { display: flex; diff --git a/src/components/account-info.jsx b/src/components/account-info.jsx index 2f020918..b4b92c43 100644 --- a/src/components/account-info.jsx +++ b/src/components/account-info.jsx @@ -1,5 +1,12 @@ import './account-info.css'; +import { + Menu, + MenuDivider, + MenuHeader, + MenuItem, + SubMenu, +} from '@szhsin/react-menu'; import { useEffect, useRef, useState } from 'preact/hooks'; import RelativeTime from '../components/relative-time'; @@ -9,6 +16,7 @@ import enhanceContent from '../utils/enhance-content'; import handleContentLinks from '../utils/handle-content-links'; import niceDateTime from '../utils/nice-date-time'; import shortenNumber from '../utils/shorten-number'; +import showToast from '../utils/show-toast'; import states, { hideAllModals } from '../utils/states'; import store from '../utils/store'; @@ -17,6 +25,27 @@ import Avatar from './avatar'; import Icon from './icon'; import Link from './link'; +const MUTE_DURATIONS = [ + 1000 * 60 * 5, // 5 minutes + 1000 * 60 * 30, // 30 minutes + 1000 * 60 * 60, // 1 hour + 1000 * 60 * 60 * 6, // 6 hours + 1000 * 60 * 60 * 24, // 1 day + 1000 * 60 * 60 * 24 * 3, // 3 days + 1000 * 60 * 60 * 24 * 7, // 1 week + 0, // forever +]; +const MUTE_DURATIONS_LABELS = { + 0: 'Forever', + 300_000: '5 minutes', + 1_800_000: '30 minutes', + 3_600_000: '1 hour', + 21_600_000: '6 hours', + 86_400_000: '1 day', + 259_200_000: '3 days', + 604_800_000: '1 week', +}; + function AccountInfo({ account, fetchAccount = () => {}, @@ -360,7 +389,7 @@ function RelatedActions({ info, instance, authenticated }) { const [relationship, setRelationship] = useState(null); const [familiarFollowers, setFamiliarFollowers] = useState([]); - const { id, locked, lastStatusAt } = info; + const { id, acct, url, username, locked, lastStatusAt } = info; const accountID = useRef(id); const { @@ -445,6 +474,8 @@ function RelatedActions({ info, instance, authenticated }) { } }, [info, authenticated]); + const loading = relationshipUIState === 'loading'; + return ( <> {familiarFollowers?.length > 0 && ( @@ -481,65 +512,277 @@ function RelatedActions({ info, instance, authenticated }) { Last status: )}{' '} - {relationshipUIState !== 'loading' && relationship && ( - + } + > + {currentAuthenticated && ( + <> + { + states.showCompose = { + draftStatus: { + status: `@${acct} `, + }, + }; + }} + > + + Mention @{username} + + + + )} + + + {niceAccountURL(url)} + + + {!!relationship && ( + <> + + {muting ? ( + { + setRelationshipUIState('loading'); + (async () => { + try { + const newRelationship = + await currentMasto.v1.accounts.unmute(id); + console.log('unmuting', newRelationship); + setRelationship(newRelationship); + setRelationshipUIState('default'); + showToast(`Unmuted @${username}`); + } catch (e) { + console.error(e); + setRelationshipUIState('error'); + } + })(); + }} + > + + Unmute @{username} + + ) : ( + + + Mute @{username}… + + + + + + } + > + + + )} + { + if (!blocking && !confirm(`Block @${username}?`)) { + return; + } + setRelationshipUIState('loading'); + (async () => { + try { + if (blocking) { + const newRelationship = + await currentMasto.v1.accounts.unblock(id); + console.log('unblocking', newRelationship); + setRelationship(newRelationship); + setRelationshipUIState('default'); + showToast(`Unblocked @${username}`); + } else { + const newRelationship = + await currentMasto.v1.accounts.block(id); + console.log('blocking', newRelationship); + setRelationship(newRelationship); + setRelationshipUIState('default'); + showToast(`Blocked @${username}`); + } + } catch (e) { + console.error(e); + setRelationshipUIState('error'); + if (blocking) { + showToast(`Unable to unblock @${username}`); + } else { + showToast(`Unable to block @${username}`); + } + } + })(); + }} + > + {blocking ? ( + <> + + Unblock @{username} + + ) : ( + <> + + Block @{username}… + + )} + + {/* + + Report @{username}… + */} + + )} + + {!!relationship && ( + - )} + if (newRelationship) setRelationship(newRelationship); + setRelationshipUIState('default'); + } catch (e) { + alert(e); + setRelationshipUIState('error'); + } + })(); + }} + > + {following ? ( + <> + Following + Unfollow… + + ) : requested ? ( + <> + Requested + Withdraw… + + ) : locked ? ( + <> + Follow + + ) : ( + 'Follow' + )} + + )} +

); @@ -561,4 +804,18 @@ function lightenRGB([r, g, b]) { return [r, g, b, alpha]; } +function niceAccountURL(url) { + if (!url) return; + const urlObj = new URL(url); + const { host, pathname } = urlObj; + const path = pathname.replace(/\/$/, '').replace(/^\//, ''); + return ( + <> + {host}/ + + {path} + + ); +} + export default AccountInfo; diff --git a/src/components/compose.jsx b/src/components/compose.jsx index 981516b2..c6076604 100644 --- a/src/components/compose.jsx +++ b/src/components/compose.jsx @@ -244,12 +244,12 @@ function Compose({ textareaRef.current.value = status; oninputTextarea(); focusTextarea(); - spoilerTextRef.current.value = spoilerText; - setVisibility(visibility); + if (spoilerText) spoilerTextRef.current.value = spoilerText; + if (visibility) setVisibility(visibility); setLanguage(language || prefs.postingDefaultLanguage || DEFAULT_LANG); - setSensitive(sensitive); - setPoll(composablePoll); - setMediaAttachments(mediaAttachments); + if (sensitive !== null) setSensitive(sensitive); + if (composablePoll) setPoll(composablePoll); + if (mediaAttachments) setMediaAttachments(mediaAttachments); } }, [draftStatus, editStatus, replyToStatus]); @@ -442,10 +442,6 @@ function Compose({ useEffect(() => { const handleItems = (e) => { - if (mediaAttachments.length >= maxMediaAttachments) { - alert(`You can only attach up to ${maxMediaAttachments} files.`); - return; - } const { items } = e.clipboardData || e.dataTransfer; const files = []; for (let i = 0; i < items.length; i++) { @@ -457,6 +453,10 @@ function Compose({ } } } + if (files.length > 0 && mediaAttachments.length >= maxMediaAttachments) { + alert(`You can only attach up to ${maxMediaAttachments} files.`); + return; + } console.log({ files }); if (files.length > 0) { e.preventDefault(); @@ -895,7 +895,7 @@ function Compose({ ? 'Edit your status' : 'What are you doing?' } - required={mediaAttachments.length === 0} + required={mediaAttachments?.length === 0} disabled={uiState === 'loading'} lang={language} onInput={() => { @@ -906,7 +906,7 @@ function Compose({ return masto.v2.search(params); }} /> - {mediaAttachments.length > 0 && ( + {mediaAttachments?.length > 0 && (
{mediaAttachments.map((attachment, i) => { const { id, file } = attachment; diff --git a/src/components/icon.jsx b/src/components/icon.jsx index f3f423ec..0529cd0d 100644 --- a/src/components/icon.jsx +++ b/src/components/icon.jsx @@ -66,6 +66,12 @@ const ICONS = { translate: 'mingcute:translate-line', play: 'mingcute:play-fill', trash: 'mingcute:delete-2-line', + mute: 'mingcute:volume-mute-line', + unmute: 'mingcute:volume-line', + block: 'mingcute:forbid-circle-line', + unblock: ['mingcute:forbid-circle-line', '180deg'], + flag: 'mingcute:flag-4-line', + time: 'mingcute:time-line', }; const modules = import.meta.glob('/node_modules/@iconify-icons/mingcute/*.js');