Merge branch 'revoltchat:master' into master

This commit is contained in:
kate
2022-12-09 15:11:59 +08:00
committed by GitHub
6 changed files with 99 additions and 21 deletions

View File

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

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 { Channel, User } from "revolt.js";
import { Emoji as CustomEmoji } from "revolt.js/esm/maps/Emojis";
import styled, { css } from "styled-components/macro"; import styled, { css } from "styled-components/macro";
import { StateUpdater, useState } from "preact/hooks"; import { StateUpdater, useState } from "preact/hooks";
@@ -7,6 +10,8 @@ import { emojiDictionary } from "../../assets/emojis";
import { useClient } from "../../controllers/client/ClientController"; import { useClient } from "../../controllers/client/ClientController";
import ChannelIcon from "./ChannelIcon"; import ChannelIcon from "./ChannelIcon";
import Emoji from "./Emoji"; import Emoji from "./Emoji";
import ServerIcon from "./ServerIcon";
import Tooltip from "./Tooltip";
import UserIcon from "./user/UserIcon"; import UserIcon from "./user/UserIcon";
export type AutoCompleteState = export type AutoCompleteState =
@@ -14,7 +19,7 @@ export type AutoCompleteState =
| ({ selected: number; within: boolean } & ( | ({ selected: number; within: boolean } & (
| { | {
type: "emoji"; type: "emoji";
matches: string[]; matches: (string | CustomEmoji)[];
} }
| { | {
type: "user"; type: "user";
@@ -104,16 +109,23 @@ export function useAutoComplete(
if (type === "emoji") { if (type === "emoji") {
// ! TODO: we should convert it to a Binary Search Tree and use that // ! TODO: we should convert it to a Binary Search Tree and use that
const matches = Object.keys(emojiDictionary) const matches = [
.filter((emoji: string) => emoji.match(regex)) ...Object.keys(emojiDictionary).filter((emoji: string) =>
.splice(0, 5); emoji.match(regex),
),
...Array.from(client.emojis.values()).filter((emoji) =>
emoji.name.match(regex),
),
].splice(0, 5);
if (matches.length > 0) { if (matches.length > 0) {
const currentPosition = const currentPosition =
state.type !== "none" ? state.selected : 0; state.type !== "none" ? state.selected : 0;
setState({ setState({
// @ts-ignore-next-line are you high
type: "emoji", type: "emoji",
// @ts-ignore-next-line
matches, matches,
selected: Math.min(currentPosition, matches.length - 1), selected: Math.min(currentPosition, matches.length - 1),
within: false, within: false,
@@ -233,10 +245,13 @@ export function useAutoComplete(
const content = el.value.split(""); const content = el.value.split("");
if (state.type === "emoji") { if (state.type === "emoji") {
const selected = state.matches[state.selected];
content.splice( content.splice(
index, index,
search.length, search.length,
state.matches[state.selected], selected instanceof CustomEmoji
? selected._id
: selected,
": ", ": ",
); );
} else if (state.type === "user") { } else if (state.type === "user") {
@@ -388,12 +403,17 @@ export default function AutoComplete({
setState, setState,
onClick, onClick,
}: Pick<AutoCompleteProps, "detached" | "state" | "setState" | "onClick">) { }: Pick<AutoCompleteProps, "detached" | "state" | "setState" | "onClick">) {
const client = useClient();
return ( return (
<Base detached={detached}> <Base detached={detached}>
<div> <div>
{state.type === "emoji" && {state.type === "emoji" &&
state.matches.map((match, i) => ( state.matches.map((match, i) => (
<button <button
style={{
display: "flex",
justifyContent: "space-between",
}}
key={match} key={match}
className={i === state.selected ? "active" : ""} className={i === state.selected ? "active" : ""}
onMouseEnter={() => onMouseEnter={() =>
@@ -412,15 +432,61 @@ export default function AutoComplete({
}) })
} }
onClick={onClick}> onClick={onClick}>
<Emoji <div
emoji={ style={{
(emojiDictionary as Record<string, string>)[ display: "flex",
match flexDirection: "row",
] justifyContent: "center",
} }}>
size={20} {match instanceof CustomEmoji ? (
/> <img
:{match}: 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> </button>
))} ))}
{state.type === "user" && {state.type === "user" &&

View File

@@ -45,6 +45,8 @@ import { PermissionTooltip } from "../Tooltip";
import FilePreview from "./bars/FilePreview"; import FilePreview from "./bars/FilePreview";
import ReplyBar from "./bars/ReplyBar"; import ReplyBar from "./bars/ReplyBar";
import { DraftObject } from "../../../mobx/stores/Draft";
type Props = { type Props = {
channel: Channel; channel: Channel;
}; };
@@ -277,7 +279,12 @@ export default observer(({ channel }: Props) => {
// Push message content to draft. // Push message content to draft.
const setMessage = useCallback( 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], [state.draft, channel._id],
); );
@@ -317,7 +324,7 @@ export default observer(({ channel }: Props) => {
if (uploadState.type === "uploading" || uploadState.type === "sending") if (uploadState.type === "uploading" || uploadState.type === "sending")
return; 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 (uploadState.type === "attached") return sendFile(content);
if (content.length === 0) return; if (content.length === 0) return;
@@ -526,7 +533,7 @@ export default observer(({ channel }: Props) => {
} }
function isInCodeBlock(cursor: number): boolean { 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); const contentBeforeCursor = content.substring(0, cursor);
let delimiterCount = 0; let delimiterCount = 0;
@@ -607,9 +614,12 @@ export default observer(({ channel }: Props) => {
<HackAlertThisFileWillBeReplaced <HackAlertThisFileWillBeReplaced
onSelect={(emoji) => { onSelect={(emoji) => {
const v = state.draft.get(channel._id); const v = state.draft.get(channel._id);
const cnt: DraftObject = {
content: (v == null ? "" : `${v.content} `) + `:${emoji}:`
}
state.draft.set( state.draft.set(
channel._id, channel._id,
`${v ? `${v} ` : ""}:${emoji}:`, cnt,
); );
}} }}
onClose={closePicker} onClose={closePicker}
@@ -664,7 +674,7 @@ export default observer(({ channel }: Props) => {
id="message" id="message"
maxLength={2000} maxLength={2000}
onKeyUp={onKeyUp} onKeyUp={onKeyUp}
value={state.draft.get(channel._id) ?? ""} value={state.draft.get(channel._id)?.content ?? ""}
padding="var(--message-box-padding)" padding="var(--message-box-padding)"
onKeyDown={(e) => { onKeyDown={(e) => {
if (e.ctrlKey && e.key === "Enter") { if (e.ctrlKey && e.key === "Enter") {

View File

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

View File

@@ -5,7 +5,7 @@ import { mapToRecord } from "../../lib/conversion";
import Persistent from "../interfaces/Persistent"; import Persistent from "../interfaces/Persistent";
import Store from "../interfaces/Store"; import Store from "../interfaces/Store";
interface DraftObject { export interface DraftObject {
content?: string; content?: string;
masquerade?: { masquerade?: {
avatar: string; avatar: string;

View File

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