mirror of
https://github.com/stoatchat/for-legacy-web.git
synced 2026-03-06 08:38:37 +00:00
Merge branch 'revoltchat:master' into master
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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" &&
|
||||||
|
|||||||
@@ -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") {
|
||||||
|
|||||||
@@ -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",
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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++) {
|
||||||
|
|||||||
Reference in New Issue
Block a user