chore: update prettier setting

This commit is contained in:
zerosoul
2022-06-12 15:30:14 +08:00
parent 14b4678d9e
commit 516794d352
209 changed files with 2435 additions and 4588 deletions
+12 -12
View File
@@ -4,44 +4,44 @@ import { KEY_UID, CACHE_VERSION } from "../config";
const tables = [
{
storeName: "channels",
description: "store channel list",
description: "store channel list"
},
{
storeName: "contacts",
description: "store contact list",
description: "store contact list"
},
{
storeName: "messageDM",
description: "store DM message with IDs",
description: "store DM message with IDs"
},
{
storeName: "messageChannel",
description: "store channel message with IDs",
description: "store channel message with IDs"
},
{
storeName: "message",
description: "store message with key-val full data",
description: "store message with key-val full data"
},
{
storeName: "messageFile",
description: "store file message list refs",
description: "store file message list refs"
},
{
storeName: "messageReaction",
description: "store message reaction with key-val full data",
description: "store message reaction with key-val full data"
},
{
storeName: "footprint",
description: "store user visit data",
description: "store user visit data"
},
{
storeName: "server",
description: "store server data",
description: "store server data"
},
{
storeName: "ui",
description: "store UI state",
},
description: "store UI state"
}
// {
// storeName: "message",
// description: "store message with key-val full data",
@@ -55,7 +55,7 @@ const initCache = () => {
window.CACHE[storeName] = localforage.createInstance({
name,
storeName,
description,
description
});
});
};
+1 -1
View File
@@ -25,7 +25,7 @@ const useRehydrate = () => {
message: { replying: {} },
footprint: {},
ui: {},
server: {},
server: {}
};
const tables = Object.keys(window.CACHE);
const results = await Promise.all(
+4 -4
View File
@@ -7,7 +7,7 @@ export const ContentTypes = {
file: "rustchat/file",
formData: "multipart/form-data",
json: "application/json",
archive: "rustchat/archive",
archive: "rustchat/archive"
};
export const firebaseConfig = {
apiKey: "AIzaSyDyJ6B1Ouenoha_gdGkBwIkBNStlwhlbO0",
@@ -16,11 +16,11 @@ export const firebaseConfig = {
storageBucket: "rustchat-develop.appspot.com",
messagingSenderId: "418687074928",
appId: "1:418687074928:web:753286adbf239f5af9eab5",
measurementId: "G-XV476KEC8P",
measurementId: "G-XV476KEC8P"
};
export const ChatPrefixs = {
channel: "#",
user: "@",
user: "@"
};
export const vapidKey = `BGXCn-5YRXSFw38Q9lUKJ5bibL212-yIQn1pCvthGhp6_KwA29FO1Ax_d_7if1vfC2a5wTSVO8AcZrc-Hm1aS0Y`;
// "840319286941-6ds7lbvk55eq8mjortf68cb2ll65lprt.apps.googleusercontent.com";
@@ -38,6 +38,6 @@ export const KEY_PWA_INSTALLED = "RUSTCHAT_PWA_INSTALLED";
export const Emojis = ["👍", "❤️", "😄", "👀", "👎", "🎉", "🙁", "🚀"];
export const Views = {
item: "item",
grid: "grid",
grid: "grid"
};
export default BASE_URL;
+13 -13
View File
@@ -21,7 +21,7 @@ const operations = [
"message",
"ui",
"footprint",
"server",
"server"
];
// Create the middleware instance and methods
@@ -51,7 +51,7 @@ listenerMiddleware.startListening({
rtkqHandler({
operation,
payload,
dispatch: listenerApi.dispatch,
dispatch: listenerApi.dispatch
});
}
break;
@@ -60,7 +60,7 @@ listenerMiddleware.startListening({
await channelsHandler({
operation,
payload,
data: state,
data: state
});
}
break;
@@ -69,7 +69,7 @@ listenerMiddleware.startListening({
await contactsHandler({
operation,
payload,
data: state,
data: state
});
}
break;
@@ -78,7 +78,7 @@ listenerMiddleware.startListening({
await channelMsgHandler({
operation,
payload,
data: state,
data: state
});
}
break;
@@ -87,7 +87,7 @@ listenerMiddleware.startListening({
await dmMsgHandler({
operation,
payload,
data: state,
data: state
});
}
break;
@@ -96,7 +96,7 @@ listenerMiddleware.startListening({
await fileMessageHandler({
operation,
// payload,
data: state,
data: state
});
}
break;
@@ -105,7 +105,7 @@ listenerMiddleware.startListening({
await messageHandler({
operation,
payload,
data: state,
data: state
});
}
break;
@@ -114,7 +114,7 @@ listenerMiddleware.startListening({
await reactionHandler({
operation,
payload,
data: state,
data: state
});
}
break;
@@ -123,7 +123,7 @@ listenerMiddleware.startListening({
await footprintHandler({
operation,
payload,
data: state,
data: state
});
}
break;
@@ -132,7 +132,7 @@ listenerMiddleware.startListening({
await UIHandler({
operation,
payload,
data: state,
data: state
});
}
break;
@@ -141,7 +141,7 @@ listenerMiddleware.startListening({
await serverHandler({
operation,
payload,
data: state,
data: state
});
}
break;
@@ -149,7 +149,7 @@ listenerMiddleware.startListening({
default:
break;
}
},
}
});
export default listenerMiddleware;
+146 -146
View File
@@ -4,157 +4,157 @@ import baseQuery from "./base.query";
import { setAuthData, updateToken, resetAuthData, updateInitialized } from "../slices/auth.data";
import BASE_URL, { KEY_DEVICE_KEY } from "../config";
const getDeviceId = () => {
let d = localStorage.getItem(KEY_DEVICE_KEY);
if (!d) {
d = `web:${nanoid()}`;
localStorage.setItem(KEY_DEVICE_KEY, d);
}
return d;
let d = localStorage.getItem(KEY_DEVICE_KEY);
if (!d) {
d = `web:${nanoid()}`;
localStorage.setItem(KEY_DEVICE_KEY, d);
}
return d;
};
export const authApi = createApi({
reducerPath: "authApi",
baseQuery,
endpoints: (builder) => ({
login: builder.mutation({
query: (credentials) => ({
url: "token/login",
method: "POST",
body: {
credential: credentials,
device: getDeviceId(),
device_token: "test"
}
}),
transformResponse: (data) => {
const { avatar_updated_at } = data.user;
data.user.avatar =
avatar_updated_at == 0
? ""
: `${BASE_URL}/resource/avatar?uid=${data.user.uid}&t=${avatar_updated_at}`;
return data;
},
async onQueryStarted(params, { dispatch, queryFulfilled }) {
try {
const { data } = await queryFulfilled;
if (data) {
console.log("login resp", data);
dispatch(setAuthData(data));
}
} catch {
console.log("login error");
}
}
}),
// 更新token
renew: builder.mutation({
query: ({ token, refreshToken }) => ({
url: "/token/renew",
method: "POST",
body: {
token,
refresh_token: refreshToken
}
}),
async onQueryStarted(params, { dispatch, queryFulfilled }) {
try {
const { data } = await queryFulfilled;
dispatch(updateToken(data));
} catch {
dispatch(resetAuthData());
console.log("renew token error");
}
}
}),
// 更新 device token
updateDeviceToken: builder.mutation({
query: (device_token) => ({
url: "/token/device_token",
method: "PUT",
body: {
device_token
}
})
}),
// 获取openid
getOpenid: builder.mutation({
query: ({ issuer, redirect_uri }) => ({
url: "/token/openid/authorize",
method: "POST",
body: {
issuer,
redirect_uri
}
})
}),
reducerPath: "authApi",
baseQuery,
endpoints: (builder) => ({
login: builder.mutation({
query: (credentials) => ({
url: "token/login",
method: "POST",
body: {
credential: credentials,
device: getDeviceId(),
device_token: "test"
}
}),
transformResponse: (data) => {
const { avatar_updated_at } = data.user;
data.user.avatar =
avatar_updated_at == 0
? ""
: `${BASE_URL}/resource/avatar?uid=${data.user.uid}&t=${avatar_updated_at}`;
return data;
},
async onQueryStarted(params, { dispatch, queryFulfilled }) {
try {
const { data } = await queryFulfilled;
if (data) {
console.log("login resp", data);
dispatch(setAuthData(data));
}
} catch {
console.log("login error");
}
}
}),
// 更新token
renew: builder.mutation({
query: ({ token, refreshToken }) => ({
url: "/token/renew",
method: "POST",
body: {
token,
refresh_token: refreshToken
}
}),
async onQueryStarted(params, { dispatch, queryFulfilled }) {
try {
const { data } = await queryFulfilled;
dispatch(updateToken(data));
} catch {
dispatch(resetAuthData());
console.log("renew token error");
}
}
}),
// 更新 device token
updateDeviceToken: builder.mutation({
query: (device_token) => ({
url: "/token/device_token",
method: "PUT",
body: {
device_token
}
})
}),
// 获取openid
getOpenid: builder.mutation({
query: ({ issuer, redirect_uri }) => ({
url: "/token/openid/authorize",
method: "POST",
body: {
issuer,
redirect_uri
}
})
}),
checkInviteTokenValid: builder.mutation({
query: (token) => ({
url: "user/check_invite_magic_token",
method: "POST",
body: { magic_token: token }
})
}),
updatePassword: builder.mutation({
query: ({ old_password, new_password }) => ({
url: "user/change_password",
method: "POST",
body: { old_password, new_password }
})
}),
sendMagicLink: builder.mutation({
query: (email) => ({
url: "token/send_magic_link",
method: "POST",
body: { email }
})
}),
getMetamaskNonce: builder.query({
query: (address) => ({
url: `/token/metamask/nonce?public_address=${address}`
})
}),
getCredentials: builder.query({
query: () => ({
url: `/token/credentials`
})
}),
logout: builder.query({
query: () => ({ url: `token/logout` }),
async onQueryStarted(params, { dispatch, queryFulfilled }) {
try {
await queryFulfilled;
dispatch(resetAuthData());
} catch {
console.log("logout error");
}
}
}),
getInitialized: builder.query({
query: () => ({
url: `/admin/system/initialized`
}),
async onQueryStarted(params, { dispatch, queryFulfilled }) {
try {
const { data: isInitialized } = await queryFulfilled;
dispatch(updateInitialized(isInitialized));
} catch {
console.log("api initialized error");
}
}
checkInviteTokenValid: builder.mutation({
query: (token) => ({
url: "user/check_invite_magic_token",
method: "POST",
body: { magic_token: token }
})
}),
updatePassword: builder.mutation({
query: ({ old_password, new_password }) => ({
url: "user/change_password",
method: "POST",
body: { old_password, new_password }
})
}),
sendMagicLink: builder.mutation({
query: (email) => ({
url: "token/send_magic_link",
method: "POST",
body: { email }
})
}),
getMetamaskNonce: builder.query({
query: (address) => ({
url: `/token/metamask/nonce?public_address=${address}`
})
}),
getCredentials: builder.query({
query: () => ({
url: `/token/credentials`
})
}),
logout: builder.query({
query: () => ({ url: `token/logout` }),
async onQueryStarted(params, { dispatch, queryFulfilled }) {
try {
await queryFulfilled;
dispatch(resetAuthData());
} catch {
console.log("logout error");
}
}
}),
getInitialized: builder.query({
query: () => ({
url: `/admin/system/initialized`
}),
async onQueryStarted(params, { dispatch, queryFulfilled }) {
try {
const { data: isInitialized } = await queryFulfilled;
dispatch(updateInitialized(isInitialized));
} catch {
console.log("api initialized error");
}
}
})
})
})
});
export const {
useGetInitializedQuery,
useSendMagicLinkMutation,
useGetCredentialsQuery,
useUpdateDeviceTokenMutation,
useGetOpenidMutation,
useRenewMutation,
useLazyGetMetamaskNonceQuery,
useLoginMutation,
useLazyLogoutQuery,
useCheckInviteTokenValidMutation,
useUpdatePasswordMutation
useGetInitializedQuery,
useSendMagicLinkMutation,
useGetCredentialsQuery,
useUpdateDeviceTokenMutation,
useGetOpenidMutation,
useRenewMutation,
useLazyGetMetamaskNonceQuery,
useLoginMutation,
useLazyLogoutQuery,
useCheckInviteTokenValidMutation,
useUpdatePasswordMutation
} = authApi;
+6 -13
View File
@@ -18,7 +18,7 @@ const whiteList = [
"getMetamaskNonce",
"renew",
"getInitialized",
"createAdmin",
"createAdmin"
];
const baseQuery = fetchBaseQuery({
baseUrl: BASE_URL,
@@ -29,7 +29,7 @@ const baseQuery = fetchBaseQuery({
headers.set(tokenHeader, token);
}
return headers;
},
}
});
let waitingForRenew = null;
const baseQueryWithTokenCheck = async (args, api, extraOptions) => {
@@ -37,16 +37,9 @@ const baseQueryWithTokenCheck = async (args, api, extraOptions) => {
await waitingForRenew;
}
// 先检查token是否过期,过期则renew
const {
token,
refreshToken,
expireTime = +new Date(),
} = api.getState().authData;
const { token, refreshToken, expireTime = +new Date() } = api.getState().authData;
let result = null;
if (
!whiteList.includes(api.endpoint) &&
dayjs().isAfter(new Date(expireTime - 20 * 1000))
) {
if (!whiteList.includes(api.endpoint) && dayjs().isAfter(new Date(expireTime - 20 * 1000))) {
// 快过期了,renew
waitingForRenew = baseQuery(
{
@@ -54,8 +47,8 @@ const baseQueryWithTokenCheck = async (args, api, extraOptions) => {
method: "POST",
body: {
token,
refresh_token: refreshToken,
},
refresh_token: refreshToken
}
},
api,
extraOptions
+31 -34
View File
@@ -15,10 +15,10 @@ export const channelApi = createApi({
refetchOnFocus: true,
endpoints: (builder) => ({
getChannels: builder.query({
query: () => ({ url: `group` }),
query: () => ({ url: `group` })
}),
getChannel: builder.query({
query: (id) => ({ url: `group/${id}` }),
query: (id) => ({ url: `group/${id}` })
}),
leaveChannel: builder.query({
query: (id) => ({ url: `group/${id}/leave` }),
@@ -29,25 +29,22 @@ export const channelApi = createApi({
} catch {
console.log("channel update failed");
}
},
}
}),
createChannel: builder.mutation({
query: (data) => ({
url: "group",
method: "POST",
body: data,
}),
body: data
})
}),
updateChannel: builder.mutation({
query: ({ id, ...data }) => ({
url: `group/${id}`,
method: "PUT",
body: data,
body: data
}),
async onQueryStarted(
{ id, name, description },
{ dispatch, queryFulfilled }
) {
async onQueryStarted({ id, name, description }, { dispatch, queryFulfilled }) {
// id: who send to ,from_uid: who sent
const patchResult = dispatch(updateChannel({ id, name, description }));
try {
@@ -56,13 +53,13 @@ export const channelApi = createApi({
console.log("channel update failed");
patchResult.undo();
}
},
}
}),
getHistoryMessages: builder.query({
query: ({ id, mid = null, limit = 100 }) => ({
url: mid
? `/group/${id}/history?before=${mid}&limit=${limit}`
: `/group/${id}/history?limit=${limit}`,
: `/group/${id}/history?limit=${limit}`
}),
async onQueryStarted(params, { dispatch, getState, queryFulfilled }) {
const { data: messages } = await queryFulfilled;
@@ -71,34 +68,34 @@ export const channelApi = createApi({
handleChatMessage(msg, dispatch, getState());
});
}
},
}
}),
createInviteLink: builder.query({
query: (gid) => ({
headers: {
"content-type": "text/plain",
accept: "text/plain",
accept: "text/plain"
},
url: `/group/${gid}/create_invite_link`,
responseHandler: (response) => response.text(),
responseHandler: (response) => response.text()
}),
transformResponse: (link) => {
// 替换掉域名
const invite = new URL(link);
return `${location.origin}${invite.pathname}${invite.search}${invite.hash}`;
},
}
}),
removeChannel: builder.query({
query: (id) => ({
url: `group/${id}`,
method: "DELETE",
method: "DELETE"
}),
async onQueryStarted(id, { dispatch, getState, queryFulfilled }) {
const {
channelMessage,
ui: {
remeberedNavs: { chat: remeberedPath },
},
remeberedNavs: { chat: remeberedPath }
}
} = getState();
try {
await queryFulfilled;
@@ -115,7 +112,7 @@ export const channelApi = createApi({
} catch {
console.log("remove channel error");
}
},
}
}),
sendChannelMsg: builder.mutation({
query: ({ id, content, type = "text", properties = "" }) => ({
@@ -123,38 +120,38 @@ export const channelApi = createApi({
"content-type": ContentTypes[type],
"X-Properties": properties
? btoa(unescape(encodeURIComponent(JSON.stringify(properties))))
: "",
: ""
},
url: `group/${id}/send`,
method: "POST",
body: type == "file" ? JSON.stringify(content) : content,
body: type == "file" ? JSON.stringify(content) : content
}),
async onQueryStarted(param1, param2) {
await onMessageSendStarted.call(this, param1, param2, "channel");
},
}
}),
addMembers: builder.mutation({
query: ({ id, members }) => ({
url: `group/${id}/members/add`,
method: "POST",
body: members,
}),
body: members
})
}),
removeMembers: builder.mutation({
query: ({ id, members }) => ({
url: `group/${id}/members/remove`,
method: "POST",
body: members,
}),
body: members
})
}),
updateIcon: builder.mutation({
query: ({ gid, image }) => ({
headers: {
"content-type": "image/png",
"content-type": "image/png"
},
url: `/group/${gid}/avatar`,
method: "POST",
body: image,
body: image
}),
async onQueryStarted({ gid }, { dispatch, queryFulfilled }) {
try {
@@ -162,15 +159,15 @@ export const channelApi = createApi({
dispatch(
updateChannel({
id: gid,
icon: `${BASE_URL}/resource/group_avatar?gid=${gid}&t=${+new Date()}`,
icon: `${BASE_URL}/resource/group_avatar?gid=${gid}&t=${+new Date()}`
})
);
} catch (error) {
console.log("err", error);
}
},
}),
}),
}
})
})
});
export const {
@@ -186,5 +183,5 @@ export const {
useSendChannelMsgMutation,
useAddMembersMutation,
useRemoveMembersMutation,
useUpdateIconMutation,
useUpdateIconMutation
} = channelApi;
+21 -21
View File
@@ -45,23 +45,23 @@ export const contactApi = createApi({
} catch {
console.log("get contact list error");
}
},
}
}),
deleteContact: builder.query({
query: (uid) => ({ url: `/admin/user/${uid}`, method: "DELETE" }),
query: (uid) => ({ url: `/admin/user/${uid}`, method: "DELETE" })
}),
updateContact: builder.mutation({
query: ({ id, ...rest }) => ({
url: `/admin/user/${id}`,
body: rest,
method: "PUT",
}),
method: "PUT"
})
}),
updateMuteSetting: builder.mutation({
query: (data) => ({
url: `/user/mute`,
method: "POST",
body: data,
body: data
}),
async onQueryStarted(data, { dispatch, queryFulfilled }) {
try {
@@ -70,31 +70,31 @@ export const contactApi = createApi({
} catch (error) {
console.log("update mute failed", error);
}
},
}
}),
updateAvatar: builder.mutation({
query: (data) => ({
headers: {
"content-type": "image/png",
"content-type": "image/png"
},
url: `user/avatar`,
method: "POST",
body: data,
}),
body: data
})
}),
updateInfo: builder.mutation({
query: (data) => ({
url: `user`,
method: "PUT",
body: data,
}),
body: data
})
}),
register: builder.mutation({
query: (data) => ({
url: `user/register`,
method: "POST",
body: data,
}),
body: data
})
}),
sendMsg: builder.mutation({
query: ({ id, content, type = "text", properties = "" }) => ({
@@ -102,21 +102,21 @@ export const contactApi = createApi({
"content-type": ContentTypes[type],
"X-Properties": properties
? btoa(unescape(encodeURIComponent(JSON.stringify(properties))))
: "",
: ""
},
url: `user/${id}/send`,
method: "POST",
body: type == "file" ? JSON.stringify(content) : content,
body: type == "file" ? JSON.stringify(content) : content
}),
async onQueryStarted(param1, param2) {
await onMessageSendStarted.call(this, param1, param2, "user");
},
}
}),
getHistoryMessages: builder.query({
query: ({ id, mid = null, limit = 100 }) => ({
url: mid
? `/user/${id}/history?before=${mid}&limit=${limit}`
: `/user/${id}/history?limit=${limit}`,
: `/user/${id}/history?limit=${limit}`
}),
async onQueryStarted(params, { dispatch, getState, queryFulfilled }) {
const { data: messages } = await queryFulfilled;
@@ -125,9 +125,9 @@ export const contactApi = createApi({
handleChatMessage(msg, dispatch, getState());
});
}
},
}),
}),
}
})
})
});
export const {
@@ -140,5 +140,5 @@ export const {
useGetContactsQuery,
useLazyGetContactsQuery,
useSendMsgMutation,
useRegisterMutation,
useRegisterMutation
} = contactApi;
+3 -4
View File
@@ -12,7 +12,7 @@ export const onMessageSendStarted = async (
type = "text",
from_uid,
reply_mid = null,
properties = { local_id: +new Date() },
properties = { local_id: +new Date() }
},
{ dispatch, queryFulfilled },
from = "channel"
@@ -42,11 +42,10 @@ export const onMessageSendStarted = async (
properties,
from_uid,
reply_mid,
sending: true,
sending: true
};
const addContextMessage = from == "channel" ? addChannelMsg : addUserMsg;
const removeContextMessage =
from == "channel" ? removeChannelMsg : removeUserMsg;
const removeContextMessage = from == "channel" ? removeChannelMsg : removeUserMsg;
if (!ignoreLocal) {
batch(() => {
dispatch(addMessage({ mid: ts, ...tmpMsg }));
+205 -205
View File
@@ -1,214 +1,214 @@
import { createApi } from '@reduxjs/toolkit/query/react';
import { ContentTypes } from '../config';
import { updateReadChannels, updateReadUsers } from '../slices/footprint';
import { createApi } from "@reduxjs/toolkit/query/react";
import { ContentTypes } from "../config";
import { updateReadChannels, updateReadUsers } from "../slices/footprint";
import {
fullfillFavorites,
populateFavorite,
addFavorite,
deleteFavorite
} from '../slices/favorites';
import { onMessageSendStarted } from './handlers';
import { normalizeArchiveData } from '../../common/utils';
fullfillFavorites,
populateFavorite,
addFavorite,
deleteFavorite
} from "../slices/favorites";
import { onMessageSendStarted } from "./handlers";
import { normalizeArchiveData } from "../../common/utils";
// import { updateMessage } from "../slices/message";
import baseQuery from './base.query';
import baseQuery from "./base.query";
export const messageApi = createApi({
reducerPath: 'messageApi',
baseQuery,
endpoints: (builder) => ({
editMessage: builder.mutation({
query: ({ mid, content, type = 'text' }) => ({
headers: {
'content-type': ContentTypes[type]
},
url: `/message/${mid}/edit`,
method: 'PUT',
body: content
})
// async onQueryStarted({mid,content},{dispatch}){
// dispatch()
// }
}),
reactMessage: builder.mutation({
query: ({ mid, action }) => ({
url: `/message/${mid}/like`,
method: 'PUT',
body: { action }
})
}),
deleteMessage: builder.query({
query: (mid) => ({
url: `/message/${mid}`,
method: 'DELETE'
})
}),
prepareUploadFile: builder.mutation({
query: (meta = {}) => ({
url: `/resource/file/prepare`,
method: 'POST',
body: meta
})
}),
createArchive: builder.mutation({
query: (mids = []) => ({
url: `/resource/archive`,
method: 'POST',
body: { mid_list: mids }
})
}),
uploadFile: builder.mutation({
query: (formData) => ({
// headers: {
// "content-type": ContentTypes.formData,
// },
url: `/resource/file/upload`,
method: 'POST',
body: formData
}),
transformResponse: (data) => {
console.log('upload file response', data);
return data ? data : {};
}
}),
getOGInfo: builder.query({
query: (url) => ({
url: `/resource/open_graphic_parse?url=${encodeURIComponent(url)}`
})
}),
getArchiveMessage: builder.query({
query: (file_path) => ({
url: `/resource/archive?file_path=${encodeURIComponent(file_path)}`
})
}),
pinMessage: builder.mutation({
query: ({ gid, mid }) => ({
url: `/group/${gid}/pin`,
method: 'POST',
body: { mid }
})
}),
unpinMessage: builder.mutation({
query: ({ gid, mid }) => ({
url: `/group/${gid}/unpin`,
method: 'POST',
body: { mid }
})
}),
favoriteMessage: builder.mutation({
query: (mids) => ({
url: `/favorite`,
method: 'POST',
body: { mid_list: mids }
}),
async onQueryStarted(mids, { dispatch, queryFulfilled }) {
try {
const { data } = await queryFulfilled;
const { created_at, id } = data;
dispatch(addFavorite({ id, created_at }));
dispatch(messageApi.endpoints.getFavoriteDetails.initiate(id));
} catch (err) {
console.log('get favorite list error', err);
}
}
}),
removeFavorite: builder.query({
query: (id) => ({
url: `/favorite/${id}`,
method: 'DELETE'
}),
async onQueryStarted(id, { dispatch, queryFulfilled }) {
try {
await queryFulfilled;
dispatch(deleteFavorite(id));
} catch (err) {
console.log('get favorite list error', err);
}
}
}),
getFavoriteDetails: builder.query({
query: (id) => ({
url: `/favorite/${id}`
}),
async onQueryStarted(id, { dispatch, queryFulfilled, getState }) {
try {
const { data } = await queryFulfilled;
const loginUid = getState().authData.uid;
const messages = normalizeArchiveData(data, id, loginUid);
dispatch(populateFavorite({ id, messages }));
} catch (err) {
console.log('get favorite list error', err);
}
}
}),
getFavorites: builder.query({
query: () => ({
url: `/favorite`
}),
async onQueryStarted(data, { dispatch, queryFulfilled }) {
try {
const { data: favorites } = await queryFulfilled;
dispatch(fullfillFavorites(favorites));
for (const fav of favorites) {
const { id } = fav;
dispatch(messageApi.endpoints.getFavoriteDetails.initiate(id));
}
} catch (err) {
console.log('get favorite list error', err);
}
}
}),
replyMessage: builder.mutation({
query: ({ reply_mid, content, type = 'text' }) => ({
headers: {
'content-type': ContentTypes[type]
},
url: `/message/${reply_mid}/reply`,
method: 'POST',
body: content
}),
async onQueryStarted(param1, param2) {
await onMessageSendStarted.call(this, param1, param2, param1.context);
}
}),
readMessage: builder.mutation({
query: (data) => ({
url: `/user/read-index`,
method: 'POST',
body: data
}),
async onQueryStarted(data, { dispatch, queryFulfilled }) {
const { users = null, groups = null } = data;
if (users) {
dispatch(updateReadUsers(users));
}
if (groups) {
dispatch(updateReadChannels(groups));
}
try {
await queryFulfilled;
} catch {
// todo
}
}
reducerPath: "messageApi",
baseQuery,
endpoints: (builder) => ({
editMessage: builder.mutation({
query: ({ mid, content, type = "text" }) => ({
headers: {
"content-type": ContentTypes[type]
},
url: `/message/${mid}/edit`,
method: "PUT",
body: content
})
// async onQueryStarted({mid,content},{dispatch}){
// dispatch()
// }
}),
reactMessage: builder.mutation({
query: ({ mid, action }) => ({
url: `/message/${mid}/like`,
method: "PUT",
body: { action }
})
}),
deleteMessage: builder.query({
query: (mid) => ({
url: `/message/${mid}`,
method: "DELETE"
})
}),
prepareUploadFile: builder.mutation({
query: (meta = {}) => ({
url: `/resource/file/prepare`,
method: "POST",
body: meta
})
}),
createArchive: builder.mutation({
query: (mids = []) => ({
url: `/resource/archive`,
method: "POST",
body: { mid_list: mids }
})
}),
uploadFile: builder.mutation({
query: (formData) => ({
// headers: {
// "content-type": ContentTypes.formData,
// },
url: `/resource/file/upload`,
method: "POST",
body: formData
}),
transformResponse: (data) => {
console.log("upload file response", data);
return data ? data : {};
}
}),
getOGInfo: builder.query({
query: (url) => ({
url: `/resource/open_graphic_parse?url=${encodeURIComponent(url)}`
})
}),
getArchiveMessage: builder.query({
query: (file_path) => ({
url: `/resource/archive?file_path=${encodeURIComponent(file_path)}`
})
}),
pinMessage: builder.mutation({
query: ({ gid, mid }) => ({
url: `/group/${gid}/pin`,
method: "POST",
body: { mid }
})
}),
unpinMessage: builder.mutation({
query: ({ gid, mid }) => ({
url: `/group/${gid}/unpin`,
method: "POST",
body: { mid }
})
}),
favoriteMessage: builder.mutation({
query: (mids) => ({
url: `/favorite`,
method: "POST",
body: { mid_list: mids }
}),
async onQueryStarted(mids, { dispatch, queryFulfilled }) {
try {
const { data } = await queryFulfilled;
const { created_at, id } = data;
dispatch(addFavorite({ id, created_at }));
dispatch(messageApi.endpoints.getFavoriteDetails.initiate(id));
} catch (err) {
console.log("get favorite list error", err);
}
}
}),
removeFavorite: builder.query({
query: (id) => ({
url: `/favorite/${id}`,
method: "DELETE"
}),
async onQueryStarted(id, { dispatch, queryFulfilled }) {
try {
await queryFulfilled;
dispatch(deleteFavorite(id));
} catch (err) {
console.log("get favorite list error", err);
}
}
}),
getFavoriteDetails: builder.query({
query: (id) => ({
url: `/favorite/${id}`
}),
async onQueryStarted(id, { dispatch, queryFulfilled, getState }) {
try {
const { data } = await queryFulfilled;
const loginUid = getState().authData.uid;
const messages = normalizeArchiveData(data, id, loginUid);
dispatch(populateFavorite({ id, messages }));
} catch (err) {
console.log("get favorite list error", err);
}
}
}),
getFavorites: builder.query({
query: () => ({
url: `/favorite`
}),
async onQueryStarted(data, { dispatch, queryFulfilled }) {
try {
const { data: favorites } = await queryFulfilled;
dispatch(fullfillFavorites(favorites));
for (const fav of favorites) {
const { id } = fav;
dispatch(messageApi.endpoints.getFavoriteDetails.initiate(id));
}
} catch (err) {
console.log("get favorite list error", err);
}
}
}),
replyMessage: builder.mutation({
query: ({ reply_mid, content, type = "text" }) => ({
headers: {
"content-type": ContentTypes[type]
},
url: `/message/${reply_mid}/reply`,
method: "POST",
body: content
}),
async onQueryStarted(param1, param2) {
await onMessageSendStarted.call(this, param1, param2, param1.context);
}
}),
readMessage: builder.mutation({
query: (data) => ({
url: `/user/read-index`,
method: "POST",
body: data
}),
async onQueryStarted(data, { dispatch, queryFulfilled }) {
const { users = null, groups = null } = data;
if (users) {
dispatch(updateReadUsers(users));
}
if (groups) {
dispatch(updateReadChannels(groups));
}
try {
await queryFulfilled;
} catch {
// todo
}
}
})
})
})
});
export const {
useLazyRemoveFavoriteQuery,
useUnpinMessageMutation,
useLazyGetFavoritesQuery,
useFavoriteMessageMutation,
usePinMessageMutation,
useLazyGetArchiveMessageQuery,
useGetArchiveMessageQuery,
useLazyGetOGInfoQuery,
usePrepareUploadFileMutation,
useUploadFileMutation,
useEditMessageMutation,
useReactMessageMutation,
useReplyMessageMutation,
useLazyDeleteMessageQuery,
useReadMessageMutation,
useCreateArchiveMutation
useLazyRemoveFavoriteQuery,
useUnpinMessageMutation,
useLazyGetFavoritesQuery,
useFavoriteMessageMutation,
usePinMessageMutation,
useLazyGetArchiveMessageQuery,
useGetArchiveMessageQuery,
useLazyGetOGInfoQuery,
usePrepareUploadFileMutation,
useUploadFileMutation,
useEditMessageMutation,
useReactMessageMutation,
useReplyMessageMutation,
useLazyDeleteMessageQuery,
useReadMessageMutation,
useCreateArchiveMutation
} = messageApi;
+46 -46
View File
@@ -20,7 +20,7 @@ export const serverApi = createApi({
} catch {
console.log("get server info error");
}
},
}
}),
getThirdPartySecret: builder.query({
query: () => ({
@@ -29,142 +29,142 @@ export const serverApi = createApi({
// accept: "text/plain",
// },
url: `/admin/system/third_party_secret`,
responseHandler: (response) => response.text(),
responseHandler: (response) => response.text()
}),
keepUnusedDataFor: 0,
keepUnusedDataFor: 0
}),
updateThirdPartySecret: builder.mutation({
query: () => ({
url: `/admin/system/third_party_secret`,
method: "POST",
responseHandler: (response) => response.text(),
}),
responseHandler: (response) => response.text()
})
}),
getMetrics: builder.query({
query: () => ({ url: `/admin/system/metrics` }),
query: () => ({ url: `/admin/system/metrics` })
}),
getServerVersion: builder.query({
query: () => ({
headers: {
// "content-type": "text/plain",
accept: "text/plain",
accept: "text/plain"
},
url: `/admin/system/version`,
responseHandler: (response) => response.text(),
}),
responseHandler: (response) => response.text()
})
}),
getFirebaseConfig: builder.query({
query: () => ({ url: `admin/fcm/config` }),
query: () => ({ url: `admin/fcm/config` })
}),
getGoogleAuthConfig: builder.query({
query: () => ({ url: `admin/google_auth/config` }),
query: () => ({ url: `admin/google_auth/config` })
}),
updateGoogleAuthConfig: builder.mutation({
query: (data) => ({
url: `admin/google_auth/config`,
method: "POST",
body: data,
}),
body: data
})
}),
getGithubAuthConfig: builder.query({
query: () => ({ url: `admin/github_auth/config` }),
query: () => ({ url: `admin/github_auth/config` })
}),
updateGithubAuthConfig: builder.mutation({
query: (data) => ({
url: `admin/github_auth/config`,
method: "POST",
body: data,
}),
body: data
})
}),
sendTestEmail: builder.mutation({
query: (data) => ({
url: `/admin/system/send_mail`,
method: "POST",
body: data,
}),
body: data
})
}),
updateFirebaseConfig: builder.mutation({
query: (data) => ({
url: `admin/fcm/config`,
method: "POST",
body: data,
}),
body: data
})
}),
getAgoraConfig: builder.query({
query: () => ({ url: `admin/agora/config` }),
query: () => ({ url: `admin/agora/config` })
}),
updateAgoraConfig: builder.mutation({
query: (data) => ({
url: `admin/agora/config`,
method: "POST",
body: data,
}),
body: data
})
}),
getSMTPConfig: builder.query({
query: () => ({ url: `admin/smtp/config` }),
query: () => ({ url: `admin/smtp/config` })
}),
getSMTPStatus: builder.query({
query: () => ({ url: `/admin/smtp/enabled` }),
query: () => ({ url: `/admin/smtp/enabled` })
}),
updateSMTPConfig: builder.mutation({
query: (data) => ({
url: `admin/smtp/config`,
method: "POST",
body: data,
}),
body: data
})
}),
getLoginConfig: builder.query({
query: () => ({ url: `admin/login/config` }),
query: () => ({ url: `admin/login/config` })
}),
updateLoginConfig: builder.mutation({
query: (data) => ({
url: `admin/login/config`,
method: "POST",
body: data,
}),
body: data
})
}),
updateLogo: builder.mutation({
query: (data) => ({
headers: {
"content-type": "image/png",
"content-type": "image/png"
},
url: `admin/system/organization/logo`,
method: "POST",
body: data,
body: data
}),
async onQueryStarted(data, { dispatch, queryFulfilled }) {
try {
await queryFulfilled;
dispatch(
updateInfo({
logo: `${BASE_URL}/resource/organization/logo?t=${+new Date()}`,
logo: `${BASE_URL}/resource/organization/logo?t=${+new Date()}`
})
);
} catch {
console.log("update server logo error");
}
},
}
}),
createInviteLink: builder.query({
query: (expired_in = defaultExpireDuration) => ({
headers: {
"content-type": "text/plain",
accept: "text/plain",
accept: "text/plain"
},
url: `/admin/system/create_invite_link?expired_in=${expired_in}`,
responseHandler: (response) => response.text(),
responseHandler: (response) => response.text()
}),
transformResponse: (link) => {
// 替换掉域名
const invite = new URL(link);
return `${location.origin}${invite.pathname}${invite.search}${invite.hash}`;
},
}
}),
updateServer: builder.mutation({
query: (data) => ({
url: `admin/system/organization`,
method: "POST",
body: data,
body: data
}),
async onQueryStarted(data, { dispatch, queryFulfilled, getState }) {
const { name: prevName, description: prevDesc } = getState().server;
@@ -174,21 +174,21 @@ export const serverApi = createApi({
} catch {
dispatch(updateInfo({ name: prevName, description: prevDesc }));
}
},
}
}),
createAdmin: builder.mutation({
query: (data) => ({
url: `/admin/system/create_admin`,
method: "POST",
body: data,
}),
body: data
})
}),
getInitialized: builder.query({
query: () => ({
url: `/admin/system/initialized`,
}),
}),
}),
url: `/admin/system/initialized`
})
})
})
});
export const {
@@ -217,5 +217,5 @@ export const {
useGetThirdPartySecretQuery,
useUpdateThirdPartySecretMutation,
useCreateAdminMutation,
useGetInitializedQuery,
useGetInitializedQuery
} = serverApi;
+8 -19
View File
@@ -1,24 +1,18 @@
import { createSlice } from "@reduxjs/toolkit";
import {
KEY_PWA_INSTALLED,
KEY_REFRESH_TOKEN,
KEY_TOKEN,
KEY_UID,
KEY_EXPIRE,
} from "../config";
import { KEY_PWA_INSTALLED, KEY_REFRESH_TOKEN, KEY_TOKEN, KEY_UID, KEY_EXPIRE } from "../config";
const initialState = {
initialized: true,
uid: null,
token: localStorage.getItem(KEY_TOKEN),
expireTime: localStorage.getItem(KEY_EXPIRE) || +new Date(),
refreshToken: localStorage.getItem(KEY_REFRESH_TOKEN),
refreshToken: localStorage.getItem(KEY_REFRESH_TOKEN)
};
const emptyState = {
initialized: true,
uid: null,
token: null,
expireTime: +new Date(),
refreshToken: null,
refreshToken: null
};
const authDataSlice = createSlice({
name: "authData",
@@ -30,7 +24,7 @@ const authDataSlice = createSlice({
user: { uid },
token,
refresh_token,
expired_in = 0,
expired_in = 0
} = action.payload;
state.initialized = initialized;
state.uid = uid;
@@ -76,14 +70,9 @@ const authDataSlice = createSlice({
localStorage.setItem(KEY_EXPIRE, et);
localStorage.setItem(KEY_TOKEN, token);
localStorage.setItem(KEY_REFRESH_TOKEN, refresh_token);
},
},
}
}
});
export const {
updateInitialized,
setAuthData,
resetAuthData,
setUid,
updateToken,
} = authDataSlice.actions;
export const { updateInitialized, setAuthData, resetAuthData, setUid, updateToken } =
authDataSlice.actions;
export default authDataSlice.reducer;
+6 -10
View File
@@ -3,7 +3,7 @@ import BASE_URL from "../config";
import { getNonNullValues } from "../../common/utils";
const initialState = {
ids: [],
byId: {},
byId: {}
};
const channelsSlice = createSlice({
name: `channels`,
@@ -36,18 +36,14 @@ const channelsSlice = createSlice({
icon:
avatar_updated_at == 0
? ""
: `${BASE_URL}/resource/group_avatar?gid=${gid}&t=${avatar_updated_at}`,
: `${BASE_URL}/resource/group_avatar?gid=${gid}&t=${avatar_updated_at}`
};
},
updateChannel(state, action) {
const ignoreInPublic = ["add_member", "remove_member"];
const { id, operation, members = [], ...rest } = action.payload;
const currChannel = state.byId[id];
if (
!currChannel ||
(currChannel.is_public && ignoreInPublic.includes(operation))
)
return;
if (!currChannel || (currChannel.is_public && ignoreInPublic.includes(operation))) return;
switch (operation) {
case "remove_member":
{
@@ -99,8 +95,8 @@ const channelsSlice = createSlice({
state.ids.splice(idx, 1);
delete state.byId[gid];
}
},
},
}
}
});
export const {
updatePinMessage,
@@ -108,6 +104,6 @@ export const {
fullfillChannels,
addChannel,
updateChannel,
removeChannel,
removeChannel
} = channelsSlice.actions;
export default channelsSlice.reducer;
+6 -12
View File
@@ -3,7 +3,7 @@ import { getNonNullValues } from "../../common/utils";
import BASE_URL from "../config";
const initialState = {
ids: [],
byId: {},
byId: {}
};
const contactsSlice = createSlice({
name: `contacts`,
@@ -39,9 +39,7 @@ const contactsSlice = createSlice({
Object.keys(vals).forEach((k) => {
state.byId[uid][k] = vals[k];
if (k == "avatar_updated_at") {
state.byId[
uid
].avatar = `${BASE_URL}/resource/avatar?uid=${uid}&t=${vals[k]}`;
state.byId[uid].avatar = `${BASE_URL}/resource/avatar?uid=${uid}&t=${vals[k]}`;
}
});
}
@@ -80,13 +78,9 @@ const contactsSlice = createSlice({
state.byId[uid].online = online;
}
});
},
},
}
}
});
export const {
resetContacts,
fullfillContacts,
updateUsersByLogs,
updateUsersStatus,
} = contactsSlice.actions;
export const { resetContacts, fullfillContacts, updateUsersByLogs, updateUsersStatus } =
contactsSlice.actions;
export default contactsSlice.reducer;
+4 -8
View File
@@ -24,13 +24,9 @@ const favoritesSlice = createSlice({
if (idx > -1) {
state[idx].messages = messages;
}
},
},
}
}
});
export const {
addFavorite,
deleteFavorite,
fullfillFavorites,
populateFavorite,
} = favoritesSlice.actions;
export const { addFavorite, deleteFavorite, fullfillFavorites, populateFavorite } =
favoritesSlice.actions;
export default favoritesSlice.reducer;
+6 -6
View File
@@ -5,7 +5,7 @@ const initialState = {
readUsers: {},
readChannels: {},
muteUsers: {},
muteChannels: {},
muteChannels: {}
};
const footprintSlice = createSlice({
name: "footprint",
@@ -21,7 +21,7 @@ const footprintSlice = createSlice({
readUsers = {},
readChannels = {},
muteUsers = {},
muteChannels = {},
muteChannels = {}
} = action.payload;
return {
usersVersion,
@@ -29,7 +29,7 @@ const footprintSlice = createSlice({
readUsers,
readChannels,
muteUsers,
muteChannels,
muteChannels
};
},
updateUsersVersion(state, action) {
@@ -94,8 +94,8 @@ const footprintSlice = createSlice({
reads.forEach(({ gid, mid }) => {
state.readChannels[gid] = mid;
});
},
},
}
}
});
export const {
resetFootprint,
@@ -104,6 +104,6 @@ export const {
updateUsersVersion,
updateReadChannels,
updateReadUsers,
updateMute,
updateMute
} = footprintSlice.actions;
export default footprintSlice.reducer;
+5 -8
View File
@@ -15,8 +15,7 @@ const channelMsgSlice = createSlice({
const { id, mid, local_id = null } = action.payload;
if (state[id]) {
const midExsited = state[id].findIndex((id) => id == mid) > -1;
const localMsgExsited =
state[id].findIndex((id) => id == local_id) > -1;
const localMsgExsited = state[id].findIndex((id) => id == local_id) > -1;
if (midExsited || localMsgExsited) return;
state[id].push(+mid);
} else {
@@ -44,14 +43,12 @@ const channelMsgSlice = createSlice({
}
},
removeChannelSession(state, action) {
const ids = Array.isArray(action.payload)
? action.payload
: [action.payload];
const ids = Array.isArray(action.payload) ? action.payload : [action.payload];
ids.forEach((id) => {
delete state[id];
});
},
},
}
}
});
export const {
removeChannelSession,
@@ -59,6 +56,6 @@ export const {
fullfillChannelMsg,
addChannelMsg,
removeChannelMsg,
replaceChannelMsg,
replaceChannelMsg
} = channelMsgSlice.actions;
export default channelMsgSlice.reducer;
+5 -11
View File
@@ -20,9 +20,7 @@ const fileMessageSlice = createSlice({
}
},
removeFileMessage(state, action) {
const mids = Array.isArray(action.payload)
? action.payload
: [action.payload];
const mids = Array.isArray(action.payload) ? action.payload : [action.payload];
mids.forEach((id) => {
// 从file message 列表删掉
const fidIdx = state.findIndex((fid) => fid == id);
@@ -30,13 +28,9 @@ const fileMessageSlice = createSlice({
state.splice(fidIdx, 1);
}
});
},
},
}
}
});
export const {
removeFileMessage,
resetFileMessage,
fullfillFileMessage,
addFileMessage,
} = fileMessageSlice.actions;
export const { removeFileMessage, resetFileMessage, fullfillFileMessage, addFileMessage } =
fileMessageSlice.actions;
export default fileMessageSlice.reducer;
+5 -7
View File
@@ -2,7 +2,7 @@ import { createSlice } from "@reduxjs/toolkit";
import BASE_URL, { ContentTypes } from "../config";
import { isImage } from "../../common/utils";
const initialState = {
replying: {},
replying: {}
};
const messageSlice = createSlice({
name: "message",
@@ -48,9 +48,7 @@ const messageSlice = createSlice({
state[mid] = data;
},
removeMessage(state, action) {
const mids = Array.isArray(action.payload)
? action.payload
: [action.payload];
const mids = Array.isArray(action.payload) ? action.payload : [action.payload];
mids.forEach((id) => {
delete state[id];
});
@@ -65,8 +63,8 @@ const messageSlice = createSlice({
if (state.replying[key]) {
delete state.replying[key];
}
},
},
}
}
});
export const {
resetMessage,
@@ -76,6 +74,6 @@ export const {
addMessage,
removeMessage,
addReplyingMessage,
removeReplyingMessage,
removeReplyingMessage
} = messageSlice.actions;
export default messageSlice.reducer;
+4 -6
View File
@@ -12,9 +12,7 @@ const reactionMessageSlice = createSlice({
return action.payload;
},
removeReactionMessage(state, action) {
const mids = Array.isArray(action.payload)
? action.payload
: [action.payload];
const mids = Array.isArray(action.payload) ? action.payload : [action.payload];
mids.forEach((id) => {
delete state[id];
});
@@ -48,13 +46,13 @@ const reactionMessageSlice = createSlice({
state[mid][reaction] = [from_uid];
}
state[rid] = true;
},
},
}
}
});
export const {
removeReactionMessage,
resetReactionMessage,
fullfillReactionMessage,
toggleReactionMessage,
toggleReactionMessage
} = reactionMessageSlice.actions;
export default reactionMessageSlice.reducer;
+6 -9
View File
@@ -1,7 +1,7 @@
import { createSlice } from "@reduxjs/toolkit";
const initialState = {
ids: [],
byId: {},
byId: {}
};
const userMsgSlice = createSlice({
name: "userMessage",
@@ -18,8 +18,7 @@ const userMsgSlice = createSlice({
const { id, mid, local_id } = action.payload;
if (state.byId[id]) {
const midExsited = state.byId[id].findIndex((id) => id == mid) > -1;
const localMsgExsited =
state.byId[id].findIndex((id) => id == local_id) > -1;
const localMsgExsited = state.byId[id].findIndex((id) => id == local_id) > -1;
if (midExsited || localMsgExsited) return;
state.byId[id].push(+mid);
@@ -53,15 +52,13 @@ const userMsgSlice = createSlice({
}
},
removeUserSession(state, action) {
const ids = Array.isArray(action.payload)
? action.payload
: [action.payload];
const ids = Array.isArray(action.payload) ? action.payload : [action.payload];
state.ids = state.ids.filter((id) => ids.findIndex((i) => i == id) == -1);
// ids.forEach((id) => {
// delete state.byId[id];
// });
},
},
}
}
});
export const {
removeUserSession,
@@ -69,6 +66,6 @@ export const {
fullfillUserMsg,
addUserMsg,
removeUserMsg,
replaceUserMsg,
replaceUserMsg
} = userMsgSlice.actions;
export default userMsgSlice.reducer;
+1 -1
View File
@@ -4,4 +4,4 @@ set
add
update
remove
toggle
toggle
+6 -6
View File
@@ -5,8 +5,8 @@ const initialState = {
logo: "",
inviteLink: {
link: "",
expire: 0,
},
expire: 0
}
};
const serverSlice = createSlice({
name: "server",
@@ -19,10 +19,10 @@ const serverSlice = createSlice({
const {
inviteLink = {
link: "",
expire: 0,
expire: 0
},
name = "",
description = "",
description = ""
} = action.payload || {};
return { name, description, inviteLink };
},
@@ -31,8 +31,8 @@ const serverSlice = createSlice({
Object.keys(values).forEach((_key) => {
state[_key] = values[_key];
});
},
},
}
}
});
export const { updateInfo, resetServer, fullfillServer } = serverSlice.actions;
export default serverSlice.reducer;
+8 -18
View File
@@ -5,7 +5,7 @@ const initialState = {
ready: false,
userGuide: {
visible: false,
step: 1,
step: 1
},
inputMode: "text",
menuExpand: false,
@@ -16,8 +16,8 @@ const initialState = {
draftMixedText: {},
remeberedNavs: {
chat: null,
contact: null,
},
contact: null
}
};
const uiSlice = createSlice({
name: "ui",
@@ -61,12 +61,7 @@ const uiSlice = createSlice({
},
updateUploadFiles(state, action) {
const {
context = "channel",
id = null,
operation = "add",
...rest
} = action.payload;
const { context = "channel", id = null, operation = "add", ...rest } = action.payload;
if (!id || !context) return;
const _key = `${context}_${id}`;
let files = state.uploadFiles[_key];
@@ -126,12 +121,7 @@ const uiSlice = createSlice({
}
},
updateSelectMessages(state, action) {
const {
context = "channel",
id = null,
operation = "add",
data = null,
} = action.payload;
const { context = "channel", id = null, operation = "add", data = null } = action.payload;
let currData = state.selectMessages[`${context}_${id}`];
switch (operation) {
case "add": {
@@ -151,8 +141,8 @@ const uiSlice = createSlice({
break;
}
state.selectMessages[`${context}_${id}`] = currData;
},
},
}
}
});
export const {
fullfillUI,
@@ -166,6 +156,6 @@ export const {
updateUserGuide,
updateDraftMarkdown,
updateDraftMixedText,
updateRemeberedNavs,
updateRemeberedNavs
} = uiSlice.actions;
export default uiSlice.reducer;
+34 -41
View File
@@ -36,7 +36,7 @@ const reducer = combineReducers({
[messageApi.reducerPath]: messageApi.reducer,
[contactApi.reducerPath]: contactApi.reducer,
[channelApi.reducerPath]: channelApi.reducer,
[serverApi.reducerPath]: serverApi.reducer,
[serverApi.reducerPath]: serverApi.reducer
});
const store = configureStore({
@@ -50,48 +50,41 @@ const store = configureStore({
serverApi.middleware,
messageApi.middleware
)
.prepend(listenerMiddleware.middleware),
.prepend(listenerMiddleware.middleware)
});
let initialized = false;
setupListeners(
store.dispatch,
(dispatch, { onOnline, onOffline, onFocus, onFocusLost }) => {
const handleFocus = () => dispatch(onFocus());
const handleFocusLost = () => dispatch(onFocusLost());
const handleOnline = () => dispatch(onOnline());
const handleOffline = () => dispatch(onOffline());
const handleVisibilityChange = () => {
if (window.document.visibilityState === "visible") {
handleFocus();
} else {
handleFocusLost();
}
};
if (!initialized) {
if (typeof window !== "undefined" && window.addEventListener) {
// Handle focus events
window.addEventListener(
"visibilitychange",
handleVisibilityChange,
false
);
window.addEventListener("focus", handleFocus, false);
// Handle connection events
window.addEventListener("online", handleOnline, false);
window.addEventListener("offline", handleOffline, false);
initialized = true;
}
setupListeners(store.dispatch, (dispatch, { onOnline, onOffline, onFocus, onFocusLost }) => {
const handleFocus = () => dispatch(onFocus());
const handleFocusLost = () => dispatch(onFocusLost());
const handleOnline = () => dispatch(onOnline());
const handleOffline = () => dispatch(onOffline());
const handleVisibilityChange = () => {
if (window.document.visibilityState === "visible") {
handleFocus();
} else {
handleFocusLost();
}
};
if (!initialized) {
if (typeof window !== "undefined" && window.addEventListener) {
// Handle focus events
window.addEventListener("visibilitychange", handleVisibilityChange, false);
window.addEventListener("focus", handleFocus, false);
// Handle connection events
window.addEventListener("online", handleOnline, false);
window.addEventListener("offline", handleOffline, false);
initialized = true;
}
const unsubscribe = () => {
window.removeEventListener("focus", handleFocus);
window.removeEventListener("visibilitychange", handleVisibilityChange);
window.removeEventListener("online", handleOnline);
window.removeEventListener("offline", handleOffline);
initialized = false;
};
return unsubscribe;
}
);
const unsubscribe = () => {
window.removeEventListener("focus", handleFocus);
window.removeEventListener("visibilitychange", handleVisibilityChange);
window.removeEventListener("online", handleOnline);
window.removeEventListener("offline", handleOffline);
initialized = false;
};
return unsubscribe;
});
export default store;
+14 -14
View File
@@ -1,20 +1,20 @@
::-webkit-scrollbar-thumb {
background: #ddd;
border-radius: 4px;
background: #ddd;
border-radius: 4px;
}
::-webkit-scrollbar {
background: transparent;
width: 5px;
height: 5px;
background: transparent;
width: 5px;
height: 5px;
}
:root{
/* border radius */
--br:8px;
--rustchat-navs-bg:#E5E7EB;
--rustchat-body-bg:#F5F6F7;
--rustchat-chat-bg:#fff;
--rustchat-channel-text-color:#1C1C1E;
--rustchat-msg-text-color:#374151;
:root {
/* border radius */
--br: 8px;
--rustchat-navs-bg: #e5e7eb;
--rustchat-body-bg: #f5f6f7;
--rustchat-chat-bg: #fff;
--rustchat-channel-text-color: #1c1c1e;
--rustchat-msg-text-color: #374151;
}
/* @media (prefers-color-scheme: dark) {
:root{
@@ -25,4 +25,4 @@
--rustchat-channel-text-color:#fff;
--rustchat-msg-text-color:#fff;
}
} */
} */
File diff suppressed because it is too large Load Diff
+5 -16
View File
@@ -42,9 +42,7 @@ const Styled = styled.ul`
}
`;
export default function AddEntriesMenu() {
const currentUser = useSelector(
(store) => store.contacts.byId[store.authData.uid]
);
const currentUser = useSelector((store) => store.contacts.byId[store.authData.uid]);
const [isPrivate, setIsPrivate] = useState(false);
const [inviteModalVisible, setInviteModalVisible] = useState(false);
@@ -78,10 +76,7 @@ export default function AddEntriesMenu() {
<>
<Styled>
{currentUser?.is_admin && (
<li
className="item"
onClick={handleOpenChannelModal.bind(null, false)}
>
<li className="item" onClick={handleOpenChannelModal.bind(null, false)}>
<ChannelIcon className="icon" />
New Channel
</li>
@@ -99,15 +94,9 @@ export default function AddEntriesMenu() {
Invite People
</li>
</Styled>
{channelModalVisible && (
<ChannelModal personal={isPrivate} closeModal={handleCloseModal} />
)}
{contactsModalVisible && (
<ContactsModal closeModal={toggleContactsModalVisible} />
)}
{inviteModalVisible && (
<InviteModal closeModal={toggleInviteModalVisible} />
)}
{channelModalVisible && <ChannelModal personal={isPrivate} closeModal={handleCloseModal} />}
{contactsModalVisible && <ContactsModal closeModal={toggleContactsModalVisible} />}
{inviteModalVisible && <InviteModal closeModal={toggleInviteModalVisible} />}
</>
);
}
+2 -2
View File
@@ -8,7 +8,7 @@ const Avatar = ({ url = "", name = "unkonw name", type = "user", ...rest }) => {
const tmp = getInitialsAvatar({
initials: getInitials(name),
background: type == "channel" ? "#EAECF0" : undefined,
foreground: type == "channel" ? "#475467" : undefined,
foreground: type == "channel" ? "#475467" : undefined
});
setSrc(tmp);
};
@@ -17,7 +17,7 @@ const Avatar = ({ url = "", name = "unkonw name", type = "user", ...rest }) => {
const tmp = getInitialsAvatar({
initials: getInitials(name),
background: type == "channel" ? "#EAECF0" : undefined,
foreground: type == "channel" ? "#475467" : undefined,
foreground: type == "channel" ? "#475467" : undefined
});
setSrc(tmp);
} else {
+2 -4
View File
@@ -64,7 +64,7 @@ export default function AvatarUploader({
name = "",
type = "user",
uploadImage,
disabled = false,
disabled = false
}) {
const [uploading, setUploading] = useState(false);
const handleUpload = async (evt) => {
@@ -80,9 +80,7 @@ export default function AvatarUploader({
<Avatar type={type} url={url} name={name} />
{!disabled && (
<>
<div className="tip">
{uploading ? `Uploading` : `Change Avatar`}
</div>
<div className="tip">{uploading ? `Uploading` : `Change Avatar`}</div>
<input
multiple={false}
onChange={handleUpload}
+6 -13
View File
@@ -80,19 +80,16 @@ export default function BlankPlaceholder({ type = "chat" }) {
setInviteModalVisible((prev) => !prev);
};
const chatTip =
type == "chat"
? "Create a Channel to Start a Conversation"
: "Send a Direct Message";
const chatHanlder =
type == "chat" ? toggleChannelModalVisible : toggleContactListVisible;
type == "chat" ? "Create a Channel to Start a Conversation" : "Send a Direct Message";
const chatHanlder = type == "chat" ? toggleChannelModalVisible : toggleContactListVisible;
return (
<>
<Styled>
<div className="head">
<h2 className="title">Welcome to {server.name} server</h2>
<p className="desc">
Here are some steps to help you get started. For more, check out our
Getting Started guide
Here are some steps to help you get started. For more, check out our Getting Started
guide
</p>
</div>
<div className="boxes">
@@ -117,12 +114,8 @@ export default function BlankPlaceholder({ type = "chat" }) {
{createChannelVisible && (
<ChannelModal personal={true} closeModal={toggleChannelModalVisible} />
)}
{contactListVisible && (
<ContactsModal closeModal={toggleContactListVisible} />
)}
{inviteModalVisible && (
<InviteModal closeModal={toggleInviteModalVisible} />
)}
{contactListVisible && <ContactsModal closeModal={toggleContactListVisible} />}
{inviteModalVisible && <InviteModal closeModal={toggleInviteModalVisible} />}
</>
);
}
+4 -12
View File
@@ -47,16 +47,11 @@ const StyledWrapper = styled.div`
}
}
`;
export default function Channel({
interactive = true,
id = "",
compact = false,
avatarSize = 32,
}) {
export default function Channel({ interactive = true, id = "", compact = false, avatarSize = 32 }) {
const { channel, totalMemberCount } = useSelector((store) => {
return {
channel: store.channels.byId[id],
totalMemberCount: store.contacts.ids.length,
totalMemberCount: store.contacts.ids.length
};
});
console.log("channel item", id, channel);
@@ -65,17 +60,14 @@ export default function Channel({
return (
<StyledWrapper
size={avatarSize}
className={`${interactive ? "interactive" : ""} ${
compact ? "compact" : ""
}`}
className={`${interactive ? "interactive" : ""} ${compact ? "compact" : ""}`}
>
<div className="avatar">
<Avatar type="channel" url={avatar} name={"#"} alt="avatar" />
</div>
{!compact && (
<div className="name">
<span className="txt">{name}</span> (
{is_public ? totalMemberCount : members.length})
<span className="txt">{name}</span> ({is_public ? totalMemberCount : members.length})
</div>
)}
</StyledWrapper>
+1 -5
View File
@@ -8,11 +8,7 @@ const Styled = styled.div`
fill: #d0d5dd;
}
`;
export default function ChannelIcon({
personal = false,
muted = false,
className,
}) {
export default function ChannelIcon({ personal = false, muted = false, className }) {
return (
<Styled className={`${muted ? "muted" : ""} ${className}`}>
{personal ? <LockHashIcon /> : <HashIcon />}
+6 -18
View File
@@ -22,13 +22,11 @@ export default function ChannelModal({ personal = false, closeModal }) {
name: "",
dsecription: "",
members: [loginUid],
is_public: !personal,
is_public: !personal
});
const { contacts, input, updateInput } = useFilteredUsers();
const [
createChannel,
{ isSuccess, isError, isLoading, data: newChannelId },
] = useCreateChannelMutation();
const [createChannel, { isSuccess, isError, isLoading, data: newChannelId }] =
useCreateChannelMutation();
const handleToggle = () => {
const { is_public } = data;
@@ -72,9 +70,7 @@ export default function ChannelModal({ personal = false, closeModal }) {
const toggleCheckMember = ({ currentTarget }) => {
const { members } = data;
const { uid } = currentTarget.dataset;
let tmp = members.includes(+uid)
? members.filter((m) => m != uid)
: [...members, +uid];
let tmp = members.includes(+uid) ? members.filter((m) => m != uid) : [...members, +uid];
console.log(uid, currentTarget);
setData((prev) => {
return { ...prev, members: tmp };
@@ -135,11 +131,7 @@ export default function ChannelModal({ personal = false, closeModal }) {
<div className="name">
<span className="label normal">Channel Name</span>
<div className="input">
<input
onChange={handleNameInput}
value={name}
placeholder="new channel"
/>
<input onChange={handleNameInput} value={name} placeholder="new channel" />
<ChannelIcon personal={!is_public} className="icon" />
</div>
</div>
@@ -155,11 +147,7 @@ export default function ChannelModal({ personal = false, closeModal }) {
<Button onClick={closeModal} className="normal cancel">
Cancel
</Button>
<Button
disabled={isLoading}
onClick={handleCreate}
className="normal"
>
<Button disabled={isLoading} onClick={handleCreate} className="normal">
Create
</Button>
</div>
+9 -16
View File
@@ -4,14 +4,7 @@ import Tippy from "@tippyjs/react";
import useContactOperation from "../../hook/useContactOperation";
import ContextMenu from "../ContextMenu";
export default function ContactContextMenu({
enable = false,
uid,
cid,
visible,
hide,
children,
}) {
export default function ContactContextMenu({ enable = false, uid, cid, visible, hide, children }) {
const {
canCall,
call,
@@ -21,10 +14,10 @@ export default function ContactContextMenu({
canRemove,
canRemoveFromChannel,
removeFromChannel,
removeUser,
removeUser
} = useContactOperation({
uid,
cid,
cid
});
return (
<>
@@ -43,26 +36,26 @@ export default function ContactContextMenu({
items={[
{
title: "Message",
handler: startChat,
handler: startChat
},
canCall && {
title: "Call",
handler: call,
handler: call
},
canCopyEmail && {
title: "Copy Email",
handler: copyEmail,
handler: copyEmail
},
canRemoveFromChannel && {
danger: true,
title: "Remove From Channel",
handler: removeFromChannel,
handler: removeFromChannel
},
canRemove && {
danger: true,
title: "Remove From Server",
handler: removeUser,
},
handler: removeUser
}
]}
/>
}
+4 -12
View File
@@ -17,14 +17,10 @@ export default function Contact({
popover = false,
compact = false,
avatarSize = 32,
enableContextMenu = false,
enableContextMenu = false
}) {
const navigate = useNavigate();
const {
visible: contextMenuVisible,
handleContextMenuEvent,
hideContextMenu,
} = useContextMenu();
const { visible: contextMenuVisible, handleContextMenuEvent, hideContextMenu } = useContextMenu();
const curr = useSelector((store) => store.contacts.byId[uid]);
const handleDoubleClick = () => {
navigate(`/chat/dm/${uid}`);
@@ -50,15 +46,11 @@ export default function Contact({
onContextMenu={enableContextMenu ? handleContextMenuEvent : null}
size={avatarSize}
onDoubleClick={dm ? handleDoubleClick : null}
className={`${interactive ? "interactive" : ""} ${
compact ? "compact" : ""
}`}
className={`${interactive ? "interactive" : ""} ${compact ? "compact" : ""}`}
>
<div className="avatar">
<Avatar url={curr.avatar} name={curr.name} alt="avatar" />
<div
className={`status ${curr.online ? "online" : "offline"}`}
></div>
<div className={`status ${curr.online ? "online" : "offline"}`}></div>
</div>
{!compact && <span className="name">{curr?.name}</span>}
{owner && <IconOwner />}
+1 -5
View File
@@ -64,11 +64,7 @@ export default function ContactsModal({ closeModal }) {
<Modal>
<StyledWrapper ref={wrapperRef}>
<div className="search">
<input
value={input}
onChange={handleSearch}
placeholder="Type Username to search"
/>
<input value={input} onChange={handleSearch} placeholder="Type Username to search" />
</div>
{contacts && (
<ul className="users">
+2 -4
View File
@@ -16,13 +16,11 @@ export default function ContextMenu({ items = [], hideMenu = null }) {
}
},
underline = false,
danger = false,
danger = false
} = item;
return (
<li
className={`item ${underline ? "underline" : ""} ${
danger ? "danger" : ""
}`}
className={`item ${underline ? "underline" : ""} ${danger ? "danger" : ""}`}
key={title}
onClick={(evt) => {
evt.stopPropagation();
+1 -5
View File
@@ -25,11 +25,7 @@ export default function DeleteMessageConfirmModal({ closeModal, mids = [] }) {
<Button className="cancel" onClick={closeModal.bind(null, false)}>
Cancel
</Button>
<Button
disabled={isDeleting}
onClick={handleDelete}
className="danger"
>
<Button disabled={isDeleting} onClick={handleDelete} className="danger">
{isDeleting ? "Deleting" : `Delete`}
</Button>
</>
+1 -3
View File
@@ -13,9 +13,7 @@ export default function FAQ() {
<Styled>
<div className="item">Client Version: {process.env.VERSION}</div>
<div className="item">Server Version: {serverVersion}</div>
<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>
);
}
+7 -11
View File
@@ -9,7 +9,7 @@ import {
ImagePreview,
PdfPreview,
CodePreview,
DocPreview,
DocPreview
} from "./preview";
import { getFileIcon, formatBytes } from "../../utils";
import IconDownload from "../../../assets/icons/download.svg";
@@ -24,7 +24,7 @@ const renderPreview = (data) => {
video: /^video/gi,
code: /(json|javascript|java|rb|c|php|xml|css|html)$/gi,
doc: /^text/gi,
pdf: /\/pdf$/gi,
pdf: /\/pdf$/gi
};
const _arr = name.split(".");
const _type = file_type || _arr[_arr.length - 1];
@@ -65,7 +65,7 @@ export default function FileBox({
size,
created_at,
from_uid,
content,
content
}) {
const fromUser = useSelector((store) => store.contacts.byId[from_uid]);
const icon = getFileIcon(file_type, name);
@@ -75,9 +75,9 @@ export default function FileBox({
const withPreview = preview && previewContent;
return (
<Styled
className={`file_box ${flex ? "flex" : ""} ${
withPreview ? "preview" : ""
} ${file_type.startsWith("audio") ? "audio" : ""}`}
className={`file_box ${flex ? "flex" : ""} ${withPreview ? "preview" : ""} ${
file_type.startsWith("audio") ? "audio" : ""
}`}
>
<div className="basic">
{icon}
@@ -91,11 +91,7 @@ export default function FileBox({
</i>
</span>
</div>
<a
className="download"
download={name}
href={`${content}&download=true`}
>
<a className="download" download={name} href={`${content}&download=true`}>
<IconDownload />
</a>
</div>
@@ -5,11 +5,4 @@ import PdfPreview from "./Pdf";
import CodePreview from "./Code";
import DocPreview from "./Doc";
export {
VideoPreview,
AudioPreview,
ImagePreview,
PdfPreview,
CodePreview,
DocPreview,
};
export { VideoPreview, AudioPreview, ImagePreview, PdfPreview, CodePreview, DocPreview };
@@ -37,7 +37,7 @@ export default function ImageMessage({
thumbnail,
download,
content,
properties = {},
properties = {}
}) {
const [url, setUrl] = useState(thumbnail);
const { width = 0, height = 0 } = getDefaultSize(properties);
@@ -64,7 +64,7 @@ export default function ImageMessage({
strokeWidth={50}
styles={buildStyles({
storke: "#000",
strokeLinecap: "butt",
strokeLinecap: "butt"
})}
/>
</div>
@@ -74,7 +74,7 @@ export default function ImageMessage({
className="img preview"
style={{
width: width ? `${width}px` : "",
height: height ? `${height}px` : "",
height: height ? `${height}px` : ""
}}
data-meta={JSON.stringify(properties)}
data-origin={content}
+7 -23
View File
@@ -23,7 +23,7 @@ export default function FileMessage({
content = "",
download = "",
thumbnail = "",
properties = { local_id: 0, name: "", size: 0, content_type: "" },
properties = { local_id: 0, name: "", size: 0, content_type: "" }
}) {
const [imageSize, setImageSize] = useState(null);
const [uploadingFile, setUploadingFile] = useState(false);
@@ -31,19 +31,13 @@ export default function FileMessage({
const {
sendMessage,
isSuccess: sendMessageSuccess,
isSending,
isSending
} = useSendMessage({
context,
from: from_uid,
to,
to
});
const {
stopUploading,
data,
uploadFile,
progress,
isSuccess: uploadSuccess,
} = useUploadFile();
const { stopUploading, data, uploadFile, progress, isSuccess: uploadSuccess } = useUploadFile();
const fromUser = useSelector((store) => store.contacts.byId[from_uid]);
const { size, name, content_type } = properties ?? {};
useEffect(() => {
@@ -75,20 +69,14 @@ export default function FileMessage({
const propsV2 = imageSize ? { ...props, ...imageSize } : props;
// 本地文件 并且上传成功
if (uploadSuccess && isLocalFile(content)) {
console.log(
"send local file message",
uploadSuccess,
propsV2,
data,
content
);
console.log("send local file message", uploadSuccess, propsV2, data, content);
// 把已经上传的东西当做消息发出去
const { path } = data;
sendMessage({
ignoreLocal: true,
type: "file",
content: { path },
properties: propsV2,
properties: propsV2
});
}
}, [uploadSuccess, data, properties, content]);
@@ -151,11 +139,7 @@ export default function FileMessage({
{sending ? (
<IconClose className="cancel" onClick={handleCancel} />
) : (
<a
className="download"
download={name}
href={`${content}&download=true`}
>
<a className="download" download={name} href={`${content}&download=true`}>
<IconDownload />
</a>
)}
+8 -26
View File
@@ -25,7 +25,7 @@ export default function ForwardModal({ mids, closeModal }) {
const {
channels,
// input: channelInput,
updateInput: updateChannelInput,
updateInput: updateChannelInput
} = useFilteredChannels();
const { contacts, input, updateInput } = useFilteredUsers();
// const { conactsData, loginUid } = useSelector((store) => {
@@ -34,8 +34,7 @@ export default function ForwardModal({ mids, closeModal }) {
const toggleCheck = ({ currentTarget }) => {
const { id, type = "user" } = currentTarget.dataset;
const ids = type == "user" ? selectedMembers : selectedChannels;
const updateState =
type == "user" ? setSelectedMembers : setSelectedChannels;
const updateState = type == "user" ? setSelectedMembers : setSelectedChannels;
let tmp = ids.includes(+id) ? ids.filter((m) => m != id) : [...ids, +id];
console.log(id, currentTarget);
updateState(tmp);
@@ -47,13 +46,13 @@ export default function ForwardModal({ mids, closeModal }) {
await forwardMessage({
mids: mids.map((mid) => +mid),
users: selectedMembers,
channels: selectedChannels,
channels: selectedChannels
});
if (appendText.trim()) {
await sendMessages({
content: appendText,
users: selectedMembers,
channels: selectedChannels,
channels: selectedChannels
});
}
toast.success("Forward Message Successfully");
@@ -99,12 +98,7 @@ export default function ForwardModal({ mids, closeModal }) {
className="user channel"
onClick={toggleCheck}
>
<StyledCheckbox
readOnly
checked={checked}
name="cb"
id="cb"
/>
<StyledCheckbox readOnly checked={checked} name="cb" id="cb" />
<Channel id={gid} interactive={false} />
</li>
);
@@ -122,12 +116,7 @@ export default function ForwardModal({ mids, closeModal }) {
className="user"
onClick={toggleCheck}
>
<StyledCheckbox
readOnly
checked={checked}
name="cb"
id="cb"
/>
<StyledCheckbox readOnly checked={checked} name="cb" id="cb" />
<Contact uid={uid} interactive={false} />
</li>
);
@@ -162,10 +151,7 @@ export default function ForwardModal({ mids, closeModal }) {
interactive={false}
// avatarSize={40}
/>
<CloseIcon
className="remove"
onClick={removeSelected.bind(null, uid, "user")}
/>
<CloseIcon className="remove" onClick={removeSelected.bind(null, uid, "user")} />
</li>
);
})}
@@ -185,11 +171,7 @@ export default function ForwardModal({ mids, closeModal }) {
<Button onClick={closeModal} className="normal cancel">
Cancel
</Button>
<Button
className="normal"
disabled={sendButtonDisabled}
onClick={handleForward}
>
<Button className="normal" disabled={sendButtonDisabled} onClick={handleForward}>
Send To {selectedCount == 0 ? null : `(${selectedCount})`}
</Button>
</div>
+1 -5
View File
@@ -58,11 +58,7 @@ const StyledWrapper = styled.div`
}
}
`;
export default function ImagePreviewModal({
download = true,
data,
closeModal,
}) {
export default function ImagePreviewModal({ download = true, data, closeModal }) {
const [url, setUrl] = useState(data?.thumbnail);
const [loading, setLoading] = useState(true);
const wrapperRef = useRef();
+3 -16
View File
@@ -38,28 +38,15 @@ const StyledWrapper = styled.div`
}
`;
export default function InviteLink() {
const {
generating,
link,
linkCopied,
copyLink,
generateNewLink,
} = useInviteLink();
const { generating, link, linkCopied, copyLink, generateNewLink } = useInviteLink();
const handleNewLink = () => {
generateNewLink();
};
return (
<StyledWrapper>
<span className="tip">
Share this link to invite people to this server.
</span>
<span className="tip">Share this link to invite people to this server.</span>
<div className="link">
<Input
readOnly
className={"large"}
placeholder="Generating"
value={link}
/>
<Input readOnly className={"large"} placeholder="Generating" value={link} />
<Button onClick={copyLink} className="ghost small border_less">
{linkCopied ? "Copied" : `Copy`}
</Button>
+4 -15
View File
@@ -97,15 +97,12 @@ const Styled = styled.div`
}
`;
export default function AddMembers({ cid = null, closeModal }) {
const [
addMembers,
{ isLoading: isAdding, isSuccess },
] = useAddMembersMutation();
const [addMembers, { isLoading: isAdding, isSuccess }] = useAddMembersMutation();
const [selects, setSelects] = useState([]);
const { channel, contactData } = useSelector((store) => {
return {
channel: store.channels.byId[cid],
contactData: store.contacts.byId,
contactData: store.contacts.byId
};
});
useEffect(() => {
@@ -145,11 +142,7 @@ export default function AddMembers({ cid = null, closeModal }) {
return (
<li className="select" key={uid}>
{contactData[uid].name}
<CloseIcon
data-uid={uid}
onClick={toggleCheckMember}
className="close"
/>
<CloseIcon data-uid={uid} onClick={toggleCheckMember} className="close" />
</li>
);
})}
@@ -185,11 +178,7 @@ export default function AddMembers({ cid = null, closeModal }) {
);
})}
</ul>
<Button
disabled={selects.length == 0 || isAdding}
className="btn"
onClick={handleAddMembers}
>
<Button disabled={selects.length == 0 || isAdding} className="btn" onClick={handleAddMembers}>
{isAdding ? `Adding` : "Add"} to #{channel.name}
</Button>
</Styled>
@@ -68,14 +68,8 @@ import Button from "../styled/Button";
import Input from "../styled/Input";
export default function InviteByEmail({ cid = null }) {
const [email, setEmail] = useState("");
const {
enableSMTP,
linkCopied,
link,
copyLink,
generateNewLink,
generating,
} = useInviteLink(cid);
const { enableSMTP, linkCopied, link, copyLink, generateNewLink, generating } =
useInviteLink(cid);
useEffect(() => {
if (linkCopied) {
toast.success("Invite Link Copied!");
@@ -104,12 +98,7 @@ export default function InviteByEmail({ cid = null }) {
<div className="link">
<label htmlFor="">Or Send invite link to your friends</label>
<div className="input">
<Input
readOnly
className="invite"
placeholder="Generating"
value={link}
/>
<Input readOnly className="invite" placeholder="Generating" value={link} />
<button className="copy" onClick={copyLink}>
Copy
</button>
+4 -12
View File
@@ -28,20 +28,14 @@ const Styled = styled.div`
`;
import Modal from "../Modal";
// type: server,channel
export default function InviteModal({
type = "server",
cid = null,
title = "",
closeModal,
}) {
export default function InviteModal({ type = "server", cid = null, title = "", closeModal }) {
const { channel, server } = useSelector((store) => {
return {
channel: store.channels.byId[cid],
server: store.server,
server: store.server
};
});
const finalTitle =
type == "server" ? server.name : `#${title || channel?.name}`;
const finalTitle = type == "server" ? server.name : `#${title || channel?.name}`;
return (
<Modal>
<Styled>
@@ -49,9 +43,7 @@ export default function InviteModal({
Add friends to {finalTitle}
<CloseIcon className="close" onClick={closeModal} />
</h2>
{!channel?.is_public && (
<AddMembers cid={cid} closeModal={closeModal} />
)}
{!channel?.is_public && <AddMembers cid={cid} closeModal={closeModal} />}
<InviteByEmail cid={channel?.is_public ? null : cid} />
</Styled>
</Modal>
@@ -30,10 +30,7 @@ export default function LeaveConfirmModal({ id, closeModal, handleNextStep }) {
}
buttons={
<>
<Button
onClick={closeModal.bind(null, undefined)}
className="cancel"
>
<Button onClick={closeModal.bind(null, undefined)} className="cancel">
Cancel
</Button>
{isOwner ? (
@@ -28,11 +28,7 @@ const UserList = styled.ul`
}
}
`;
export default function TransferOwnerModal({
id,
closeModal,
withLeave = true,
}) {
export default function TransferOwnerModal({ id, closeModal, withLeave = true }) {
const {
transferOwner,
otherMembers,
@@ -40,7 +36,7 @@ export default function TransferOwnerModal({
leaveChannel,
leaveSuccess,
transferSuccess,
transfering,
transfering
} = useLeaveChannel(id);
const [uid, setUid] = useState(null);
@@ -75,17 +71,10 @@ export default function TransferOwnerModal({
description={"This cannot be undone."}
buttons={
<>
<Button
onClick={closeModal.bind(null, undefined)}
className="cancel"
>
<Button onClick={closeModal.bind(null, undefined)} className="cancel">
Cancel
</Button>
<Button
disabled={!uid}
onClick={handleTransferAndLeave}
className="danger"
>
<Button disabled={!uid} onClick={handleTransferAndLeave} className="danger">
{operating ? "Assigning" : `Assign and Leave`}
</Button>
</>
+3 -14
View File
@@ -2,22 +2,11 @@ import { useState } from "react";
// import styled from "styled-components";
import TransferOwnerModal from "./TransferOwnerModal";
import LeaveConfirmModal from "./LeaveConfirmModal";
export default function LeaveChannel({
id = null,
isOwner = false,
closeModal,
}) {
export default function LeaveChannel({ id = null, isOwner = false, closeModal }) {
const [transferOwner, setTransferOwner] = useState(isOwner);
const handleNextStep = () => {
setTransferOwner(true);
};
if (transferOwner)
return <TransferOwnerModal id={id} closeModal={closeModal} />;
return (
<LeaveConfirmModal
id={id}
closeModal={closeModal}
handleNextStep={handleNextStep}
/>
);
if (transferOwner) return <TransferOwnerModal id={id} closeModal={closeModal} />;
return <LeaveConfirmModal id={id} closeModal={closeModal} handleNextStep={handleNextStep} />;
}
+2 -11
View File
@@ -45,17 +45,8 @@ export default function Loading({ reload = false, fullscreen = false }) {
};
return (
<StyledWrapper className={fullscreen ? "fullscreen" : ""}>
<Ring
className="loading"
size={40}
lineWeight={5}
speed={2}
color="black"
/>
<Button
className={`reload danger ${reload ? "visible" : ""}`}
onClick={handleReload}
>
<Ring className="loading" size={40} lineWeight={5} speed={2} color="black" />
<Button className={`reload danger ${reload ? "visible" : ""}`} onClick={handleReload}>
Reload
</Button>
</StyledWrapper>
+11 -33
View File
@@ -119,20 +119,12 @@ export default function ManageMembers({ cid = null }) {
return {
contacts: store.contacts,
channels: store.channels,
loginUser: store.contacts.byId[store.authData.uid],
loginUser: store.contacts.byId[store.authData.uid]
};
});
const {
copyEmail,
removeFromChannel,
removeUser,
canRemove,
canRemoveFromChannel,
} = useContactOperation({ cid });
const [
updateContact,
{ isSuccess: updateSuccess },
] = useUpdateContactMutation();
const { copyEmail, removeFromChannel, removeUser, canRemove, canRemoveFromChannel } =
useContactOperation({ cid });
const [updateContact, { isSuccess: updateSuccess }] = useUpdateContactMutation();
useEffect(() => {
if (updateSuccess) {
@@ -146,11 +138,7 @@ export default function ManageMembers({ cid = null }) {
updateContact({ id: uid, is_admin: isAdmin });
};
const channel = channels.byId[cid] ?? null;
const uids = channel
? channel.is_public
? contacts.ids
: channel.members
: contacts.ids;
const uids = channel ? (channel.is_public ? contacts.ids : channel.members) : contacts.ids;
return (
<StyledWrapper>
@@ -158,8 +146,7 @@ export default function ManageMembers({ cid = null }) {
<div className="intro">
<h4 className="title">Manage Members</h4>
<p className="desc">
Disabling your account means you can recover it at any time after
taking this action.
Disabling your account means you can recover it at any time after taking this action.
</p>
</div>
<ul className="members">
@@ -194,7 +181,7 @@ export default function ManageMembers({ cid = null }) {
onClick={handleToggleRole.bind(null, {
ignore: is_admin,
uid,
isAdmin: true,
isAdmin: true
})}
>
Admin
@@ -205,7 +192,7 @@ export default function ManageMembers({ cid = null }) {
onClick={handleToggleRole.bind(null, {
ignore: !is_admin,
uid,
isAdmin: false,
isAdmin: false
})}
>
User
@@ -226,10 +213,7 @@ export default function ManageMembers({ cid = null }) {
content={
<StyledMenu className="menu">
{email && (
<li
className="item"
onClick={copyEmail.bind(null, email)}
>
<li className="item" onClick={copyEmail.bind(null, email)}>
Copy Email
</li>
)}
@@ -237,18 +221,12 @@ export default function ManageMembers({ cid = null }) {
{/* <li className="item underline">Change Nickname</li> */}
{/* <li className="item danger">Ban</li> */}
{canRemoveFromChannel && (
<li
className="item danger"
onClick={removeFromChannel.bind(null, uid)}
>
<li className="item danger" onClick={removeFromChannel.bind(null, uid)}>
Remove From Channel
</li>
)}
{canRemove && !cid && (
<li
className="item danger"
onClick={removeUser.bind(null, uid)}
>
<li className="item danger" onClick={removeUser.bind(null, uid)}>
Remove From Server
</li>
)}
+1 -1
View File
@@ -11,6 +11,6 @@ export default function usePrompt() {
return {
setCanneled: setPrompt,
prompted: !!localStorage.getItem(KEY_PWA_INSTALLED),
resetPrompt,
resetPrompt
};
}
+1 -1
View File
@@ -16,7 +16,7 @@ function MarkdownEditor({
height = "50vh",
placeholder,
sendMarkdown,
setEditorInstance,
setEditorInstance
}) {
const editorRef = useRef(undefined);
const { uploadFile } = useUploadFile();
+9 -17
View File
@@ -62,12 +62,7 @@ const StyledCmds = styled.ul`
transform: translateX(-100%);
}
`;
export default function Commands({
context = "user",
contextId = 0,
mid = 0,
toggleEditMessage,
}) {
export default function Commands({ context = "user", contextId = 0, mid = 0, toggleEditMessage }) {
const {
canDelete,
canReply,
@@ -80,11 +75,11 @@ export default function Commands({
togglePinModal,
PinModal,
DeleteModal,
ForwardModal,
ForwardModal
} = useMessageOperation({ mid, context, contextId });
const { setReplying } = useSendMessage({ context, to: contextId });
const { addFavorite, isFavorited } = useFavMessage({
cid: context == "channel" ? contextId : null,
cid: context == "channel" ? contextId : null
});
const dispatch = useDispatch();
const [tippyVisible, setTippyVisible] = useState(false);
@@ -126,10 +121,7 @@ export default function Commands({
return (
<>
<StyledCmds
ref={cmdsRef}
className={`cmds ${tippyVisible ? "visible" : ""}`}
>
<StyledCmds ref={cmdsRef} className={`cmds ${tippyVisible ? "visible" : ""}`}>
<Tippy
onShow={handleTippyVisible.bind(null, true)}
onHide={handleTippyVisible.bind(null, false)}
@@ -175,25 +167,25 @@ export default function Commands({
canPin && {
title: pinned ? `Unpin Message` : `Pin Message`,
icon: <IconPin className="icon" />,
handler: pinned ? handleUnpin : togglePinModal,
handler: pinned ? handleUnpin : togglePinModal
},
{
title: "Forward",
icon: <IconForward className="icon" />,
handler: toggleForwardModal,
handler: toggleForwardModal
},
{
title: "Select",
icon: <IconSelect className="icon" />,
handler: handleSelect.bind(null, mid),
handler: handleSelect.bind(null, mid)
},
canDelete && {
title: " Delete",
danger: true,
icon: <IconDelete className="icon" />,
handler: toggleDeleteModal,
},
handler: toggleDeleteModal
}
]}
/>
}
+10 -10
View File
@@ -20,7 +20,7 @@ export default function MessageContextMenu({
visible,
hide,
editMessage,
children,
children
}) {
const {
copyContent,
@@ -37,7 +37,7 @@ export default function MessageContextMenu({
togglePinModal,
PinModal,
ForwardModal,
DeleteModal,
DeleteModal
} = useMessageOperation({ mid, contextId, context });
const dispatch = useDispatch();
const { setReplying } = useSendMessage({ context, to: contextId });
@@ -71,39 +71,39 @@ export default function MessageContextMenu({
canEdit && {
title: "Edit Message",
icon: <IconEdit className="icon" />,
handler: editMessage,
handler: editMessage
},
canReply && {
title: "Reply",
icon: <IconReply className="icon" />,
handler: handleReply,
handler: handleReply
},
canCopy && {
title: "Copy",
icon: <IconCopy className="icon" />,
handler: copyContent,
handler: copyContent
},
canPin && {
title: pinned ? "Unpin" : "Pin",
icon: <IconPin className="icon" />,
handler: pinned ? unPin.bind(null, mid) : togglePinModal,
handler: pinned ? unPin.bind(null, mid) : togglePinModal
},
{
title: "Forward",
icon: <IconForward className="icon" />,
handler: toggleForwardModal,
handler: toggleForwardModal
},
{
title: "Select",
icon: <IconSelect className="icon" />,
handler: handleSelect,
handler: handleSelect
},
canDelete && {
title: "Delete",
danger: true,
icon: <IconDelete className="icon" />,
handler: toggleDeleteModal,
},
handler: toggleDeleteModal
}
]}
/>
}
+2 -3
View File
@@ -91,7 +91,7 @@ export default function EditMessage({ mid, cancelEdit }) {
edit({
mid,
content: currMsg,
type: msg.content_type == ContentTypes.markdown ? "markdown" : "text",
type: msg.content_type == ContentTypes.markdown ? "markdown" : "text"
});
};
if (!msg) return null;
@@ -121,8 +121,7 @@ export default function EditMessage({ mid, cancelEdit }) {
esc to <button onClick={cancelEdit}>cancel</button>
</span>
<span className="opt">
enter to{" "}
<button onClick={handleSave}>{isEditing ? "saving" : `save`}</button>
enter to <button onClick={handleSave}>{isEditing ? "saving" : `save`}</button>
</span>
</div>
</StyledWrapper>
@@ -23,20 +23,10 @@ const FavoritedMessage = ({ id }) => {
const favorite_mids = messages.map(({ from_mid }) => from_mid) || [];
setMsgs(
<StyledFav
data-favorite-mids={favorite_mids.join(",")}
className="favorite"
>
<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;
const { user = {}, download, content, content_type, properties, thumbnail } = msg;
return (
<StyledMsg className="archive" key={idx}>
{user && (
@@ -54,7 +44,7 @@ const FavoritedMessage = ({ id }) => {
content,
content_type,
properties,
thumbnail,
thumbnail
})}
</div>
</div>
@@ -56,7 +56,7 @@ const ForwardedMessage = ({ context, to, from_uid, id }) => {
content,
content_type,
properties,
thumbnail,
thumbnail
} = msg;
return (
<StyledMsg className="archive" key={idx}>
@@ -78,7 +78,7 @@ const ForwardedMessage = ({ context, to, from_uid, id }) => {
content,
content_type,
properties,
thumbnail,
thumbnail
})}
</div>
</div>
+3 -12
View File
@@ -9,14 +9,7 @@ export default function PreviewMessage({ mid = 0 }) {
return { msg: store.message[mid], contactsData: store.contacts.byId };
});
if (!msg) return null;
const {
from_uid,
created_at,
content_type,
content,
thumbnail,
properties,
} = msg;
const { from_uid, created_at, content_type, content, thumbnail, properties } = msg;
const { name, avatar } = contactsData[from_uid];
return (
<StyledWrapper className={`preview`}>
@@ -26,9 +19,7 @@ export default function PreviewMessage({ mid = 0 }) {
<div className="details">
<div className="up">
<span className="name">{name}</span>
<i className="time">
{dayjs(created_at).format("YYYY-MM-DD h:mm:ss A")}
</i>
<i className="time">{dayjs(created_at).format("YYYY-MM-DD h:mm:ss A")}</i>
</div>
<div className={`down`}>
{renderContent({
@@ -36,7 +27,7 @@ export default function PreviewMessage({ mid = 0 }) {
content,
thumbnail,
from_uid,
properties,
properties
})}
</div>
</div>
+4 -9
View File
@@ -70,8 +70,7 @@ const StyledDetails = styled.div`
position: relative;
background: #ffffff;
border-radius: var(--br);
box-shadow: 0px 12px 16px -4px rgba(16, 24, 40, 0.08),
0px 4px 6px -2px rgba(16, 24, 40, 0.03);
box-shadow: 0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03);
display: flex;
align-items: flex-start;
gap: 8px;
@@ -132,7 +131,7 @@ export default function Reaction({ mid, reactions = null }) {
const [reactWithEmoji] = useReactMessageMutation();
const { currUid } = useSelector((store) => {
return {
currUid: store.authData.uid,
currUid: store.authData.uid
};
});
const handleReact = (emoji) => {
@@ -156,18 +155,14 @@ export default function Reaction({ mid, reactions = null }) {
// visible={true}
interactive
placement="top"
content={
<ReactionDetails uids={uids} emoji={reaction} index={idx} />
}
content={<ReactionDetails uids={uids} emoji={reaction} index={idx} />}
>
<i className="emoji">
<ReactionItem native={reaction} />
</i>
</Tippy>
{uids.length > 1 ? (
<em className="count">{`${uids.length}`} </em>
) : null}
{uids.length > 1 ? <em className="count">{`${uids.length}`} </em> : null}
</span>
) : null;
})}
@@ -41,7 +41,7 @@ export default function ReactionPicker({ mid, hidePicker }) {
const { reactionData, currUid } = useSelector((store) => {
return {
reactionData: store.reactionMessage[mid] || {},
currUid: store.authData.uid,
currUid: store.authData.uid
};
});
// useOutsideClick(wrapperRef, hidePicker);
@@ -55,8 +55,7 @@ export default function ReactionPicker({ mid, hidePicker }) {
<ul className={`emojis ${isLoading ? "reacting" : ""}`}>
{Emojis.map((emoji) => {
let reacted =
reactionData[emoji] &&
reactionData[emoji].findIndex((id) => id == currUid) > -1;
reactionData[emoji] && reactionData[emoji].findIndex((id) => id == currUid) > -1;
return (
<li
+1 -5
View File
@@ -66,11 +66,7 @@ const Styled = styled.div`
width: 100%;
height: 100%;
content: "";
background: linear-gradient(
180deg,
rgba(255, 255, 255, 0) 63.54%,
#e5e7eb 93.09%
);
background: linear-gradient(180deg, rgba(255, 255, 255, 0) 63.54%, #e5e7eb 93.09%);
}
}
.pic {
+19 -25
View File
@@ -26,26 +26,24 @@ function Message({
mid = "",
context = "user",
updateReadIndex,
read = true,
read = true
}) {
const {
visible: contextMenuVisible,
handleContextMenuEvent,
hideContextMenu,
} = useContextMenu();
const { visible: contextMenuVisible, handleContextMenuEvent, hideContextMenu } = useContextMenu();
const inviewRef = useInView();
const [edit, setEdit] = useState(false);
const avatarRef = useRef(null);
const { getPinInfo } = usePinMessage(context == "channel" ? contextId : null);
const { message = {}, reactionMessageData, contactsData } = useSelector(
(store) => {
return {
reactionMessageData: store.reactionMessage,
message: store.message[mid] || {},
contactsData: store.contacts.byId,
};
}
);
const {
message = {},
reactionMessageData,
contactsData
} = useSelector((store) => {
return {
reactionMessageData: store.reactionMessage,
message: store.message[mid] || {},
contactsData: store.contacts.byId
};
});
const toggleEditMessage = () => {
setEdit((prev) => !prev);
@@ -61,7 +59,7 @@ function Message({
download,
content_type = "text/plain",
edited,
properties,
properties
} = message;
useEffect(() => {
@@ -79,11 +77,7 @@ function Message({
// if (!message) return null;
let timePrefix = null;
const dayjsTime = dayjs(time);
timePrefix = dayjsTime.isToday()
? "Today"
: dayjsTime.isYesterday()
? "Yesterday"
: null;
timePrefix = dayjsTime.isToday() ? "Today" : dayjsTime.isYesterday() ? "Yesterday" : null;
const pinInfo = getPinInfo(mid);
// return null;
@@ -94,9 +88,9 @@ function Message({
onContextMenu={handleContextMenuEvent}
data-msg-mid={mid}
ref={inviewRef}
className={`message ${readOnly ? "readonly" : ""} ${
pinInfo ? "pinned" : ""
} ${contextMenuVisible ? "contextVisible" : ""} `}
className={`message ${readOnly ? "readonly" : ""} ${pinInfo ? "pinned" : ""} ${
contextMenuVisible ? "contextVisible" : ""
} `}
>
<Tippy
key={_key}
@@ -153,7 +147,7 @@ function Message({
content,
thumbnail,
download,
edited,
edited
})
)}
{reactions && <Reaction mid={mid} reactions={reactions} />}
+9 -24
View File
@@ -20,7 +20,7 @@ const renderContent = ({
content,
download,
thumbnail,
edited = false,
edited = false
}) => {
let ctn = null;
switch (content_type) {
@@ -30,38 +30,23 @@ const renderContent = ({
<Linkit
componentDecorator={(decoratedHref, decoratedText, key) => (
<React.Fragment key={key}>
<a
className="link"
target="_blank"
href={decoratedHref}
key={key}
rel="noreferrer"
>
<a className="link" target="_blank" href={decoratedHref} key={key} rel="noreferrer">
{decoratedText}
</a>
{!decoratedHref.startsWith("mailto") && (
<URLPreview url={decoratedHref} />
)}
{!decoratedHref.startsWith("mailto") && <URLPreview url={decoratedHref} />}
</React.Fragment>
)}
>
{reactStringReplace(
content,
/(\s{1}@[0-9]+\s{1})/g,
(match, idx) => {
console.log("match", match);
const uid = match.trim().slice(1);
return <Mention key={idx} uid={uid} cid={to} />;
}
)}
{reactStringReplace(content, /(\s{1}@[0-9]+\s{1})/g, (match, idx) => {
console.log("match", match);
const uid = match.trim().slice(1);
return <Mention key={idx} uid={uid} cid={to} />;
})}
{/* {content.replace(/\s{1}\@[1-9]+\s{1}/g,)} */}
{/* {new RegExp(/\s{1}\@[1-9]+\s{1}/g).exec(content)} */}
</Linkit>
{edited && (
<span
className="edited"
title={dayjs(edited).format("YYYY-MM-DD h:mm:ss A")}
>
<span className="edited" title={dayjs(edited).format("YYYY-MM-DD h:mm:ss A")}>
(edited)
</span>
)}
@@ -10,17 +10,15 @@ import usePinMessage from "../../hook/usePinMessage";
export default function useMessageOperation({ mid, context, contextId }) {
const { copy } = useCopy();
const { content_type, properties, currUid, from_uid, content } = useSelector(
(store) => {
return {
content: store.message[mid]?.content,
from_uid: store.message[mid]?.from_uid,
content_type: store.message[mid]?.content_type,
properties: store.message[mid]?.properties,
currUid: store.authData.uid,
};
}
);
const { content_type, properties, currUid, from_uid, content } = useSelector((store) => {
return {
content: store.message[mid]?.content,
from_uid: store.message[mid]?.from_uid,
content_type: store.message[mid]?.content_type,
properties: store.message[mid]?.properties,
currUid: store.authData.uid
};
});
const { canPin, pins, unpinMessage, isUnpinSuccess } = usePinMessage(
context == "channel" ? contextId : undefined
);
@@ -71,16 +69,11 @@ export default function useMessageOperation({ mid, context, contextId }) {
properties?.content_type &&
properties?.content_type.startsWith("image");
const enableEdit =
currUid == from_uid &&
[ContentTypes.text, ContentTypes.markdown].includes(content_type);
currUid == from_uid && [ContentTypes.text, ContentTypes.markdown].includes(content_type);
const canDelete = currUid == from_uid;
const canCopy =
[ContentTypes.text, ContentTypes.markdown].includes(content_type) ||
isImage;
const canCopy = [ContentTypes.text, ContentTypes.markdown].includes(content_type) || isImage;
return {
copyContent: isImage
? copyContent.bind(null, true)
: copyContent.bind(null, false),
copyContent: isImage ? copyContent.bind(null, true) : copyContent.bind(null, false),
canCopy,
isImage,
isMarkdown: content_type == ContentTypes.markdown,
@@ -101,6 +94,6 @@ export default function useMessageOperation({ mid, context, contextId }) {
) : null,
PinModal: pinModalVisible ? (
<PinMessageModal mid={mid} gid={contextId} closeModal={togglePinModal} />
) : null,
) : null
};
}
+15 -15
View File
@@ -4,7 +4,7 @@ export const CONFIG = {
editableProps: {
spellCheck: false,
autoFocus: true,
placeholder: "Type…",
placeholder: "Type…"
},
trailingBlock: { type: ELEMENT_PARAGRAPH },
softBreak: {
@@ -13,11 +13,11 @@ export const CONFIG = {
{
hotkey: "shift+enter",
query: {
allow: [ELEMENT_IMAGE, ELEMENT_PARAGRAPH],
},
},
],
},
allow: [ELEMENT_IMAGE, ELEMENT_PARAGRAPH]
}
}
]
}
},
exitBreak: {
options: {
@@ -25,9 +25,9 @@ export const CONFIG = {
{
hotkey: "mod+enter",
query: {
allow: [ELEMENT_IMAGE, ELEMENT_PARAGRAPH],
},
},
allow: [ELEMENT_IMAGE, ELEMENT_PARAGRAPH]
}
}
// {
// hotkey: "mod+shift+enter",
// before: true,
@@ -40,14 +40,14 @@ export const CONFIG = {
// allow: KEYS_HEADING,
// },
// },
],
},
]
}
},
selectOnBackspace: {
options: {
query: {
allow: [ELEMENT_IMAGE],
},
},
},
allow: [ELEMENT_IMAGE]
}
}
}
};
+23 -39
View File
@@ -23,7 +23,7 @@ import {
// usePlateEditorRef,
// ELEMENT_IMAGE,
// useComboboxControls,
MentionCombobox,
MentionCombobox
} from "@udecode/plate";
import { createComboboxPlugin } from "@udecode/plate-combobox";
import { ReactEditor } from "slate-react";
@@ -44,7 +44,7 @@ const Plugins = ({
id = "",
placeholder = "Write some markdown...",
sendMessages,
members = [],
members = []
}) => {
// const { getMenuProps, getItemProps } = useComboboxControls();
const [context, to] = id.split("_");
@@ -57,7 +57,7 @@ const Plugins = ({
const initialProps = {
...CONFIG.editableProps,
className: "box",
placeholder,
placeholder
};
const plateEditor = getPlateEditorRef(`${TEXT_EDITOR_PREFIX}_${id}`);
useEffect(() => {
@@ -91,13 +91,7 @@ const Plugins = ({
(evt) => {
// 是否在at操作
const mentionInputs = findMentionInput(plateEditor);
if (
mentionInputs ||
evt.shiftKey ||
evt.ctrlKey ||
evt.altKey ||
evt.isComposing
) {
if (mentionInputs || evt.shiftKey || evt.ctrlKey || evt.altKey || evt.isComposing) {
return true;
}
evt.preventDefault();
@@ -107,14 +101,14 @@ const Plugins = ({
Transforms.delete(plateEditor, {
at: {
anchor: Editor.start(plateEditor, []),
focus: Editor.end(plateEditor, []),
},
focus: Editor.end(plateEditor, [])
}
});
},
{
// eventTypes: ["keydown"],
target: editableRef,
when: !cmdKey,
when: !cmdKey
}
);
useKey(
@@ -125,7 +119,7 @@ const Plugins = ({
},
{
eventTypes: ["keydown", "keyup"],
target: editableRef,
target: editableRef
}
);
const pluginArr = [
@@ -133,7 +127,7 @@ const Plugins = ({
createNodeIdPlugin(),
createSoftBreakPlugin(CONFIG.softBreak),
createTrailingBlockPlugin(CONFIG.trailingBlock),
createExitBreakPlugin(CONFIG.exitBreak),
createExitBreakPlugin(CONFIG.exitBreak)
];
const plugins = createPlugins(
enableMentions
@@ -145,17 +139,17 @@ const Plugins = ({
console.log("mention", item);
const {
text,
data: { uid },
data: { uid }
} = item;
return { value: `@${text}`, uid };
},
insertSpaceAfterMention: true,
},
}),
insertSpaceAfterMention: true
}
})
])
: pluginArr,
{
components,
components
}
);
@@ -179,20 +173,16 @@ const Plugins = ({
const { value, mentions } = getMixedText(v.children);
const prev = tmps[tmps.length - 1];
if (!prev) {
tmps.push([
{ type: "text", content: value, properties: { mentions } },
]);
tmps.push([{ type: "text", content: value, properties: { mentions } }]);
} else {
if (Array.isArray(prev)) {
tmps[tmps.length - 1].push({
type: "text",
content: value,
properties: { mentions },
properties: { mentions }
});
} else {
tmps.push([
{ type: "text", content: value, properties: { mentions } },
]);
tmps.push([{ type: "text", content: value, properties: { mentions } }]);
}
}
}
@@ -202,8 +192,8 @@ const Plugins = ({
type: "text",
content: tmp.map((t) => t.content).join("\n"),
properties: {
mentions: tmp.map((t) => t.properties?.mentions || []).flat(),
},
mentions: tmp.map((t) => t.properties?.mentions || []).flat()
}
}
: tmp;
});
@@ -229,13 +219,7 @@ const Plugins = ({
// component={StyledCombobox}
onRenderItem={({ item }) => {
console.log("wtf", item);
return (
<Contact
key={item.data.uid}
uid={item.data.uid}
interactive={false}
/>
);
return <Contact key={item.data.uid} uid={item.data.uid} interactive={false} />;
}}
items={members.map((id) => {
const data = contactData[id];
@@ -246,8 +230,8 @@ const Plugins = ({
text: name,
data: {
uid,
...rest,
},
...rest
}
};
})}
/>
@@ -273,7 +257,7 @@ export const useMixedEditor = (key) => {
};
return {
focus,
insertText,
insertText
};
};
export default Plugins;
+4 -4
View File
@@ -2,12 +2,12 @@ import {
createBasicElementsPlugin,
createImagePlugin,
createParagraphPlugin,
createSelectOnBackspacePlugin,
createSelectOnBackspacePlugin
} from "@udecode/plate";
import { CONFIG } from "./config";
const basicElements = [
createParagraphPlugin(), // paragraph element
createParagraphPlugin() // paragraph element
];
export const PLUGINS = {
@@ -16,6 +16,6 @@ export const PLUGINS = {
image: [
createBasicElementsPlugin(),
createImagePlugin(),
createSelectOnBackspacePlugin(CONFIG.selectOnBackspace),
],
createSelectOnBackspacePlugin(CONFIG.selectOnBackspace)
]
};
@@ -1,6 +1,6 @@
const nodeTypes = {
paragraph: "p",
image: "img",
image: "img"
};
export default nodeTypes;
@@ -1,13 +1,10 @@
import {
ELEMENT_PARAGRAPH,
ELEMENT_PARAGRAPH
// TElement,
} from "@udecode/plate";
// import { Text } from 'slate'
export const createElement = (
text = "",
{ type = ELEMENT_PARAGRAPH, mark } = {}
) => {
export const createElement = (text = "", { type = ELEMENT_PARAGRAPH, mark } = {}) => {
const leaf = { text };
if (mark) {
leaf[mark] = true;
@@ -15,7 +12,7 @@ export const createElement = (
return {
type,
children: [leaf],
children: [leaf]
};
};
+4 -8
View File
@@ -24,11 +24,11 @@ const Styled = styled.div`
}
`;
export default function MrakdownRender({ content }) {
const mdContainer = useRef(null);
const mdContainer = useRef(undefined);
const [previewImage, setPreviewImage] = useState(null);
useEffect(() => {
if (mdContainer) {
const container = mdContainer.current;
const container = mdContainer?.current;
if (container) {
// 点击查看大图
container.addEventListener(
"click",
@@ -56,11 +56,7 @@ export default function MrakdownRender({ content }) {
return (
<>
{previewImage && (
<ImagePreviewModal
download={false}
data={previewImage}
closeModal={closePreviewModal}
/>
<ImagePreviewModal download={false} data={previewImage} closeModal={closePreviewModal} />
)}
<Styled ref={mdContainer}>
<Viewer
+2 -8
View File
@@ -21,15 +21,9 @@ const Notification = () => {
navigateTo(newPath);
};
// https only
navigator.serviceWorker?.addEventListener(
"message",
handleServiceworkerMessage
);
navigator.serviceWorker?.addEventListener("message", handleServiceworkerMessage);
return () => {
navigator.serviceWorker?.removeEventListener(
"message",
handleServiceworkerMessage
);
navigator.serviceWorker?.removeEventListener("message", handleServiceworkerMessage);
};
}, []);
@@ -9,7 +9,7 @@ const useDeviceToken = (vapidKey) => {
const messaging = getMessaging(initializeApp(firebaseConfig));
getToken(messaging, {
vapidKey,
vapidKey
})
.then((currentToken) => {
if (currentToken) {
@@ -19,9 +19,7 @@ const useDeviceToken = (vapidKey) => {
// Perform any other neccessary action with the token
} else {
// 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.");
}
})
.catch((err) => {
+4 -5
View File
@@ -21,12 +21,12 @@ export default function Profile({ uid = null, type = "embed", cid = null }) {
removeFromChannel,
canRemoveFromChannel,
canRemove,
removeUser,
removeUser
} = useContactOperation({ uid, cid });
const { data } = useSelector((store) => {
return {
data: store.contacts.byId[uid],
data: store.contacts.byId[uid]
};
});
@@ -35,13 +35,12 @@ export default function Profile({ uid = null, type = "embed", cid = null }) {
const {
name,
email,
avatar,
avatar
// introduction = "This guy has nothing to introduce",
} = data;
const enableCall = type == "card" && canCall;
const canRemoveFromServer = type == "embed" && canRemove;
const hasMore =
enableCall || email || canRemoveFromChannel || canRemoveFromServer;
const hasMore = enableCall || email || canRemoveFromChannel || canRemoveFromServer;
return (
<StyledWrapper className={type}>
<Avatar className="avatar" url={avatar} name={name} />
+1 -2
View File
@@ -86,8 +86,7 @@ const StyledWrapper = styled.div`
padding: 16px;
width: 280px;
background: #ffffff;
box-shadow: 0px 20px 25px 20px rgba(31, 41, 55, 0.1),
0px 10px 10px rgba(31, 41, 55, 0.04);
box-shadow: 0px 20px 25px 20px rgba(31, 41, 55, 0.1), 0px 10px 10px rgba(31, 41, 55, 0.04);
border-radius: 6px;
.icons {
padding-bottom: 2px;
+1 -1
View File
@@ -15,7 +15,7 @@ const Emojis = {
"🚀": <EmojiRocket className="emoji" />,
"❤️": <EmojiHeart className="emoji" />,
"🙁": <EmojiUnhappy className="emoji" />,
"🎉": <EmojiCelebrate className="emoji" />,
"🎉": <EmojiCelebrate className="emoji" />
};
export default function ReactionItem({ native = "" }) {
+1 -2
View File
@@ -13,8 +13,7 @@ const StyledWrapper = styled.div`
background: #fff;
/* gap: 20px; */
border: 1px solid #e5e7eb;
box-shadow: 0px 4px 8px -2px rgba(16, 24, 40, 0.1),
0px 2px 4px -2px rgba(16, 24, 40, 0.06);
box-shadow: 0px 4px 8px -2px rgba(16, 24, 40, 0.1), 0px 2px 4px -2px rgba(16, 24, 40, 0.06);
border-radius: 25px;
.txt {
padding: 8px;
+1 -6
View File
@@ -44,12 +44,7 @@ export default function Search() {
<input placeholder="Search..." className="input" />
</div>
<Tooltip tip="More" placement="bottom">
<Tippy
interactive
placement="bottom-end"
trigger="click"
content={<AddEntriesMenu />}
>
<Tippy interactive placement="bottom-end" trigger="click" content={<AddEntriesMenu />}>
<img src={addIcon} alt="add icon" className="add" />
</Tippy>
</Tooltip>
+2 -7
View File
@@ -45,8 +45,7 @@ export default function EmojiPicker({ selectEmoji }) {
const clickEle = evt.target;
const ignore =
(clickEle.nodeName == "svg" && clickEle.dataset.emoji == "toggler") ||
(clickEle.nodeName == "path" &&
clickEle.parentElement.dataset.emoji == "toggler");
(clickEle.nodeName == "path" && clickEle.parentElement.dataset.emoji == "toggler");
// console.log("outside click", clickEle, clickEle.parentElement, ignore);
if (ignore) return;
// if(clickEle)
@@ -60,11 +59,7 @@ export default function EmojiPicker({ selectEmoji }) {
<div ref={ref} className={`picker ${visible ? "visible" : ""}`}>
<Picker onSelect={handleSelect} />
</div>
<SmileIcon
data-emoji="toggler"
className="emoji"
onClick={togglePickerVisible}
/>
<SmileIcon data-emoji="toggler" className="emoji" onClick={togglePickerVisible} />
</Styled>
</Tooltip>
);
+6 -14
View File
@@ -57,11 +57,7 @@ const Styled = styled.div`
width: 100%;
height: 100%;
content: "";
background: linear-gradient(
180deg,
rgba(255, 255, 255, 0) 63.54%,
#f3f4f6 93.09%
);
background: linear-gradient(180deg, rgba(255, 255, 255, 0) 63.54%, #f3f4f6 93.09%);
}
}
.icon {
@@ -87,15 +83,11 @@ const renderContent = (data) => {
let res = null;
switch (content_type) {
case ContentTypes.text:
res = reactStringReplace(
content,
/(\s{1}@[0-9]+\s{1})/g,
(match, idx) => {
console.log("match", match);
const uid = match.trim().slice(1);
return <Mention popover={false} key={idx} uid={uid} />;
}
);
res = reactStringReplace(content, /(\s{1}@[0-9]+\s{1})/g, (match, idx) => {
console.log("match", match);
const uid = match.trim().slice(1);
return <Mention popover={false} key={idx} uid={uid} />;
});
break;
case ContentTypes.markdown:
res = (
+1 -1
View File
@@ -47,7 +47,7 @@ export default function Toolbar({
toggleMode,
mode,
to,
context,
context
}) {
const { addStageFile } = useUploadFile({ context, id: to });
const fileInputRef = useRef(null);
@@ -13,7 +13,7 @@ export default function UploadFileList({ context = "", id = null }) {
const [editInfo, setEditInfo] = useState(null);
const { stageFiles, updateStageFile, removeStageFile } = useUploadFile({
context,
id,
id
});
const toggleModalVisible = (info) => {
setEditInfo((prev) => (prev ? null : info));
@@ -50,19 +50,12 @@ export default function UploadFileList({ context = "", id = null }) {
return (
<li className="file" key={url}>
<div className="preview">
{type.startsWith("image") ? (
<img src={url} alt="image" />
) : (
getFileIcon(type, name)
)}
{type.startsWith("image") ? <img src={url} alt="image" /> : getFileIcon(type, name)}
</div>
<h4 className="name">{name}</h4>
<span className="size">{formatBytes(size)}</span>
<ul className="opts">
<li
className="opt edit"
onClick={handleOpenEditModal.bind(null, idx)}
>
<li className="opt edit" onClick={handleOpenEditModal.bind(null, idx)}>
<EditIcon />
</li>
<li
+11 -18
View File
@@ -18,12 +18,12 @@ import useUploadFile from "../../hook/useUploadFile";
const Modes = {
text: "text",
markdown: "markdown",
markdown: "markdown"
};
function Send({
// 发给谁,或者是channel,或者是user
context = "channel",
id = "",
id = ""
}) {
const { resetStageFiles } = useUploadFile({ context, id });
const { getDraft, getUpdateDraft } = useDraft({ context, id });
@@ -40,7 +40,7 @@ function Send({
uploadFiles,
channelsData,
contactsData,
uids,
uids
} = useSelector((store) => {
return {
channelsData: store.channels.byId,
@@ -49,7 +49,7 @@ function Send({
mode: store.ui.inputMode,
from_uid: store.authData.uid,
replying_mid: store.message.replying[`${context}_${id}`],
uploadFiles: store.ui.uploadFiles[`${context}_${id}`],
uploadFiles: store.ui.uploadFiles[`${context}_${id}`]
};
});
const { sendMessage } = useSendMessage({ context, from: from_uid, to: id });
@@ -83,7 +83,7 @@ function Send({
type: content_type,
content,
from_uid,
properties,
properties
});
}
}
@@ -101,10 +101,10 @@ function Send({
content_type: type,
name,
size,
local_id: ts,
local_id: ts
},
from_uid,
sending: true,
sending: true
};
addLocalFileMesage(tmpMsg);
});
@@ -118,7 +118,7 @@ function Send({
type: "markdown",
content,
from_uid,
properties: { local_id: +new Date() },
properties: { local_id: +new Date() }
});
};
const toggleMode = () => {
@@ -127,24 +127,17 @@ function Send({
const toggleMarkdownFullscreen = () => {
setMarkdownFullscreen((prev) => !prev);
};
const name =
context == "channel" ? channelsData[id]?.name : contactsData[id]?.name;
const name = context == "channel" ? channelsData[id]?.name : contactsData[id]?.name;
const placeholder = `Send to ${ChatPrefixs[context]}${name} `;
const members =
context == "channel"
? channelsData[id]?.is_public
? uids
: channelsData[id]?.members
: [];
context == "channel" ? (channelsData[id]?.is_public ? uids : channelsData[id]?.members) : [];
return (
<StyledSend
className={`send ${mode} ${markdownFullscreen ? "fullscreen" : ""} ${
replying_mid ? "reply" : ""
} ${context}`}
>
{replying_mid && (
<Replying context={context} mid={replying_mid} id={id} />
)}
{replying_mid && <Replying context={context} mid={replying_mid} id={id} />}
{mode == Modes.text && <UploadFileList context={context} id={id} />}
<div className={`send_box ${mode}`}>
+1 -1
View File
@@ -8,6 +8,6 @@ export default function useFiles(initialFiles = []) {
return {
files,
setFiles,
resetFiles,
resetFiles
};
}
+2 -7
View File
@@ -57,7 +57,7 @@ export default function Server() {
const { server, userCount } = useSelector((store) => {
return {
userCount: store.contacts.ids.length,
server: store.server,
server: store.server
};
});
// console.log("server info", server);
@@ -78,12 +78,7 @@ export default function Server() {
</div>
</NavLink>
<Tooltip tip="More" placement="bottom">
<Tippy
interactive
placement="bottom-end"
trigger="click"
content={<AddEntriesMenu />}
>
<Tippy interactive placement="bottom-end" trigger="click" content={<AddEntriesMenu />}>
<img src={addIcon} alt="add icon" className="add" />
</Tippy>
</Tooltip>
@@ -87,7 +87,7 @@ export default function StyledSettingContainer({
navs = [],
dangers = [],
nav,
children,
children
}) {
const { pathname } = useLocation();
return (
@@ -101,10 +101,7 @@ export default function StyledSettingContainer({
<ul key={title} data-title={title} className="items">
{items.map(({ name, title }) => {
return (
<li
key={name}
className={`item ${name == nav?.name ? "curr" : ""}`}
>
<li key={name} className={`item ${name == nav?.name ? "curr" : ""}`}>
<NavLink to={`${pathname}?nav=${name}`}>{title}</NavLink>
</li>
);
+1 -1
View File
@@ -4,7 +4,7 @@ export default function TippyDefault() {
tippy.setDefaultProps({
duration: 0,
delay: [0, 0],
plugins: [followCursor],
plugins: [followCursor]
});
return null;
}
+1 -2
View File
@@ -10,8 +10,7 @@ const StyledTip = styled.div`
line-height: 18px;
color: #1d2939;
border-radius: var(--br);
box-shadow: 0px 12px 16px -4px rgba(16, 24, 40, 0.08),
0px 4px 6px -2px rgba(16, 24, 40, 0.03);
box-shadow: 0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03);
&::after {
background-color: inherit;
position: absolute;
+1 -3
View File
@@ -7,9 +7,7 @@ export default function FileItem({ file = null }) {
if (file) {
const { type, name } = file;
if (isTreatAsImage(file)) {
setIcon(
<img src={URL.createObjectURL(file)} alt="thumb" className="thumb" />
);
setIcon(<img src={URL.createObjectURL(file)} alt="thumb" className="thumb" />);
return;
}
console.log("file type", type, name);
+6 -21
View File
@@ -8,29 +8,18 @@ import Button from "../styled/Button";
import StyledWrapper from "./styled";
import useSendMessage from "../../hook/useSendMessage";
export default function UploadModal({
context = "user",
sendTo = 0,
files = [],
closeModal,
}) {
export default function UploadModal({ context = "user", sendTo = 0, files = [], closeModal }) {
const from_uid = useSelector((store) => store.authData.uid);
const {
sendMessage,
isSuccess: sendMessageSuccess,
isSending,
isSending
} = useSendMessage({
context,
from: from_uid,
to: sendTo,
to: sendTo
});
const {
data,
uploadFile,
progress,
isUploading,
isSuccess: uploadSuccess,
} = useUploadFile();
const { data, uploadFile, progress, isUploading, isSuccess: uploadSuccess } = useUploadFile();
const handleUpload = () => {
const file = files[0];
uploadFile(file);
@@ -42,7 +31,7 @@ export default function UploadModal({
sendMessage({
type: "file",
content: { path },
properties: rest,
properties: rest
});
}
}, [uploadSuccess, data]);
@@ -65,11 +54,7 @@ export default function UploadModal({
<Button className="cancel" onClick={closeModal}>
Cancel
</Button>
<Button
className="upload"
disabled={sending}
onClick={handleUpload}
>
<Button className="upload" disabled={sending} onClick={handleUpload}>
{sending ? `Uploading (${progress}%)` : `Upload`}
</Button>
</>
+3 -11
View File
@@ -11,8 +11,7 @@ const Styled = styled.div`
padding: 24px;
background: #06aed4;
border-radius: var(--br);
box-shadow: 0px 12px 16px -4px rgba(16, 24, 40, 0.08),
0px 4px 6px -2px rgba(16, 24, 40, 0.03);
box-shadow: 0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03);
color: #ffffff;
min-width: 362px;
.title {
@@ -57,16 +56,9 @@ const OverrideTippyArrowColor = createGlobalStyle`
}
`;
import steps from "./steps";
export default function UserGuide({
step = 1,
placement = "right-start",
delay = null,
children,
}) {
export default function UserGuide({ step = 1, placement = "right-start", delay = null, children }) {
const dispatch = useDispatch();
const { visible, step: reduxStep } = useSelector(
(store) => store.ui.userGuide
);
const { visible, step: reduxStep } = useSelector((store) => store.ui.userGuide);
const currStep = steps[step - 1];
const isLastStep = steps.length == step;
// if (!visible) return children;
+6 -6
View File
@@ -1,23 +1,23 @@
const steps = [
{
title: "Step 1",
description: "hello world 1",
description: "hello world 1"
},
{
title: "Step 2",
description: "hello world 1",
description: "hello world 1"
},
{
title: "Step 3",
description: "hello world 1",
description: "hello world 1"
},
{
title: "Step 4",
description: "hello world 1",
description: "hello world 1"
},
{
title: "Step 5",
description: "hello world 1",
},
description: "hello world 1"
}
];
export default steps;
+4 -21
View File
@@ -65,39 +65,22 @@ const StyledInput = styled.input`
}
`;
export default function Input({
type = "text",
prefix = "",
className,
...rest
}) {
export default function Input({ type = "text", prefix = "", className, ...rest }) {
const [inputType, setInputType] = useState(type);
const togglePasswordVisible = () => {
setInputType((prev) => (prev == "password" ? "text" : "password"));
};
return type == "password" ? (
<StyledWrapper className={className}>
<StyledInput
type={inputType}
className={`inner ${className}`}
{...rest}
/>
<StyledInput type={inputType} className={`inner ${className}`} {...rest} />
<div className="view" onClick={togglePasswordVisible}>
{inputType == "password" ? (
<HiEyeOff color="#78787c" />
) : (
<HiEye color="#78787c" />
)}
{inputType == "password" ? <HiEyeOff color="#78787c" /> : <HiEye color="#78787c" />}
</div>
</StyledWrapper>
) : prefix ? (
<StyledWrapper className={className}>
<span className="prefix">{prefix}</span>
<StyledInput
className={`inner ${className}`}
type={inputType}
{...rest}
/>
<StyledInput className={`inner ${className}`} type={inputType} {...rest} />
</StyledWrapper>
) : (
<StyledInput type={inputType} className={className} {...rest} />
+1 -2
View File
@@ -5,8 +5,7 @@ const StyledMenu = styled.ul`
gap: 2px;
padding: 4px;
background-color: #fff;
box-shadow: 0px 20px 25px 20px rgba(31, 41, 55, 0.1),
0px 10px 10px rgba(31, 41, 55, 0.04);
box-shadow: 0px 20px 25px 20px rgba(31, 41, 55, 0.1), 0px 10px 10px rgba(31, 41, 55, 0.04);
border-radius: 12px;
min-width: 200px;
.item {
+75 -75
View File
@@ -3,89 +3,89 @@ import styled from "styled-components";
import { nanoid } from "@reduxjs/toolkit";
const StyledForm = styled.form`
> .option {
&:not(:last-child) {
margin-bottom: 8px;
}
> input[type="radio"] {
display: none;
& + .box {
width: 512px;
background: #ffffff;
border: 1px solid #d0d5dd;
box-shadow: 0 1px 2px rgba(16, 24, 40, 0.05);
border-radius: 8px;
transition: all ease-in-out 250ms;
& > label {
display: flex;
flex-direction: row;
align-items: center;
font-weight: 400;
font-size: 16px;
line-height: 24px;
color: #667085;
cursor: pointer;
user-select: none;
transition: all ease-in-out 250ms;
&:before {
content: "";
display: inline-block;
width: 14px;
height: 14px;
border-radius: 8px;
background: #ffffff;
box-shadow: inset 0 0 0 4px #ffffff;
border: 1px solid #d0d5dd;
margin: 14px 8px 14px 14px;
transition: all ease-in-out 500ms;
}
> .option {
&:not(:last-child) {
margin-bottom: 8px;
}
}
&:checked + .box {
background: #22ccee;
border: 1px solid #d0d5dd;
> input[type="radio"] {
display: none;
& > label {
color: #ffffff;
& + .box {
width: 512px;
background: #ffffff;
border: 1px solid #d0d5dd;
box-shadow: 0 1px 2px rgba(16, 24, 40, 0.05);
border-radius: 8px;
transition: all ease-in-out 250ms;
&:before {
background: #ffffff;
box-shadow: inset 0 0 0 4px #22ccee;
border: 1px solid #ffffff;
}
& > label {
display: flex;
flex-direction: row;
align-items: center;
font-weight: 400;
font-size: 16px;
line-height: 24px;
color: #667085;
cursor: pointer;
user-select: none;
transition: all ease-in-out 250ms;
&:before {
content: "";
display: inline-block;
width: 14px;
height: 14px;
border-radius: 8px;
background: #ffffff;
box-shadow: inset 0 0 0 4px #ffffff;
border: 1px solid #d0d5dd;
margin: 14px 8px 14px 14px;
transition: all ease-in-out 500ms;
}
}
}
&:checked + .box {
background: #22ccee;
border: 1px solid #d0d5dd;
& > label {
color: #ffffff;
&:before {
background: #ffffff;
box-shadow: inset 0 0 0 4px #22ccee;
border: 1px solid #ffffff;
}
}
}
}
}
}
}
`;
export default function Radio({ options, value = undefined, onChange = undefined }) {
const [innerValue, setInnerValue] = useState(0);
const id = useRef(nanoid());
const [innerValue, setInnerValue] = useState(0);
const id = useRef(nanoid());
return (
<StyledForm>
{options.map((item, index) => (
<div className="option" key={index}>
<input
type="radio"
checked={(value !== undefined ? value : innerValue) === index}
onChange={() => {
value === undefined && setInnerValue(index);
onChange !== null && onChange(index);
}}
id={`${id.current}-${index}`}
/>
<div className="box">
<label htmlFor={`${id.current}-${index}`}>{item}</label>
</div>
</div>
))}
</StyledForm>
);
return (
<StyledForm>
{options.map((item, index) => (
<div className="option" key={index}>
<input
type="radio"
checked={(value !== undefined ? value : innerValue) === index}
onChange={() => {
value === undefined && setInnerValue(index);
onChange !== null && onChange(index);
}}
id={`${id.current}-${index}`}
/>
<div className="box">
<label htmlFor={`${id.current}-${index}`}>{item}</label>
</div>
</div>
))}
</StyledForm>
);
}
+1 -3
View File
@@ -48,9 +48,7 @@ export default function Select({ options = [], updateSelect = null }) {
{options.map(({ title, value, selected, underline }) => {
return (
<li
onClick={
selected ? null : handleSelect.bind(null, { title, value })
}
onClick={selected ? null : handleSelect.bind(null, { title, value })}
className={`item sb ${underline ? "underline" : ""}`}
data-disabled={selected}
key={value}

Some files were not shown because too many files have changed in this diff Show More