mirror of
https://github.com/cheeaun/phanpy.git
synced 2024-11-26 11:15:43 +03:00
Add account info into Account statuses page
This commit is contained in:
parent
b4f8f92431
commit
6fd9c106c6
8 changed files with 401 additions and 194 deletions
|
@ -16,7 +16,7 @@ import {
|
||||||
} from 'react-router-dom';
|
} from 'react-router-dom';
|
||||||
import { useSnapshot } from 'valtio';
|
import { useSnapshot } from 'valtio';
|
||||||
|
|
||||||
import Account from './components/account';
|
import AccountSheet from './components/account-sheet';
|
||||||
import Compose from './components/compose';
|
import Compose from './components/compose';
|
||||||
import Drafts from './components/drafts';
|
import Drafts from './components/drafts';
|
||||||
import Loader from './components/loader';
|
import Loader from './components/loader';
|
||||||
|
@ -409,7 +409,7 @@ function App() {
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Account
|
<AccountSheet
|
||||||
account={snapStates.showAccount?.account || snapStates.showAccount}
|
account={snapStates.showAccount?.account || snapStates.showAccount}
|
||||||
instance={snapStates.showAccount?.instance}
|
instance={snapStates.showAccount?.instance}
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import './account-block.css';
|
import './account-block.css';
|
||||||
|
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
import emojifyText from '../utils/emojify-text';
|
import emojifyText from '../utils/emojify-text';
|
||||||
import niceDateTime from '../utils/nice-date-time';
|
import niceDateTime from '../utils/nice-date-time';
|
||||||
import states from '../utils/states';
|
import states from '../utils/states';
|
||||||
|
@ -12,6 +14,7 @@ function AccountBlock({
|
||||||
avatarSize = 'xl',
|
avatarSize = 'xl',
|
||||||
instance,
|
instance,
|
||||||
external,
|
external,
|
||||||
|
internal,
|
||||||
onClick,
|
onClick,
|
||||||
showActivity = false,
|
showActivity = false,
|
||||||
}) {
|
}) {
|
||||||
|
@ -22,13 +25,16 @@ function AccountBlock({
|
||||||
<span>
|
<span>
|
||||||
<b>████████</b>
|
<b>████████</b>
|
||||||
<br />
|
<br />
|
||||||
@██████
|
<span class="account-block-acct">@██████</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
id,
|
||||||
acct,
|
acct,
|
||||||
avatar,
|
avatar,
|
||||||
avatarStatic,
|
avatarStatic,
|
||||||
|
@ -40,6 +46,7 @@ function AccountBlock({
|
||||||
lastStatusAt,
|
lastStatusAt,
|
||||||
} = account;
|
} = account;
|
||||||
const displayNameWithEmoji = emojifyText(displayName, emojis);
|
const displayNameWithEmoji = emojifyText(displayName, emojis);
|
||||||
|
const [_, acct1, acct2] = acct.match(/([^@]+)(@.+)/i) || [, acct];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<a
|
<a
|
||||||
|
@ -51,10 +58,14 @@ function AccountBlock({
|
||||||
if (external) return;
|
if (external) return;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (onClick) return onClick(e);
|
if (onClick) return onClick(e);
|
||||||
|
if (internal) {
|
||||||
|
navigate(`/${instance}/a/${id}`);
|
||||||
|
} else {
|
||||||
states.showAccount = {
|
states.showAccount = {
|
||||||
account,
|
account,
|
||||||
instance,
|
instance,
|
||||||
};
|
};
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Avatar url={avatar} size={avatarSize} />
|
<Avatar url={avatar} size={avatarSize} />
|
||||||
|
@ -68,7 +79,12 @@ function AccountBlock({
|
||||||
) : (
|
) : (
|
||||||
<b>{username}</b>
|
<b>{username}</b>
|
||||||
)}
|
)}
|
||||||
<br />@{acct}
|
<br />
|
||||||
|
<span class="account-block-acct">
|
||||||
|
@{acct1}
|
||||||
|
<wbr />
|
||||||
|
{acct2}
|
||||||
|
</span>
|
||||||
{showActivity && (
|
{showActivity && (
|
||||||
<>
|
<>
|
||||||
<br />
|
<br />
|
||||||
|
|
225
src/components/account-info.css
Normal file
225
src/components/account-info.css
Normal file
|
@ -0,0 +1,225 @@
|
||||||
|
.account-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.account-container.skeleton {
|
||||||
|
color: var(--outline-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.account-container .header-banner {
|
||||||
|
/* pointer-events: none; */
|
||||||
|
aspect-ratio: 6 / 1;
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
object-fit: cover;
|
||||||
|
/* mask fade out bottom of banner */
|
||||||
|
mask-image: linear-gradient(
|
||||||
|
to bottom,
|
||||||
|
hsl(0, 0%, 0%) 0%,
|
||||||
|
hsla(0, 0%, 0%, 0.987) 14%,
|
||||||
|
hsla(0, 0%, 0%, 0.951) 26.2%,
|
||||||
|
hsla(0, 0%, 0%, 0.896) 36.8%,
|
||||||
|
hsla(0, 0%, 0%, 0.825) 45.9%,
|
||||||
|
hsla(0, 0%, 0%, 0.741) 53.7%,
|
||||||
|
hsla(0, 0%, 0%, 0.648) 60.4%,
|
||||||
|
hsla(0, 0%, 0%, 0.55) 66.2%,
|
||||||
|
hsla(0, 0%, 0%, 0.45) 71.2%,
|
||||||
|
hsla(0, 0%, 0%, 0.352) 75.6%,
|
||||||
|
hsla(0, 0%, 0%, 0.259) 79.6%,
|
||||||
|
hsla(0, 0%, 0%, 0.175) 83.4%,
|
||||||
|
hsla(0, 0%, 0%, 0.104) 87.2%,
|
||||||
|
hsla(0, 0%, 0%, 0.049) 91.1%,
|
||||||
|
hsla(0, 0%, 0%, 0.013) 95.3%,
|
||||||
|
hsla(0, 0%, 0%, 0) 100%
|
||||||
|
);
|
||||||
|
margin-bottom: -44px;
|
||||||
|
}
|
||||||
|
.account-container .header-banner:hover {
|
||||||
|
animation: position-object 5s ease-in-out 1s 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-height: 480px) {
|
||||||
|
.account-container .header-banner {
|
||||||
|
aspect-ratio: 3 / 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.account-container header {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
text-shadow: -8px 0 12px -6px var(--bg-color), 8px 0 12px -6px var(--bg-color),
|
||||||
|
-8px 0 24px var(--header-color-3, --bg-color),
|
||||||
|
8px 0 24px var(--header-color-4, --bg-color);
|
||||||
|
}
|
||||||
|
.account-container header .avatar {
|
||||||
|
box-shadow: -8px 0 24px var(--header-color-3, --bg-color),
|
||||||
|
8px 0 24px var(--header-color-4, --bg-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.account-container .note {
|
||||||
|
font-size: 95%;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
.account-container .note:not(:has(p)):not(:empty) {
|
||||||
|
/* Some notes don't have <p> tags, so we need to add some padding */
|
||||||
|
padding: 1em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.account-container .stats {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: space-around;
|
||||||
|
gap: 16px;
|
||||||
|
opacity: 0.75;
|
||||||
|
font-size: 90%;
|
||||||
|
background-color: var(--bg-faded-color);
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 8px;
|
||||||
|
line-height: 1.25;
|
||||||
|
}
|
||||||
|
.account-container .stats > * {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.account-container .stats a {
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.account-container .actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
justify-content: space-between;
|
||||||
|
min-height: 2.5em;
|
||||||
|
}
|
||||||
|
.account-container .actions button {
|
||||||
|
align-self: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.account-container .profile-metadata {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
.account-container .profile-field {
|
||||||
|
min-width: 0;
|
||||||
|
flex-grow: 1;
|
||||||
|
font-size: 90%;
|
||||||
|
background-color: var(--bg-faded-color);
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 8px;
|
||||||
|
filter: saturate(0.75);
|
||||||
|
line-height: 1.25;
|
||||||
|
}
|
||||||
|
|
||||||
|
.account-container :is(.note, .profile-field) .invisible {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.account-container :is(.note, .profile-field) .ellipsis::after {
|
||||||
|
content: '…';
|
||||||
|
}
|
||||||
|
|
||||||
|
.account-container .profile-field b {
|
||||||
|
font-size: 90%;
|
||||||
|
color: var(--text-insignificant-color);
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
.account-container .profile-field b .icon {
|
||||||
|
color: var(--green-color);
|
||||||
|
}
|
||||||
|
.account-container .profile-field p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.account-container .common-followers {
|
||||||
|
border-top: 1px solid var(--outline-color);
|
||||||
|
border-bottom: 1px solid var(--outline-color);
|
||||||
|
padding: 8px 0;
|
||||||
|
font-size: 90%;
|
||||||
|
line-height: 1.5;
|
||||||
|
color: var(--text-insignificant-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-start .account-container {
|
||||||
|
border-bottom: 1px solid var(--outline-color);
|
||||||
|
}
|
||||||
|
.timeline-start .account-container :is(header, main) {
|
||||||
|
padding: 16px 16px 4px;
|
||||||
|
}
|
||||||
|
.timeline-start .account-container .account-block .account-block-acct {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
.timeline-start .account-container .actions {
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes shine {
|
||||||
|
0% {
|
||||||
|
left: -100%;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
left: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.timeline-start .account-container {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.timeline-start .account-container:before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
z-index: 2;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-image: linear-gradient(
|
||||||
|
100deg,
|
||||||
|
rgba(255, 255, 255, 0) 30%,
|
||||||
|
rgba(255, 255, 255, 0.25),
|
||||||
|
rgba(255, 255, 255, 0) 70%
|
||||||
|
);
|
||||||
|
top: 0;
|
||||||
|
left: -100%;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.timeline-start .account-container:hover:before {
|
||||||
|
animation: shine 1s ease-in-out 1s;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 40em) {
|
||||||
|
.timeline-start .account-container {
|
||||||
|
--item-radius: 16px;
|
||||||
|
border: 1px solid var(--divider-color);
|
||||||
|
margin: 16px 0;
|
||||||
|
background-color: var(--bg-color);
|
||||||
|
border-radius: var(--item-radius);
|
||||||
|
overflow: hidden;
|
||||||
|
/* box-shadow: 0px 1px var(--bg-blur-color), 0 0 64px var(--bg-color); */
|
||||||
|
--shadow-offset: 16px;
|
||||||
|
--shadow-blur: 32px;
|
||||||
|
--shadow-spread: calc(var(--shadow-blur) * -0.75);
|
||||||
|
box-shadow: calc(var(--shadow-offset) * -1) var(--shadow-offset)
|
||||||
|
var(--shadow-blur) var(--shadow-spread)
|
||||||
|
var(--header-color-1, var(--drop-shadow-color)),
|
||||||
|
var(--shadow-offset) var(--shadow-offset) var(--shadow-blur)
|
||||||
|
var(--shadow-spread) var(--header-color-2, var(--drop-shadow-color));
|
||||||
|
}
|
||||||
|
.timeline-start .account-container .header-banner {
|
||||||
|
margin-bottom: -77px;
|
||||||
|
}
|
||||||
|
.timeline-start .account-container header .account-block {
|
||||||
|
font-size: 175%;
|
||||||
|
margin-bottom: -8px;
|
||||||
|
line-height: 1.1;
|
||||||
|
letter-spacing: -1px;
|
||||||
|
mix-blend-mode: multiply;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
.timeline-start .account-container header .account-block .avatar {
|
||||||
|
width: 112px !important;
|
||||||
|
height: 112px !important;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,6 @@
|
||||||
import './account.css';
|
import './account-info.css';
|
||||||
|
|
||||||
import { useEffect, useRef, useState } from 'preact/hooks';
|
import { useEffect, useRef, useState } from 'preact/hooks';
|
||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
|
||||||
|
|
||||||
import { api } from '../utils/api';
|
import { api } from '../utils/api';
|
||||||
import emojifyText from '../utils/emojify-text';
|
import emojifyText from '../utils/emojify-text';
|
||||||
|
@ -17,49 +16,32 @@ import Avatar from './avatar';
|
||||||
import Icon from './icon';
|
import Icon from './icon';
|
||||||
import Link from './link';
|
import Link from './link';
|
||||||
|
|
||||||
function Account({ account, instance: propInstance, onClose }) {
|
function AccountInfo({
|
||||||
const { masto, instance, authenticated } = api({ instance: propInstance });
|
account,
|
||||||
|
fetchAccount = () => {},
|
||||||
|
standalone,
|
||||||
|
instance,
|
||||||
|
authenticated,
|
||||||
|
}) {
|
||||||
const [uiState, setUIState] = useState('default');
|
const [uiState, setUIState] = useState('default');
|
||||||
const isString = typeof account === 'string';
|
const isString = typeof account === 'string';
|
||||||
const [info, setInfo] = useState(isString ? null : account);
|
const [info, setInfo] = useState(isString ? null : account);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isString) {
|
if (!isString) return;
|
||||||
setUIState('loading');
|
setUIState('loading');
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
const info = await masto.v1.accounts.lookup({
|
const info = await fetchAccount();
|
||||||
acct: account,
|
|
||||||
skip_webfinger: false,
|
|
||||||
});
|
|
||||||
setInfo(info);
|
setInfo(info);
|
||||||
setUIState('default');
|
setUIState('default');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
try {
|
console.error(e);
|
||||||
const result = await masto.v2.search({
|
|
||||||
q: account,
|
|
||||||
type: 'accounts',
|
|
||||||
limit: 1,
|
|
||||||
resolve: authenticated,
|
|
||||||
});
|
|
||||||
if (result.accounts.length) {
|
|
||||||
setInfo(result.accounts[0]);
|
|
||||||
setUIState('default');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setInfo(null);
|
setInfo(null);
|
||||||
setUIState('error');
|
setUIState('error');
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
setInfo(null);
|
|
||||||
setUIState('error');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
} else {
|
}, [isString, fetchAccount]);
|
||||||
setInfo(account);
|
|
||||||
}
|
|
||||||
}, [account]);
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
acct,
|
acct,
|
||||||
|
@ -84,13 +66,17 @@ function Account({ account, instance: propInstance, onClose }) {
|
||||||
username,
|
username,
|
||||||
} = info || {};
|
} = info || {};
|
||||||
|
|
||||||
const escRef = useHotkeys('esc', onClose, [onClose]);
|
const [headerCornerColors, setHeaderCornerColors] = useState([]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={escRef}
|
class={`account-container ${uiState === 'loading' ? 'skeleton' : ''}`}
|
||||||
id="account-container"
|
style={{
|
||||||
class={`sheet ${uiState === 'loading' ? 'skeleton' : ''}`}
|
'--header-color-1': headerCornerColors[0],
|
||||||
|
'--header-color-2': headerCornerColors[1],
|
||||||
|
'--header-color-3': headerCornerColors[2],
|
||||||
|
'--header-color-4': headerCornerColors[3],
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{uiState === 'error' && (
|
{uiState === 'error' && (
|
||||||
<div class="ui-state">
|
<div class="ui-state">
|
||||||
|
@ -128,7 +114,47 @@ function Account({ account, instance: propInstance, onClose }) {
|
||||||
alt=""
|
alt=""
|
||||||
class="header-banner"
|
class="header-banner"
|
||||||
onError={(e) => {
|
onError={(e) => {
|
||||||
|
if (e.target.crossOrigin) {
|
||||||
|
if (e.target.src !== headerStatic) {
|
||||||
e.target.src = headerStatic;
|
e.target.src = headerStatic;
|
||||||
|
} else {
|
||||||
|
e.target.removeAttribute('crossorigin');
|
||||||
|
e.target.src = header;
|
||||||
|
}
|
||||||
|
} else if (e.target.src !== headerStatic) {
|
||||||
|
e.target.src = headerStatic;
|
||||||
|
} else {
|
||||||
|
e.target.remove();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
crossOrigin="anonymous"
|
||||||
|
onLoad={(e) => {
|
||||||
|
try {
|
||||||
|
// Get color from four corners of image
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
canvas.width = e.target.width;
|
||||||
|
canvas.height = e.target.height;
|
||||||
|
ctx.drawImage(e.target, 0, 0);
|
||||||
|
const colors = [
|
||||||
|
ctx.getImageData(0, 0, 1, 1).data,
|
||||||
|
ctx.getImageData(e.target.width - 1, 0, 1, 1).data,
|
||||||
|
ctx.getImageData(0, e.target.height - 1, 1, 1).data,
|
||||||
|
ctx.getImageData(
|
||||||
|
e.target.width - 1,
|
||||||
|
e.target.height - 1,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
).data,
|
||||||
|
];
|
||||||
|
const rgbColors = colors.map((color) => {
|
||||||
|
return `rgb(${color[0]}, ${color[1]}, ${color[2]}, 0.3)`;
|
||||||
|
});
|
||||||
|
setHeaderCornerColors(rgbColors);
|
||||||
|
console.log({ colors, rgbColors });
|
||||||
|
} catch (e) {
|
||||||
|
// Silently fail
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
@ -137,7 +163,8 @@ function Account({ account, instance: propInstance, onClose }) {
|
||||||
account={info}
|
account={info}
|
||||||
instance={instance}
|
instance={instance}
|
||||||
avatarSize="xxxl"
|
avatarSize="xxxl"
|
||||||
external
|
external={standalone}
|
||||||
|
internal={!standalone}
|
||||||
/>
|
/>
|
||||||
</header>
|
</header>
|
||||||
<main tabIndex="-1">
|
<main tabIndex="-1">
|
||||||
|
@ -429,4 +456,4 @@ function RelatedActions({ info, instance, authenticated }) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Account;
|
export default AccountInfo;
|
56
src/components/account-sheet.jsx
Normal file
56
src/components/account-sheet.jsx
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
|
|
||||||
|
import { api } from '../utils/api';
|
||||||
|
|
||||||
|
import AccountInfo from './account-info';
|
||||||
|
|
||||||
|
function AccountSheet({ account, instance: propInstance, onClose }) {
|
||||||
|
const { masto, instance, authenticated } = api({ instance: propInstance });
|
||||||
|
const isString = typeof account === 'string';
|
||||||
|
|
||||||
|
const escRef = useHotkeys('esc', onClose, [onClose]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
ref={escRef}
|
||||||
|
class="sheet"
|
||||||
|
onClick={(e) => {
|
||||||
|
const accountBlock = e.target.closest('.account-block');
|
||||||
|
if (accountBlock) {
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<AccountInfo
|
||||||
|
instance={instance}
|
||||||
|
authenticated={authenticated}
|
||||||
|
account={account}
|
||||||
|
fetchAccount={async () => {
|
||||||
|
if (isString) {
|
||||||
|
try {
|
||||||
|
const info = await masto.v1.accounts.lookup({
|
||||||
|
acct: account,
|
||||||
|
skip_webfinger: false,
|
||||||
|
});
|
||||||
|
return info;
|
||||||
|
} catch (e) {
|
||||||
|
const result = await masto.v2.search({
|
||||||
|
q: account,
|
||||||
|
type: 'accounts',
|
||||||
|
limit: 1,
|
||||||
|
resolve: authenticated,
|
||||||
|
});
|
||||||
|
if (result.accounts.length) {
|
||||||
|
return result.accounts[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return account;
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AccountSheet;
|
|
@ -1,134 +0,0 @@
|
||||||
#account-container.skeleton {
|
|
||||||
color: var(--outline-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
#account-container .header-banner {
|
|
||||||
/* pointer-events: none; */
|
|
||||||
aspect-ratio: 6 / 1;
|
|
||||||
width: 100%;
|
|
||||||
height: auto;
|
|
||||||
object-fit: cover;
|
|
||||||
/* mask fade out bottom of banner */
|
|
||||||
mask-image: linear-gradient(
|
|
||||||
to bottom,
|
|
||||||
hsl(0, 0%, 0%) 0%,
|
|
||||||
hsla(0, 0%, 0%, 0.987) 14%,
|
|
||||||
hsla(0, 0%, 0%, 0.951) 26.2%,
|
|
||||||
hsla(0, 0%, 0%, 0.896) 36.8%,
|
|
||||||
hsla(0, 0%, 0%, 0.825) 45.9%,
|
|
||||||
hsla(0, 0%, 0%, 0.741) 53.7%,
|
|
||||||
hsla(0, 0%, 0%, 0.648) 60.4%,
|
|
||||||
hsla(0, 0%, 0%, 0.55) 66.2%,
|
|
||||||
hsla(0, 0%, 0%, 0.45) 71.2%,
|
|
||||||
hsla(0, 0%, 0%, 0.352) 75.6%,
|
|
||||||
hsla(0, 0%, 0%, 0.259) 79.6%,
|
|
||||||
hsla(0, 0%, 0%, 0.175) 83.4%,
|
|
||||||
hsla(0, 0%, 0%, 0.104) 87.2%,
|
|
||||||
hsla(0, 0%, 0%, 0.049) 91.1%,
|
|
||||||
hsla(0, 0%, 0%, 0.013) 95.3%,
|
|
||||||
hsla(0, 0%, 0%, 0) 100%
|
|
||||||
);
|
|
||||||
margin-bottom: -44px;
|
|
||||||
}
|
|
||||||
#account-container .header-banner:hover {
|
|
||||||
animation: position-object 5s ease-in-out 1s 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-height: 480px) {
|
|
||||||
#account-container .header-banner {
|
|
||||||
aspect-ratio: 3 / 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#account-container header {
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
text-shadow: 0 0 24px var(--bg-color);
|
|
||||||
}
|
|
||||||
#account-container header .avatar {
|
|
||||||
box-shadow: 0 0 24px var(--bg-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
#account-container .note {
|
|
||||||
font-size: 95%;
|
|
||||||
line-height: 1.4;
|
|
||||||
}
|
|
||||||
#account-container .note:not(:has(p)):not(:empty) {
|
|
||||||
/* Some notes don't have <p> tags, so we need to add some padding */
|
|
||||||
padding: 1em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#account-container .stats {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: space-around;
|
|
||||||
gap: 16px;
|
|
||||||
opacity: 0.75;
|
|
||||||
font-size: 90%;
|
|
||||||
background-color: var(--bg-faded-color);
|
|
||||||
padding: 12px;
|
|
||||||
border-radius: 8px;
|
|
||||||
line-height: 1.25;
|
|
||||||
}
|
|
||||||
#account-container .stats > * {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
#account-container .stats a {
|
|
||||||
color: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
#account-container .actions {
|
|
||||||
display: flex;
|
|
||||||
gap: 8px;
|
|
||||||
justify-content: space-between;
|
|
||||||
min-height: 2.5em;
|
|
||||||
}
|
|
||||||
#account-container .actions button {
|
|
||||||
align-self: flex-end;
|
|
||||||
}
|
|
||||||
|
|
||||||
#account-container .profile-metadata {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
#account-container .profile-field {
|
|
||||||
min-width: 0;
|
|
||||||
flex-grow: 1;
|
|
||||||
font-size: 90%;
|
|
||||||
background-color: var(--bg-faded-color);
|
|
||||||
padding: 12px;
|
|
||||||
border-radius: 8px;
|
|
||||||
filter: saturate(0.75);
|
|
||||||
line-height: 1.25;
|
|
||||||
}
|
|
||||||
|
|
||||||
#account-container :is(.note, .profile-field) .invisible {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
#account-container :is(.note, .profile-field) .ellipsis::after {
|
|
||||||
content: '…';
|
|
||||||
}
|
|
||||||
|
|
||||||
#account-container .profile-field b {
|
|
||||||
font-size: 90%;
|
|
||||||
color: var(--text-insignificant-color);
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
#account-container .profile-field b .icon {
|
|
||||||
color: var(--green-color);
|
|
||||||
}
|
|
||||||
#account-container .profile-field p {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#account-container .common-followers {
|
|
||||||
border-top: 1px solid var(--outline-color);
|
|
||||||
border-bottom: 1px solid var(--outline-color);
|
|
||||||
padding: 8px 0;
|
|
||||||
font-size: 90%;
|
|
||||||
line-height: 1.5;
|
|
||||||
color: var(--text-insignificant-color);
|
|
||||||
}
|
|
|
@ -27,6 +27,7 @@ function Timeline({
|
||||||
checkForUpdatesInterval = 60_000, // 1 minute
|
checkForUpdatesInterval = 60_000, // 1 minute
|
||||||
headerStart,
|
headerStart,
|
||||||
headerEnd,
|
headerEnd,
|
||||||
|
timelineStart,
|
||||||
}) {
|
}) {
|
||||||
const [items, setItems] = useState([]);
|
const [items, setItems] = useState([]);
|
||||||
const [uiState, setUIState] = useState('default');
|
const [uiState, setUIState] = useState('default');
|
||||||
|
@ -292,6 +293,7 @@ function Timeline({
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</header>
|
</header>
|
||||||
|
{!!timelineStart && <div class="timeline-start">{timelineStart}</div>}
|
||||||
{!!items.length ? (
|
{!!items.length ? (
|
||||||
<>
|
<>
|
||||||
<ul class="timeline">
|
<ul class="timeline">
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import { useEffect, useRef, useState } from 'preact/hooks';
|
import { useEffect, useMemo, useRef, useState } from 'preact/hooks';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import { useSnapshot } from 'valtio';
|
import { useSnapshot } from 'valtio';
|
||||||
|
|
||||||
|
import AccountInfo from '../components/account-info';
|
||||||
import Timeline from '../components/timeline';
|
import Timeline from '../components/timeline';
|
||||||
import { api } from '../utils/api';
|
import { api } from '../utils/api';
|
||||||
import emojifyText from '../utils/emojify-text';
|
import emojifyText from '../utils/emojify-text';
|
||||||
|
@ -13,7 +14,7 @@ const LIMIT = 20;
|
||||||
function AccountStatuses() {
|
function AccountStatuses() {
|
||||||
const snapStates = useSnapshot(states);
|
const snapStates = useSnapshot(states);
|
||||||
const { id, ...params } = useParams();
|
const { id, ...params } = useParams();
|
||||||
const { masto, instance } = api({ instance: params.instance });
|
const { masto, instance, authenticated } = api({ instance: params.instance });
|
||||||
const accountStatusesIterator = useRef();
|
const accountStatusesIterator = useRef();
|
||||||
async function fetchAccountStatuses(firstLoad) {
|
async function fetchAccountStatuses(firstLoad) {
|
||||||
const results = [];
|
const results = [];
|
||||||
|
@ -27,7 +28,7 @@ function AccountStatuses() {
|
||||||
pinnedStatuses.forEach((status) => {
|
pinnedStatuses.forEach((status) => {
|
||||||
status._pinned = true;
|
status._pinned = true;
|
||||||
});
|
});
|
||||||
if (pinnedStatuses.length > 1) {
|
if (pinnedStatuses.length >= 3) {
|
||||||
const pinnedStatusesIds = pinnedStatuses.map((status) => status.id);
|
const pinnedStatusesIds = pinnedStatuses.map((status) => status.id);
|
||||||
results.push({
|
results.push({
|
||||||
id: pinnedStatusesIds,
|
id: pinnedStatusesIds,
|
||||||
|
@ -54,7 +55,7 @@ function AccountStatuses() {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const [account, setAccount] = useState({});
|
const [account, setAccount] = useState();
|
||||||
useTitle(
|
useTitle(
|
||||||
`${account?.acct ? '@' + account.acct : 'Posts'}`,
|
`${account?.acct ? '@' + account.acct : 'Posts'}`,
|
||||||
'/:instance?/a/:id',
|
'/:instance?/a/:id',
|
||||||
|
@ -71,7 +72,20 @@ function AccountStatuses() {
|
||||||
})();
|
})();
|
||||||
}, [id]);
|
}, [id]);
|
||||||
|
|
||||||
const { displayName, acct, emojis } = account;
|
const { displayName, acct, emojis } = account || {};
|
||||||
|
|
||||||
|
const TimelineStart = useMemo(
|
||||||
|
() => (
|
||||||
|
<AccountInfo
|
||||||
|
instance={instance}
|
||||||
|
account={id}
|
||||||
|
fetchAccount={() => masto.v1.accounts.fetch(id)}
|
||||||
|
authenticated={authenticated}
|
||||||
|
standalone
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
[id, instance, authenticated],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Timeline
|
<Timeline
|
||||||
|
@ -103,6 +117,7 @@ function AccountStatuses() {
|
||||||
errorText="Unable to load statuses"
|
errorText="Unable to load statuses"
|
||||||
fetchItems={fetchAccountStatuses}
|
fetchItems={fetchAccountStatuses}
|
||||||
boostsCarousel={snapStates.settings.boostsCarousel}
|
boostsCarousel={snapStates.settings.boostsCarousel}
|
||||||
|
timelineStart={TimelineStart}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue