gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

detail.jsx (6790B)


      1 /*
      2 	GoToSocial
      3 	Copyright (C) GoToSocial Authors admin@gotosocial.org
      4 	SPDX-License-Identifier: AGPL-3.0-or-later
      5 
      6 	This program is free software: you can redistribute it and/or modify
      7 	it under the terms of the GNU Affero General Public License as published by
      8 	the Free Software Foundation, either version 3 of the License, or
      9 	(at your option) any later version.
     10 
     11 	This program is distributed in the hope that it will be useful,
     12 	but WITHOUT ANY WARRANTY; without even the implied warranty of
     13 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     14 	GNU Affero General Public License for more details.
     15 
     16 	You should have received a copy of the GNU Affero General Public License
     17 	along with this program.  If not, see <http://www.gnu.org/licenses/>.
     18 */
     19 
     20 "use strict";
     21 
     22 const React = require("react");
     23 const { useRoute, Redirect } = require("wouter");
     24 
     25 const query = require("../../lib/query");
     26 
     27 const FormWithData = require("../../lib/form/form-with-data");
     28 const BackButton = require("../../components/back-button");
     29 
     30 const { useValue, useTextInput } = require("../../lib/form");
     31 const useFormSubmit = require("../../lib/form/submit");
     32 
     33 const { TextArea } = require("../../components/form/inputs");
     34 
     35 const MutationButton = require("../../components/form/mutation-button");
     36 const Username = require("./username");
     37 const { useBaseUrl } = require("../../lib/navigation/util");
     38 
     39 module.exports = function ReportDetail({ }) {
     40 	const baseUrl = useBaseUrl();
     41 	let [_match, params] = useRoute(`${baseUrl}/:reportId`);
     42 	if (params?.reportId == undefined) {
     43 		return <Redirect to={baseUrl} />;
     44 	} else {
     45 		return (
     46 			<div className="report-detail">
     47 				<h1>
     48 					<BackButton to={baseUrl} /> Report Details
     49 				</h1>
     50 				<FormWithData
     51 					dataQuery={query.useGetReportQuery}
     52 					queryArg={params.reportId}
     53 					DataForm={ReportDetailForm}
     54 				/>
     55 			</div>
     56 		);
     57 	}
     58 };
     59 
     60 function ReportDetailForm({ data: report }) {
     61 	const from = report.account;
     62 	const target = report.target_account;
     63 
     64 	return (
     65 		<div className="report detail">
     66 			<div className="usernames">
     67 				<Username user={from} /> reported <Username user={target} />
     68 			</div>
     69 
     70 			{report.action_taken &&
     71 				<div className="info">
     72 					<h3>Resolved by @{report.action_taken_by_account.account.acct}</h3>
     73 					<span className="timestamp">at {new Date(report.action_taken_at).toLocaleString()}</span>
     74 					<br />
     75 					<b>Comment: </b><span>{report.action_taken_comment}</span>
     76 				</div>
     77 			}
     78 
     79 			<div className="info-block">
     80 				<h3>Report info:</h3>
     81 				<div className="details">
     82 					<b>Created: </b>
     83 					<span>{new Date(report.created_at).toLocaleString()}</span>
     84 
     85 					<b>Forwarded: </b> <span>{report.forwarded ? "Yes" : "No"}</span>
     86 					<b>Category: </b> <span>{report.category}</span>
     87 
     88 					<b>Reason: </b>
     89 					{report.comment.length > 0
     90 						? <p>{report.comment}</p>
     91 						: <i className="no-comment">none provided</i>
     92 					}
     93 
     94 				</div>
     95 			</div>
     96 
     97 			{!report.action_taken && <ReportActionForm report={report} />}
     98 
     99 			{
    100 				report.statuses.length > 0 &&
    101 				<div className="info-block">
    102 					<h3>Reported toots ({report.statuses.length}):</h3>
    103 					<div className="reported-toots">
    104 						{report.statuses.map((status) => (
    105 							<ReportedToot key={status.id} toot={status} />
    106 						))}
    107 					</div>
    108 				</div>
    109 			}
    110 		</div>
    111 	);
    112 }
    113 
    114 function ReportActionForm({ report }) {
    115 	const form = {
    116 		id: useValue("id", report.id),
    117 		comment: useTextInput("action_taken_comment")
    118 	};
    119 
    120 	const [submit, result] = useFormSubmit(form, query.useResolveReportMutation(), { changedOnly: false });
    121 
    122 	return (
    123 		<form onSubmit={submit} className="info-block">
    124 			<h3>Resolving this report</h3>
    125 			<p>
    126 				An optional comment can be included while resolving this report.
    127 				Useful for providing an explanation about what action was taken (if any) before the report was marked as resolved.<br />
    128 				<b>This will be visible to the user that created the report!</b>
    129 			</p>
    130 			<TextArea
    131 				field={form.comment}
    132 				label="Comment"
    133 			/>
    134 			<MutationButton label="Resolve" result={result} />
    135 		</form>
    136 	);
    137 }
    138 
    139 function ReportedToot({ toot }) {
    140 	const account = toot.account;
    141 
    142 	return (
    143 		<article className="toot expanded">
    144 			<section className="author">
    145 				<a>
    146 					<img className="avatar" src={account.avatar} alt="" />
    147 					<span className="displayname">
    148 						{account.display_name.trim().length > 0 ? account.display_name : account.username}
    149 						<span className="sr-only">.</span>
    150 					</span>
    151 					<span className="username">@{account.username}</span>
    152 				</a>
    153 			</section>
    154 			<section className="body">
    155 				<div className="text">
    156 					<div className="content">
    157 						{toot.spoiler_text?.length > 0
    158 							? <TootCW content={toot.content} note={toot.spoiler_text} />
    159 							: toot.content
    160 						}
    161 					</div>
    162 				</div>
    163 				{toot.media_attachments?.length > 0 &&
    164 					<TootMedia media={toot.media_attachments} sensitive={toot.sensitive} />
    165 				}
    166 			</section>
    167 			<aside className="info">
    168 				<time dateTime={toot.created_at}>{new Date(toot.created_at).toLocaleString()}</time>
    169 			</aside>
    170 		</article>
    171 	);
    172 }
    173 
    174 function TootCW({ note, content }) {
    175 	const [visible, setVisible] = React.useState(false);
    176 
    177 	function toggleVisible() {
    178 		setVisible(!visible);
    179 	}
    180 
    181 	return (
    182 		<>
    183 			<div className="spoiler">
    184 				<span>{note}</span>
    185 				<label className="button spoiler-label" onClick={toggleVisible}>Show {visible ? "less" : "more"}</label>
    186 			</div>
    187 			{visible && content}
    188 		</>
    189 	);
    190 }
    191 
    192 function TootMedia({ media, sensitive }) {
    193 	let classes = (media.length % 2 == 0) ? "even" : "odd";
    194 	if (media.length == 1) {
    195 		classes += " single";
    196 	}
    197 
    198 	return (
    199 		<div className={`media photoswipe-gallery ${classes}`}>
    200 			{media.map((m) => (
    201 				<div key={m.id} className="media-wrapper">
    202 					{sensitive && <>
    203 						<input id={`sensitiveMedia-${m.id}`} type="checkbox" className="sensitive-checkbox hidden" />
    204 						<div className="sensitive">
    205 							<div className="open">
    206 								<label htmlFor={`sensitiveMedia-${m.id}`} className="button" role="button" tabIndex="0">
    207 									<i className="fa fa-eye-slash" title="Hide sensitive media"></i>
    208 								</label>
    209 							</div>
    210 							<div className="closed" title={m.description}>
    211 								<label htmlFor={`sensitiveMedia-${m.id}`} className="button" role="button" tabIndex="0">
    212 									Show sensitive media
    213 								</label>
    214 							</div>
    215 						</div>
    216 					</>}
    217 					<a
    218 						href={m.url}
    219 						title={m.description}
    220 						target="_blank"
    221 						rel="noreferrer"
    222 						data-cropped="true"
    223 						data-pswp-width={`${m.meta?.original.width}px`}
    224 						data-pswp-height={`${m.meta?.original.height}px`}
    225 					>
    226 						<img
    227 							alt={m.description}
    228 							src={m.url}
    229 							// thumb={m.preview_url}
    230 							size={m.meta?.original}
    231 							type={m.type}
    232 						/>
    233 					</a>
    234 				</div>
    235 			))}
    236 		</div>
    237 	);
    238 }