
* lemmy/main: (35 commits) fix(a11y): Fix non-list item being inside ul list in navbar fix: Fix non-unique ID attribute on re-used element fix: Fix some emoji escape logic fix: Button doesn't need tabindex fix: Fix incorrect function reference fix: Emoji picker can be closed with escape key, other a11y fixes fix: Fix some a11y issues on jump to content button fix: Clarify a comment fix: Fix merge error Remove federation worker count fix: Add triangle alert icon to language warning added litely-compact changed where custom compact code goes added darkly-compact - issue 552 Refactor first load handling Fix issue when navigating awat from settings Give function better name Change function name Make date distance format use correct verbiage Extract date fns setup ...
379 lines
13 KiB
TypeScript
379 lines
13 KiB
TypeScript
import { myAuthRequired } from "@utils/app";
|
|
import { Component, InfernoNode, linkEvent } from "inferno";
|
|
import {
|
|
CreatePrivateMessage,
|
|
CreatePrivateMessageReport,
|
|
DeletePrivateMessage,
|
|
EditPrivateMessage,
|
|
MarkPrivateMessageAsRead,
|
|
Person,
|
|
PrivateMessageView,
|
|
} from "lemmy-js-client";
|
|
import { mdToHtml } from "../../markdown";
|
|
import { I18NextService, UserService } from "../../services";
|
|
import { Icon, Spinner } from "../common/icon";
|
|
import { MomentTime } from "../common/moment-time";
|
|
import { PersonListing } from "../person/person-listing";
|
|
import { PrivateMessageForm } from "./private-message-form";
|
|
|
|
interface PrivateMessageState {
|
|
showReply: boolean;
|
|
showEdit: boolean;
|
|
collapsed: boolean;
|
|
viewSource: boolean;
|
|
showReportDialog: boolean;
|
|
reportReason?: string;
|
|
deleteLoading: boolean;
|
|
readLoading: boolean;
|
|
reportLoading: boolean;
|
|
}
|
|
|
|
interface PrivateMessageProps {
|
|
private_message_view: PrivateMessageView;
|
|
onDelete(form: DeletePrivateMessage): void;
|
|
onMarkRead(form: MarkPrivateMessageAsRead): void;
|
|
onReport(form: CreatePrivateMessageReport): void;
|
|
onCreate(form: CreatePrivateMessage): void;
|
|
onEdit(form: EditPrivateMessage): void;
|
|
}
|
|
|
|
export class PrivateMessage extends Component<
|
|
PrivateMessageProps,
|
|
PrivateMessageState
|
|
> {
|
|
state: PrivateMessageState = {
|
|
showReply: false,
|
|
showEdit: false,
|
|
collapsed: false,
|
|
viewSource: false,
|
|
showReportDialog: false,
|
|
deleteLoading: false,
|
|
readLoading: false,
|
|
reportLoading: false,
|
|
};
|
|
|
|
constructor(props: any, context: any) {
|
|
super(props, context);
|
|
this.handleReplyCancel = this.handleReplyCancel.bind(this);
|
|
}
|
|
|
|
get mine(): boolean {
|
|
return (
|
|
UserService.Instance.myUserInfo?.local_user_view.person.id ==
|
|
this.props.private_message_view.creator.id
|
|
);
|
|
}
|
|
|
|
componentWillReceiveProps(
|
|
nextProps: Readonly<{ children?: InfernoNode } & PrivateMessageProps>
|
|
): void {
|
|
if (this.props != nextProps) {
|
|
this.setState({
|
|
showReply: false,
|
|
showEdit: false,
|
|
collapsed: false,
|
|
viewSource: false,
|
|
showReportDialog: false,
|
|
deleteLoading: false,
|
|
readLoading: false,
|
|
reportLoading: false,
|
|
});
|
|
}
|
|
}
|
|
|
|
render() {
|
|
const message_view = this.props.private_message_view;
|
|
const otherPerson: Person = this.mine
|
|
? message_view.recipient
|
|
: message_view.creator;
|
|
|
|
return (
|
|
<div className="private-message border-top border-light">
|
|
<div>
|
|
<ul className="list-inline mb-0 text-muted small">
|
|
{/* TODO refactor this */}
|
|
<li className="list-inline-item">
|
|
{this.mine
|
|
? I18NextService.i18n.t("to")
|
|
: I18NextService.i18n.t("from")}
|
|
</li>
|
|
<li className="list-inline-item">
|
|
<PersonListing person={otherPerson} />
|
|
</li>
|
|
<li className="list-inline-item">
|
|
<span>
|
|
<MomentTime
|
|
published={message_view.private_message.published}
|
|
updated={message_view.private_message.updated}
|
|
/>
|
|
</span>
|
|
</li>
|
|
<li className="list-inline-item">
|
|
<div
|
|
role="button"
|
|
className="pointer text-monospace"
|
|
onClick={linkEvent(this, this.handleMessageCollapse)}
|
|
>
|
|
{this.state.collapsed ? (
|
|
<Icon icon="plus-square" classes="icon-inline" />
|
|
) : (
|
|
<Icon icon="minus-square" classes="icon-inline" />
|
|
)}
|
|
</div>
|
|
</li>
|
|
</ul>
|
|
{this.state.showEdit && (
|
|
<PrivateMessageForm
|
|
recipient={otherPerson}
|
|
privateMessageView={message_view}
|
|
onEdit={this.props.onEdit}
|
|
onCancel={this.handleReplyCancel}
|
|
/>
|
|
)}
|
|
{!this.state.showEdit && !this.state.collapsed && (
|
|
<div>
|
|
{this.state.viewSource ? (
|
|
<pre>{this.messageUnlessRemoved}</pre>
|
|
) : (
|
|
<div
|
|
className="md-div"
|
|
dangerouslySetInnerHTML={mdToHtml(this.messageUnlessRemoved)}
|
|
/>
|
|
)}
|
|
<ul className="list-inline mb-0 text-muted fw-bold">
|
|
{!this.mine && (
|
|
<>
|
|
<li className="list-inline-item">
|
|
<button
|
|
type="button"
|
|
className="btn btn-link btn-animate text-muted"
|
|
onClick={linkEvent(this, this.handleMarkRead)}
|
|
data-tippy-content={
|
|
message_view.private_message.read
|
|
? I18NextService.i18n.t("mark_as_unread")
|
|
: I18NextService.i18n.t("mark_as_read")
|
|
}
|
|
aria-label={
|
|
message_view.private_message.read
|
|
? I18NextService.i18n.t("mark_as_unread")
|
|
: I18NextService.i18n.t("mark_as_read")
|
|
}
|
|
>
|
|
{this.state.readLoading ? (
|
|
<Spinner />
|
|
) : (
|
|
<Icon
|
|
icon="check"
|
|
classes={`icon-inline ${
|
|
message_view.private_message.read &&
|
|
"text-success"
|
|
}`}
|
|
/>
|
|
)}
|
|
</button>
|
|
</li>
|
|
<li className="list-inline-item">{this.reportButton}</li>
|
|
<li className="list-inline-item">
|
|
<button
|
|
type="button"
|
|
className="btn btn-link btn-animate text-muted"
|
|
onClick={linkEvent(this, this.handleReplyClick)}
|
|
data-tippy-content={I18NextService.i18n.t("reply")}
|
|
aria-label={I18NextService.i18n.t("reply")}
|
|
>
|
|
<Icon icon="reply1" classes="icon-inline" />
|
|
</button>
|
|
</li>
|
|
</>
|
|
)}
|
|
{this.mine && (
|
|
<>
|
|
<li className="list-inline-item">
|
|
<button
|
|
type="button"
|
|
className="btn btn-link btn-animate text-muted"
|
|
onClick={linkEvent(this, this.handleEditClick)}
|
|
data-tippy-content={I18NextService.i18n.t("edit")}
|
|
aria-label={I18NextService.i18n.t("edit")}
|
|
>
|
|
<Icon icon="edit" classes="icon-inline" />
|
|
</button>
|
|
</li>
|
|
<li className="list-inline-item">
|
|
<button
|
|
type="button"
|
|
className="btn btn-link btn-animate text-muted"
|
|
onClick={linkEvent(this, this.handleDeleteClick)}
|
|
data-tippy-content={
|
|
!message_view.private_message.deleted
|
|
? I18NextService.i18n.t("delete")
|
|
: I18NextService.i18n.t("restore")
|
|
}
|
|
aria-label={
|
|
!message_view.private_message.deleted
|
|
? I18NextService.i18n.t("delete")
|
|
: I18NextService.i18n.t("restore")
|
|
}
|
|
>
|
|
{this.state.deleteLoading ? (
|
|
<Spinner />
|
|
) : (
|
|
<Icon
|
|
icon="trash"
|
|
classes={`icon-inline ${
|
|
message_view.private_message.deleted &&
|
|
"text-danger"
|
|
}`}
|
|
/>
|
|
)}
|
|
</button>
|
|
</li>
|
|
</>
|
|
)}
|
|
<li className="list-inline-item">
|
|
<button
|
|
type="button"
|
|
className="btn btn-link btn-animate text-muted"
|
|
onClick={linkEvent(this, this.handleViewSource)}
|
|
data-tippy-content={I18NextService.i18n.t("view_source")}
|
|
aria-label={I18NextService.i18n.t("view_source")}
|
|
>
|
|
<Icon
|
|
icon="file-text"
|
|
classes={`icon-inline ${
|
|
this.state.viewSource && "text-success"
|
|
}`}
|
|
/>
|
|
</button>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
)}
|
|
</div>
|
|
{this.state.showReportDialog && (
|
|
<form
|
|
className="form-inline"
|
|
onSubmit={linkEvent(this, this.handleReportSubmit)}
|
|
>
|
|
<label className="visually-hidden" htmlFor="pm-report-reason">
|
|
{I18NextService.i18n.t("reason")}
|
|
</label>
|
|
<input
|
|
type="text"
|
|
id="pm-report-reason"
|
|
className="form-control me-2"
|
|
placeholder={I18NextService.i18n.t("reason")}
|
|
required
|
|
value={this.state.reportReason}
|
|
onInput={linkEvent(this, this.handleReportReasonChange)}
|
|
/>
|
|
<button
|
|
type="submit"
|
|
className="btn btn-secondary"
|
|
aria-label={I18NextService.i18n.t("create_report")}
|
|
>
|
|
{this.state.reportLoading ? (
|
|
<Spinner />
|
|
) : (
|
|
I18NextService.i18n.t("create_report")
|
|
)}
|
|
</button>
|
|
</form>
|
|
)}
|
|
{this.state.showReply && (
|
|
<div className="row">
|
|
<div className="col-sm-6">
|
|
<PrivateMessageForm
|
|
privateMessageView={message_view}
|
|
replyType={true}
|
|
recipient={otherPerson}
|
|
onCreate={this.props.onCreate}
|
|
onCancel={this.handleReplyCancel}
|
|
/>
|
|
</div>
|
|
</div>
|
|
)}
|
|
{/* A collapsed clearfix */}
|
|
{this.state.collapsed && <div className="row col-12"></div>}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
get reportButton() {
|
|
return (
|
|
<button
|
|
type="button"
|
|
className="btn btn-link btn-animate text-muted py-0"
|
|
onClick={linkEvent(this, this.handleShowReportDialog)}
|
|
data-tippy-content={I18NextService.i18n.t("show_report_dialog")}
|
|
aria-label={I18NextService.i18n.t("show_report_dialog")}
|
|
>
|
|
<Icon icon="flag" inline />
|
|
</button>
|
|
);
|
|
}
|
|
|
|
get messageUnlessRemoved(): string {
|
|
const message = this.props.private_message_view.private_message;
|
|
return message.deleted
|
|
? `*${I18NextService.i18n.t("deleted")}*`
|
|
: message.content;
|
|
}
|
|
|
|
handleReplyClick(i: PrivateMessage) {
|
|
i.setState({ showReply: true });
|
|
}
|
|
|
|
handleEditClick(i: PrivateMessage) {
|
|
i.setState({ showEdit: true });
|
|
i.setState(i.state);
|
|
}
|
|
|
|
handleDeleteClick(i: PrivateMessage) {
|
|
i.setState({ deleteLoading: true });
|
|
i.props.onDelete({
|
|
private_message_id: i.props.private_message_view.private_message.id,
|
|
deleted: !i.props.private_message_view.private_message.deleted,
|
|
auth: myAuthRequired(),
|
|
});
|
|
}
|
|
|
|
handleReplyCancel() {
|
|
this.setState({ showReply: false, showEdit: false });
|
|
}
|
|
|
|
handleMarkRead(i: PrivateMessage) {
|
|
i.setState({ readLoading: true });
|
|
i.props.onMarkRead({
|
|
private_message_id: i.props.private_message_view.private_message.id,
|
|
read: !i.props.private_message_view.private_message.read,
|
|
auth: myAuthRequired(),
|
|
});
|
|
}
|
|
|
|
handleMessageCollapse(i: PrivateMessage) {
|
|
i.setState({ collapsed: !i.state.collapsed });
|
|
}
|
|
|
|
handleViewSource(i: PrivateMessage) {
|
|
i.setState({ viewSource: !i.state.viewSource });
|
|
}
|
|
|
|
handleShowReportDialog(i: PrivateMessage) {
|
|
i.setState({ showReportDialog: !i.state.showReportDialog });
|
|
}
|
|
|
|
handleReportReasonChange(i: PrivateMessage, event: any) {
|
|
i.setState({ reportReason: event.target.value });
|
|
}
|
|
|
|
handleReportSubmit(i: PrivateMessage, event: any) {
|
|
event.preventDefault();
|
|
i.setState({ reportLoading: true });
|
|
i.props.onReport({
|
|
private_message_id: i.props.private_message_view.private_message.id,
|
|
reason: i.state.reportReason ?? "",
|
|
auth: myAuthRequired(),
|
|
});
|
|
}
|
|
}
|