refactor: more TS code
This commit is contained in:
Vendored
+16
-1
@@ -26,7 +26,22 @@
|
|||||||
"[javascript]": {
|
"[javascript]": {
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
},
|
},
|
||||||
"cSpell.words": ["btns", "navs", "oidc", "reduxjs", "thirdparty", "tippyjs", "vocechat"],
|
"cSpell.words": [
|
||||||
|
"appinstalled",
|
||||||
|
"beforeinstallprompt",
|
||||||
|
"btns",
|
||||||
|
"localstorage",
|
||||||
|
"magiclink",
|
||||||
|
"navs",
|
||||||
|
"oidc",
|
||||||
|
"reduxjs",
|
||||||
|
"Swiper",
|
||||||
|
"thirdparty",
|
||||||
|
"tippyjs",
|
||||||
|
"toastui",
|
||||||
|
"uiball",
|
||||||
|
"vocechat"
|
||||||
|
],
|
||||||
"reactSnippets.settings.prettierEnabled": true,
|
"reactSnippets.settings.prettierEnabled": true,
|
||||||
"reactSnippets.settings.importReactOnTop": false
|
"reactSnippets.settings.importReactOnTop": false
|
||||||
}
|
}
|
||||||
|
|||||||
+34
-28
@@ -3,7 +3,13 @@ import { nanoid } from "@reduxjs/toolkit";
|
|||||||
import baseQuery from "./base.query";
|
import baseQuery from "./base.query";
|
||||||
import { setAuthData, updateToken, resetAuthData, updateInitialized } from "../slices/auth.data";
|
import { setAuthData, updateToken, resetAuthData, updateInitialized } from "../slices/auth.data";
|
||||||
import BASE_URL, { KEY_DEVICE_KEY, KEY_LOCAL_MAGIC_TOKEN } from "../config";
|
import BASE_URL, { KEY_DEVICE_KEY, KEY_LOCAL_MAGIC_TOKEN } from "../config";
|
||||||
import { AuthData, LoginCredential } from "../../types/auth";
|
import {
|
||||||
|
AuthData,
|
||||||
|
CredentialResponse,
|
||||||
|
LoginCredential,
|
||||||
|
RenewTokenDTO,
|
||||||
|
RenewTokenResponse
|
||||||
|
} from "../../types/auth";
|
||||||
|
|
||||||
const getDeviceId = () => {
|
const getDeviceId = () => {
|
||||||
let d = localStorage.getItem(KEY_DEVICE_KEY);
|
let d = localStorage.getItem(KEY_DEVICE_KEY);
|
||||||
@@ -59,14 +65,11 @@ export const authApi = createApi({
|
|||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
// 更新token
|
// 更新token
|
||||||
renew: builder.mutation({
|
renew: builder.mutation<RenewTokenResponse, RenewTokenDTO>({
|
||||||
query: ({ token, refreshToken }) => ({
|
query: (data) => ({
|
||||||
url: "/token/renew",
|
url: "/token/renew",
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: {
|
body: data
|
||||||
token,
|
|
||||||
refresh_token: refreshToken
|
|
||||||
}
|
|
||||||
}),
|
}),
|
||||||
async onQueryStarted(params, { dispatch, queryFulfilled }) {
|
async onQueryStarted(params, { dispatch, queryFulfilled }) {
|
||||||
try {
|
try {
|
||||||
@@ -79,7 +82,7 @@ export const authApi = createApi({
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
// 更新 device token
|
// 更新 device token
|
||||||
updateDeviceToken: builder.mutation({
|
updateDeviceToken: builder.mutation<void, { device_token: string }>({
|
||||||
query: (device_token) => ({
|
query: (device_token) => ({
|
||||||
url: "/token/device_token",
|
url: "/token/device_token",
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
@@ -89,66 +92,69 @@ export const authApi = createApi({
|
|||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
// 获取openid
|
// 获取openid
|
||||||
getOpenid: builder.mutation({
|
getOpenid: builder.mutation<{ url: string }, { issuer: string; redirect_uri: string }>({
|
||||||
query: ({ issuer, redirect_uri }) => ({
|
query: (data) => ({
|
||||||
url: "/token/openid/authorize",
|
url: "/token/openid/authorize",
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: {
|
body: data
|
||||||
issuer,
|
|
||||||
redirect_uri
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
|
|
||||||
checkMagicTokenValid: builder.mutation({
|
checkMagicTokenValid: builder.mutation<boolean, string>({
|
||||||
query: (token) => ({
|
query: (token) => ({
|
||||||
url: "user/check_magic_token",
|
url: "user/check_magic_token",
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: { magic_token: token }
|
body: { magic_token: token }
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
updatePassword: builder.mutation({
|
updatePassword: builder.mutation<void, { old_password: string; new_password: string }>({
|
||||||
query: ({ old_password, new_password }) => ({
|
query: (data) => ({
|
||||||
url: "user/change_password",
|
url: "user/change_password",
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: { old_password, new_password }
|
body: data
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
sendLoginMagicLink: builder.mutation({
|
sendLoginMagicLink: builder.mutation<string, string>({
|
||||||
query: (email) => ({
|
query: (email) => ({
|
||||||
headers: {
|
headers: {
|
||||||
// "content-type": "text/plain",
|
|
||||||
accept: "text/plain"
|
accept: "text/plain"
|
||||||
},
|
},
|
||||||
url: `user/send_login_magic_link?email=${encodeURIComponent(email)}`,
|
url: `user/send_login_magic_link?email=${encodeURIComponent(email)}`,
|
||||||
method: "POST",
|
method: "POST",
|
||||||
responseHandler: (response: Response) => response.text()
|
responseHandler: (response: Response) => response.text()
|
||||||
// body: { email }
|
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
sendRegMagicLink: builder.mutation({
|
sendRegMagicLink: builder.mutation<
|
||||||
|
{
|
||||||
|
new_magic_token: "string";
|
||||||
|
mail_is_sent: boolean;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
magic_token: "string";
|
||||||
|
email: "string";
|
||||||
|
password: "string";
|
||||||
|
}
|
||||||
|
>({
|
||||||
query: (data) => ({
|
query: (data) => ({
|
||||||
url: `user/send_reg_magic_link`,
|
url: `user/send_reg_magic_link`,
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: data
|
body: data
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
getMetamaskNonce: builder.query({
|
getMetamaskNonce: builder.query<string, string>({
|
||||||
query: (address) => ({
|
query: (address) => ({
|
||||||
url: `/token/metamask/nonce?public_address=${address}`
|
url: `/token/metamask/nonce?public_address=${address}`
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
checkEmail: builder.query({
|
checkEmail: builder.query<boolean, string>({
|
||||||
query: (email) => ({
|
query: (email) => ({
|
||||||
url: `/user/check_email?email=${encodeURIComponent(email)}`
|
url: `/user/check_email?email=${encodeURIComponent(email)}`
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
// todo: check return type
|
getCredentials: builder.query<CredentialResponse, void>({
|
||||||
getCredentials: builder.query<any, void>({
|
|
||||||
query: () => ({ url: "/token/credentials" })
|
query: () => ({ url: "/token/credentials" })
|
||||||
}),
|
}),
|
||||||
// todo: check return type
|
logout: builder.query<void, void>({
|
||||||
logout: builder.query<any, void>({
|
|
||||||
query: () => ({ url: "token/logout" }),
|
query: () => ({ url: "token/logout" }),
|
||||||
async onQueryStarted(params, { dispatch, queryFulfilled }) {
|
async onQueryStarted(params, { dispatch, queryFulfilled }) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -9,9 +9,10 @@ import { removeChannelSession } from "../slices/message.channel";
|
|||||||
import { removeReactionMessage } from "../slices/message.reaction";
|
import { removeReactionMessage } from "../slices/message.reaction";
|
||||||
import { onMessageSendStarted } from "./handlers";
|
import { onMessageSendStarted } from "./handlers";
|
||||||
import handleChatMessage from "../../common/hook/useStreaming/chat.handler";
|
import handleChatMessage from "../../common/hook/useStreaming/chat.handler";
|
||||||
import { Channel, CreateChannelDTO } from "../../types/channel";
|
import { Channel, ChannelDTO, CreateChannelDTO } from "../../types/channel";
|
||||||
import { RootState } from "../store";
|
import { RootState } from "../store";
|
||||||
import { ContentTypeKey } from "../../types/message";
|
import { ContentTypeKey, ChatMessage } from "../../types/message";
|
||||||
|
|
||||||
export const channelApi = createApi({
|
export const channelApi = createApi({
|
||||||
reducerPath: "channelApi",
|
reducerPath: "channelApi",
|
||||||
baseQuery,
|
baseQuery,
|
||||||
@@ -41,7 +42,7 @@ export const channelApi = createApi({
|
|||||||
body: data
|
body: data
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
updateChannel: builder.mutation({
|
updateChannel: builder.mutation<void, ChannelDTO>({
|
||||||
query: ({ id, ...data }) => ({
|
query: ({ id, ...data }) => ({
|
||||||
url: `group/${id}`,
|
url: `group/${id}`,
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
@@ -49,7 +50,7 @@ export const channelApi = createApi({
|
|||||||
}),
|
}),
|
||||||
async onQueryStarted({ id, name, description }, { dispatch, queryFulfilled }) {
|
async onQueryStarted({ id, name, description }, { dispatch, queryFulfilled }) {
|
||||||
// id: who send to ,from_uid: who sent
|
// id: who send to ,from_uid: who sent
|
||||||
const patchResult = dispatch(updateChannel({ id, name, description }));
|
const patchResult = dispatch(updateChannel({ gid: id, name, description }));
|
||||||
try {
|
try {
|
||||||
await queryFulfilled;
|
await queryFulfilled;
|
||||||
} catch {
|
} catch {
|
||||||
@@ -58,7 +59,7 @@ export const channelApi = createApi({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
getHistoryMessages: builder.query({
|
getHistoryMessages: builder.query<ChatMessage[], { id: number; mid?: number; limit: number }>({
|
||||||
query: ({ id, mid = null, limit = 100 }) => ({
|
query: ({ id, mid = null, limit = 100 }) => ({
|
||||||
url: mid
|
url: mid
|
||||||
? `/group/${id}/history?before=${mid}&limit=${limit}`
|
? `/group/${id}/history?before=${mid}&limit=${limit}`
|
||||||
@@ -145,21 +146,21 @@ export const channelApi = createApi({
|
|||||||
await onMessageSendStarted.call(this, param1, param2, "channel");
|
await onMessageSendStarted.call(this, param1, param2, "channel");
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
addMembers: builder.mutation({
|
addMembers: builder.mutation<void, { id: number; members: number[] }>({
|
||||||
query: ({ id, members }) => ({
|
query: ({ id, members }) => ({
|
||||||
url: `group/${id}/members/add`,
|
url: `group/${id}/members/add`,
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: members
|
body: members
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
removeMembers: builder.mutation({
|
removeMembers: builder.mutation<void, { id: number; members: number[] }>({
|
||||||
query: ({ id, members }) => ({
|
query: ({ id, members }) => ({
|
||||||
url: `group/${id}/members/remove`,
|
url: `group/${id}/members/remove`,
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: members
|
body: members
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
updateIcon: builder.mutation({
|
updateIcon: builder.mutation<void, { gid: number; image: File }>({
|
||||||
query: ({ gid, image }) => ({
|
query: ({ gid, image }) => ({
|
||||||
headers: {
|
headers: {
|
||||||
"content-type": "image/png"
|
"content-type": "image/png"
|
||||||
|
|||||||
@@ -25,18 +25,6 @@ export const onMessageSendStarted = async (
|
|||||||
console.log("handlers data", content, type, properties, ignoreLocal, id);
|
console.log("handlers data", content, type, properties, ignoreLocal, id);
|
||||||
const isImage = properties.content_type?.startsWith("image");
|
const isImage = properties.content_type?.startsWith("image");
|
||||||
const ts = properties.local_id || +new Date();
|
const ts = properties.local_id || +new Date();
|
||||||
// let imageData = null;
|
|
||||||
// if (type == "image") {
|
|
||||||
// if (typeof content == "string" && content.startsWith("data:image")) {
|
|
||||||
// // base64
|
|
||||||
// // const resp = await fetch(content);
|
|
||||||
// // const blob = await resp.blob();
|
|
||||||
// // imageData = new File([blob], "tmp.png", { type: "image/png" });
|
|
||||||
// imageData = content;
|
|
||||||
// } else {
|
|
||||||
// imageData = URL.createObjectURL(content);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
const tmpMsg = {
|
const tmpMsg = {
|
||||||
content: isImage ? content.path : content,
|
content: isImage ? content.path : content,
|
||||||
content_type: ContentTypes[type],
|
content_type: ContentTypes[type],
|
||||||
|
|||||||
+15
-19
@@ -4,23 +4,16 @@ import { updateReadChannels, updateReadUsers } from "../slices/footprint";
|
|||||||
import { fillFavorites, populateFavorite, addFavorite, deleteFavorite } from "../slices/favorites";
|
import { fillFavorites, populateFavorite, addFavorite, deleteFavorite } from "../slices/favorites";
|
||||||
import { onMessageSendStarted } from "./handlers";
|
import { onMessageSendStarted } from "./handlers";
|
||||||
import { normalizeArchiveData } from "../../common/utils";
|
import { normalizeArchiveData } from "../../common/utils";
|
||||||
// import { updateMessage } from "../slices/message";
|
|
||||||
import baseQuery from "./base.query";
|
import baseQuery from "./base.query";
|
||||||
import { OG } from "../../types/common";
|
import { Archive, FavoriteArchive, OG } from "../../types/resource";
|
||||||
type UploadFileResponse = {
|
import { ContentTypeKey, UploadFileResponse } from "../../types/message";
|
||||||
path: string;
|
import { RootState } from "../store";
|
||||||
size: number;
|
|
||||||
hash: string;
|
|
||||||
image_properties: {
|
|
||||||
width: number;
|
|
||||||
height: number;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
export const messageApi = createApi({
|
export const messageApi = createApi({
|
||||||
reducerPath: "messageApi",
|
reducerPath: "messageApi",
|
||||||
baseQuery,
|
baseQuery,
|
||||||
endpoints: (builder) => ({
|
endpoints: (builder) => ({
|
||||||
editMessage: builder.mutation({
|
editMessage: builder.mutation<number, { mid: number; content: string; type: ContentTypeKey }>({
|
||||||
query: ({ mid, content, type = "text" }) => ({
|
query: ({ mid, content, type = "text" }) => ({
|
||||||
headers: {
|
headers: {
|
||||||
"content-type": ContentTypes[type]
|
"content-type": ContentTypes[type]
|
||||||
@@ -50,7 +43,7 @@ export const messageApi = createApi({
|
|||||||
body: meta
|
body: meta
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
createArchive: builder.mutation({
|
createArchive: builder.mutation<string, number[]>({
|
||||||
query: (mids = []) => ({
|
query: (mids = []) => ({
|
||||||
url: `/resource/archive`,
|
url: `/resource/archive`,
|
||||||
method: "POST",
|
method: "POST",
|
||||||
@@ -73,7 +66,7 @@ export const messageApi = createApi({
|
|||||||
url: `/resource/open_graphic_parse?url=${encodeURIComponent(url)}`
|
url: `/resource/open_graphic_parse?url=${encodeURIComponent(url)}`
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
getArchiveMessage: builder.query({
|
getArchiveMessage: builder.query<Archive, string>({
|
||||||
query: (file_path) => ({
|
query: (file_path) => ({
|
||||||
url: `/resource/archive?file_path=${encodeURIComponent(file_path)}`
|
url: `/resource/archive?file_path=${encodeURIComponent(file_path)}`
|
||||||
})
|
})
|
||||||
@@ -92,7 +85,7 @@ export const messageApi = createApi({
|
|||||||
body: { mid }
|
body: { mid }
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
favoriteMessage: builder.mutation({
|
favoriteMessage: builder.mutation<FavoriteArchive, number[]>({
|
||||||
query: (mids) => ({
|
query: (mids) => ({
|
||||||
url: `/favorite`,
|
url: `/favorite`,
|
||||||
method: "POST",
|
method: "POST",
|
||||||
@@ -123,14 +116,14 @@ export const messageApi = createApi({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
getFavoriteDetails: builder.query({
|
getFavoriteDetails: builder.query<Archive, number>({
|
||||||
query: (id) => ({
|
query: (id) => ({
|
||||||
url: `/favorite/${id}`
|
url: `/favorite/${id}`
|
||||||
}),
|
}),
|
||||||
async onQueryStarted(id, { dispatch, queryFulfilled, getState }) {
|
async onQueryStarted(id, { dispatch, queryFulfilled, getState }) {
|
||||||
try {
|
try {
|
||||||
const { data } = await queryFulfilled;
|
const { data } = await queryFulfilled;
|
||||||
const loginUid = getState().authData.user.uid;
|
const loginUid = (getState() as RootState).authData.user.uid;
|
||||||
const messages = normalizeArchiveData(data, id, loginUid);
|
const messages = normalizeArchiveData(data, id, loginUid);
|
||||||
dispatch(populateFavorite({ id, messages }));
|
dispatch(populateFavorite({ id, messages }));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -138,7 +131,7 @@ export const messageApi = createApi({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
getFavorites: builder.query({
|
getFavorites: builder.query<FavoriteArchive[], void>({
|
||||||
query: () => ({
|
query: () => ({
|
||||||
url: `/favorite`
|
url: `/favorite`
|
||||||
}),
|
}),
|
||||||
@@ -155,7 +148,10 @@ export const messageApi = createApi({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
replyMessage: builder.mutation({
|
replyMessage: builder.mutation<
|
||||||
|
number,
|
||||||
|
{ reply_mid: number; content: string; type: ContentTypeKey }
|
||||||
|
>({
|
||||||
query: ({ reply_mid, content, type = "text" }) => ({
|
query: ({ reply_mid, content, type = "text" }) => ({
|
||||||
headers: {
|
headers: {
|
||||||
"content-type": ContentTypes[type]
|
"content-type": ContentTypes[type]
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import BASE_URL from "../config";
|
|||||||
import { updateInfo } from "../slices/server";
|
import { updateInfo } from "../slices/server";
|
||||||
import baseQuery from "./base.query";
|
import baseQuery from "./base.query";
|
||||||
import { RootState } from "../store";
|
import { RootState } from "../store";
|
||||||
import { User } from "../../types/auth";
|
import { User } from "../../types/user";
|
||||||
import {
|
import {
|
||||||
FirebaseConfig,
|
FirebaseConfig,
|
||||||
GoogleAuthConfig,
|
GoogleAuthConfig,
|
||||||
@@ -51,7 +51,6 @@ export const serverApi = createApi({
|
|||||||
getServerVersion: builder.query<string, void>({
|
getServerVersion: builder.query<string, void>({
|
||||||
query: () => ({
|
query: () => ({
|
||||||
headers: {
|
headers: {
|
||||||
// "content-type": "text/plain",
|
|
||||||
accept: "text/plain"
|
accept: "text/plain"
|
||||||
},
|
},
|
||||||
url: `/admin/system/version`,
|
url: `/admin/system/version`,
|
||||||
@@ -128,7 +127,7 @@ export const serverApi = createApi({
|
|||||||
body: data
|
body: data
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
updateLogo: builder.mutation({
|
updateLogo: builder.mutation<void, File>({
|
||||||
query: (data) => ({
|
query: (data) => ({
|
||||||
headers: {
|
headers: {
|
||||||
"content-type": "image/png"
|
"content-type": "image/png"
|
||||||
|
|||||||
@@ -9,13 +9,14 @@ import BASE_URL, { ContentTypes } from "../config";
|
|||||||
import { onMessageSendStarted } from "./handlers";
|
import { onMessageSendStarted } from "./handlers";
|
||||||
import handleChatMessage from "../../common/hook/useStreaming/chat.handler";
|
import handleChatMessage from "../../common/hook/useStreaming/chat.handler";
|
||||||
import { User, UserDTO, UserForAdmin, UserForAdminDTO } from "../../types/user";
|
import { User, UserDTO, UserForAdmin, UserForAdminDTO } from "../../types/user";
|
||||||
import { ContentType, MuteDTO } from "../../types/message";
|
import { ChatMessage, ContentTypeKey, MuteDTO } from "../../types/message";
|
||||||
|
import { RootState } from "../store";
|
||||||
|
|
||||||
export const userApi = createApi({
|
export const userApi = createApi({
|
||||||
reducerPath: "userApi",
|
reducerPath: "userApi",
|
||||||
baseQuery,
|
baseQuery,
|
||||||
endpoints: (builder) => ({
|
endpoints: (builder) => ({
|
||||||
getUsers: builder.query({
|
getUsers: builder.query<User[], void>({
|
||||||
query: () => ({ url: `user` }),
|
query: () => ({ url: `user` }),
|
||||||
transformResponse: (data: User[]) => {
|
transformResponse: (data: User[]) => {
|
||||||
return data.map((user) => {
|
return data.map((user) => {
|
||||||
@@ -74,7 +75,7 @@ export const userApi = createApi({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
updateAvatar: builder.mutation({
|
updateAvatar: builder.mutation<void, File>({
|
||||||
query: (data) => ({
|
query: (data) => ({
|
||||||
headers: {
|
headers: {
|
||||||
"content-type": "image/png"
|
"content-type": "image/png"
|
||||||
@@ -92,7 +93,10 @@ export const userApi = createApi({
|
|||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
|
|
||||||
sendMsg: builder.mutation({
|
sendMsg: builder.mutation<
|
||||||
|
number,
|
||||||
|
{ id: number; content: string; type: ContentTypeKey; properties?: object }
|
||||||
|
>({
|
||||||
query: ({ id, content, type = "text", properties = "" }) => ({
|
query: ({ id, content, type = "text", properties = "" }) => ({
|
||||||
headers: {
|
headers: {
|
||||||
"content-type": ContentTypes[type],
|
"content-type": ContentTypes[type],
|
||||||
@@ -108,7 +112,7 @@ export const userApi = createApi({
|
|||||||
await onMessageSendStarted.call(this, param1, param2, "user");
|
await onMessageSendStarted.call(this, param1, param2, "user");
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
getHistoryMessages: builder.query({
|
getHistoryMessages: builder.query<ChatMessage[], { id: number; mid?: number; limit: number }>({
|
||||||
query: ({ id, mid = null, limit = 100 }) => ({
|
query: ({ id, mid = null, limit = 100 }) => ({
|
||||||
url: mid
|
url: mid
|
||||||
? `/user/${id}/history?before=${mid}&limit=${limit}`
|
? `/user/${id}/history?before=${mid}&limit=${limit}`
|
||||||
@@ -118,7 +122,7 @@ export const userApi = createApi({
|
|||||||
const { data: messages } = await queryFulfilled;
|
const { data: messages } = await queryFulfilled;
|
||||||
if (messages?.length) {
|
if (messages?.length) {
|
||||||
messages.forEach((msg) => {
|
messages.forEach((msg) => {
|
||||||
handleChatMessage(msg, dispatch, getState());
|
handleChatMessage(msg, dispatch, getState() as RootState);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
KEY_TOKEN,
|
KEY_TOKEN,
|
||||||
KEY_UID
|
KEY_UID
|
||||||
} from "../config";
|
} from "../config";
|
||||||
import { AuthData, AuthToken, User } from "../../types/auth";
|
import { AuthData, AuthToken, RenewTokenResponse, User } from "../../types/auth";
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
initialized: boolean;
|
initialized: boolean;
|
||||||
@@ -67,7 +67,7 @@ const authDataSlice = createSlice({
|
|||||||
updateInitialized(state, action: PayloadAction<boolean>) {
|
updateInitialized(state, action: PayloadAction<boolean>) {
|
||||||
state.initialized = action.payload;
|
state.initialized = action.payload;
|
||||||
},
|
},
|
||||||
updateToken(state, action: PayloadAction<AuthToken>) {
|
updateToken(state, action: PayloadAction<RenewTokenResponse>) {
|
||||||
const { token, refresh_token, expired_in } = action.payload;
|
const { token, refresh_token, expired_in } = action.payload;
|
||||||
state.token = token;
|
state.token = token;
|
||||||
const et = +new Date() + Number(expired_in) * 1000;
|
const et = +new Date() + Number(expired_in) * 1000;
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ const channelsSlice = createSlice({
|
|||||||
},
|
},
|
||||||
updateChannel(state, action: PayloadAction<UpdateChannelDTO>) {
|
updateChannel(state, action: PayloadAction<UpdateChannelDTO>) {
|
||||||
const ignoreInPublic = ["add_member", "remove_member"];
|
const ignoreInPublic = ["add_member", "remove_member"];
|
||||||
const { gid, operation, members = [], ...rest } = action.payload;
|
const { gid, operation = "", members = [], ...rest } = action.payload;
|
||||||
const currChannel = state.byId[gid];
|
const currChannel = state.byId[gid];
|
||||||
if (!currChannel || (currChannel.is_public && ignoreInPublic.includes(operation))) return;
|
if (!currChannel || (currChannel.is_public && ignoreInPublic.includes(operation))) return;
|
||||||
switch (operation) {
|
switch (operation) {
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ const uiSlice = createSlice({
|
|||||||
},
|
},
|
||||||
updateRememberedNavs(
|
updateRememberedNavs(
|
||||||
state,
|
state,
|
||||||
action: PayloadAction<{ key?: "chat" | "user"; path: string | null } | undefined>
|
action: PayloadAction<{ key?: "chat" | "user"; path?: string | null } | undefined>
|
||||||
) {
|
) {
|
||||||
const { key = "chat", path = null } = action.payload || {};
|
const { key = "chat", path = null } = action.payload || {};
|
||||||
state.rememberedNavs[key] = path;
|
state.rememberedNavs[key] = path;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||||
import { isNull, omitBy } from "lodash";
|
import { isNull, omitBy } from "lodash";
|
||||||
import BASE_URL from "../config";
|
import BASE_URL from "../config";
|
||||||
import { User } from "../../types/auth";
|
import { User } from "../../types/user";
|
||||||
import { UserLog, UserState } from "../../types/sse";
|
import { UserLog, UserState } from "../../types/sse";
|
||||||
|
|
||||||
export interface StoredUser extends User {
|
export interface StoredUser extends User {
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ const Styled = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
type?: "chat";
|
type?: "chat" | "user";
|
||||||
}
|
}
|
||||||
|
|
||||||
const BlankPlaceholder: FC<Props> = ({ type = "chat" }) => {
|
const BlankPlaceholder: FC<Props> = ({ type = "chat" }) => {
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ const StyledWrapper = styled.div`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.name {
|
.name {
|
||||||
/* user-select: text; */
|
|
||||||
display: flex;
|
display: flex;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
|||||||
@@ -3,18 +3,17 @@ import styled from "styled-components";
|
|||||||
import HashIcon from "../../assets/icons/channel.svg";
|
import HashIcon from "../../assets/icons/channel.svg";
|
||||||
import LockHashIcon from "../../assets/icons/channel.private.svg";
|
import LockHashIcon from "../../assets/icons/channel.private.svg";
|
||||||
|
|
||||||
interface Props {
|
|
||||||
personal?: boolean;
|
|
||||||
muted?: boolean;
|
|
||||||
className?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Styled = styled.div`
|
const Styled = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
&.muted path {
|
&.muted path {
|
||||||
fill: #d0d5dd;
|
fill: #d0d5dd;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
interface Props {
|
||||||
|
personal?: boolean;
|
||||||
|
muted?: boolean;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
const ChannelIcon: FC<Props> = ({ personal = false, muted = false, className = "" }) => {
|
const ChannelIcon: FC<Props> = ({ personal = false, muted = false, className = "" }) => {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import micIcon from "../../assets/icons/mic.on.svg?url";
|
|||||||
import Avatar from "./Avatar";
|
import Avatar from "./Avatar";
|
||||||
import useConfig from "../hook/useConfig";
|
import useConfig from "../hook/useConfig";
|
||||||
import { useAppSelector } from "../../app/store";
|
import { useAppSelector } from "../../app/store";
|
||||||
|
import { FC } from "react";
|
||||||
|
|
||||||
const StyledWrapper = styled.div`
|
const StyledWrapper = styled.div`
|
||||||
background-color: #f4f4f5;
|
background-color: #f4f4f5;
|
||||||
@@ -60,8 +61,8 @@ const StyledWrapper = styled.div`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
type Props = {};
|
||||||
export default function CurrentUser() {
|
const CurrentUser: FC<Props> = () => {
|
||||||
const { values: agoraConfig } = useConfig("agora");
|
const { values: agoraConfig } = useConfig("agora");
|
||||||
const currUser = useAppSelector((store) => {
|
const currUser = useAppSelector((store) => {
|
||||||
return store.authData.user;
|
return store.authData.user;
|
||||||
@@ -86,4 +87,5 @@ export default function CurrentUser() {
|
|||||||
)}
|
)}
|
||||||
</StyledWrapper>
|
</StyledWrapper>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
export default CurrentUser;
|
||||||
|
|||||||
@@ -11,8 +11,6 @@ interface Props {
|
|||||||
const StyledWrapper = styled.div`
|
const StyledWrapper = styled.div`
|
||||||
filter: drop-shadow(0px 25px 50px rgba(31, 41, 55, 0.25));
|
filter: drop-shadow(0px 25px 50px rgba(31, 41, 55, 0.25));
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
/* height: 358px;
|
|
||||||
overflow: hidden; */
|
|
||||||
.emoji-mart {
|
.emoji-mart {
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
@@ -43,10 +41,6 @@ const EmojiPicker: FC<Props> = ({ onSelect, ...rest }) => {
|
|||||||
perLine={10}
|
perLine={10}
|
||||||
emojiSize={24}
|
emojiSize={24}
|
||||||
emojiTooltip={true}
|
emojiTooltip={true}
|
||||||
// set="twitter"
|
|
||||||
// data={data}
|
|
||||||
// set="twitter"
|
|
||||||
// showPreview={false}
|
|
||||||
showSkinTones={false}
|
showSkinTones={false}
|
||||||
onSelect={onSelect}
|
onSelect={onSelect}
|
||||||
{...rest}
|
{...rest}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { FC } from "react";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import { useGetServerVersionQuery } from "../../app/services/server";
|
import { useGetServerVersionQuery } from "../../app/services/server";
|
||||||
|
|
||||||
@@ -6,8 +7,8 @@ const Styled = styled.div`
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
`;
|
`;
|
||||||
|
type Props = {};
|
||||||
export default function FAQ() {
|
const FAQ: FC<Props> = () => {
|
||||||
const { data: serverVersion } = useGetServerVersionQuery();
|
const { data: serverVersion } = useGetServerVersionQuery();
|
||||||
return (
|
return (
|
||||||
<Styled>
|
<Styled>
|
||||||
@@ -16,4 +17,5 @@ export default function FAQ() {
|
|||||||
<div className="item">Build Timestamp: {process.env.REACT_APP_BUILD_TIME}</div>
|
<div className="item">Build Timestamp: {process.env.REACT_APP_BUILD_TIME}</div>
|
||||||
</Styled>
|
</Styled>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
export default FAQ;
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ interface Props {
|
|||||||
context: "user" | "channel";
|
context: "user" | "channel";
|
||||||
to: number;
|
to: number;
|
||||||
created_at: number;
|
created_at: number;
|
||||||
from_uid?: number;
|
from_uid: number;
|
||||||
content: string;
|
content: string;
|
||||||
download: string;
|
download: string;
|
||||||
thumbnail: string;
|
thumbnail: string;
|
||||||
@@ -41,7 +41,7 @@ const FileMessage: FC<Props> = ({
|
|||||||
context,
|
context,
|
||||||
to,
|
to,
|
||||||
created_at,
|
created_at,
|
||||||
from_uid = null,
|
from_uid,
|
||||||
content = "",
|
content = "",
|
||||||
download = "",
|
download = "",
|
||||||
thumbnail = "",
|
thumbnail = "",
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useState, MouseEvent, ChangeEvent } from "react";
|
import { useState, MouseEvent, ChangeEvent, FC } from "react";
|
||||||
// import toast from "react-hot-toast";
|
// import toast from "react-hot-toast";
|
||||||
import Modal from "../Modal";
|
import Modal from "../Modal";
|
||||||
import Button from "../styled/Button";
|
import Button from "../styled/Button";
|
||||||
@@ -15,8 +15,12 @@ import useFilteredUsers from "../../hook/useFilteredUsers";
|
|||||||
import CloseIcon from "../../../assets/icons/close.circle.svg";
|
import CloseIcon from "../../../assets/icons/close.circle.svg";
|
||||||
import StyledCheckbox from "../styled/Checkbox";
|
import StyledCheckbox from "../styled/Checkbox";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
|
interface IProps {
|
||||||
|
mids: number[];
|
||||||
|
closeModal: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
export default function ForwardModal({ mids, closeModal }) {
|
const ForwardModal: FC<IProps> = ({ mids, closeModal }) => {
|
||||||
const [appendText, setAppendText] = useState("");
|
const [appendText, setAppendText] = useState("");
|
||||||
const { sendMessages } = useSendMessage();
|
const { sendMessages } = useSendMessage();
|
||||||
const { forwardMessage, forwarding } = useForwardMessage();
|
const { forwardMessage, forwarding } = useForwardMessage();
|
||||||
@@ -176,4 +180,5 @@ export default function ForwardModal({ mids, closeModal }) {
|
|||||||
</StyledWrapper>
|
</StyledWrapper>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
export default ForwardModal;
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { KEY_LOCAL_MAGIC_TOKEN } from "../../app/config";
|
|||||||
import Button from "./styled/Button";
|
import Button from "./styled/Button";
|
||||||
import { useLoginMutation } from "../../app/services/auth";
|
import { useLoginMutation } from "../../app/services/auth";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
|
import { FetchBaseQueryError } from "@reduxjs/toolkit/dist/query";
|
||||||
|
|
||||||
const StyledSocialButton = styled(Button)`
|
const StyledSocialButton = styled(Button)`
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -51,7 +52,8 @@ const GithubLoginButton: FC<Props> = ({ type = "login", client_id }) => {
|
|||||||
}, [isSuccess]);
|
}, [isSuccess]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (error) {
|
if (error) {
|
||||||
switch (error?.status) {
|
// todo: why?
|
||||||
|
switch ((error as FetchBaseQueryError).status) {
|
||||||
case 410:
|
case 410:
|
||||||
toast.error(
|
toast.error(
|
||||||
"No associated account found, please user admin for an invitation link to join."
|
"No associated account found, please user admin for an invitation link to join."
|
||||||
|
|||||||
@@ -66,13 +66,13 @@ const GoogleLoginInner: FC<Props> = ({ type = "login", loaded, loadError }) => {
|
|||||||
}, [error]);
|
}, [error]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledSocialButton disabled={!loaded || isLoading} onClick={null}>
|
<StyledSocialButton disabled={!loaded || isLoading}>
|
||||||
<div className="invisible">
|
<div className="invisible">
|
||||||
<GoogleLogin
|
<GoogleLogin
|
||||||
onSuccess={(res) => {
|
onSuccess={(res) => {
|
||||||
login({
|
login({
|
||||||
magic_token,
|
magic_token,
|
||||||
id_token: res.credential,
|
id_token: res.credential || "",
|
||||||
type: "google"
|
type: "google"
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -66,10 +66,10 @@ const StyledWrapper = styled.div`
|
|||||||
|
|
||||||
export interface PreviewImageData {
|
export interface PreviewImageData {
|
||||||
originUrl: string;
|
originUrl: string;
|
||||||
thumbnail: string;
|
thumbnail?: string;
|
||||||
downloadLink: string;
|
downloadLink?: string;
|
||||||
name: string;
|
name?: string;
|
||||||
type: string;
|
type?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import styled from "styled-components";
|
|||||||
import useInviteLink from "../hook/useInviteLink";
|
import useInviteLink from "../hook/useInviteLink";
|
||||||
import Input from "./styled/Input";
|
import Input from "./styled/Input";
|
||||||
import Button from "./styled/Button";
|
import Button from "./styled/Button";
|
||||||
|
import { FC } from "react";
|
||||||
|
|
||||||
const StyledWrapper = styled.div`
|
const StyledWrapper = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -37,8 +38,8 @@ const StyledWrapper = styled.div`
|
|||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
type Props = {};
|
||||||
export default function InviteLink() {
|
const InviteLink: FC<Props> = () => {
|
||||||
const { generating, link, linkCopied, copyLink, generateNewLink } = useInviteLink();
|
const { generating, link, linkCopied, copyLink, generateNewLink } = useInviteLink();
|
||||||
const handleNewLink = () => {
|
const handleNewLink = () => {
|
||||||
generateNewLink();
|
generateNewLink();
|
||||||
@@ -59,4 +60,6 @@ export default function InviteLink() {
|
|||||||
</Button>
|
</Button>
|
||||||
</StyledWrapper>
|
</StyledWrapper>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default InviteLink;
|
||||||
|
|||||||
@@ -135,7 +135,15 @@ const ManageMembers: FC<Props> = ({ cid }) => {
|
|||||||
}
|
}
|
||||||
}, [updateSuccess]);
|
}, [updateSuccess]);
|
||||||
|
|
||||||
const handleToggleRole = ({ ignore = false, uid = null, isAdmin = true }) => {
|
const handleToggleRole = ({
|
||||||
|
ignore = false,
|
||||||
|
uid,
|
||||||
|
isAdmin = true
|
||||||
|
}: {
|
||||||
|
ignore: boolean;
|
||||||
|
uid: number;
|
||||||
|
isAdmin: boolean;
|
||||||
|
}) => {
|
||||||
hideAll();
|
hideAll();
|
||||||
if (ignore) return;
|
if (ignore) return;
|
||||||
updateUser({ id: uid, is_admin: isAdmin });
|
updateUser({ id: uid, is_admin: isAdmin });
|
||||||
@@ -154,7 +162,9 @@ const ManageMembers: FC<Props> = ({ cid }) => {
|
|||||||
</div>
|
</div>
|
||||||
<ul className="members">
|
<ul className="members">
|
||||||
{uids.map((uid) => {
|
{uids.map((uid) => {
|
||||||
const { name, email, is_admin } = users.byId[uid];
|
const currUser = users.byId[uid];
|
||||||
|
if (!currUser) return null;
|
||||||
|
const { name, email, is_admin } = currUser;
|
||||||
const owner = channel && channel.owner == uid;
|
const owner = channel && channel.owner == uid;
|
||||||
const switchRoleVisible = loginUser.is_admin && loginUser.uid !== uid;
|
const switchRoleVisible = loginUser.is_admin && loginUser.uid !== uid;
|
||||||
const dotsVisible = email || loginUser?.is_admin;
|
const dotsVisible = email || loginUser?.is_admin;
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
import { useEffect, useState, useRef, SyntheticEvent } from "react";
|
import { useEffect, useState, useRef, FC } from "react";
|
||||||
|
import { BeforeInstallPromptEvent } from "../../../types/global";
|
||||||
import Prompt from "./Prompt";
|
import Prompt from "./Prompt";
|
||||||
import usePrompt from "./usePrompt";
|
import usePrompt from "./usePrompt";
|
||||||
|
interface IProps {}
|
||||||
export default function Manifest() {
|
const Manifest: FC<IProps> = () => {
|
||||||
const { setCanceled: setCanceled, prompted } = usePrompt();
|
const { setCanceled: setCanceled, prompted } = usePrompt();
|
||||||
const deferredPromptRef = useRef(null);
|
const deferredPromptRef = useRef<null | BeforeInstallPromptEvent>(null);
|
||||||
const [popup, setPopup] = useState(false);
|
const [popup, setPopup] = useState(false);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleInstallPromotion = (e: SyntheticEvent) => {
|
const handleInstallPromotion = (e: BeforeInstallPromptEvent) => {
|
||||||
// Prevent the mini-infobar from appearing on mobile
|
// Prevent the mini-infobar from appearing on mobile
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
// Stash the event so it can be triggered later.
|
// Stash the event so it can be triggered later.
|
||||||
@@ -21,20 +22,6 @@ export default function Manifest() {
|
|||||||
deferredPromptRef.current = null;
|
deferredPromptRef.current = null;
|
||||||
setPopup(false);
|
setPopup(false);
|
||||||
};
|
};
|
||||||
// if (isSuccess && data) {
|
|
||||||
// console.log("server", data);
|
|
||||||
// manifest.name = `${data.name}'s Chat`;
|
|
||||||
// // const stringManifest = JSON.stringify(manifest);
|
|
||||||
// // const blob = new Blob([stringManifest], { type: "application/json" });
|
|
||||||
// // const manifestURL = URL.createObjectURL(blob);
|
|
||||||
// let content = encodeURIComponent(JSON.stringify(manifest));
|
|
||||||
// let manifestURL = "data:application/manifest+json," + content;
|
|
||||||
// const manifestEle = document.querySelector("#my-manifest-placeholder");
|
|
||||||
// if (manifestEle) {
|
|
||||||
// manifestEle.setAttribute("href", manifestURL);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// }
|
|
||||||
window.addEventListener("beforeinstallprompt", handleInstallPromotion, true);
|
window.addEventListener("beforeinstallprompt", handleInstallPromotion, true);
|
||||||
window.addEventListener("appinstalled", handleInstalled);
|
window.addEventListener("appinstalled", handleInstalled);
|
||||||
return () => {
|
return () => {
|
||||||
@@ -61,4 +48,5 @@ export default function Manifest() {
|
|||||||
};
|
};
|
||||||
if (!popup || prompted) return null;
|
if (!popup || prompted) return null;
|
||||||
return <Prompt handleInstall={handleInstall} closePrompt={handleClose} />;
|
return <Prompt handleInstall={handleInstall} closePrompt={handleClose} />;
|
||||||
}
|
};
|
||||||
|
export default Manifest;
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ function MarkdownEditor({
|
|||||||
const editorInstance = editor.getInstance();
|
const editorInstance = editor.getInstance();
|
||||||
editorInstance.removeHook("addImageBlobHook");
|
editorInstance.removeHook("addImageBlobHook");
|
||||||
editorInstance.addHook("addImageBlobHook", async (blob, callback) => {
|
editorInstance.addHook("addImageBlobHook", async (blob, callback) => {
|
||||||
const { thumbnail = "" } = await uploadFile(blob);
|
const { thumbnail = "" } = (await uploadFile(blob)) || {};
|
||||||
callback(thumbnail);
|
callback(thumbnail);
|
||||||
});
|
});
|
||||||
setEditorInstance(editorInstance);
|
setEditorInstance(editorInstance);
|
||||||
|
|||||||
@@ -1,14 +1,8 @@
|
|||||||
import { useEffect, useState, useRef, FC, MouseEvent } from "react";
|
import { useEffect, useState, useRef, FC } from "react";
|
||||||
import "prismjs/themes/prism.css";
|
import "prismjs/themes/prism.css";
|
||||||
import "@toast-ui/editor-plugin-code-syntax-highlight/dist/toastui-editor-plugin-code-syntax-highlight.css";
|
import "@toast-ui/editor-plugin-code-syntax-highlight/dist/toastui-editor-plugin-code-syntax-highlight.css";
|
||||||
import codeSyntaxHighlight from "@toast-ui/editor-plugin-code-syntax-highlight/dist/toastui-editor-plugin-code-syntax-highlight-all.js";
|
import codeSyntaxHighlight from "@toast-ui/editor-plugin-code-syntax-highlight/dist/toastui-editor-plugin-code-syntax-highlight-all.js";
|
||||||
|
|
||||||
// import "prismjs/themes/prism.css";
|
|
||||||
// import "@toast-ui/editor-plugin-code-syntax-highlight/dist/toastui-editor-plugin-code-syntax-highlight.css";
|
|
||||||
// import codeSyntaxHighlight from "@toast-ui/editor-plugin-code-syntax-highlight";
|
|
||||||
// Import prismjs
|
|
||||||
// import Prism from "prismjs";
|
|
||||||
|
|
||||||
import { Viewer } from "@toast-ui/react-editor";
|
import { Viewer } from "@toast-ui/react-editor";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import ImagePreviewModal, { PreviewImageData } from "./ImagePreviewModal";
|
import ImagePreviewModal, { PreviewImageData } from "./ImagePreviewModal";
|
||||||
@@ -24,37 +18,37 @@ const Styled = styled.div`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
interface Props {
|
interface IProps {
|
||||||
content: string;
|
content: string;
|
||||||
}
|
}
|
||||||
|
const MarkdownRender: FC<IProps> = ({ content }) => {
|
||||||
const MarkdownRender: FC<Props> = ({ content }) => {
|
const mdContainer = useRef<HTMLDivElement>();
|
||||||
const mdContainer = useRef<HTMLDivElement>(null);
|
|
||||||
const [previewImage, setPreviewImage] = useState<PreviewImageData | null>(null);
|
const [previewImage, setPreviewImage] = useState<PreviewImageData | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const container = mdContainer?.current;
|
const container = mdContainer?.current;
|
||||||
if (container) {
|
if (!container) return;
|
||||||
// 点击查看大图
|
// 点击查看大图
|
||||||
container.addEventListener(
|
// todo: 事件代理
|
||||||
"click",
|
container.addEventListener(
|
||||||
(evt) => {
|
"click",
|
||||||
console.log(evt);
|
(evt) => {
|
||||||
evt.stopPropagation();
|
console.log(evt);
|
||||||
const { target } = evt;
|
evt.stopPropagation();
|
||||||
// 图片
|
const target = evt.target as HTMLImageElement;
|
||||||
if (target.nodeName == "IMG") {
|
if (!target) return;
|
||||||
const urlObj = new URL(target.src);
|
// 图片
|
||||||
const originUrl = `${urlObj.origin}${
|
if (target.nodeName == "IMG") {
|
||||||
urlObj.pathname
|
const urlObj = new URL(target.src);
|
||||||
}?file_path=${urlObj.searchParams.get("file_path")}`;
|
const originUrl = `${urlObj.origin}${urlObj.pathname}?file_path=${urlObj.searchParams.get(
|
||||||
const data = { originUrl };
|
"file_path"
|
||||||
setPreviewImage(data);
|
)}`;
|
||||||
}
|
const data = { originUrl };
|
||||||
},
|
setPreviewImage(data);
|
||||||
true
|
}
|
||||||
);
|
},
|
||||||
}
|
true
|
||||||
|
);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const closePreviewModal = () => {
|
const closePreviewModal = () => {
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
import { FC, ReactElement, useEffect, useState } from "react";
|
||||||
|
import styled from "styled-components";
|
||||||
|
import StyledMsg from "./styled";
|
||||||
|
import renderContent from "./renderContent";
|
||||||
|
import Avatar from "../Avatar";
|
||||||
|
import useFavMessage from "../../hook/useFavMessage";
|
||||||
|
|
||||||
|
const StyledFav = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
border-radius: var(--br);
|
||||||
|
background-color: #f4f4f5;
|
||||||
|
`;
|
||||||
|
type Props = {
|
||||||
|
id?: number;
|
||||||
|
};
|
||||||
|
const FavoredMessage: FC<Props> = ({ id }) => {
|
||||||
|
const { favorites } = useFavMessage({});
|
||||||
|
const [msgs, setMsgs] = useState<ReactElement | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const current = favorites.find((f) => f.id == id);
|
||||||
|
const { messages } = current || {};
|
||||||
|
if (!messages) return;
|
||||||
|
const favorite_mids = messages.map(({ from_mid }) => +from_mid) || [];
|
||||||
|
|
||||||
|
setMsgs(
|
||||||
|
<StyledFav data-favorite-mids={favorite_mids.join(",")} className="favorite">
|
||||||
|
<div className="list">
|
||||||
|
{messages.map((msg, idx) => {
|
||||||
|
const { user = {}, download, content, content_type, properties, thumbnail } = msg;
|
||||||
|
return (
|
||||||
|
<StyledMsg className="archive" key={idx}>
|
||||||
|
{user && (
|
||||||
|
<div className="avatar">
|
||||||
|
<Avatar url={user.avatar} name={user.name} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="details">
|
||||||
|
<div className="up">
|
||||||
|
<span className="name">{user?.name}</span>
|
||||||
|
</div>
|
||||||
|
<div className="down">
|
||||||
|
{renderContent({
|
||||||
|
download,
|
||||||
|
content,
|
||||||
|
content_type,
|
||||||
|
properties,
|
||||||
|
thumbnail
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</StyledMsg>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</StyledFav>
|
||||||
|
);
|
||||||
|
}, [favorites, id]);
|
||||||
|
|
||||||
|
if (!id) return null;
|
||||||
|
|
||||||
|
return msgs;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FavoredMessage;
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
import { useEffect, useState } from "react";
|
|
||||||
import styled from "styled-components";
|
|
||||||
import StyledMsg from "./styled";
|
|
||||||
import renderContent from "./renderContent";
|
|
||||||
import Avatar from "../Avatar";
|
|
||||||
import useFavMessage from "../../hook/useFavMessage";
|
|
||||||
|
|
||||||
const StyledFav = styled.div`
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
border-radius: var(--br);
|
|
||||||
background-color: #f4f4f5;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const FavoritedMessage = ({ id }) => {
|
|
||||||
const { favorites } = useFavMessage({});
|
|
||||||
const [msgs, setMsgs] = useState(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const current = favorites.find((f) => f.id == id);
|
|
||||||
const { messages } = current || {};
|
|
||||||
if (messages) {
|
|
||||||
const favorite_mids = messages.map(({ from_mid }) => from_mid) || [];
|
|
||||||
|
|
||||||
setMsgs(
|
|
||||||
<StyledFav data-favorite-mids={favorite_mids.join(",")} className="favorite">
|
|
||||||
<div className="list">
|
|
||||||
{messages.map((msg, idx) => {
|
|
||||||
const { user = {}, download, content, content_type, properties, thumbnail } = msg;
|
|
||||||
return (
|
|
||||||
<StyledMsg className="archive" key={idx}>
|
|
||||||
{user && (
|
|
||||||
<div className="avatar">
|
|
||||||
<Avatar url={user.avatar} name={user.name} />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className="details">
|
|
||||||
<div className="up">
|
|
||||||
<span className="name">{user?.name}</span>
|
|
||||||
</div>
|
|
||||||
<div className="down">
|
|
||||||
{renderContent({
|
|
||||||
download,
|
|
||||||
content,
|
|
||||||
content_type,
|
|
||||||
properties,
|
|
||||||
thumbnail
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</StyledMsg>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</StyledFav>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}, [favorites, id]);
|
|
||||||
|
|
||||||
if (!id) return null;
|
|
||||||
|
|
||||||
return msgs;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default FavoritedMessage;
|
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
|
import { FC } from "react";
|
||||||
import { Helmet } from "react-helmet";
|
import { Helmet } from "react-helmet";
|
||||||
import BASE_URL from "../../app/config";
|
import BASE_URL from "../../app/config";
|
||||||
import { useGetServerQuery } from "../../app/services/server";
|
import { useGetServerQuery } from "../../app/services/server";
|
||||||
|
type Props = {};
|
||||||
export default function Meta() {
|
const Meta: FC<Props> = () => {
|
||||||
const { data, isSuccess } = useGetServerQuery();
|
const { data, isSuccess } = useGetServerQuery();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -11,4 +12,5 @@ export default function Meta() {
|
|||||||
{isSuccess && <title>{data.name} Web App</title>}
|
{isSuccess && <title>{data.name} Web App</title>}
|
||||||
</Helmet>
|
</Helmet>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
export default Meta;
|
||||||
|
|||||||
@@ -2,9 +2,8 @@ import { useState } from "react";
|
|||||||
import { initializeApp } from "firebase/app";
|
import { initializeApp } from "firebase/app";
|
||||||
import { getToken, getMessaging } from "firebase/messaging";
|
import { getToken, getMessaging } from "firebase/messaging";
|
||||||
import { firebaseConfig } from "../../../app/config";
|
import { firebaseConfig } from "../../../app/config";
|
||||||
|
const useDeviceToken = (vapidKey: string) => {
|
||||||
const useDeviceToken = (vapidKey) => {
|
const [token, setToken] = useState<string | null>(null);
|
||||||
const [token, setToken] = useState(null);
|
|
||||||
// https only
|
// https only
|
||||||
if (navigator.serviceWorker) {
|
if (navigator.serviceWorker) {
|
||||||
const messaging = getMessaging(initializeApp(firebaseConfig));
|
const messaging = getMessaging(initializeApp(firebaseConfig));
|
||||||
@@ -17,7 +16,7 @@ const useDeviceToken = (vapidKey) => {
|
|||||||
console.log("current token for client: ", currentToken);
|
console.log("current token for client: ", currentToken);
|
||||||
setToken(currentToken);
|
setToken(currentToken);
|
||||||
// updateDeviceToken(currentToken)
|
// updateDeviceToken(currentToken)
|
||||||
// Perform any other neccessary action with the token
|
// Perform any other necessary action with the token
|
||||||
} else {
|
} else {
|
||||||
// Show permission request UI
|
// Show permission request UI
|
||||||
console.log("No registration token available. Request permission to generate one.");
|
console.log("No registration token available. Request permission to generate one.");
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ import useUserOperation from "../../hook/useUserOperation";
|
|||||||
import { useAppSelector } from "../../../app/store";
|
import { useAppSelector } from "../../../app/store";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
uid?: number;
|
uid: number;
|
||||||
type: string;
|
type?: "embed" | "card";
|
||||||
cid?: number;
|
cid?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import searchIcon from "../../assets/icons/search.svg?url";
|
|||||||
import addIcon from "../../assets/icons/add.svg?url";
|
import addIcon from "../../assets/icons/add.svg?url";
|
||||||
import AddEntriesMenu from "./AddEntriesMenu";
|
import AddEntriesMenu from "./AddEntriesMenu";
|
||||||
import Tooltip from "./Tooltip";
|
import Tooltip from "./Tooltip";
|
||||||
|
import { FC } from "react";
|
||||||
|
|
||||||
const StyledWrapper = styled.div`
|
const StyledWrapper = styled.div`
|
||||||
position: relative;
|
position: relative;
|
||||||
@@ -32,8 +33,8 @@ const StyledWrapper = styled.div`
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
type Props = {};
|
||||||
export default function Search() {
|
const Search: FC<Props> = () => {
|
||||||
return (
|
return (
|
||||||
<StyledWrapper>
|
<StyledWrapper>
|
||||||
<div className="search">
|
<div className="search">
|
||||||
@@ -47,4 +48,5 @@ export default function Search() {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</StyledWrapper>
|
</StyledWrapper>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
export default Search;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState, FC } from "react";
|
||||||
import useSendMessage from "../../hook/useSendMessage";
|
import useSendMessage from "../../hook/useSendMessage";
|
||||||
import useAddLocalFileMessage from "../../hook/useAddLocalFileMessage";
|
import useAddLocalFileMessage from "../../hook/useAddLocalFileMessage";
|
||||||
import { updateInputMode } from "../../../app/slices/ui";
|
import { updateInputMode } from "../../../app/slices/ui";
|
||||||
@@ -20,11 +20,15 @@ const Modes = {
|
|||||||
text: "text",
|
text: "text",
|
||||||
markdown: "markdown"
|
markdown: "markdown"
|
||||||
};
|
};
|
||||||
function Send({
|
interface IProps {
|
||||||
|
context?: "channel" | "user";
|
||||||
|
id: number;
|
||||||
|
}
|
||||||
|
const Send: FC<IProps> = ({
|
||||||
// 发给谁,或者是channel,或者是user
|
// 发给谁,或者是channel,或者是user
|
||||||
context = "channel",
|
context = "channel",
|
||||||
id = ""
|
id
|
||||||
}) {
|
}) => {
|
||||||
const { resetStageFiles } = useUploadFile({ context, id });
|
const { resetStageFiles } = useUploadFile({ context, id });
|
||||||
const { getDraft, getUpdateDraft } = useDraft({ context, id });
|
const { getDraft, getUpdateDraft } = useDraft({ context, id });
|
||||||
const editor = useMixedEditor(`${context}_${id}`);
|
const editor = useMixedEditor(`${context}_${id}`);
|
||||||
@@ -111,7 +115,7 @@ function Send({
|
|||||||
resetStageFiles();
|
resetStageFiles();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const sendMarkdown = async (content) => {
|
const sendMarkdown = async (content: string) => {
|
||||||
sendMessage({
|
sendMessage({
|
||||||
id,
|
id,
|
||||||
reply_mid: replying_mid,
|
reply_mid: replying_mid,
|
||||||
@@ -173,7 +177,7 @@ function Send({
|
|||||||
</div>
|
</div>
|
||||||
</StyledSend>
|
</StyledSend>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default Send;
|
export default Send;
|
||||||
// export default memo(Send, (prevs, nexts) => {
|
// export default memo(Send, (prevs, nexts) => {
|
||||||
|
|||||||
@@ -5,16 +5,16 @@ import { addChannelMsg } from "../../app/slices/message.channel";
|
|||||||
import { addUserMsg } from "../../app/slices/message.user";
|
import { addUserMsg } from "../../app/slices/message.user";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
context: "channel" | "uesr";
|
context: "channel" | "user";
|
||||||
to: number;
|
to: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function useAddLocalFileMessage({ context, to }: IProps) {
|
export default function useAddLocalFileMessage({ context, to }: IProps) {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const addContextMessage = context == "channel" ? addChannelMsg : addUserMsg;
|
const addContextMessage = context == "channel" ? addChannelMsg : addUserMsg;
|
||||||
const addLocalFileMesage = (data) => {
|
const addLocalFileMessage = (data) => {
|
||||||
dispatch(addMessage(data));
|
dispatch(addMessage(data));
|
||||||
dispatch(addContextMessage({ id: to, mid: data.mid }));
|
dispatch(addContextMessage({ id: to, mid: data.mid }));
|
||||||
};
|
};
|
||||||
return addLocalFileMesage;
|
return addLocalFileMessage;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ export default function useStreaming() {
|
|||||||
isError
|
isError
|
||||||
} = await renewToken({
|
} = await renewToken({
|
||||||
token,
|
token,
|
||||||
refreshToken
|
refresh_token: refreshToken
|
||||||
});
|
});
|
||||||
if (isError) return;
|
if (isError) return;
|
||||||
api_token = newToken;
|
api_token = newToken;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useState, useRef } from "react";
|
import { useState, useRef, FC } from "react";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
import { updateUploadFiles } from "../../app/slices/ui";
|
import { updateUploadFiles } from "../../app/slices/ui";
|
||||||
import BASE_URL, { FILE_SLICE_SIZE } from "../../app/config";
|
import BASE_URL, { FILE_SLICE_SIZE } from "../../app/config";
|
||||||
@@ -6,8 +6,12 @@ import { usePrepareUploadFileMutation, useUploadFileMutation } from "../../app/s
|
|||||||
import { useAppDispatch, useAppSelector } from "../../app/store";
|
import { useAppDispatch, useAppSelector } from "../../app/store";
|
||||||
|
|
||||||
// todo: check props type
|
// todo: check props type
|
||||||
export default function useUploadFile(props: { context: string; id: string } | object = {}) {
|
interface IProps {
|
||||||
const { context = "", id = "" } = props;
|
context: "channel" | "user";
|
||||||
|
id: number;
|
||||||
|
}
|
||||||
|
const useUploadFile = (props: IProps) => {
|
||||||
|
const { context, id } = props;
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { stageFiles, replying } = useAppSelector((store) => {
|
const { stageFiles, replying } = useAppSelector((store) => {
|
||||||
return {
|
return {
|
||||||
@@ -112,7 +116,7 @@ export default function useUploadFile(props: { context: string; id: string } | o
|
|||||||
canceledRef.current = true;
|
canceledRef.current = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeStageFile = (idx) => {
|
const removeStageFile = (idx: number) => {
|
||||||
dispatch(updateUploadFiles({ context, id, operation: "remove", index: idx }));
|
dispatch(updateUploadFiles({ context, id, operation: "remove", index: idx }));
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -128,7 +132,7 @@ export default function useUploadFile(props: { context: string; id: string } | o
|
|||||||
dispatch(updateUploadFiles({ context, id, operation: "reset" }));
|
dispatch(updateUploadFiles({ context, id, operation: "reset" }));
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateStageFile = (idx, data = {}) => {
|
const updateStageFile = (idx: number, data = {}) => {
|
||||||
dispatch(
|
dispatch(
|
||||||
updateUploadFiles({
|
updateUploadFiles({
|
||||||
context,
|
context,
|
||||||
@@ -156,4 +160,5 @@ export default function useUploadFile(props: { context: string; id: string } | o
|
|||||||
removeStageFile,
|
removeStageFile,
|
||||||
updateStageFile
|
updateStageFile
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
|
export default useUploadFile;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useState, useEffect, FC } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
// import { ContentTypes } from "../../../app/config";
|
// import { ContentTypes } from "../../../app/config";
|
||||||
import { useNavigate, useMatch } from "react-router-dom";
|
import { useNavigate, useMatch } from "react-router-dom";
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// import React from "react";
|
// import React from "react";
|
||||||
// import { useSelector } from "react-redux";
|
// import { useSelector } from "react-redux";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import FavoritedMessage from "../../common/component/Message/FavoritedMessage";
|
import FavoredMessage from "../../common/component/Message/FavoredMessage";
|
||||||
import IconSurprise from "../../assets/icons/emoji.suprise.svg";
|
import IconSurprise from "../../assets/icons/emoji.suprise.svg";
|
||||||
// import IconForward from "../../../assets/icons/forward.svg";
|
// import IconForward from "../../../assets/icons/forward.svg";
|
||||||
import IconRemove from "../../assets/icons/close.svg";
|
import IconRemove from "../../assets/icons/close.svg";
|
||||||
@@ -107,7 +107,7 @@ export default function FavList({ cid = null, uid = null }) {
|
|||||||
console.log("favv", id);
|
console.log("favv", id);
|
||||||
return (
|
return (
|
||||||
<li key={id} className="fav">
|
<li key={id} className="fav">
|
||||||
<FavoritedMessage id={id} />
|
<FavoredMessage id={id} />
|
||||||
<div className="opts">
|
<div className="opts">
|
||||||
{/* <button
|
{/* <button
|
||||||
className="btn"
|
className="btn"
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import Styled from "./styled";
|
import Styled from "./styled";
|
||||||
import { useSelector } from "react-redux";
|
import FavoredMessage from "../../common/component/Message/FavoredMessage";
|
||||||
import FavoritedMessage from "../../common/component/Message/FavoritedMessage";
|
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import IconAudio from "../../assets/icons/file.audio.svg";
|
import IconAudio from "../../assets/icons/file.audio.svg";
|
||||||
import IconVideo from "../../assets/icons/file.video.svg";
|
import IconVideo from "../../assets/icons/file.video.svg";
|
||||||
@@ -10,6 +9,7 @@ import IconUnknown from "../../assets/icons/file.unknown.svg";
|
|||||||
import IconImage from "../../assets/icons/file.image.svg";
|
import IconImage from "../../assets/icons/file.image.svg";
|
||||||
import IconChannel from "../../assets/icons/channel.svg";
|
import IconChannel from "../../assets/icons/channel.svg";
|
||||||
import { ContentTypes } from "../../app/config";
|
import { ContentTypes } from "../../app/config";
|
||||||
|
import { useAppSelector } from "../../app/store";
|
||||||
const Filters = [
|
const Filters = [
|
||||||
{
|
{
|
||||||
icon: <IconUnknown className="icon" />,
|
icon: <IconUnknown className="icon" />,
|
||||||
@@ -40,7 +40,7 @@ const Filters = [
|
|||||||
function FavsPage() {
|
function FavsPage() {
|
||||||
const [filter, setFilter] = useState("");
|
const [filter, setFilter] = useState("");
|
||||||
const [favs, setFavs] = useState([]);
|
const [favs, setFavs] = useState([]);
|
||||||
const { favorites, channelData, userData } = useSelector((store) => {
|
const { favorites, channelData, userData } = useAppSelector((store) => {
|
||||||
console.log("favs", store.favorites);
|
console.log("favs", store.favorites);
|
||||||
return {
|
return {
|
||||||
favorites: store.favorites,
|
favorites: store.favorites,
|
||||||
@@ -153,7 +153,7 @@ function FavsPage() {
|
|||||||
{tip}
|
{tip}
|
||||||
{dayjs(created_at).format("YYYY-MM-DD")}
|
{dayjs(created_at).format("YYYY-MM-DD")}
|
||||||
</h4>
|
</h4>
|
||||||
<FavoritedMessage key={id} id={id} />
|
<FavoredMessage key={id} id={id} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
/* eslint-disable no-undef */
|
/* eslint-disable no-undef */
|
||||||
import { useState } from "react";
|
import { FC, useState } from "react";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import StyledModal from "../../common/component/styled/Modal";
|
import StyledModal from "../../common/component/styled/Modal";
|
||||||
import Modal from "../../common/component/Modal";
|
import Modal from "../../common/component/Modal";
|
||||||
import { StyledSocialButton } from "./styled";
|
import { StyledSocialButton } from "./styled";
|
||||||
import StyledButton from "../../common/component/styled/Button";
|
import StyledButton from "../../common/component/styled/Button";
|
||||||
import OidcLoginEntry from "./OidcLoginEntry";
|
import OidcLoginEntry from "./OidcLoginEntry";
|
||||||
|
import { OIDCConfig } from "../../types/auth";
|
||||||
|
|
||||||
const StyledOicdLoginModal = styled(StyledModal)`
|
const StyledOidcLoginModal = styled(StyledModal)`
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 32px 32px 16px;
|
padding: 32px 32px 16px;
|
||||||
|
|
||||||
@@ -21,13 +22,15 @@ const StyledOicdLoginModal = styled(StyledModal)`
|
|||||||
height: 24px;
|
height: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&Cancel {
|
&.buttonCancel {
|
||||||
color: #8f8f8f;
|
color: #8f8f8f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
interface IProps {
|
||||||
export default function OidcLoginButton({ issuers }) {
|
issuers?: OIDCConfig[];
|
||||||
|
}
|
||||||
|
const OidcLoginButton: FC<IProps> = ({ issuers }) => {
|
||||||
const [modal, setModal] = useState(false);
|
const [modal, setModal] = useState(false);
|
||||||
if (!issuers) return null;
|
if (!issuers) return null;
|
||||||
return (
|
return (
|
||||||
@@ -41,7 +44,7 @@ export default function OidcLoginButton({ issuers }) {
|
|||||||
</StyledSocialButton>
|
</StyledSocialButton>
|
||||||
{modal && (
|
{modal && (
|
||||||
<Modal id="modal-modal">
|
<Modal id="modal-modal">
|
||||||
<StyledOicdLoginModal title="Login with OIDC">
|
<StyledOidcLoginModal title="Login with OIDC">
|
||||||
{issuers
|
{issuers
|
||||||
.filter((issuer) => issuer.enable)
|
.filter((issuer) => issuer.enable)
|
||||||
.map((issuer, index) => (
|
.map((issuer, index) => (
|
||||||
@@ -55,9 +58,10 @@ export default function OidcLoginButton({ issuers }) {
|
|||||||
>
|
>
|
||||||
Close
|
Close
|
||||||
</StyledButton>
|
</StyledButton>
|
||||||
</StyledOicdLoginModal>
|
</StyledOidcLoginModal>
|
||||||
</Modal>
|
</Modal>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
export default OidcLoginButton;
|
||||||
|
|||||||
@@ -1,27 +1,24 @@
|
|||||||
/* eslint-disable no-undef */
|
import { useEffect, FC } from "react";
|
||||||
import { useEffect } from "react";
|
|
||||||
import { useGetOpenidMutation } from "../../app/services/auth";
|
import { useGetOpenidMutation } from "../../app/services/auth";
|
||||||
|
import { OIDCConfig } from "../../types/auth";
|
||||||
import { StyledSocialButton } from "./styled";
|
import { StyledSocialButton } from "./styled";
|
||||||
|
|
||||||
export default function OidcLoginEntry({ issuer }) {
|
const OidcLoginEntry: FC<{ issuer: OIDCConfig }> = ({ issuer }) => {
|
||||||
const [getOpenId, { data, isLoading, isSuccess }] = useGetOpenidMutation();
|
const [getOpenId, { data, isLoading, isSuccess }] = useGetOpenidMutation();
|
||||||
|
|
||||||
const handleSolidLogin = () => {
|
const handleSolidLogin = () => {
|
||||||
getOpenId({
|
getOpenId({
|
||||||
// issuer: "solidweb.org",
|
issuer: issuer.domain,
|
||||||
issuer,
|
|
||||||
redirect_uri: `${location.origin}/#/login`
|
redirect_uri: `${location.origin}/#/login`
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isSuccess) {
|
if (isSuccess && data) {
|
||||||
console.log("wtf", data);
|
|
||||||
const { url } = data;
|
const { url } = data;
|
||||||
location.href = url;
|
location.href = url;
|
||||||
}
|
}
|
||||||
}, [data, isSuccess]);
|
}, [data, isSuccess]);
|
||||||
console.log(issuer);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledSocialButton disabled={isLoading} onClick={handleSolidLogin}>
|
<StyledSocialButton disabled={isLoading} onClick={handleSolidLogin}>
|
||||||
@@ -29,4 +26,5 @@ export default function OidcLoginEntry({ issuer }) {
|
|||||||
Login with {issuer.domain}
|
Login with {issuer.domain}
|
||||||
</StyledSocialButton>
|
</StyledSocialButton>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
export default OidcLoginEntry;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/* eslint-disable no-undef */
|
/* eslint-disable no-undef */
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect, FormEvent, ChangeEvent } from "react";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
import BASE_URL from "../../app/config";
|
import BASE_URL from "../../app/config";
|
||||||
// import web3 from "web3";
|
// import web3 from "web3";
|
||||||
@@ -16,6 +16,7 @@ import useGoogleAuthConfig from "../../common/hook/useGoogleAuthConfig";
|
|||||||
import useGithubAuthConfig from "../../common/hook/useGithubAuthConfig";
|
import useGithubAuthConfig from "../../common/hook/useGithubAuthConfig";
|
||||||
import GoogleLoginButton from "../../common/component/GoogleLoginButton";
|
import GoogleLoginButton from "../../common/component/GoogleLoginButton";
|
||||||
import GithubLoginButton from "../../common/component/GithubLoginButton";
|
import GithubLoginButton from "../../common/component/GithubLoginButton";
|
||||||
|
import { FetchBaseQueryError } from "@reduxjs/toolkit/dist/query";
|
||||||
|
|
||||||
export default function LoginPage() {
|
export default function LoginPage() {
|
||||||
const { data: enableSMTP } = useGetSMTPStatusQuery();
|
const { data: enableSMTP } = useGetSMTPStatusQuery();
|
||||||
@@ -53,7 +54,6 @@ export default function LoginPage() {
|
|||||||
}
|
}
|
||||||
// magic link
|
// magic link
|
||||||
if (magic_token && typeof exists !== "undefined") {
|
if (magic_token && typeof exists !== "undefined") {
|
||||||
// console.log("tokken", token, exists);
|
|
||||||
const isLogin = exists == "true";
|
const isLogin = exists == "true";
|
||||||
if (isLogin) {
|
if (isLogin) {
|
||||||
// login
|
// login
|
||||||
@@ -71,7 +71,7 @@ export default function LoginPage() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (error) {
|
if (error) {
|
||||||
console.log("login err", error);
|
console.log("login err", error);
|
||||||
switch (error.status) {
|
switch ((error as FetchBaseQueryError).status) {
|
||||||
case 401:
|
case 401:
|
||||||
toast.error("Username or Password incorrect");
|
toast.error("Username or Password incorrect");
|
||||||
break;
|
break;
|
||||||
@@ -94,7 +94,7 @@ export default function LoginPage() {
|
|||||||
}
|
}
|
||||||
}, [isSuccess]);
|
}, [isSuccess]);
|
||||||
|
|
||||||
const handleLogin = (evt) => {
|
const handleLogin = (evt: FormEvent<HTMLFormElement>) => {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
console.log("wtf", input);
|
console.log("wtf", input);
|
||||||
login({
|
login({
|
||||||
@@ -103,10 +103,10 @@ export default function LoginPage() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleInput = (evt) => {
|
const handleInput = (evt: ChangeEvent<HTMLInputElement>) => {
|
||||||
const { type } = evt.target.dataset;
|
const { type } = evt.target.dataset as { type?: "email" | "password" };
|
||||||
const { value } = evt.target;
|
const { value } = evt.target;
|
||||||
// console.log(type, value);
|
if (!type) return;
|
||||||
setInput((prev) => {
|
setInput((prev) => {
|
||||||
prev[type] = value;
|
prev[type] = value;
|
||||||
return { ...prev };
|
return { ...prev };
|
||||||
|
|||||||
@@ -8,10 +8,10 @@ import toast from "react-hot-toast";
|
|||||||
import { setAuthData } from "../../app/slices/auth.data";
|
import { setAuthData } from "../../app/slices/auth.data";
|
||||||
|
|
||||||
export default function OAuthPage() {
|
export default function OAuthPage() {
|
||||||
const [login, { data, isSuccess, isError, error: loginError }] = useLoginMutation();
|
const [login, { data, isSuccess, isError }] = useLoginMutation();
|
||||||
const { token } = useParams();
|
const { token } = useParams();
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const navigateTo = useNavigate();
|
const navigateTo = useNavigate();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -28,9 +28,9 @@ export default function OAuthPage() {
|
|||||||
}, [token]);
|
}, [token]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isError) {
|
if (isError) {
|
||||||
setError(loginError);
|
setError("Something Error");
|
||||||
}
|
}
|
||||||
}, [isError, loginError]);
|
}, [isError]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isSuccess && data) {
|
if (isSuccess && data) {
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ const StyledConfirm = styled.div`
|
|||||||
gap: 10px;
|
gap: 10px;
|
||||||
width: 250px;
|
width: 250px;
|
||||||
.tip {
|
.tip {
|
||||||
/* word-break: break-all; */
|
|
||||||
color: orange;
|
color: orange;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
@@ -100,7 +99,7 @@ export default function APIConfig() {
|
|||||||
</Tippy>
|
</Tippy>
|
||||||
<div className="tip">
|
<div className="tip">
|
||||||
Tip: The security key agreed between the server and the third-party app is used to encrypt
|
Tip: The security key agreed between the server and the third-party app is used to encrypt
|
||||||
the communication data.{" "}
|
the communication data.
|
||||||
</div>
|
</div>
|
||||||
</Styled>
|
</Styled>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import StyledSettingContainer from "../../common/component/StyledSettingContaine
|
|||||||
import useNavs from "./navs";
|
import useNavs from "./navs";
|
||||||
import LogoutConfirmModal from "./LogoutConfirmModal";
|
import LogoutConfirmModal from "./LogoutConfirmModal";
|
||||||
|
|
||||||
let from: string | null = null;
|
let from: string = "";
|
||||||
|
|
||||||
export default function Setting() {
|
export default function Setting() {
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
@@ -15,10 +15,9 @@ export default function Setting() {
|
|||||||
const [logoutConfirm, setLogoutConfirm] = useState(false);
|
const [logoutConfirm, setLogoutConfirm] = useState(false);
|
||||||
const navigateTo = useNavigate();
|
const navigateTo = useNavigate();
|
||||||
const close = () => {
|
const close = () => {
|
||||||
// dispatch(toggleSetting());
|
|
||||||
// todo: check usage
|
// todo: check usage
|
||||||
navigateTo(from!);
|
navigateTo(from!);
|
||||||
from = null;
|
from = "";
|
||||||
};
|
};
|
||||||
|
|
||||||
const toggleLogoutConfirm = () => {
|
const toggleLogoutConfirm = () => {
|
||||||
|
|||||||
@@ -6,20 +6,20 @@ import DeleteConfirmModal from "./DeleteConfirmModal";
|
|||||||
import useNavs from "./navs";
|
import useNavs from "./navs";
|
||||||
import { useAppSelector } from "../../app/store";
|
import { useAppSelector } from "../../app/store";
|
||||||
|
|
||||||
let from: string | null = null;
|
let from: string = "";
|
||||||
|
|
||||||
export default function ChannelSetting() {
|
export default function ChannelSetting() {
|
||||||
const { cid } = useParams();
|
const { cid = 0 } = useParams();
|
||||||
const { loginUser, channel } = useAppSelector((store) => {
|
const { loginUser, channel } = useAppSelector((store) => {
|
||||||
return {
|
return {
|
||||||
loginUser: store.authData.user,
|
loginUser: store.authData.user,
|
||||||
channel: store.channels.byId[cid]
|
channel: cid ? store.channels.byId[+cid] : undefined
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
const navs = useNavs(cid);
|
const navs = useNavs(+cid);
|
||||||
const flatenNavs = navs
|
const flattenNavs = navs
|
||||||
.map(({ items }) => {
|
.map(({ items }) => {
|
||||||
return items;
|
return items;
|
||||||
})
|
})
|
||||||
@@ -30,16 +30,16 @@ export default function ChannelSetting() {
|
|||||||
const [leaveConfirm, setLeaveConfirm] = useState(false);
|
const [leaveConfirm, setLeaveConfirm] = useState(false);
|
||||||
const close = () => {
|
const close = () => {
|
||||||
navigate(from);
|
navigate(from);
|
||||||
from = null;
|
from = "";
|
||||||
};
|
};
|
||||||
const toggleDeleteConfrim = () => {
|
const toggleDeleteConfirm = () => {
|
||||||
setDeleteConfirm((prev) => !prev);
|
setDeleteConfirm((prev) => !prev);
|
||||||
};
|
};
|
||||||
const toggleLeaveConfrim = () => {
|
const toggleLeaveConfirm = () => {
|
||||||
setLeaveConfirm((prev) => !prev);
|
setLeaveConfirm((prev) => !prev);
|
||||||
};
|
};
|
||||||
if (!cid) return null;
|
if (!cid) return null;
|
||||||
const currNav = flatenNavs.find((n) => n.name == navKey) || flatenNavs[0];
|
const currNav = flattenNavs.find((n) => n.name == navKey) || flattenNavs[0];
|
||||||
const canDelete = loginUser.isAdmin || channel?.owner == loginUser.uid;
|
const canDelete = loginUser.isAdmin || channel?.owner == loginUser.uid;
|
||||||
const canLeave = !channel?.is_public;
|
const canLeave = !channel?.is_public;
|
||||||
|
|
||||||
@@ -53,18 +53,18 @@ export default function ChannelSetting() {
|
|||||||
dangers={[
|
dangers={[
|
||||||
canLeave && {
|
canLeave && {
|
||||||
title: "Leave Channel",
|
title: "Leave Channel",
|
||||||
handler: toggleLeaveConfrim
|
handler: toggleLeaveConfirm
|
||||||
},
|
},
|
||||||
canDelete && {
|
canDelete && {
|
||||||
title: "Delete Channel",
|
title: "Delete Channel",
|
||||||
handler: toggleDeleteConfrim
|
handler: toggleDeleteConfirm
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
{currNav.component}
|
{currNav.component}
|
||||||
</StyledSettingContainer>
|
</StyledSettingContainer>
|
||||||
{deleteConfirm && <DeleteConfirmModal closeModal={toggleDeleteConfrim} id={cid} />}
|
{deleteConfirm && <DeleteConfirmModal closeModal={toggleDeleteConfirm} id={+cid} />}
|
||||||
{leaveConfirm && <LeaveChannel closeModal={toggleLeaveConfrim} id={cid} />}
|
{leaveConfirm && <LeaveChannel closeModal={toggleLeaveConfirm} id={+cid} />}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { NavLink, useParams, useLocation } from "react-router-dom";
|
import { NavLink, useParams, useLocation } from "react-router-dom";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch } from "react-redux";
|
||||||
import { updateRememberedNavs } from "../../app/slices/ui";
|
import { updateRememberedNavs } from "../../app/slices/ui";
|
||||||
import Search from "../../common/component/Search";
|
import Search from "../../common/component/Search";
|
||||||
import User from "../../common/component/User";
|
import User from "../../common/component/User";
|
||||||
@@ -8,13 +8,14 @@ import Profile from "../../common/component/Profile";
|
|||||||
|
|
||||||
import StyledWrapper from "./styled";
|
import StyledWrapper from "./styled";
|
||||||
import BlankPlaceholder from "../../common/component/BlankPlaceholder";
|
import BlankPlaceholder from "../../common/component/BlankPlaceholder";
|
||||||
|
import { useAppSelector } from "../../app/store";
|
||||||
|
|
||||||
export default function UsersPage() {
|
export default function UsersPage() {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const { pathname } = useLocation();
|
const { pathname } = useLocation();
|
||||||
|
|
||||||
const { user_id } = useParams();
|
const { user_id } = useParams();
|
||||||
const userIds = useSelector((store) => store.users.ids);
|
const userIds = useAppSelector((store) => store.users.ids);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch(updateRememberedNavs({ key: "user" }));
|
dispatch(updateRememberedNavs({ key: "user" }));
|
||||||
return () => {
|
return () => {
|
||||||
@@ -30,7 +31,7 @@ export default function UsersPage() {
|
|||||||
<Search />
|
<Search />
|
||||||
<div className="list">
|
<div className="list">
|
||||||
<nav className="nav">
|
<nav className="nav">
|
||||||
{userIds.map((uid) => {
|
{userIds.map((uid: number) => {
|
||||||
return (
|
return (
|
||||||
<NavLink key={uid} className="session" to={`/users/${uid}`}>
|
<NavLink key={uid} className="session" to={`/users/${uid}`}>
|
||||||
<User uid={uid} enableContextMenu={true} />
|
<User uid={uid} enableContextMenu={true} />
|
||||||
@@ -43,7 +44,7 @@ export default function UsersPage() {
|
|||||||
</div>
|
</div>
|
||||||
{user_id ? (
|
{user_id ? (
|
||||||
<div className="right">
|
<div className="right">
|
||||||
<Profile uid={user_id} />
|
<Profile uid={+user_id} />
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="right placeholder">
|
<div className="right placeholder">
|
||||||
|
|||||||
@@ -6,6 +6,9 @@ export interface AuthToken {
|
|||||||
refresh_token: string;
|
refresh_token: string;
|
||||||
expired_in: number;
|
expired_in: number;
|
||||||
}
|
}
|
||||||
|
export interface RenewTokenDTO extends Pick<AuthToken, "token" | "refresh_token"> {}
|
||||||
|
export interface RenewTokenResponse
|
||||||
|
extends Pick<AuthToken, "token" | "refresh_token" | "expired_in"> {}
|
||||||
|
|
||||||
export interface AuthData extends AuthToken {
|
export interface AuthData extends AuthToken {
|
||||||
initialized?: boolean;
|
initialized?: boolean;
|
||||||
@@ -57,3 +60,15 @@ export type LoginCredential =
|
|||||||
| GoogleCredential
|
| GoogleCredential
|
||||||
| ThirdPartyCredential
|
| ThirdPartyCredential
|
||||||
| MagicLinkCredential;
|
| MagicLinkCredential;
|
||||||
|
|
||||||
|
export type CredentialResponse = {
|
||||||
|
password: boolean;
|
||||||
|
google: string;
|
||||||
|
metamask: string;
|
||||||
|
oidc: string[];
|
||||||
|
};
|
||||||
|
export interface OIDCConfig {
|
||||||
|
enable: boolean;
|
||||||
|
favicon: string;
|
||||||
|
domain: string;
|
||||||
|
}
|
||||||
|
|||||||
@@ -34,8 +34,12 @@ export interface CreateChannelDTO {
|
|||||||
is_public: boolean;
|
is_public: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ChannelDTO extends Pick<Channel, "owner" | "description" | "name"> {
|
||||||
|
id: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface UpdateChannelDTO {
|
export interface UpdateChannelDTO {
|
||||||
operation: "add_member" | "remove_member";
|
operation?: "add_member" | "remove_member";
|
||||||
members?: number[];
|
members?: number[];
|
||||||
gid: number; // todo check
|
gid: number; // todo check
|
||||||
name?: string;
|
name?: string;
|
||||||
@@ -43,8 +47,6 @@ export interface UpdateChannelDTO {
|
|||||||
owner?: number;
|
owner?: number;
|
||||||
avatar_updated_at?: number;
|
avatar_updated_at?: number;
|
||||||
type?: string;
|
type?: string;
|
||||||
// type = 'user_joined_group' | 'user_leaved_group'
|
|
||||||
// gid
|
|
||||||
uid?: number[];
|
uid?: number[];
|
||||||
icon?: string;
|
icon?: string;
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+26
@@ -1,3 +1,26 @@
|
|||||||
|
interface BeforeInstallPromptEvent extends Event {
|
||||||
|
/**
|
||||||
|
* Returns an array of DOMString items containing the platforms on which the event was dispatched.
|
||||||
|
* This is provided for user agents that want to present a choice of versions to the user such as,
|
||||||
|
* for example, "web" or "play" which would allow the user to chose between a web version or
|
||||||
|
* an Android version.
|
||||||
|
*/
|
||||||
|
readonly platforms: Array<string>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a Promise that resolves to a DOMString containing either "accepted" or "dismissed".
|
||||||
|
*/
|
||||||
|
readonly userChoice: Promise<{
|
||||||
|
outcome: "accepted" | "dismissed";
|
||||||
|
platform: string;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows a developer to show the install prompt at a time of their own choosing.
|
||||||
|
* This method returns a Promise.
|
||||||
|
*/
|
||||||
|
prompt(): Promise<void>;
|
||||||
|
}
|
||||||
export declare global {
|
export declare global {
|
||||||
import { PrecacheEntry } from "workbox-precaching/src/_types";
|
import { PrecacheEntry } from "workbox-precaching/src/_types";
|
||||||
import localforage from "localforage";
|
import localforage from "localforage";
|
||||||
@@ -7,4 +30,7 @@ export declare global {
|
|||||||
skipWaiting: () => void;
|
skipWaiting: () => void;
|
||||||
CACHE: { [key: string]: typeof localforage | undefined };
|
CACHE: { [key: string]: typeof localforage | undefined };
|
||||||
}
|
}
|
||||||
|
interface WindowEventMap {
|
||||||
|
beforeinstallprompt: BeforeInstallPromptEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { ChatEvent } from "./sse";
|
||||||
|
|
||||||
export type ContentType = "text/plain" | "text/markdown" | "vocechat/file" | "vocechat/archive";
|
export type ContentType = "text/plain" | "text/markdown" | "vocechat/file" | "vocechat/archive";
|
||||||
export type ContentTypeKey = "text" | "markdown" | "file" | "archive";
|
export type ContentTypeKey = "text" | "markdown" | "file" | "archive";
|
||||||
|
|
||||||
@@ -13,3 +15,13 @@ export interface MuteDTO {
|
|||||||
remove_users?: number[];
|
remove_users?: number[];
|
||||||
remove_groups?: number[];
|
remove_groups?: number[];
|
||||||
}
|
}
|
||||||
|
export interface UploadFileResponse {
|
||||||
|
path: string;
|
||||||
|
size: number;
|
||||||
|
hash: string;
|
||||||
|
image_properties: {
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
export interface ChatMessage extends Omit<ChatEvent, "type"> {}
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
export interface Archive {
|
||||||
|
users: ArchiveUser[];
|
||||||
|
messages: ArchiveMessage[];
|
||||||
|
num_attachments: number;
|
||||||
|
}
|
||||||
export interface ArchiveUser {
|
export interface ArchiveUser {
|
||||||
name: string;
|
name: string;
|
||||||
avatar?: number;
|
avatar?: number;
|
||||||
@@ -13,10 +18,9 @@ export interface ArchiveMessage {
|
|||||||
file_id?: number;
|
file_id?: number;
|
||||||
thumbnail_id?: number;
|
thumbnail_id?: number;
|
||||||
}
|
}
|
||||||
export interface Archive {
|
export interface FavoriteArchive {
|
||||||
users: ArchiveUser[];
|
id: string;
|
||||||
messages: ArchiveMessage[];
|
created_at: number;
|
||||||
num_attachments: number;
|
|
||||||
}
|
}
|
||||||
// 上传文件API响应
|
// 上传文件API响应
|
||||||
export interface UploadResponse {
|
export interface UploadResponse {
|
||||||
|
|||||||
+8
-3
@@ -1,4 +1,4 @@
|
|||||||
import { User } from "./auth";
|
import { User } from "./user";
|
||||||
import { Channel } from "./channel";
|
import { Channel } from "./channel";
|
||||||
import { ContentType } from "./message";
|
import { ContentType } from "./message";
|
||||||
|
|
||||||
@@ -110,13 +110,18 @@ export interface ReplyMessage {
|
|||||||
content_type: ContentType;
|
content_type: ContentType;
|
||||||
content: string;
|
content: string;
|
||||||
}
|
}
|
||||||
|
type MessageTargetUser = {
|
||||||
|
uid: number;
|
||||||
|
};
|
||||||
|
type MessageTargetChannel = {
|
||||||
|
gid: number;
|
||||||
|
};
|
||||||
export interface ChatEvent {
|
export interface ChatEvent {
|
||||||
type: "chat";
|
type: "chat";
|
||||||
mid: number;
|
mid: number;
|
||||||
from_uid: number;
|
from_uid: number;
|
||||||
created_at: number;
|
created_at: number;
|
||||||
target: { uid: number };
|
target: MessageTargetChannel | MessageTargetUser;
|
||||||
detail: NormalMessage | ReactionMessage | ReplyMessage;
|
detail: NormalMessage | ReactionMessage | ReplyMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+4
-6
@@ -2,7 +2,7 @@ export type Gender = 0 | 1;
|
|||||||
|
|
||||||
export interface User {
|
export interface User {
|
||||||
uid: number;
|
uid: number;
|
||||||
email: string;
|
email?: string;
|
||||||
name: string;
|
name: string;
|
||||||
gender: Gender;
|
gender: Gender;
|
||||||
language: string;
|
language: string;
|
||||||
@@ -25,9 +25,7 @@ export interface UserForAdmin extends User {
|
|||||||
status: UserStatus;
|
status: UserStatus;
|
||||||
online_devices: UserDevice[];
|
online_devices: UserDevice[];
|
||||||
}
|
}
|
||||||
export interface UserForAdminDTO
|
export interface UserForAdminDTO extends Partial<UserForAdmin> {
|
||||||
extends Pick<
|
id?: number;
|
||||||
UserForAdmin,
|
}
|
||||||
"email" | "password" | "name" | "gender" | "is_admin" | "language" | "status"
|
|
||||||
> {}
|
|
||||||
export interface UserDTO extends Pick<User, "name" | "gender" | "language"> {}
|
export interface UserDTO extends Pick<User, "name" | "gender" | "language"> {}
|
||||||
|
|||||||
Reference in New Issue
Block a user