mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-11-02 16:52:25 -06:00 
			
		
		
		
	
		
			
				
	
	
		
			242 lines
		
	
	
	
		
			6.2 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			242 lines
		
	
	
	
		
			6.2 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
/*
 | 
						|
	GoToSocial
 | 
						|
	Copyright (C) GoToSocial Authors admin@gotosocial.org
 | 
						|
	SPDX-License-Identifier: AGPL-3.0-or-later
 | 
						|
 | 
						|
	This program is free software: you can redistribute it and/or modify
 | 
						|
	it under the terms of the GNU Affero General Public License as published by
 | 
						|
	the Free Software Foundation, either version 3 of the License, or
 | 
						|
	(at your option) any later version.
 | 
						|
 | 
						|
	This program is distributed in the hope that it will be useful,
 | 
						|
	but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
						|
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
						|
	GNU Affero General Public License for more details.
 | 
						|
 | 
						|
	You should have received a copy of the GNU Affero General Public License
 | 
						|
	along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
						|
*/
 | 
						|
 | 
						|
import React from "react";
 | 
						|
import { useVerifyCredentialsQuery } from "../lib/query/oauth";
 | 
						|
import { MediaAttachment, Status as StatusType } from "../lib/types/status";
 | 
						|
import sanitize from "sanitize-html";
 | 
						|
 | 
						|
export function FakeStatus({ children }) {
 | 
						|
	const { data: account = {
 | 
						|
		avatar: "/assets/default_avatars/GoToSocial_icon1.webp",
 | 
						|
		display_name: "",
 | 
						|
		username: ""
 | 
						|
	} } = useVerifyCredentialsQuery();
 | 
						|
 | 
						|
	return (
 | 
						|
		<article className="status expanded">
 | 
						|
			<header className="status-header">
 | 
						|
				<address>
 | 
						|
					<a style={{ margin: 0 }}>
 | 
						|
						<img className="avatar" src={account.avatar} alt="" />
 | 
						|
						<dl className="author-strapline">
 | 
						|
							<dt className="sr-only">Display name</dt>
 | 
						|
							<dd className="displayname text-cutoff">
 | 
						|
								{account.display_name.trim().length > 0 ? account.display_name : account.username}
 | 
						|
							</dd>
 | 
						|
							<dt className="sr-only">Username</dt>
 | 
						|
							<dd className="username text-cutoff">@{account.username}</dd>
 | 
						|
						</dl>
 | 
						|
					</a>
 | 
						|
				</address>
 | 
						|
			</header>
 | 
						|
			<section className="status-body">
 | 
						|
				<div className="text">
 | 
						|
					<div className="content">
 | 
						|
						{children}
 | 
						|
					</div>
 | 
						|
				</div>
 | 
						|
			</section>
 | 
						|
		</article>
 | 
						|
	);
 | 
						|
}
 | 
						|
 | 
						|
export function Status({ status }: { status: StatusType }) {
 | 
						|
	return (
 | 
						|
		<article
 | 
						|
			className="status expanded"
 | 
						|
			id={status.id}
 | 
						|
			role="region"
 | 
						|
		>
 | 
						|
			<StatusHeader status={status} />
 | 
						|
			<StatusBody status={status} />
 | 
						|
			<StatusFooter status={status} />
 | 
						|
			<a
 | 
						|
				href={status.url}
 | 
						|
				target="_blank"
 | 
						|
				className="status-link"
 | 
						|
				data-nosnippet
 | 
						|
				title="Open this status (opens in new tab)"
 | 
						|
			>
 | 
						|
				Open this status (opens in new tab)
 | 
						|
			</a>
 | 
						|
		</article>
 | 
						|
	);
 | 
						|
}
 | 
						|
 | 
						|
function StatusHeader({ status }: { status: StatusType }) {
 | 
						|
	const author = status.account;
 | 
						|
 | 
						|
	return (
 | 
						|
		<header className="status-header">
 | 
						|
			<address>
 | 
						|
				<a
 | 
						|
					href={author.url}
 | 
						|
					rel="author"
 | 
						|
					title="Open profile"
 | 
						|
					target="_blank"
 | 
						|
				>
 | 
						|
					<img
 | 
						|
						className="avatar"
 | 
						|
						aria-hidden="true"
 | 
						|
						src={author.avatar}
 | 
						|
						alt={`Avatar for ${author.username}`}
 | 
						|
						title={`Avatar for ${author.username}`}
 | 
						|
					/>
 | 
						|
					<div className="author-strapline">
 | 
						|
						<span className="displayname text-cutoff">{author.display_name}</span>
 | 
						|
						<span className="sr-only">,</span>
 | 
						|
						<span className="username text-cutoff">@{author.acct}</span>
 | 
						|
					</div>
 | 
						|
					<span className="sr-only">(open profile)</span>
 | 
						|
				</a>
 | 
						|
			</address>
 | 
						|
		</header>
 | 
						|
	);
 | 
						|
}
 | 
						|
 | 
						|
function StatusBody({ status }: { status: StatusType }) {
 | 
						|
	let content: string;
 | 
						|
	if (status.content.length === 0) {
 | 
						|
		content = "[no content set]";
 | 
						|
	} else {
 | 
						|
		// HTML has already been through
 | 
						|
		// the instance sanitizer by now,
 | 
						|
		// but do it again just in case.
 | 
						|
		content = sanitize(status.content);
 | 
						|
	}
 | 
						|
 | 
						|
	return (
 | 
						|
		<div className="status-body">
 | 
						|
			<details className="text-spoiler">
 | 
						|
				<summary>
 | 
						|
					<span
 | 
						|
						className="spoiler-text"
 | 
						|
						lang={status.language}
 | 
						|
					>
 | 
						|
						{status.spoiler_text
 | 
						|
							? status.spoiler_text + " "
 | 
						|
							: "[no content warning set] "
 | 
						|
						}
 | 
						|
					</span>
 | 
						|
					<span
 | 
						|
						className="button"
 | 
						|
						role="button"
 | 
						|
						tabIndex={0}
 | 
						|
						aria-label="Toggle content visibility"
 | 
						|
					>
 | 
						|
						Toggle content visibility
 | 
						|
					</span>
 | 
						|
				</summary>
 | 
						|
				<div
 | 
						|
					className="text"
 | 
						|
					dangerouslySetInnerHTML={{ __html: content }}
 | 
						|
				/>
 | 
						|
			</details>
 | 
						|
			<StatusMedia status={status} />
 | 
						|
		</div>
 | 
						|
	);
 | 
						|
}
 | 
						|
 | 
						|
function StatusMedia({ status }: { status: StatusType }) {
 | 
						|
	if (status.media_attachments.length === 0) {
 | 
						|
		return null;
 | 
						|
	}
 | 
						|
 | 
						|
	const count = status.media_attachments.length;
 | 
						|
	const aria_label = count === 1 ? "1 attachment" : `${count} attachments`;
 | 
						|
	const oddOrEven = count % 2 === 0 ? "even" : "odd";
 | 
						|
	const single = count === 1 ? " single" : "";
 | 
						|
 | 
						|
	return (
 | 
						|
		<div
 | 
						|
			className={`media ${oddOrEven}${single}`}
 | 
						|
			role="group"
 | 
						|
			aria-label={aria_label}
 | 
						|
		>
 | 
						|
			{status.media_attachments.map((media) => {
 | 
						|
				return (
 | 
						|
					<StatusMediaEntry
 | 
						|
						key={media.id}
 | 
						|
						media={media}
 | 
						|
					/>
 | 
						|
				);
 | 
						|
			})}
 | 
						|
		</div>
 | 
						|
	);
 | 
						|
}
 | 
						|
 | 
						|
function StatusMediaEntry({ media }: { media: MediaAttachment }) {
 | 
						|
	return (
 | 
						|
		<div className="media-wrapper">
 | 
						|
			<details className="image-spoiler media-spoiler">
 | 
						|
				<summary>
 | 
						|
					<div className="show sensitive button" aria-hidden="true">Show media</div>
 | 
						|
					<span className="eye button" role="button" tabIndex={0} aria-label="Toggle show media">
 | 
						|
						<i className="hide fa fa-fw fa-eye-slash" aria-hidden="true"></i>
 | 
						|
						<i className="show fa fa-fw fa-eye" aria-hidden="true"></i>
 | 
						|
					</span>
 | 
						|
					<img
 | 
						|
						src={media.preview_url}
 | 
						|
						loading="lazy"
 | 
						|
						alt={media.description}
 | 
						|
						title={media.description}
 | 
						|
						width={media.meta.small.width}
 | 
						|
						height={media.meta.small.height}
 | 
						|
					/>
 | 
						|
				</summary>
 | 
						|
				<a
 | 
						|
					href={media.url}
 | 
						|
					target="_blank"
 | 
						|
				>
 | 
						|
					<img
 | 
						|
						src={media.url}
 | 
						|
						loading="lazy"
 | 
						|
						alt={media.description}
 | 
						|
						width={media.meta.original.width}
 | 
						|
						height={media.meta.original.height}
 | 
						|
					/>
 | 
						|
				</a>
 | 
						|
			</details>
 | 
						|
		</div>
 | 
						|
	);
 | 
						|
}
 | 
						|
 | 
						|
function StatusFooter({ status }: { status: StatusType }) {
 | 
						|
	return (
 | 
						|
		<aside className="status-info">
 | 
						|
			<dl className="status-stats">
 | 
						|
				<div className="stats-grouping">
 | 
						|
					<div className="stats-item published-at text-cutoff">
 | 
						|
						<dt className="sr-only">Published</dt>
 | 
						|
						<dd>
 | 
						|
							<time dateTime={status.created_at}>
 | 
						|
								{new Date(status.created_at).toLocaleString()}
 | 
						|
							</time>
 | 
						|
						</dd>
 | 
						|
					</div>
 | 
						|
				</div>
 | 
						|
				<div className="stats-item language">
 | 
						|
					<dt className="sr-only">Language</dt>
 | 
						|
					<dd>{status.language}</dd>
 | 
						|
				</div>
 | 
						|
			</dl>
 | 
						|
		</aside>
 | 
						|
	);
 | 
						|
}
 |