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 && (
+ <>
+
+
+ >
+ )}
+
+
+ {!!relationship && (
+ <>
+
+ {muting ? (
+
+ ) : (
+
+
+
+
+
+
+
+ >
+ }
+ >
+
+
+ )}
+
+ {/* */}
+ >
+ )}
+
+ {!!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');