55 Commits

Author SHA1 Message Date
Paul Makles
21175dffda fix: check if content actually exists
chore: update lang submodule
2023-02-24 13:53:56 +01:00
Paul Makles
5b31ce494e merge: remote-tracking branch 'origin/production' 2023-02-23 20:08:51 +01:00
Paul Makles
056ec623cb chore: move date forwards 2023-02-23 19:12:26 +01:00
Paul Makles
ff0330ec1b feat(modal): add report success modal (also allows blocking user) 2023-02-23 17:52:47 +01:00
Paul Makles
58f35a2813 feat: add changelog entry for in-app reporting 2023-02-22 18:34:35 +01:00
Paul Makles
a00d554f80 feat: render shadows and markdown in changelogs 2023-02-22 18:33:38 +01:00
Paul Makles
7f911f5d2c chore: always build highmem 2023-02-22 17:59:38 +01:00
Paul Makles
122f047c85 fix(modal): trigger on keydown instead of keyup to prevent interference 2023-02-22 17:59:26 +01:00
Paul Makles
7d544c82ab feat: add reporting UI 2023-02-22 17:13:43 +01:00
Paul Makles
46eec5a132 fix: do not trigger modal submission on <select> 2023-02-22 17:00:47 +01:00
Paul Makles
de85527cd8 chore: bump submodules 2023-02-22 15:01:17 +01:00
kate
6062a361f1 fix: allow dash char in autocorrect content validation (#838) 2023-02-05 13:45:32 +00:00
Paul Makles
95d4f61d7f feat: support Streamable embeds 2023-01-29 17:34:07 +00:00
Paul Makles
34ce1d1a86 chore: bump submodule dependencies 2023-01-29 17:33:42 +00:00
insertish
a9f23fe0e3 ci: synced local '.github/workflows/triage_pr.yml' with remote 'workflows/triage_pr.yml'
[skip ci]
2023-01-24 19:43:32 +00:00
insertish
6c08ccff52 ci: synced local '.github/workflows/triage_issue.yml' with remote 'workflows/triage_issue.yml'
[skip ci]
2023-01-24 19:43:32 +00:00
Leda
2f43a2c32d fix: category title input reverts to span on empty string (#699)
Fixes https://github.com/revoltchat/revite/issues/693
2023-01-24 17:59:31 +00:00
cheneyni-451
e34c5c99fe fix(ui): add margin to delete role button (#802)
Co-authored-by: Cheney Ni <cheneyni@umich.edu>
2023-01-24 17:56:50 +00:00
kate
89b3c9c098 fix: dash ("-" char) in emoji names (#816) 2023-01-24 17:55:17 +00:00
Sophie L
dadf0b6329 fix: update draft check (#830) 2023-01-24 17:55:04 +00:00
Lea
fcf6812151 feat: allow rolt.chat for relative navigation (#814) 2022-12-05 14:47:04 +00:00
Lea
09be8c9e4f feat: list custom emojis in autocomplete (#809)
* feat: list custom emojis in autocomplete

* fix: properly align emoji name in autocompletion
2022-12-05 14:44:47 +00:00
kate
6767ea1853 fix(ci): typing issues; broken submodules (#826)
* Remove broken submodules

* Fix yarn typecheck

* Add build:deps before typecheck to fix missing dependencies

* fix: minor linting nitpick

Co-authored-by: Sophie L <beartechtalks@gmail.com>
2022-12-05 14:42:43 +00:00
Paul Makles
9be4afe241 fix: allow setting custom remote for publish 2022-11-06 13:26:41 +00:00
Paul Makles
7e89dcfb13 chore: add notice for iCloud users 2022-11-06 13:21:06 +00:00
Paul Makles
3836156f3d merge: branch 'master' into production 2022-10-23 21:09:49 +01:00
Paul Makles
40d3356c1b merge: branch 'master' into production 2022-09-20 19:12:25 +01:00
Paul Makles
df20ab7407 merge: branch 'master' into production 2022-09-20 18:44:51 +01:00
Paul Makles
1621e3b17d merge: branch 'master' into production 2022-09-18 12:32:36 +01:00
Paul Makles
7ba2388de4 merge: branch 'master' into production 2022-09-18 10:24:23 +01:00
Paul Makles
1a9a2786bb merge: branch 'master' into production 2022-09-17 13:02:15 +01:00
Paul Makles
8fd6963f38 merge: branch 'master' into production 2022-09-16 18:47:09 +01:00
Paul Makles
1016d80e56 merge: branch 'master' into production 2022-09-08 21:28:25 +01:00
Paul Makles
263d97853a merge: branch 'master' into production 2022-09-08 17:22:00 +01:00
Paul Makles
1c69756383 merge: branch 'master' into production 2022-09-03 09:19:04 +01:00
Paul Makles
14037ef0c7 merge: branch 'master' into production 2022-09-02 16:29:27 +01:00
Paul Makles
8608257066 merge: branch 'master' into production 2022-09-02 14:42:47 +01:00
Paul Makles
88bc8f93b6 merge: branch 'master' into production 2022-09-01 14:04:27 +01:00
Paul Makles
ff41219cf4 Merge branch 'master' into production 2022-08-08 16:08:12 +01:00
Paul Makles
f76e53649b Merge branch 'master' into production 2022-08-08 15:30:21 +01:00
Paul Makles
c9264e1a49 Merge branch 'master' into production 2022-08-08 15:22:17 +01:00
Paul Makles
b2e0b5a035 merge: branch 'master' into production 2022-07-31 12:20:41 +02:00
Paul Makles
11e8cd652d merge: branch 'master' into production 2022-07-30 12:30:07 +02:00
Paul Makles
9a70bb0eff merge: branch 'master' into production 2022-07-18 13:07:04 +01:00
Paul Makles
791f4dfd89 chore: bump lang submodule 2022-07-18 13:04:03 +01:00
Paul Makles
3fbf315587 fix: add LoadSuspense around verification page 2022-07-18 13:02:40 +01:00
Paul Makles
9807ef9c9a fix: match optional whitespace in quote regex 2022-07-18 12:59:17 +01:00
Paul Makles
96017c5f33 merge: branch 'master' into production 2022-07-16 15:18:06 +01:00
Paul Makles
176c7883c8 merge: branch 'master' into production 2022-07-15 21:51:24 +01:00
Paul Makles
70bda88383 merge: branch 'master' into production 2022-07-14 17:14:12 +01:00
Paul Makles
c9066aba2d merge: branch 'master' into production 2022-07-13 13:06:47 +01:00
Paul Makles
3204d176de merge: branch 'master' into production 2022-07-13 12:57:07 +01:00
Paul Makles
57887dc86f merge: branch 'master' into production 2022-07-13 12:53:58 +01:00
Paul Makles
4541a34cef Merge branch 'master' into production 2022-05-19 13:42:15 +01:00
Paul Makles
83a3585940 chore: add notice 2022-05-07 10:45:18 +01:00
30 changed files with 509 additions and 90 deletions

View File

@@ -15,22 +15,27 @@ jobs:
gh api graphql -f query='
query {
organization(login: "revoltchat"){
projectNext(number: 3) {
projectV2(number: 3) {
id
fields(first:20) {
nodes {
id
name
settings
... on ProjectV2SingleSelectField {
id
name
options {
id
name
}
}
}
}
}
}
}' > project_data.json
echo 'PROJECT_ID='$(jq '.data.organization.projectNext.id' project_data.json) >> $GITHUB_ENV
echo 'STATUS_FIELD_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name== "Status") | .id' project_data.json) >> $GITHUB_ENV
echo 'TODO_OPTION_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name== "Status") |.settings | fromjson.options[] | select(.name=="Todo") |.id' project_data.json) >> $GITHUB_ENV
echo 'PROJECT_ID='$(jq '.data.organization.projectV2.id' project_data.json) >> $GITHUB_ENV
echo 'STATUS_FIELD_ID='$(jq '.data.organization.projectV2.fields.nodes[] | select(.name== "Status") | .id' project_data.json) >> $GITHUB_ENV
echo 'TODO_OPTION_ID='$(jq '.data.organization.projectV2.fields.nodes[] | select(.name== "Status") | .options[] | select(.name=="Todo") |.id' project_data.json) >> $GITHUB_ENV
- name: Add issue to project
env:
@@ -39,11 +44,11 @@ jobs:
run: |
item_id="$( gh api graphql -f query='
mutation($project:ID!, $issue:ID!) {
addProjectNextItem(input: {projectId: $project, contentId: $issue}) {
projectNextItem {
addProjectV2ItemById(input: {projectId: $project, contentId: $issue}) {
item {
id
}
}
}' -f project=$PROJECT_ID -f issue=$ISSUE_ID --jq '.data.addProjectNextItem.projectNextItem.id')"
}' -f project=$PROJECT_ID -f issue=$ISSUE_ID --jq '.data.addProjectV2ItemById.item.id')"
echo 'ITEM_ID='$item_id >> $GITHUB_ENV

View File

@@ -15,22 +15,27 @@ jobs:
gh api graphql -f query='
query {
organization(login: "revoltchat"){
projectNext(number: 3) {
projectV2(number: 3) {
id
fields(first:20) {
nodes {
id
name
settings
... on ProjectV2SingleSelectField {
id
name
options {
id
name
}
}
}
}
}
}
}' > project_data.json
echo 'PROJECT_ID='$(jq '.data.organization.projectNext.id' project_data.json) >> $GITHUB_ENV
echo 'STATUS_FIELD_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name== "Status") | .id' project_data.json) >> $GITHUB_ENV
echo 'INCOMING_OPTION_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name== "Status") |.settings | fromjson.options[] | select(.name=="Incoming PRs") |.id' project_data.json) >> $GITHUB_ENV
echo 'PROJECT_ID='$(jq '.data.organization.projectV2.id' project_data.json) >> $GITHUB_ENV
echo 'STATUS_FIELD_ID='$(jq '.data.organization.projectV2.fields.nodes[] | select(.name== "Status") | .id' project_data.json) >> $GITHUB_ENV
echo 'INCOMING_OPTION_ID='$(jq '.data.organization.projectV2.fields.nodes[] | select(.name== "Status") | .options[] | select(.name=="Incoming PRs") |.id' project_data.json) >> $GITHUB_ENV
- name: Add PR to project
env:
@@ -39,13 +44,13 @@ jobs:
run: |
item_id="$( gh api graphql -f query='
mutation($project:ID!, $pr:ID!) {
addProjectNextItem(input: {projectId: $project, contentId: $pr}) {
projectNextItem {
addProjectV2ItemById(input: {projectId: $project, contentId: $pr}) {
item {
id
}
}
}' -f project=$PROJECT_ID -f pr=$PR_ID --jq '.data.addProjectNextItem.projectNextItem.id')"
}' -f project=$PROJECT_ID -f pr=$PR_ID --jq '.data.addProjectV2ItemById.item.id')"
echo 'ITEM_ID='$item_id >> $GITHUB_ENV
- name: Set fields
@@ -59,14 +64,16 @@ jobs:
$status_field: ID!
$status_value: String!
) {
set_status: updateProjectNextItemField(input: {
set_status: updateProjectV2ItemFieldValue(input: {
projectId: $project
itemId: $item
fieldId: $status_field
value: $status_value
value: {
singleSelectOptionId: $status_value
}
}) {
projectNextItem {
projectV2Item {
id
}
}
}
}' -f project=$PROJECT_ID -f item=$ITEM_ID -f status_field=$STATUS_FIELD_ID -f status_value=${{ env.INCOMING_OPTION_ID }} --silent

View File

@@ -5,6 +5,7 @@ COPY . .
COPY .env.build .env
RUN yarn install --frozen-lockfile
RUN yarn build:deps
RUN yarn typecheck
RUN yarn build:highmem
RUN yarn workspaces focus --production --all

2
external/lang vendored

Submodule packages/components deleted from d314b2d191

Submodule packages/hast-util-table-cell-style deleted from 7803fa5441

Submodule packages/revolt.js deleted from 39d1f596e2

View File

@@ -2,7 +2,10 @@
# Build and publish release to production server
# Remote Server
REMOTE=revolt-de-nrb-1
if [ -z "$REMOTE" ]; then
echo "Please set REMOTE!"
exit
fi
# Remote Directory
REMOTE_DIR=/root/revite
@@ -18,7 +21,7 @@ export REVOLT_SAAS=https://github.com/revoltchat/assets
set -e
# 1. Build Revite
yarn build
yarn build:highmem
# 2. Archive built files
tar -czvf build.tar.gz dist

View File

@@ -3,6 +3,7 @@ type Element =
| {
type: "image";
src: string;
shadow?: boolean;
};
export interface ChangelogPost {
@@ -29,6 +30,19 @@ export const changelogEntries: Record<number, ChangelogPost> = {
"Other authentication methods coming later, stay tuned!",
],
},
2: {
date: new Date("2023-02-23T20:00:00.000Z"),
title: "In-App Reporting Is Here",
content: [
"You can now report any user, server, or message directly from the app.",
{
type: "image",
src: "https://autumn.revolt.chat/attachments/ZuDVIjGiCl61Pk9XGk5qfc8-idN9EnFAk55DUQp713/the.png",
shadow: true,
},
"If you want to learn more about how we're making Revolt safer for you, check out our new blog post :point_right: [https://revolt.chat/posts/improving-user-safety](https://revolt.chat/posts/improving-user-safety)",
],
},
};
export const changelogEntryArray = Object.keys(changelogEntries).map(

View File

@@ -1,4 +1,7 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
import { Link } from "react-router-dom";
import { Channel, User } from "revolt.js";
import { Emoji as CustomEmoji } from "revolt.js/esm/maps/Emojis";
import styled, { css } from "styled-components/macro";
import { StateUpdater, useState } from "preact/hooks";
@@ -7,6 +10,8 @@ import { emojiDictionary } from "../../assets/emojis";
import { useClient } from "../../controllers/client/ClientController";
import ChannelIcon from "./ChannelIcon";
import Emoji from "./Emoji";
import ServerIcon from "./ServerIcon";
import Tooltip from "./Tooltip";
import UserIcon from "./user/UserIcon";
export type AutoCompleteState =
@@ -14,7 +19,7 @@ export type AutoCompleteState =
| ({ selected: number; within: boolean } & (
| {
type: "emoji";
matches: string[];
matches: (string | CustomEmoji)[];
}
| {
type: "user";
@@ -59,7 +64,7 @@ export function useAutoComplete(
const cursor = el.selectionStart;
const content = el.value.slice(0, cursor);
const valid = /\w/;
const valid = /[\w\-]/;
let j = content.length - 1;
if (content[j] === "@") {
@@ -104,16 +109,23 @@ export function useAutoComplete(
if (type === "emoji") {
// ! TODO: we should convert it to a Binary Search Tree and use that
const matches = Object.keys(emojiDictionary)
.filter((emoji: string) => emoji.match(regex))
.splice(0, 5);
const matches = [
...Object.keys(emojiDictionary).filter((emoji: string) =>
emoji.match(regex),
),
...Array.from(client.emojis.values()).filter((emoji) =>
emoji.name.match(regex),
),
].splice(0, 5);
if (matches.length > 0) {
const currentPosition =
state.type !== "none" ? state.selected : 0;
setState({
// @ts-ignore-next-line are you high
type: "emoji",
// @ts-ignore-next-line
matches,
selected: Math.min(currentPosition, matches.length - 1),
within: false,
@@ -233,10 +245,13 @@ export function useAutoComplete(
const content = el.value.split("");
if (state.type === "emoji") {
const selected = state.matches[state.selected];
content.splice(
index,
search.length,
state.matches[state.selected],
selected instanceof CustomEmoji
? selected._id
: selected,
": ",
);
} else if (state.type === "user") {
@@ -388,12 +403,17 @@ export default function AutoComplete({
setState,
onClick,
}: Pick<AutoCompleteProps, "detached" | "state" | "setState" | "onClick">) {
const client = useClient();
return (
<Base detached={detached}>
<div>
{state.type === "emoji" &&
state.matches.map((match, i) => (
<button
style={{
display: "flex",
justifyContent: "space-between",
}}
key={match}
className={i === state.selected ? "active" : ""}
onMouseEnter={() =>
@@ -412,15 +432,61 @@ export default function AutoComplete({
})
}
onClick={onClick}>
<Emoji
emoji={
(emojiDictionary as Record<string, string>)[
match
]
}
size={20}
/>
:{match}:
<div
style={{
display: "flex",
flexDirection: "row",
justifyContent: "center",
}}>
{match instanceof CustomEmoji ? (
<img
loading="lazy"
src={match.imageURL}
style={{
width: `20px`,
height: `20px`,
}}
/>
) : (
<Emoji
emoji={
(
emojiDictionary as Record<
string,
string
>
)[match]
}
size={20}
/>
)}
<span style={{ paddingLeft: "4px" }}>{`:${
match instanceof CustomEmoji
? match.name
: match
}:`}</span>
</div>
{match instanceof CustomEmoji &&
match.parent.type == "Server" && (
<>
<Tooltip
content={
client.servers.get(
match.parent.id,
)?.name
}>
<Link
to={`/server/${match.parent.id}`}>
<ServerIcon
target={client.servers.get(
match.parent.id,
)}
size={20}
/>
</Link>
</Tooltip>
</>
)}
</button>
))}
{state.type === "user" &&

View File

@@ -23,6 +23,7 @@ import {
} from "../../../lib/renderer/Singleton";
import { state, useApplicationState } from "../../../mobx/State";
import { DraftObject } from "../../../mobx/stores/Draft";
import { Reply } from "../../../mobx/stores/MessageQueue";
import { dayjs } from "../../../context/Locale";
@@ -277,7 +278,12 @@ export default observer(({ channel }: Props) => {
// Push message content to draft.
const setMessage = useCallback(
(content?: string) => state.draft.set(channel._id, content),
(content?: string) => {
const dobj: DraftObject = {
content,
};
state.draft.set(channel._id, dobj);
},
[state.draft, channel._id],
);
@@ -317,7 +323,7 @@ export default observer(({ channel }: Props) => {
if (uploadState.type === "uploading" || uploadState.type === "sending")
return;
const content = state.draft.get(channel._id)?.trim() ?? "";
const content = state.draft.get(channel._id)?.content?.trim() ?? "";
if (uploadState.type === "attached") return sendFile(content);
if (content.length === 0) return;
@@ -526,7 +532,7 @@ export default observer(({ channel }: Props) => {
}
function isInCodeBlock(cursor: number): boolean {
const content = state.draft.get(channel._id) || "";
const content = state.draft.get(channel._id)?.content || "";
const contentBeforeCursor = content.substring(0, cursor);
let delimiterCount = 0;
@@ -607,10 +613,12 @@ export default observer(({ channel }: Props) => {
<HackAlertThisFileWillBeReplaced
onSelect={(emoji) => {
const v = state.draft.get(channel._id);
state.draft.set(
channel._id,
`${v ? `${v} ` : ""}:${emoji}:`,
);
const cnt: DraftObject = {
content:
(v?.content ? `${v.content} ` : "") +
`:${emoji}:`,
};
state.draft.set(channel._id, cnt);
}}
onClose={closePicker}
/>
@@ -664,7 +672,7 @@ export default observer(({ channel }: Props) => {
id="message"
maxLength={2000}
onKeyUp={onKeyUp}
value={state.draft.get(channel._id) ?? ""}
value={state.draft.get(channel._id)?.content ?? ""}
padding="var(--message-box-padding)"
onKeyDown={(e) => {
if (e.ctrlKey && e.key === "Enter") {

View File

@@ -67,7 +67,8 @@ export default function Embed({ embed }: Props) {
break;
}
case "Twitch":
case "Lightspeed": {
case "Lightspeed":
case "Streamable": {
mw = 1280;
mh = 720;
break;

View File

@@ -92,6 +92,16 @@ export default function EmbedMedia({ embed, width, height }: Props) {
/>
);
}
case "Streamable": {
return (
<iframe
src={`https://streamable.com/e/${embed.special.id}?loop=0`}
seamless
loading="lazy"
style={{ height }}
/>
);
}
default: {
if (embed.video) {
const url = embed.video.url;

View File

@@ -25,7 +25,7 @@ const Emoji = styled.img`
}
`;
const RE_EMOJI = /:([a-zA-Z0-9_+]+):/g;
const RE_EMOJI = /:([a-zA-Z0-9\-_]+):/g;
const RE_ULID = /^[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{26}$/;
export function RenderEmoji({ match }: CustomComponentProps) {

View File

@@ -41,6 +41,8 @@ import MFARecovery from "./components/MFARecovery";
import ModifyAccount from "./components/ModifyAccount";
import OutOfDate from "./components/OutOfDate";
import PendingFriendRequests from "./components/PendingFriendRequests";
import ReportContent from "./components/Report";
import ReportSuccess from "./components/ReportSuccess";
import ServerIdentity from "./components/ServerIdentity";
import ServerInfo from "./components/ServerInfo";
import ShowToken from "./components/ShowToken";
@@ -276,4 +278,6 @@ export const modalController = new ModalControllerExtended({
sign_out_sessions: SignOutSessions,
user_picker: UserPicker,
user_profile: UserProfile,
report: ReportContent,
report_success: ReportSuccess,
});

View File

@@ -9,16 +9,17 @@ export default observer(() => {
const history = useHistory();
useEffect(() => {
function keyUp(event: KeyboardEvent) {
function keyDown(event: KeyboardEvent) {
if (event.key === "Escape") {
modalController.pop("close");
} else if (event.key === "Enter") {
if (event.target instanceof HTMLSelectElement) return;
modalController.pop("confirm");
}
}
document.addEventListener("keyup", keyUp);
return () => document.removeEventListener("keyup", keyUp);
document.addEventListener("keydown", keyDown);
return () => document.removeEventListener("keydown", keyDown);
}, []);
return (

View File

@@ -1,5 +1,5 @@
import dayjs from "dayjs";
import styled from "styled-components";
import styled, { css } from "styled-components";
import { Text } from "preact-i18n";
import { useMemo, useState } from "preact/hooks";
@@ -14,10 +14,17 @@ import {
changelogEntryArray,
ChangelogPost,
} from "../../../assets/changelogs";
import Markdown from "../../../components/markdown/Markdown";
import { ModalProps } from "../types";
const Image = styled.img`
const Image = styled.img<{ shadow?: boolean }>`
border-radius: var(--border-radius);
${(props) =>
props.shadow &&
css`
filter: drop-shadow(4px 4px 10px rgba(0, 0, 0, 0.5));
`}
`;
function RenderLog({ post }: { post: ChangelogPost }) {
@@ -25,9 +32,9 @@ function RenderLog({ post }: { post: ChangelogPost }) {
<Column>
{post.content.map((entry) =>
typeof entry === "string" ? (
<span>{entry}</span>
<Markdown content={entry} />
) : (
<Image src={entry.src} />
<Image src={entry.src} shadow={entry.shadow} />
),
)}
</Column>

View File

@@ -0,0 +1,146 @@
import { API, Message as MessageInterface, User } from "revolt.js";
import styled from "styled-components";
import { Text } from "preact-i18n";
import { ModalForm, Row } from "@revoltchat/ui";
import Message from "../../../components/common/messaging/Message";
import UserShort from "../../../components/common/user/UserShort";
import { useClient } from "../../client/ClientController";
import { modalController } from "../ModalController";
import { ModalProps } from "../types";
const CONTENT_REASONS: API.ContentReportReason[] = [
"NoneSpecified",
"Illegal",
"PromotesHarm",
"SpamAbuse",
"Malware",
"Harassment",
];
const USER_REASONS: API.UserReportReason[] = [
"NoneSpecified",
"SpamAbuse",
"InappropriateProfile",
"Impersonation",
"BanEvasion",
"Underage",
];
/**
* Add padding to the message container
*/
const MessageContainer = styled.div`
margin-block-end: 16px;
`;
/**
* Report creation modal
*/
export default function ReportContent({
target,
...props
}: ModalProps<"report">) {
const client = useClient();
return (
<ModalForm
{...props}
title={
target instanceof MessageInterface ? (
<Text id="app.special.modals.report.message" />
) : (
<Text
id="app.special.modals.report.by_name"
fields={{
name:
target instanceof User
? target.username
: target.name,
}}
/>
)
}
schema={{
selected: "custom",
reason: "combo",
additional_context: "text",
}}
data={{
selected: {
element:
target instanceof MessageInterface ? (
<MessageContainer>
<Message message={target} head attachContext />
</MessageContainer>
) : target instanceof User ? (
<Row centred>
<UserShort user={target} size={32} />
</Row>
) : (
<></>
),
},
reason: {
field: (
<Text id="app.special.modals.report.reason" />
) as React.ReactChild,
options: (target instanceof User
? USER_REASONS
: CONTENT_REASONS
).map((value) => ({
name: (
<Text
id={
value === "NoneSpecified"
? "app.special.modals.report.no_reason"
: `app.special.modals.report.${
target instanceof User
? "user"
: "content"
}_reason.${value}`
}
/>
),
value,
})),
},
additional_context: {
field: (
<Text id="app.special.modals.report.additional_context" />
) as React.ReactChild,
},
}}
callback={async ({ reason, additional_context }) => {
await client.api.post("/safety/report", {
content: {
id: target._id,
type:
target instanceof MessageInterface
? "Message"
: target instanceof User
? "User"
: "Server",
report_reason: reason as any,
},
additional_context,
});
modalController.push({
type: "report_success",
user:
target instanceof MessageInterface
? target.author
: target instanceof User
? target
: undefined,
});
}}
submit={{
children: <Text id="app.special.modals.actions.report" />,
}}
/>
);
}

View File

@@ -0,0 +1,65 @@
import { Text } from "preact-i18n";
import { Modal } from "@revoltchat/ui";
import { noopTrue } from "../../../lib/js";
import { ModalProps } from "../types";
/**
* Report success modal
*/
export default function ReportSuccess({
user,
...props
}: ModalProps<"report_success">) {
return (
<Modal
{...props}
title={<Text id="app.special.modals.report.reported" />}
description={
<>
<Text id="app.special.modals.report.thank_you" />
{user && (
<>
<br />
<br />
<Text id="app.special.modals.report.block_user" />
</>
)}
</>
}
actions={
user
? [
{
palette: "plain",
onClick: async () => {
user.blockUser();
return true;
},
children: (
<Text id="app.special.modals.actions.block" />
),
},
{
palette: "plain-secondary",
onClick: noopTrue,
children: (
<Text id="app.special.modals.actions.dont_block" />
),
},
]
: [
{
palette: "plain",
onClick: noopTrue,
children: (
<Text id="app.special.modals.actions.done" />
),
},
]
}
/>
);
}

View File

@@ -28,16 +28,24 @@ export default function ServerInfo({
}
actions={[
{
onClick: () =>
onClick: () => {
modalController.push({
type: "server_identity",
member: server.member!,
}),
});
return true;
},
children: "Edit Identity",
palette: "primary",
},
{
onClick: () => report(server),
onClick: () => {
modalController.push({
type: "report",
target: server,
});
return true;
},
children: <Text id="app.special.modals.actions.report" />,
palette: "error",
},

View File

@@ -179,6 +179,14 @@ export type Modal = {
| {
type: "import_theme";
}
| {
type: "report";
target: Server | User | Message;
}
| {
type: "report_success";
user?: User;
}
);
export type ModalProps<T extends Modal["type"]> = Modal & { type: T } & {

View File

@@ -111,7 +111,8 @@ type Action =
action: "set_notification_state";
key: string;
state?: NotificationState;
};
}
| { action: "report"; target: User | Server | Message };
// ! FIXME: I dare someone to re-write this
// Tip: This should just be split into separate context menus per logical area.
@@ -449,6 +450,12 @@ export default function ContextMenus() {
case "open_server_settings":
history.push(`/server/${data.id}/settings`);
break;
case "report":
modalController.push({
type: "report",
target: data.target,
});
break;
}
})().catch((err) => {
modalController.push({
@@ -669,6 +676,19 @@ export default function ContextMenus() {
} as unknown as Action);
}
}
if (user._id !== userId) {
generateAction(
{
action: "report",
target: user,
},
"report_user",
undefined,
undefined,
"var(--error)",
);
}
}
if (contextualChannel) {
@@ -795,14 +815,33 @@ export default function ContextMenus() {
});
}
if (message.author_id !== userId) {
generateAction(
{
action: "report",
target: message,
},
"report_message",
undefined,
undefined,
"var(--error)",
);
}
if (
message.author_id === userId ||
channelPermissions & Permission.ManageMessages
) {
generateAction({
action: "delete_message",
target: message,
});
generateAction(
{
action: "delete_message",
target: message,
},
undefined,
undefined,
undefined,
"var(--error)",
);
}
if (
@@ -1035,6 +1074,17 @@ export default function ContextMenus() {
"var(--error)",
);
} else {
generateAction(
{
action: "report",
target: server,
},
"report_server",
undefined,
undefined,
"var(--error)",
);
generateAction(
{ action: "leave_server", target: server },
"leave_server",

View File

@@ -17,6 +17,7 @@ const ALLOWED_ORIGINS = [
"app.revolt.chat",
"nightly.revolt.chat",
"local.revolt.chat",
"rolt.chat",
];
/**

View File

@@ -5,7 +5,7 @@ import { mapToRecord } from "../../lib/conversion";
import Persistent from "../interfaces/Persistent";
import Store from "../interfaces/Store";
interface DraftObject {
export interface DraftObject {
content?: string;
masquerade?: {
avatar: string;
@@ -59,10 +59,12 @@ export default class Draft implements Store, Persistent<Data> {
* @param channel Channel ID
*/
@computed has(channel: string) {
return (
this.drafts.has(channel) &&
this.drafts.get(channel)!.content!.length > 0
);
if (!this.drafts.has(channel)) return false;
// fetch the draft object
const potentialDraft = this.drafts.get(channel)?.content;
// if it doesn't have any content return false
if (!potentialDraft) return false;
return potentialDraft.length > 0;
}
/**

View File

@@ -53,7 +53,7 @@ export default observer(() => {
const isDecember = !isTouchscreenDevice && new Date().getMonth() === 11;
const isOctober = !isTouchscreenDevice && new Date().getMonth() === 9
const snowflakes = useMemo(() => {
const flakes = [];
const flakes: string[] = [];
if (isDecember) {
for (let i = 0; i < 15; i++) {

View File

@@ -1,7 +1,7 @@
import styles from "../Login.module.scss";
import { Text } from "preact-i18n";
import { Button } from "@revoltchat/ui";
import { Button, Tip } from "@revoltchat/ui";
interface Props {
email?: string;
@@ -115,6 +115,15 @@ export function MailProvider({ email }: Props) {
/>
</Button>
</a>
{provider[0] === "iCloud Mail" && (
<Tip palette="error">
<span>
iCloud users may not receive any emails due to a block
by Proofpoint. Please use a different email provider if
you do not receive anything.
</span>
</Tip>
)}
</div>
);
}

View File

@@ -340,11 +340,13 @@ function ListElement({
const save = useCallback(() => {
setEditing(undefined);
setTitle!(editing!);
if (editing !== "") {
setTitle!(editing!);
}
}, [editing, setTitle]);
useEffect(() => {
if (!editing) return;
if (editing === undefined) return;
function onClick(ev: MouseEvent) {
if ((ev.target as HTMLElement)?.id !== category.id) {
@@ -368,7 +370,7 @@ function ListElement({
<div className="inner">
<Row>
<KanbanListHeader {...provided.dragHandleProps}>
{editing ? (
{editing !== undefined ? (
<input
value={editing}
onChange={(e) =>

View File

@@ -70,6 +70,10 @@ export const Roles = observer(({ server }: Props) => {
}
`;
const DeleteRoleButton = styled(Button)`
margin: 16px 0;
`;
return (
<PermissionsLayout
server={server}
@@ -266,12 +270,12 @@ export const Roles = observer(({ server }: Props) => {
<h1>
<Text id="app.settings.categories.danger_zone" />
</h1>
<Button
<DeleteRoleButton
palette="error"
compact
onClick={deleteRole}>
<Text id="app.settings.permissions.delete_role" />
</Button>
</DeleteRoleButton>
</>
)}
</div>

View File

@@ -8016,14 +8016,14 @@ __metadata:
languageName: node
linkType: hard
"revolt-api@npm:0.5.5-4":
version: 0.5.5-4
resolution: "revolt-api@npm:0.5.5-4"
"revolt-api@npm:0.5.8":
version: 0.5.8
resolution: "revolt-api@npm:0.5.8"
dependencies:
"@insertish/oapi": 0.1.18
axios: ^0.26.1
lodash.defaultsdeep: ^4.6.1
checksum: dfb374d58f1b8b5a6de2e7fa05e386b3df3ffb85a450a6894f23c6b9760af8bff0d198d8352063c33084f0dbc6ff84a234300efb0c62c7b27f709887402787f1
checksum: d488fa87774e4e9d0b3136779c555cbed257d2b76a298b17dca346cc3909f81e84aa516c10a415a7c20cd31990d540ade5489f69e06f69e14ba4974a26aa145e
languageName: node
linkType: hard
@@ -8040,7 +8040,7 @@ __metadata:
lodash.isequal: ^4.5.0
long: ^5.2.0
mobx: ^6.3.2
revolt-api: 0.5.5-4
revolt-api: 0.5.8
ulid: ^2.3.0
ws: ^8.2.2
languageName: node