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
+1 -1
View File
@@ -1 +1 @@
module.exports = {extends: ['@commitlint/config-conventional']}; module.exports = { extends: ["@commitlint/config-conventional"] };
+1 -1
View File
@@ -3,7 +3,7 @@
"singleQuote": false, "singleQuote": false,
"bracketSpacing": true, "bracketSpacing": true,
"jsxBracketSameLine": false, "jsxBracketSameLine": false,
"tabWidth": 1, "tabWidth": 2,
"semi": true, "semi": true,
"trailingComma": "none" "trailingComma": "none"
} }
+8 -5
View File
@@ -5,7 +5,7 @@
"comments": false, "comments": false,
"strings": true "strings": true
}, },
"editor.defaultFormatter": "dbaeumer.vscode-eslint", "editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true, "editor.formatOnSave": true,
"eslint.alwaysShowStatus": true, "eslint.alwaysShowStatus": true,
"editor.codeActionsOnSave": { "editor.codeActionsOnSave": {
@@ -13,14 +13,17 @@
}, },
"eslint.validate": ["javascript"], "eslint.validate": ["javascript"],
"[json]": { "[json]": {
"editor.defaultFormatter": "svipas.prettier-plus", "editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true "editor.formatOnSave": true
}, },
"[jsonc]": { "[jsonc]": {
"editor.defaultFormatter": "svipas.prettier-plus" "editor.defaultFormatter": "esbenp.prettier-vscode"
}, },
"[javascriptreact]": { "[javascriptreact]": {
"editor.defaultFormatter": "svipas.prettier-plus" "editor.defaultFormatter": "esbenp.prettier-vscode"
}, },
"emmet.triggerExpansionOnTab": true "emmet.triggerExpansionOnTab": true,
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
} }
+14 -2
View File
@@ -1,7 +1,19 @@
## Refs
- preview demo: https://privoce.rustchat.com/ - preview demo: https://privoce.rustchat.com/
- design: https://www.figma.com/file/EHnNr53kNmDWgUT86It6CH/UI - design: https://www.figma.com/file/EHnNr53kNmDWgUT86It6CH/UI
- backend APIs: https://dev.rustchat.com/api/swagger - backend APIs: https://dev.rustchat.com/api/swagger
- text editor: https://plate.udecode.io/docs/installation - text editor: https://plate.udecode.io/docs/installation
- markdown editor: https://nhn.github.io/tui.editor/latest/ - markdown editor: https://nhn.github.io/tui.editor/latest/
- redux: [@reduxjs/toolkit](https://redux-toolkit.js.org/introduction/getting-started) - redux: [@reduxjs/toolkit](https://redux-toolkit.js.org/introduction/getting-started)
- indexDB wrapper: https://github.com/localForage/localForage - indexDB wrapper: https://github.com/localForage/localForage
## Local Development
- [VS Code](https://code.visualstudio.com/) Editor Recommended
- VS Code plugins:
- dbaeumer.vscode-eslint: ESLint
- esbenp.prettier-vscode: Prettier
- components.vscode-styled-components: Syntax highlighting for styled-components
- jonkwheeler.styled-components-snippets: Styled-Components Snippets for VSCode
- dsznajder.es7-react-js-snippets:Extensions for React, React-Native and Redux in JS/TS with ES7+ syntax
+5 -7
View File
@@ -9,9 +9,7 @@ delete require.cache[require.resolve("./paths")];
const NODE_ENV = process.env.NODE_ENV; const NODE_ENV = process.env.NODE_ENV;
if (!NODE_ENV) { if (!NODE_ENV) {
throw new Error( throw new Error("The NODE_ENV environment variable is required but was not specified.");
"The NODE_ENV environment variable is required but was not specified."
);
} }
// https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use // https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use
@@ -22,7 +20,7 @@ const dotenvFiles = [
// results for everyone // results for everyone
NODE_ENV !== "test" && `${paths.dotenv}.local`, NODE_ENV !== "test" && `${paths.dotenv}.local`,
`${paths.dotenv}.${NODE_ENV}`, `${paths.dotenv}.${NODE_ENV}`,
paths.dotenv, paths.dotenv
].filter(Boolean); ].filter(Boolean);
// Load environment variables from .env* files. Suppress warnings using silent // Load environment variables from .env* files. Suppress warnings using silent
@@ -34,7 +32,7 @@ dotenvFiles.forEach((dotenvFile) => {
if (fs.existsSync(dotenvFile)) { if (fs.existsSync(dotenvFile)) {
require("dotenv-expand")( require("dotenv-expand")(
require("dotenv").config({ require("dotenv").config({
path: dotenvFile, path: dotenvFile
}) })
); );
} }
@@ -88,7 +86,7 @@ function getClientEnvironment(publicUrl) {
WDS_SOCKET_PORT: process.env.WDS_SOCKET_PORT, WDS_SOCKET_PORT: process.env.WDS_SOCKET_PORT,
// Whether or not react-refresh is enabled. // Whether or not react-refresh is enabled.
// It is defined here so it is available in the webpackHotDevClient. // It is defined here so it is available in the webpackHotDevClient.
FAST_REFRESH: process.env.FAST_REFRESH !== "false", FAST_REFRESH: process.env.FAST_REFRESH !== "false"
} }
); );
// Stringify all values so we can feed into webpack DefinePlugin // Stringify all values so we can feed into webpack DefinePlugin
@@ -96,7 +94,7 @@ function getClientEnvironment(publicUrl) {
"process.env": Object.keys(raw).reduce((env, key) => { "process.env": Object.keys(raw).reduce((env, key) => {
env[key] = JSON.stringify(raw[key]); env[key] = JSON.stringify(raw[key]);
return env; return env;
}, {}), }, {})
}; };
return { raw, stringified }; return { raw, stringified };
+15 -21
View File
@@ -1,10 +1,10 @@
'use strict'; "use strict";
const fs = require('fs'); const fs = require("fs");
const path = require('path'); const path = require("path");
const crypto = require('crypto'); const crypto = require("crypto");
const chalk = require('react-dev-utils/chalk'); const chalk = require("react-dev-utils/chalk");
const paths = require('./paths'); const paths = require("./paths");
// Ensure the certificate and key provided are valid and if not // Ensure the certificate and key provided are valid and if not
// throw an easy to debug error // throw an easy to debug error
@@ -12,22 +12,16 @@ function validateKeyAndCerts({ cert, key, keyFile, crtFile }) {
let encrypted; let encrypted;
try { try {
// publicEncrypt will throw an error with an invalid cert // publicEncrypt will throw an error with an invalid cert
encrypted = crypto.publicEncrypt(cert, Buffer.from('test')); encrypted = crypto.publicEncrypt(cert, Buffer.from("test"));
} catch (err) { } catch (err) {
throw new Error( throw new Error(`The certificate "${chalk.yellow(crtFile)}" is invalid.\n${err.message}`);
`The certificate "${chalk.yellow(crtFile)}" is invalid.\n${err.message}`
);
} }
try { try {
// privateDecrypt will throw an error with an invalid key // privateDecrypt will throw an error with an invalid key
crypto.privateDecrypt(key, encrypted); crypto.privateDecrypt(key, encrypted);
} catch (err) { } catch (err) {
throw new Error( throw new Error(`The certificate key "${chalk.yellow(keyFile)}" is invalid.\n${err.message}`);
`The certificate key "${chalk.yellow(keyFile)}" is invalid.\n${
err.message
}`
);
} }
} }
@@ -35,9 +29,9 @@ function validateKeyAndCerts({ cert, key, keyFile, crtFile }) {
function readEnvFile(file, type) { function readEnvFile(file, type) {
if (!fs.existsSync(file)) { if (!fs.existsSync(file)) {
throw new Error( throw new Error(
`You specified ${chalk.cyan( `You specified ${chalk.cyan(type)} in your env, but the file "${chalk.yellow(
type file
)} in your env, but the file "${chalk.yellow(file)}" can't be found.` )}" can't be found.`
); );
} }
return fs.readFileSync(file); return fs.readFileSync(file);
@@ -47,14 +41,14 @@ function readEnvFile(file, type) {
// Return cert files if provided in env, otherwise just true or false // Return cert files if provided in env, otherwise just true or false
function getHttpsConfig() { function getHttpsConfig() {
const { SSL_CRT_FILE, SSL_KEY_FILE, HTTPS } = process.env; const { SSL_CRT_FILE, SSL_KEY_FILE, HTTPS } = process.env;
const isHttps = HTTPS === 'true'; const isHttps = HTTPS === "true";
if (isHttps && SSL_CRT_FILE && SSL_KEY_FILE) { if (isHttps && SSL_CRT_FILE && SSL_KEY_FILE) {
const crtFile = path.resolve(paths.appPath, SSL_CRT_FILE); const crtFile = path.resolve(paths.appPath, SSL_CRT_FILE);
const keyFile = path.resolve(paths.appPath, SSL_KEY_FILE); const keyFile = path.resolve(paths.appPath, SSL_KEY_FILE);
const config = { const config = {
cert: readEnvFile(crtFile, 'SSL_CRT_FILE'), cert: readEnvFile(crtFile, "SSL_CRT_FILE"),
key: readEnvFile(keyFile, 'SSL_KEY_FILE'), key: readEnvFile(keyFile, "SSL_KEY_FILE")
}; };
validateKeyAndCerts({ ...config, keyFile, crtFile }); validateKeyAndCerts({ ...config, keyFile, crtFile });
+17 -18
View File
@@ -1,10 +1,10 @@
'use strict'; "use strict";
const fs = require('fs'); const fs = require("fs");
const path = require('path'); const path = require("path");
const paths = require('./paths'); const paths = require("./paths");
const chalk = require('react-dev-utils/chalk'); const chalk = require("react-dev-utils/chalk");
const resolve = require('resolve'); const resolve = require("resolve");
/** /**
* Get additional module paths based on the baseUrl of a compilerOptions object. * Get additional module paths based on the baseUrl of a compilerOptions object.
@@ -15,19 +15,19 @@ function getAdditionalModulePaths(options = {}) {
const baseUrl = options.baseUrl; const baseUrl = options.baseUrl;
if (!baseUrl) { if (!baseUrl) {
return ''; return "";
} }
const baseUrlResolved = path.resolve(paths.appPath, baseUrl); const baseUrlResolved = path.resolve(paths.appPath, baseUrl);
// We don't need to do anything if `baseUrl` is set to `node_modules`. This is // We don't need to do anything if `baseUrl` is set to `node_modules`. This is
// the default behavior. // the default behavior.
if (path.relative(paths.appNodeModules, baseUrlResolved) === '') { if (path.relative(paths.appNodeModules, baseUrlResolved) === "") {
return null; return null;
} }
// Allow the user set the `baseUrl` to `appSrc`. // Allow the user set the `baseUrl` to `appSrc`.
if (path.relative(paths.appSrc, baseUrlResolved) === '') { if (path.relative(paths.appSrc, baseUrlResolved) === "") {
return [paths.appSrc]; return [paths.appSrc];
} }
@@ -36,7 +36,7 @@ function getAdditionalModulePaths(options = {}) {
// not transpiled outside of `src`. We do allow importing them with the // not transpiled outside of `src`. We do allow importing them with the
// absolute path (e.g. `src/Components/Button.js`) but we set that up with // absolute path (e.g. `src/Components/Button.js`) but we set that up with
// an alias. // an alias.
if (path.relative(paths.appPath, baseUrlResolved) === '') { if (path.relative(paths.appPath, baseUrlResolved) === "") {
return null; return null;
} }
@@ -44,7 +44,7 @@ function getAdditionalModulePaths(options = {}) {
throw new Error( throw new Error(
chalk.red.bold( chalk.red.bold(
"Your project's `baseUrl` can only be set to `src` or `node_modules`." + "Your project's `baseUrl` can only be set to `src` or `node_modules`." +
' Create React App does not support other values at this time.' " Create React App does not support other values at this time."
) )
); );
} }
@@ -63,14 +63,13 @@ function getWebpackAliases(options = {}) {
const baseUrlResolved = path.resolve(paths.appPath, baseUrl); const baseUrlResolved = path.resolve(paths.appPath, baseUrl);
if (path.relative(paths.appPath, baseUrlResolved) === '') { if (path.relative(paths.appPath, baseUrlResolved) === "") {
return { return {
src: paths.appSrc, src: paths.appSrc
}; };
} }
} }
function getModules() { function getModules() {
// Check if TypeScript is setup // Check if TypeScript is setup
const hasTsConfig = fs.existsSync(paths.appTsConfig); const hasTsConfig = fs.existsSync(paths.appTsConfig);
@@ -78,7 +77,7 @@ function getModules() {
if (hasTsConfig && hasJsConfig) { if (hasTsConfig && hasJsConfig) {
throw new Error( throw new Error(
'You have both a tsconfig.json and a jsconfig.json. If you are using TypeScript please remove your jsconfig.json file.' "You have both a tsconfig.json and a jsconfig.json. If you are using TypeScript please remove your jsconfig.json file."
); );
} }
@@ -88,8 +87,8 @@ function getModules() {
// TypeScript project and set up the config // TypeScript project and set up the config
// based on tsconfig.json // based on tsconfig.json
if (hasTsConfig) { if (hasTsConfig) {
const ts = require(resolve.sync('typescript', { const ts = require(resolve.sync("typescript", {
basedir: paths.appNodeModules, basedir: paths.appNodeModules
})); }));
config = ts.readConfigFile(paths.appTsConfig, ts.sys.readFile).config; config = ts.readConfigFile(paths.appTsConfig, ts.sys.readFile).config;
// Otherwise we'll check if there is jsconfig.json // Otherwise we'll check if there is jsconfig.json
@@ -106,7 +105,7 @@ function getModules() {
return { return {
additionalModulePaths: additionalModulePaths, additionalModulePaths: additionalModulePaths,
webpackAliases: getWebpackAliases(options), webpackAliases: getWebpackAliases(options),
hasTsConfig, hasTsConfig
}; };
} }
+37 -39
View File
@@ -1,13 +1,13 @@
'use strict'; "use strict";
const path = require('path'); const path = require("path");
const fs = require('fs'); const fs = require("fs");
const getPublicUrlOrPath = require('react-dev-utils/getPublicUrlOrPath'); const getPublicUrlOrPath = require("react-dev-utils/getPublicUrlOrPath");
// Make sure any symlinks in the project folder are resolved: // Make sure any symlinks in the project folder are resolved:
// https://github.com/facebook/create-react-app/issues/637 // https://github.com/facebook/create-react-app/issues/637
const appDirectory = fs.realpathSync(process.cwd()); const appDirectory = fs.realpathSync(process.cwd());
const resolveApp = relativePath => path.resolve(appDirectory, relativePath); const resolveApp = (relativePath) => path.resolve(appDirectory, relativePath);
// We use `PUBLIC_URL` environment variable or "homepage" field to infer // We use `PUBLIC_URL` environment variable or "homepage" field to infer
// "public path" at which the app is served. // "public path" at which the app is served.
@@ -16,30 +16,30 @@ const resolveApp = relativePath => path.resolve(appDirectory, relativePath);
// We can't use a relative path in HTML because we don't want to load something // We can't use a relative path in HTML because we don't want to load something
// like /todos/42/static/js/bundle.7289d.js. We have to know the root. // like /todos/42/static/js/bundle.7289d.js. We have to know the root.
const publicUrlOrPath = getPublicUrlOrPath( const publicUrlOrPath = getPublicUrlOrPath(
process.env.NODE_ENV === 'development', process.env.NODE_ENV === "development",
require(resolveApp('package.json')).homepage, require(resolveApp("package.json")).homepage,
process.env.PUBLIC_URL process.env.PUBLIC_URL
); );
const buildPath = process.env.BUILD_PATH || 'build'; const buildPath = process.env.BUILD_PATH || "build";
const moduleFileExtensions = [ const moduleFileExtensions = [
'web.mjs', "web.mjs",
'mjs', "mjs",
'web.js', "web.js",
'js', "js",
'web.ts', "web.ts",
'ts', "ts",
'web.tsx', "web.tsx",
'tsx', "tsx",
'json', "json",
'web.jsx', "web.jsx",
'jsx', "jsx"
]; ];
// Resolve file paths in the same order as webpack // Resolve file paths in the same order as webpack
const resolveModule = (resolveFn, filePath) => { const resolveModule = (resolveFn, filePath) => {
const extension = moduleFileExtensions.find(extension => const extension = moduleFileExtensions.find((extension) =>
fs.existsSync(resolveFn(`${filePath}.${extension}`)) fs.existsSync(resolveFn(`${filePath}.${extension}`))
); );
@@ -52,26 +52,24 @@ const resolveModule = (resolveFn, filePath) => {
// config after eject: we're in ./config/ // config after eject: we're in ./config/
module.exports = { module.exports = {
dotenv: resolveApp('.env'), dotenv: resolveApp(".env"),
appPath: resolveApp('.'), appPath: resolveApp("."),
appBuild: resolveApp(buildPath), appBuild: resolveApp(buildPath),
appPublic: resolveApp('public'), appPublic: resolveApp("public"),
appHtml: resolveApp('public/index.html'), appHtml: resolveApp("public/index.html"),
appIndexJs: resolveModule(resolveApp, 'src/index'), appIndexJs: resolveModule(resolveApp, "src/index"),
appPackageJson: resolveApp('package.json'), appPackageJson: resolveApp("package.json"),
appSrc: resolveApp('src'), appSrc: resolveApp("src"),
appTsConfig: resolveApp('tsconfig.json'), appTsConfig: resolveApp("tsconfig.json"),
appJsConfig: resolveApp('jsconfig.json'), appJsConfig: resolveApp("jsconfig.json"),
yarnLockFile: resolveApp('yarn.lock'), yarnLockFile: resolveApp("yarn.lock"),
testsSetup: resolveModule(resolveApp, 'src/setupTests'), testsSetup: resolveModule(resolveApp, "src/setupTests"),
proxySetup: resolveApp('src/setupProxy.js'), proxySetup: resolveApp("src/setupProxy.js"),
appNodeModules: resolveApp('node_modules'), appNodeModules: resolveApp("node_modules"),
appWebpackCache: resolveApp('node_modules/.cache'), appWebpackCache: resolveApp("node_modules/.cache"),
appTsBuildInfoFile: resolveApp('node_modules/.cache/tsconfig.tsbuildinfo'), appTsBuildInfoFile: resolveApp("node_modules/.cache/tsconfig.tsbuildinfo"),
swSrc: resolveModule(resolveApp, 'src/service-worker'), swSrc: resolveModule(resolveApp, "src/service-worker"),
publicUrlOrPath, publicUrlOrPath
}; };
module.exports.moduleFileExtensions = moduleFileExtensions; module.exports.moduleFileExtensions = moduleFileExtensions;
+68 -90
View File
@@ -33,16 +33,14 @@ const babelRuntimeEntryHelpers = require.resolve(
{ paths: [babelRuntimeEntry] } { paths: [babelRuntimeEntry] }
); );
const babelRuntimeRegenerator = require.resolve("@babel/runtime/regenerator", { const babelRuntimeRegenerator = require.resolve("@babel/runtime/regenerator", {
paths: [babelRuntimeEntry], paths: [babelRuntimeEntry]
}); });
// Some apps do not need the benefits of saving a web request, so not inlining the chunk // Some apps do not need the benefits of saving a web request, so not inlining the chunk
// makes for a smoother build process. // makes for a smoother build process.
const shouldInlineRuntimeChunk = process.env.INLINE_RUNTIME_CHUNK !== "false"; const shouldInlineRuntimeChunk = process.env.INLINE_RUNTIME_CHUNK !== "false";
const imageInlineSizeLimit = parseInt( const imageInlineSizeLimit = parseInt(process.env.IMAGE_INLINE_SIZE_LIMIT || "10000");
process.env.IMAGE_INLINE_SIZE_LIMIT || "10000"
);
// Check if TypeScript is setup // Check if TypeScript is setup
const useTypeScript = fs.existsSync(paths.appTsConfig); const useTypeScript = fs.existsSync(paths.appTsConfig);
@@ -75,8 +73,7 @@ module.exports = function (webpackEnv) {
// Variable used for enabling profiling in Production // Variable used for enabling profiling in Production
// passed into alias object. Uses a flag if passed into the build command // passed into alias object. Uses a flag if passed into the build command
const isEnvProductionProfile = const isEnvProductionProfile = isEnvProduction && process.argv.includes("--profile");
isEnvProduction && process.argv.includes("--profile");
// We will provide `paths.publicUrlOrPath` to our app // We will provide `paths.publicUrlOrPath` to our app
// as %PUBLIC_URL% in `index.html` and `process.env.PUBLIC_URL` in JavaScript. // as %PUBLIC_URL% in `index.html` and `process.env.PUBLIC_URL` in JavaScript.
@@ -94,14 +91,12 @@ module.exports = function (webpackEnv) {
loader: MiniCssExtractPlugin.loader, loader: MiniCssExtractPlugin.loader,
// css is located in `static/css`, use '../../' to locate index.html folder // css is located in `static/css`, use '../../' to locate index.html folder
// in production `paths.publicUrlOrPath` can be a relative path // in production `paths.publicUrlOrPath` can be a relative path
options: paths.publicUrlOrPath.startsWith(".") options: paths.publicUrlOrPath.startsWith(".") ? { publicPath: "../../" } : {}
? { publicPath: "../../" }
: {},
}, },
{ {
loader: require.resolve("css-loader"), loader: require.resolve("css-loader"),
options: cssOptions, options: cssOptions
}, }
].filter(Boolean); ].filter(Boolean);
return loaders; return loaders;
}; };
@@ -140,13 +135,9 @@ module.exports = function (webpackEnv) {
publicPath: paths.publicUrlOrPath, publicPath: paths.publicUrlOrPath,
// Point sourcemap entries to original disk location (format as URL on Windows) // Point sourcemap entries to original disk location (format as URL on Windows)
devtoolModuleFilenameTemplate: isEnvProduction devtoolModuleFilenameTemplate: isEnvProduction
? (info) => ? (info) => path.relative(paths.appSrc, info.absoluteResourcePath).replace(/\\/g, "/")
path
.relative(paths.appSrc, info.absoluteResourcePath)
.replace(/\\/g, "/")
: isEnvDevelopment && : isEnvDevelopment &&
((info) => ((info) => path.resolve(info.absoluteResourcePath).replace(/\\/g, "/"))
path.resolve(info.absoluteResourcePath).replace(/\\/g, "/")),
}, },
cache: { cache: {
type: "filesystem", type: "filesystem",
@@ -156,13 +147,11 @@ module.exports = function (webpackEnv) {
buildDependencies: { buildDependencies: {
defaultWebpack: ["webpack/lib/"], defaultWebpack: ["webpack/lib/"],
config: [__filename], config: [__filename],
tsconfig: [paths.appTsConfig, paths.appJsConfig].filter((f) => tsconfig: [paths.appTsConfig, paths.appJsConfig].filter((f) => fs.existsSync(f))
fs.existsSync(f) }
),
},
}, },
infrastructureLogging: { infrastructureLogging: {
level: "none", level: "none"
}, },
optimization: { optimization: {
minimize: isEnvProduction, minimize: isEnvProduction,
@@ -176,7 +165,7 @@ module.exports = function (webpackEnv) {
// into invalid ecma 5 code. This is why the 'compress' and 'output' // into invalid ecma 5 code. This is why the 'compress' and 'output'
// sections only apply transformations that are ecma 5 safe // sections only apply transformations that are ecma 5 safe
// https://github.com/facebook/create-react-app/pull/4234 // https://github.com/facebook/create-react-app/pull/4234
ecma: 8, ecma: 8
}, },
compress: { compress: {
drop_console: true, drop_console: true,
@@ -191,10 +180,10 @@ module.exports = function (webpackEnv) {
// https://github.com/facebook/create-react-app/issues/5250 // https://github.com/facebook/create-react-app/issues/5250
// Pending further investigation: // Pending further investigation:
// https://github.com/terser-js/terser/issues/120 // https://github.com/terser-js/terser/issues/120
inline: 2, inline: 2
}, },
mangle: { mangle: {
safari10: true, safari10: true
}, },
// Added for profiling in devtools // Added for profiling in devtools
keep_classnames: isEnvProductionProfile, keep_classnames: isEnvProductionProfile,
@@ -204,22 +193,20 @@ module.exports = function (webpackEnv) {
comments: false, comments: false,
// Turned on because emoji and regex is not minified properly using default // Turned on because emoji and regex is not minified properly using default
// https://github.com/facebook/create-react-app/issues/2488 // https://github.com/facebook/create-react-app/issues/2488
ascii_only: true, ascii_only: true
}, }
}, }
}), }),
// This is only used in production mode // This is only used in production mode
new CssMinimizerPlugin(), new CssMinimizerPlugin()
], ]
}, },
resolve: { resolve: {
// This allows you to set a fallback for where webpack should look for modules. // This allows you to set a fallback for where webpack should look for modules.
// We placed these paths second because we want `node_modules` to "win" // We placed these paths second because we want `node_modules` to "win"
// if there are any conflicts. This matches Node resolution mechanism. // if there are any conflicts. This matches Node resolution mechanism.
// https://github.com/facebook/create-react-app/issues/253 // https://github.com/facebook/create-react-app/issues/253
modules: ["node_modules", paths.appNodeModules].concat( modules: ["node_modules", paths.appNodeModules].concat(modules.additionalModulePaths || []),
modules.additionalModulePaths || []
),
// These are the reasonable defaults supported by the Node ecosystem. // These are the reasonable defaults supported by the Node ecosystem.
// We also include JSX as a common component filename extension to support // We also include JSX as a common component filename extension to support
// some tools, although we do not recommend using it, see: // some tools, although we do not recommend using it, see:
@@ -233,9 +220,9 @@ module.exports = function (webpackEnv) {
// Allows for better profiling with ReactDevTools // Allows for better profiling with ReactDevTools
...(isEnvProductionProfile && { ...(isEnvProductionProfile && {
"react-dom$": "react-dom/profiling", "react-dom$": "react-dom/profiling",
"scheduler/tracing": "scheduler/tracing-profiling", "scheduler/tracing": "scheduler/tracing-profiling"
}), }),
...(modules.webpackAliases || {}), ...(modules.webpackAliases || {})
}, },
plugins: [ plugins: [
// Prevents users from importing files from outside of src/ (or node_modules/). // Prevents users from importing files from outside of src/ (or node_modules/).
@@ -249,9 +236,9 @@ module.exports = function (webpackEnv) {
reactRefreshWebpackPluginRuntimeEntry, reactRefreshWebpackPluginRuntimeEntry,
babelRuntimeEntry, babelRuntimeEntry,
babelRuntimeEntryHelpers, babelRuntimeEntryHelpers,
babelRuntimeRegenerator, babelRuntimeRegenerator
]), ])
], ]
}, },
module: { module: {
strictExportPresence: true, strictExportPresence: true,
@@ -261,7 +248,7 @@ module.exports = function (webpackEnv) {
enforce: "pre", enforce: "pre",
exclude: /@babel(?:\/|\\{1,2})runtime/, exclude: /@babel(?:\/|\\{1,2})runtime/,
test: /\.(js|mjs|jsx|ts|tsx|css)$/, test: /\.(js|mjs|jsx|ts|tsx|css)$/,
loader: require.resolve("source-map-loader"), loader: require.resolve("source-map-loader")
}, },
{ {
// "oneOf" will traverse all following loaders until one will // "oneOf" will traverse all following loaders until one will
@@ -276,9 +263,9 @@ module.exports = function (webpackEnv) {
mimetype: "image/avif", mimetype: "image/avif",
parser: { parser: {
dataUrlCondition: { dataUrlCondition: {
maxSize: imageInlineSizeLimit, maxSize: imageInlineSizeLimit
}, }
}, }
}, },
// "url" loader works like "file" loader except that it embeds assets // "url" loader works like "file" loader except that it embeds assets
// smaller than specified limit in bytes as data URLs to avoid requests. // smaller than specified limit in bytes as data URLs to avoid requests.
@@ -288,9 +275,9 @@ module.exports = function (webpackEnv) {
type: "asset", type: "asset",
parser: { parser: {
dataUrlCondition: { dataUrlCondition: {
maxSize: imageInlineSizeLimit, maxSize: imageInlineSizeLimit
}, }
}, }
}, },
{ {
test: /\.svg$/, test: /\.svg$/,
@@ -301,17 +288,17 @@ module.exports = function (webpackEnv) {
prettier: false, prettier: false,
svgo: false, svgo: false,
svgoConfig: { svgoConfig: {
plugins: [{ removeViewBox: false }], plugins: [{ removeViewBox: false }]
}, },
titleProp: true, titleProp: true,
ref: true, ref: true
}, }
}, }
], ],
issuer: { issuer: {
and: [/\.(ts|tsx|js|jsx|md|mdx)$/], and: [/\.(ts|tsx|js|jsx|md|mdx)$/]
}, },
resourceQuery: { not: [/url/] }, // exclude react component if *.svg?url resourceQuery: { not: [/url/] } // exclude react component if *.svg?url
}, },
// Process application JS with Babel. // Process application JS with Babel.
// The preset includes JSX, Flow, TypeScript, and some ESnext features. // The preset includes JSX, Flow, TypeScript, and some ESnext features.
@@ -320,22 +307,20 @@ module.exports = function (webpackEnv) {
include: paths.appSrc, include: paths.appSrc,
loader: require.resolve("babel-loader"), loader: require.resolve("babel-loader"),
options: { options: {
customize: require.resolve( customize: require.resolve("babel-preset-react-app/webpack-overrides"),
"babel-preset-react-app/webpack-overrides"
),
presets: [ presets: [
[ [
require.resolve("babel-preset-react-app"), require.resolve("babel-preset-react-app"),
{ {
runtime: hasJsxRuntime ? "automatic" : "classic", runtime: hasJsxRuntime ? "automatic" : "classic"
}, }
], ]
], ],
plugins: [ plugins: [
isEnvDevelopment && isEnvDevelopment &&
shouldUseReactRefresh && shouldUseReactRefresh &&
require.resolve("react-refresh/babel"), require.resolve("react-refresh/babel")
].filter(Boolean), ].filter(Boolean),
// This is a feature of `babel-loader` for webpack (not Babel itself). // This is a feature of `babel-loader` for webpack (not Babel itself).
// It enables caching results in ./node_modules/.cache/babel-loader/ // It enables caching results in ./node_modules/.cache/babel-loader/
@@ -343,8 +328,8 @@ module.exports = function (webpackEnv) {
cacheDirectory: true, cacheDirectory: true,
// See #6846 for context on why cacheCompression is disabled // See #6846 for context on why cacheCompression is disabled
cacheCompression: false, cacheCompression: false,
compact: isEnvProduction, compact: isEnvProduction
}, }
}, },
// Process any JS outside of the app with Babel. // Process any JS outside of the app with Babel.
// Unlike the application JS, we only compile the standard ES features. // Unlike the application JS, we only compile the standard ES features.
@@ -357,10 +342,7 @@ module.exports = function (webpackEnv) {
configFile: false, configFile: false,
compact: false, compact: false,
presets: [ presets: [
[ [require.resolve("babel-preset-react-app/dependencies"), { helpers: true }]
require.resolve("babel-preset-react-app/dependencies"),
{ helpers: true },
],
], ],
cacheDirectory: true, cacheDirectory: true,
// See #6846 for context on why cacheCompression is disabled // See #6846 for context on why cacheCompression is disabled
@@ -370,8 +352,8 @@ module.exports = function (webpackEnv) {
// code. Without the options below, debuggers like VSCode // code. Without the options below, debuggers like VSCode
// show incorrect code and set breakpoints on the wrong lines. // show incorrect code and set breakpoints on the wrong lines.
sourceMaps: shouldUseSourceMap, sourceMaps: shouldUseSourceMap,
inputSourceMap: shouldUseSourceMap, inputSourceMap: shouldUseSourceMap
}, }
}, },
// "css" loader resolves paths in CSS and adds assets as dependencies. // "css" loader resolves paths in CSS and adds assets as dependencies.
// "style" loader turns CSS into JS modules that inject <style> tags. // "style" loader turns CSS into JS modules that inject <style> tags.
@@ -384,18 +366,16 @@ module.exports = function (webpackEnv) {
exclude: cssModuleRegex, exclude: cssModuleRegex,
use: getStyleLoaders({ use: getStyleLoaders({
importLoaders: 1, importLoaders: 1,
sourceMap: isEnvProduction sourceMap: isEnvProduction ? shouldUseSourceMap : isEnvDevelopment,
? shouldUseSourceMap
: isEnvDevelopment,
modules: { modules: {
mode: "icss", mode: "icss"
}, }
}), }),
// Don't consider CSS imports dead code even if the // Don't consider CSS imports dead code even if the
// containing package claims to have no side effects. // containing package claims to have no side effects.
// Remove this when webpack adds a warning or an error for this. // Remove this when webpack adds a warning or an error for this.
// See https://github.com/webpack/webpack/issues/6571 // See https://github.com/webpack/webpack/issues/6571
sideEffects: true, sideEffects: true
}, },
// "file" loader makes sure those assets get served by WebpackDevServer. // "file" loader makes sure those assets get served by WebpackDevServer.
// When you `import` an asset, you get its (virtual) filename. // When you `import` an asset, you get its (virtual) filename.
@@ -408,13 +388,13 @@ module.exports = function (webpackEnv) {
// Also exclude `html` and `json` extensions so they get processed // Also exclude `html` and `json` extensions so they get processed
// by webpacks internal loaders. // by webpacks internal loaders.
exclude: [/^$/, /\.(js|mjs|jsx|ts|tsx)$/, /\.html$/, /\.json$/], exclude: [/^$/, /\.(js|mjs|jsx|ts|tsx)$/, /\.html$/, /\.json$/],
type: "asset/resource", type: "asset/resource"
}, }
// ** STOP ** Are you adding a new loader? // ** STOP ** Are you adding a new loader?
// Make sure to add the new loader(s) before the "file" loader. // Make sure to add the new loader(s) before the "file" loader.
], ]
}, }
].filter(Boolean), ].filter(Boolean)
}, },
plugins: [ plugins: [
// Generates an `index.html` file with the <script> injected. // Generates an `index.html` file with the <script> injected.
@@ -423,7 +403,7 @@ module.exports = function (webpackEnv) {
{}, {},
{ {
inject: true, inject: true,
template: paths.appHtml, template: paths.appHtml
}, },
isEnvProduction isEnvProduction
? { ? {
@@ -437,8 +417,8 @@ module.exports = function (webpackEnv) {
keepClosingSlash: true, keepClosingSlash: true,
minifyJS: true, minifyJS: true,
minifyCSS: true, minifyCSS: true,
minifyURLs: true, minifyURLs: true
}, }
} }
: undefined : undefined
) )
@@ -469,7 +449,7 @@ module.exports = function (webpackEnv) {
isEnvDevelopment && isEnvDevelopment &&
shouldUseReactRefresh && shouldUseReactRefresh &&
new ReactRefreshWebpackPlugin({ new ReactRefreshWebpackPlugin({
overlay: false, overlay: false
}), }),
// Watcher doesn't work well if you mistype casing in a path so we use // Watcher doesn't work well if you mistype casing in a path so we use
// a plugin that prints an error when you attempt to do this. // a plugin that prints an error when you attempt to do this.
@@ -480,7 +460,7 @@ module.exports = function (webpackEnv) {
// Options similar to the same options in webpackOptions.output // Options similar to the same options in webpackOptions.output
// both options are optional // both options are optional
filename: "static/css/[name].[contenthash:8].css", filename: "static/css/[name].[contenthash:8].css",
chunkFilename: "static/css/[name].[contenthash:8].chunk.css", chunkFilename: "static/css/[name].[contenthash:8].chunk.css"
}), }),
// Generate an asset manifest file with the following content: // Generate an asset manifest file with the following content:
// - "files" key: Mapping of all asset filenames to their corresponding // - "files" key: Mapping of all asset filenames to their corresponding
@@ -496,15 +476,13 @@ module.exports = function (webpackEnv) {
manifest[file.name] = file.path; manifest[file.name] = file.path;
return manifest; return manifest;
}, seed); }, seed);
const entrypointFiles = entrypoints.main.filter( const entrypointFiles = entrypoints.main.filter((fileName) => !fileName.endsWith(".map"));
(fileName) => !fileName.endsWith(".map")
);
return { return {
files: manifestFiles, files: manifestFiles,
entrypoints: entrypointFiles, entrypoints: entrypointFiles
}; };
}, }
}), }),
// Generate a service worker script that will precache, and keep up to date, // Generate a service worker script that will precache, and keep up to date,
// the HTML & assets that are part of the webpack build. // the HTML & assets that are part of the webpack build.
@@ -517,11 +495,11 @@ module.exports = function (webpackEnv) {
// Bump up the default maximum size (2mb) that's precached, // Bump up the default maximum size (2mb) that's precached,
// to make lazy-loading failure scenarios less likely. // to make lazy-loading failure scenarios less likely.
// See https://github.com/cra-template/pwa/issues/13#issuecomment-722667270 // See https://github.com/cra-template/pwa/issues/13#issuecomment-722667270
maximumFileSizeToCacheInBytes: 5 * 1024 * 1024, maximumFileSizeToCacheInBytes: 5 * 1024 * 1024
}), })
].filter(Boolean), ].filter(Boolean),
// Turn off performance processing because we utilize // Turn off performance processing because we utilize
// our own hints via the FileSizeReporter // our own hints via the FileSizeReporter
performance: false, performance: false
}; };
}; };
@@ -1,9 +1,8 @@
'use strict'; "use strict";
const { createHash } = require('crypto'); const { createHash } = require("crypto");
module.exports = env => { module.exports = (env) => {
const hash = createHash('md5'); const hash = createHash("md5");
hash.update(JSON.stringify(env)); hash.update(JSON.stringify(env));
return hash.digest("hex");
return hash.digest('hex');
}; };
+22 -23
View File
@@ -1,21 +1,20 @@
'use strict'; "use strict";
const fs = require('fs'); const fs = require("fs");
const evalSourceMapMiddleware = require('react-dev-utils/evalSourceMapMiddleware'); const evalSourceMapMiddleware = require("react-dev-utils/evalSourceMapMiddleware");
const noopServiceWorkerMiddleware = require('react-dev-utils/noopServiceWorkerMiddleware'); const noopServiceWorkerMiddleware = require("react-dev-utils/noopServiceWorkerMiddleware");
const ignoredFiles = require('react-dev-utils/ignoredFiles'); const ignoredFiles = require("react-dev-utils/ignoredFiles");
const redirectServedPath = require('react-dev-utils/redirectServedPathMiddleware'); const redirectServedPath = require("react-dev-utils/redirectServedPathMiddleware");
const paths = require('./paths'); const paths = require("./paths");
const getHttpsConfig = require('./getHttpsConfig'); const getHttpsConfig = require("./getHttpsConfig");
const host = process.env.HOST || '0.0.0.0'; const host = process.env.HOST || "0.0.0.0";
const sockHost = process.env.WDS_SOCKET_HOST; const sockHost = process.env.WDS_SOCKET_HOST;
const sockPath = process.env.WDS_SOCKET_PATH; // default: '/ws' const sockPath = process.env.WDS_SOCKET_PATH; // default: '/ws'
const sockPort = process.env.WDS_SOCKET_PORT; const sockPort = process.env.WDS_SOCKET_PORT;
module.exports = function (proxy, allowedHost) { module.exports = function (proxy, allowedHost) {
const disableFirewall = const disableFirewall = !proxy || process.env.DANGEROUSLY_DISABLE_HOST_CHECK === "true";
!proxy || process.env.DANGEROUSLY_DISABLE_HOST_CHECK === 'true';
return { return {
// WebpackDevServer 2.4.3 introduced a security fix that prevents remote // WebpackDevServer 2.4.3 introduced a security fix that prevents remote
// websites from potentially accessing local content through DNS rebinding: // websites from potentially accessing local content through DNS rebinding:
@@ -35,11 +34,11 @@ module.exports = function (proxy, allowedHost) {
// really know what you're doing with a special environment variable. // really know what you're doing with a special environment variable.
// Note: ["localhost", ".localhost"] will support subdomains - but we might // Note: ["localhost", ".localhost"] will support subdomains - but we might
// want to allow setting the allowedHosts manually for more complex setups // want to allow setting the allowedHosts manually for more complex setups
allowedHosts: disableFirewall ? 'all' : [allowedHost], allowedHosts: disableFirewall ? "all" : [allowedHost],
headers: { headers: {
'Access-Control-Allow-Origin': '*', "Access-Control-Allow-Origin": "*",
'Access-Control-Allow-Methods': '*', "Access-Control-Allow-Methods": "*",
'Access-Control-Allow-Headers': '*', "Access-Control-Allow-Headers": "*"
}, },
// Enable gzip compression of generated files. // Enable gzip compression of generated files.
compress: true, compress: true,
@@ -66,8 +65,8 @@ module.exports = function (proxy, allowedHost) {
// https://github.com/facebook/create-react-app/issues/293 // https://github.com/facebook/create-react-app/issues/293
// src/node_modules is not ignored to support absolute imports // src/node_modules is not ignored to support absolute imports
// https://github.com/facebook/create-react-app/issues/1065 // https://github.com/facebook/create-react-app/issues/1065
ignored: ignoredFiles(paths.appSrc), ignored: ignoredFiles(paths.appSrc)
}, }
}, },
client: { client: {
webSocketURL: { webSocketURL: {
@@ -76,19 +75,19 @@ module.exports = function (proxy, allowedHost) {
// to hot reloading server. // to hot reloading server.
hostname: sockHost, hostname: sockHost,
pathname: sockPath, pathname: sockPath,
port: sockPort, port: sockPort
}, },
overlay: { overlay: {
errors: true, errors: true,
warnings: false, warnings: false
}, }
}, },
devMiddleware: { devMiddleware: {
// It is important to tell WebpackDevServer to use the same "publicPath" path as // It is important to tell WebpackDevServer to use the same "publicPath" path as
// we specified in the webpack config. When homepage is '.', default to serving // we specified in the webpack config. When homepage is '.', default to serving
// from the root. // from the root.
// remove last slash so user can land on `/test` instead of `/test/` // remove last slash so user can land on `/test` instead of `/test/`
publicPath: paths.publicUrlOrPath.slice(0, -1), publicPath: paths.publicUrlOrPath.slice(0, -1)
}, },
https: getHttpsConfig(), https: getHttpsConfig(),
@@ -97,7 +96,7 @@ module.exports = function (proxy, allowedHost) {
// Paths with dots should still use the history fallback. // Paths with dots should still use the history fallback.
// See https://github.com/facebook/create-react-app/issues/387. // See https://github.com/facebook/create-react-app/issues/387.
disableDotRule: true, disableDotRule: true,
index: paths.publicUrlOrPath, index: paths.publicUrlOrPath
}, },
// `proxy` is run between `before` and `after` `webpack-dev-server` hooks // `proxy` is run between `before` and `after` `webpack-dev-server` hooks
proxy, proxy,
@@ -122,6 +121,6 @@ module.exports = function (proxy, allowedHost) {
// it used the same host and port. // it used the same host and port.
// https://github.com/facebook/create-react-app/issues/2272#issuecomment-302832432 // https://github.com/facebook/create-react-app/issues/2272#issuecomment-302832432
devServer.app.use(noopServiceWorkerMiddleware(paths.publicUrlOrPath)); devServer.app.use(noopServiceWorkerMiddleware(paths.publicUrlOrPath));
}, }
}; };
}; };
+4 -11
View File
@@ -6,7 +6,7 @@ self.addEventListener("notificationclick", function (event) {
event.waitUntil( event.waitUntil(
(async function () { (async function () {
const allClients = await clients.matchAll({ const allClients = await clients.matchAll({
includeUncontrolled: true, includeUncontrolled: true
}); });
const [firstClient] = allClients; const [firstClient] = allClients;
// 没有数据 // 没有数据
@@ -15,12 +15,7 @@ self.addEventListener("notificationclick", function (event) {
firstClient.focus(); firstClient.focus();
return; return;
} }
const { const { rustchat_from_uid, rustchat_to_uid, rustchat_to_gid } = customData;
rustchat_from_uid,
rustchat_to_uid,
rustchat_to_gid,
} = customData;
let chatClient; let chatClient;
let redirectPath = rustchat_to_uid let redirectPath = rustchat_to_uid
? `/chat/dm/${rustchat_from_uid}` ? `/chat/dm/${rustchat_from_uid}`
@@ -47,9 +42,7 @@ self.addEventListener("notificationclick", function (event) {
// importScripts( // importScripts(
// "https://www.gstatic.com/firebasejs/9.8.1/firebase-messaging-compat.js" // "https://www.gstatic.com/firebasejs/9.8.1/firebase-messaging-compat.js"
// ); // );
importScripts( importScripts("https://cdnjs.cloudflare.com/ajax/libs/firebase/9.8.1/firebase-app-compat.min.js");
"https://cdnjs.cloudflare.com/ajax/libs/firebase/9.8.1/firebase-app-compat.min.js"
);
importScripts( importScripts(
"https://cdnjs.cloudflare.com/ajax/libs/firebase/9.8.1/firebase-messaging-compat.min.js" "https://cdnjs.cloudflare.com/ajax/libs/firebase/9.8.1/firebase-messaging-compat.min.js"
); );
@@ -62,7 +55,7 @@ const firebaseConfig = {
storageBucket: "rustchat-develop.appspot.com", storageBucket: "rustchat-develop.appspot.com",
messagingSenderId: "418687074928", messagingSenderId: "418687074928",
appId: "1:418687074928:web:753286adbf239f5af9eab5", appId: "1:418687074928:web:753286adbf239f5af9eab5",
measurementId: "G-XV476KEC8P", measurementId: "G-XV476KEC8P"
}; };
firebase.initializeApp(firebaseConfig); firebase.initializeApp(firebaseConfig);
+8 -21
View File
@@ -27,8 +27,7 @@ const printHostingInstructions = require("react-dev-utils/printHostingInstructio
const FileSizeReporter = require("react-dev-utils/FileSizeReporter"); const FileSizeReporter = require("react-dev-utils/FileSizeReporter");
const printBuildError = require("react-dev-utils/printBuildError"); const printBuildError = require("react-dev-utils/printBuildError");
const measureFileSizesBeforeBuild = const measureFileSizesBeforeBuild = FileSizeReporter.measureFileSizesBeforeBuild;
FileSizeReporter.measureFileSizesBeforeBuild;
const printFileSizesAfterBuild = FileSizeReporter.printFileSizesAfterBuild; const printFileSizesAfterBuild = FileSizeReporter.printFileSizesAfterBuild;
const useYarn = fs.existsSync(paths.yarnLockFile); const useYarn = fs.existsSync(paths.yarnLockFile);
@@ -78,9 +77,7 @@ checkBrowsers(paths.appPath, isInteractive)
" to learn more about each warning." " to learn more about each warning."
); );
console.log( console.log(
"To ignore, add " + "To ignore, add " + chalk.cyan("// eslint-disable-next-line") + " to the line before.\n"
chalk.cyan("// eslint-disable-next-line") +
" to the line before.\n"
); );
} else { } else {
console.log(chalk.green("Compiled successfully.\n")); console.log(chalk.green("Compiled successfully.\n"));
@@ -98,18 +95,9 @@ checkBrowsers(paths.appPath, isInteractive)
const publicUrl = paths.publicUrlOrPath; const publicUrl = paths.publicUrlOrPath;
const publicPath = config.output.publicPath; const publicPath = config.output.publicPath;
const buildFolder = path.relative(process.cwd(), paths.appBuild); const buildFolder = path.relative(process.cwd(), paths.appBuild);
printHostingInstructions( printHostingInstructions(appPackage, publicUrl, publicPath, buildFolder, useYarn);
appPackage,
publicUrl,
publicPath,
buildFolder,
useYarn
);
// version and md5 files // version and md5 files
fs.writeFileSync( fs.writeFileSync(`${buildFolder}/VERSION`, require("../package.json").version);
`${buildFolder}/VERSION`,
require("../package.json").version
);
const hash = md5File.sync(`${buildFolder}/VERSION`); const hash = md5File.sync(`${buildFolder}/VERSION`);
fs.writeFileSync(`${buildFolder}/web.rustchat.md5`, hash); fs.writeFileSync(`${buildFolder}/web.rustchat.md5`, hash);
}, },
@@ -153,7 +141,7 @@ function build(previousFileSizes) {
messages = formatWebpackMessages({ messages = formatWebpackMessages({
errors: [errMessage], errors: [errMessage],
warnings: [], warnings: []
}); });
} else { } else {
messages = formatWebpackMessages( messages = formatWebpackMessages(
@@ -170,8 +158,7 @@ function build(previousFileSizes) {
} }
if ( if (
process.env.CI && process.env.CI &&
(typeof process.env.CI !== "string" || (typeof process.env.CI !== "string" || process.env.CI.toLowerCase() !== "false") &&
process.env.CI.toLowerCase() !== "false") &&
messages.warnings.length messages.warnings.length
) { ) {
// Ignore sourcemap warnings in CI builds. See #8227 for more info. // Ignore sourcemap warnings in CI builds. See #8227 for more info.
@@ -192,7 +179,7 @@ function build(previousFileSizes) {
const resolveArgs = { const resolveArgs = {
stats, stats,
previousFileSizes, previousFileSizes,
warnings: messages.warnings, warnings: messages.warnings
}; };
if (writeStatsJson) { if (writeStatsJson) {
@@ -210,6 +197,6 @@ function build(previousFileSizes) {
function copyPublicFolder() { function copyPublicFolder() {
fs.copySync(paths.appPublic, paths.appBuild, { fs.copySync(paths.appPublic, paths.appBuild, {
dereference: true, dereference: true,
filter: (file) => file !== paths.appHtml, filter: (file) => file !== paths.appHtml
}); });
} }
+109 -108
View File
@@ -1,38 +1,37 @@
'use strict'; "use strict";
// Do this as the first thing so that any code reading it knows the right env. // Do this as the first thing so that any code reading it knows the right env.
process.env.BABEL_ENV = 'development'; process.env.BABEL_ENV = "development";
process.env.NODE_ENV = 'development'; process.env.NODE_ENV = "development";
// Makes the script crash on unhandled rejections instead of silently // Makes the script crash on unhandled rejections instead of silently
// ignoring them. In the future, promise rejections that are not handled will // ignoring them. In the future, promise rejections that are not handled will
// terminate the Node.js process with a non-zero exit code. // terminate the Node.js process with a non-zero exit code.
process.on('unhandledRejection', (err) => { process.on("unhandledRejection", (err) => {
throw err; throw err;
}); });
// Ensure environment variables are read. // Ensure environment variables are read.
require('../config/env'); require("../config/env");
const fs = require("fs");
const fs = require('fs'); const chalk = require("react-dev-utils/chalk");
const chalk = require('react-dev-utils/chalk'); const webpack = require("webpack");
const webpack = require('webpack'); const WebpackDevServer = require("webpack-dev-server");
const WebpackDevServer = require('webpack-dev-server'); const clearConsole = require("react-dev-utils/clearConsole");
const clearConsole = require('react-dev-utils/clearConsole'); const checkRequiredFiles = require("react-dev-utils/checkRequiredFiles");
const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles');
const { const {
choosePort, choosePort,
createCompiler, createCompiler,
prepareProxy, prepareProxy,
prepareUrls prepareUrls
} = require('react-dev-utils/WebpackDevServerUtils'); } = require("react-dev-utils/WebpackDevServerUtils");
const openBrowser = require('react-dev-utils/openBrowser'); const openBrowser = require("react-dev-utils/openBrowser");
const semver = require('semver'); const semver = require("semver");
const paths = require('../config/paths'); const paths = require("../config/paths");
const configFactory = require('../config/webpack.config'); const configFactory = require("../config/webpack.config");
const createDevServerConfig = require('../config/webpackDevServer.config'); const createDevServerConfig = require("../config/webpackDevServer.config");
const getClientEnvironment = require('../config/env'); const getClientEnvironment = require("../config/env");
const react = require(require.resolve('react', { paths: [paths.appPath] })); const react = require(require.resolve("react", { paths: [paths.appPath] }));
const env = getClientEnvironment(paths.publicUrlOrPath.slice(0, -1)); const env = getClientEnvironment(paths.publicUrlOrPath.slice(0, -1));
const useYarn = fs.existsSync(paths.yarnLockFile); const useYarn = fs.existsSync(paths.yarnLockFile);
@@ -40,100 +39,102 @@ const isInteractive = process.stdout.isTTY;
// Warn and crash if required files are missing // Warn and crash if required files are missing
if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) { if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) {
process.exit(1); process.exit(1);
} }
// Tools like Cloud9 rely on this. // Tools like Cloud9 rely on this.
const DEFAULT_PORT = parseInt(process.env.PORT, 10) || 3009; const DEFAULT_PORT = parseInt(process.env.PORT, 10) || 3009;
const HOST = process.env.HOST || '0.0.0.0'; const HOST = process.env.HOST || "0.0.0.0";
if (process.env.HOST) { if (process.env.HOST) {
console.log( console.log(
chalk.cyan( chalk.cyan(
`Attempting to bind to HOST environment variable: ${chalk.yellow(chalk.bold(process.env.HOST))}` `Attempting to bind to HOST environment variable: ${chalk.yellow(
) chalk.bold(process.env.HOST)
); )}`
console.log(`If this was unintentional, check that you haven't mistakenly set it in your shell.`); )
console.log(`Learn more here: ${chalk.yellow('https://cra.link/advanced-config')}`); );
console.log(); console.log(`If this was unintentional, check that you haven't mistakenly set it in your shell.`);
console.log(`Learn more here: ${chalk.yellow("https://cra.link/advanced-config")}`);
console.log();
} }
// We require that you explicitly set browsers and do not fall back to // We require that you explicitly set browsers and do not fall back to
// browserslist defaults. // browserslist defaults.
const { checkBrowsers } = require('react-dev-utils/browsersHelper'); const { checkBrowsers } = require("react-dev-utils/browsersHelper");
checkBrowsers(paths.appPath, isInteractive) checkBrowsers(paths.appPath, isInteractive)
.then(() => { .then(() => {
// We attempt to use the default port but if it is busy, we offer the user to // We attempt to use the default port but if it is busy, we offer the user to
// run on a different port. `choosePort()` Promise resolves to the next free port. // run on a different port. `choosePort()` Promise resolves to the next free port.
return choosePort(HOST, DEFAULT_PORT); return choosePort(HOST, DEFAULT_PORT);
}) })
.then((port) => { .then((port) => {
if (port == null) { if (port == null) {
// We have not found a port. // We have not found a port.
return; return;
} }
const config = configFactory('development'); const config = configFactory("development");
const protocol = process.env.HTTPS === 'true' ? 'https' : 'http'; const protocol = process.env.HTTPS === "true" ? "https" : "http";
const appName = require(paths.appPackageJson).name; const appName = require(paths.appPackageJson).name;
const useTypeScript = fs.existsSync(paths.appTsConfig); const useTypeScript = fs.existsSync(paths.appTsConfig);
const urls = prepareUrls(protocol, HOST, port, paths.publicUrlOrPath.slice(0, -1)); const urls = prepareUrls(protocol, HOST, port, paths.publicUrlOrPath.slice(0, -1));
// Create a webpack compiler that is configured with custom messages. // Create a webpack compiler that is configured with custom messages.
const compiler = createCompiler({ const compiler = createCompiler({
appName, appName,
config, config,
urls, urls,
useYarn, useYarn,
useTypeScript, useTypeScript,
webpack webpack
});
// Load proxy config
const proxySetting = require(paths.appPackageJson).proxy;
const proxyConfig = prepareProxy(proxySetting, paths.appPublic, paths.publicUrlOrPath);
// Serve webpack assets generated by the compiler over a web server.
const serverConfig = {
...createDevServerConfig(proxyConfig, urls.lanUrlForConfig),
host: HOST,
port
};
const devServer = new WebpackDevServer(serverConfig, compiler);
// Launch WebpackDevServer.
devServer.startCallback(() => {
if (isInteractive) {
clearConsole();
}
if (env.raw.FAST_REFRESH && semver.lt(react.version, "16.10.0")) {
console.log(
chalk.yellow(
`Fast Refresh requires React 16.10 or higher. You are using React ${react.version}.`
)
);
}
console.log(chalk.cyan("Starting the development server...\n"));
openBrowser(urls.localUrlForBrowser);
});
["SIGINT", "SIGTERM"].forEach(function (sig) {
process.on(sig, function () {
devServer.close();
process.exit();
});
});
if (process.env.CI !== "true") {
// Gracefully exit when stdin ends
process.stdin.on("end", function () {
devServer.close();
process.exit();
});
}
})
.catch((err) => {
if (err && err.message) {
console.log(err.message);
}
process.exit(1);
}); });
// Load proxy config
const proxySetting = require(paths.appPackageJson).proxy;
const proxyConfig = prepareProxy(proxySetting, paths.appPublic, paths.publicUrlOrPath);
// Serve webpack assets generated by the compiler over a web server.
const serverConfig = {
...createDevServerConfig(proxyConfig, urls.lanUrlForConfig),
host: HOST,
port
};
const devServer = new WebpackDevServer(serverConfig, compiler);
// Launch WebpackDevServer.
devServer.startCallback(() => {
if (isInteractive) {
clearConsole();
}
if (env.raw.FAST_REFRESH && semver.lt(react.version, '16.10.0')) {
console.log(
chalk.yellow(
`Fast Refresh requires React 16.10 or higher. You are using React ${react.version}.`
)
);
}
console.log(chalk.cyan('Starting the development server...\n'));
openBrowser(urls.localUrlForBrowser);
});
['SIGINT', 'SIGTERM'].forEach(function (sig) {
process.on(sig, function () {
devServer.close();
process.exit();
});
});
if (process.env.CI !== 'true') {
// Gracefully exit when stdin ends
process.stdin.on('end', function () {
devServer.close();
process.exit();
});
}
})
.catch((err) => {
if (err && err.message) {
console.log(err.message);
}
process.exit(1);
});
+12 -12
View File
@@ -4,44 +4,44 @@ import { KEY_UID, CACHE_VERSION } from "../config";
const tables = [ const tables = [
{ {
storeName: "channels", storeName: "channels",
description: "store channel list", description: "store channel list"
}, },
{ {
storeName: "contacts", storeName: "contacts",
description: "store contact list", description: "store contact list"
}, },
{ {
storeName: "messageDM", storeName: "messageDM",
description: "store DM message with IDs", description: "store DM message with IDs"
}, },
{ {
storeName: "messageChannel", storeName: "messageChannel",
description: "store channel message with IDs", description: "store channel message with IDs"
}, },
{ {
storeName: "message", storeName: "message",
description: "store message with key-val full data", description: "store message with key-val full data"
}, },
{ {
storeName: "messageFile", storeName: "messageFile",
description: "store file message list refs", description: "store file message list refs"
}, },
{ {
storeName: "messageReaction", storeName: "messageReaction",
description: "store message reaction with key-val full data", description: "store message reaction with key-val full data"
}, },
{ {
storeName: "footprint", storeName: "footprint",
description: "store user visit data", description: "store user visit data"
}, },
{ {
storeName: "server", storeName: "server",
description: "store server data", description: "store server data"
}, },
{ {
storeName: "ui", storeName: "ui",
description: "store UI state", description: "store UI state"
}, }
// { // {
// storeName: "message", // storeName: "message",
// description: "store message with key-val full data", // description: "store message with key-val full data",
@@ -55,7 +55,7 @@ const initCache = () => {
window.CACHE[storeName] = localforage.createInstance({ window.CACHE[storeName] = localforage.createInstance({
name, name,
storeName, storeName,
description, description
}); });
}); });
}; };
+1 -1
View File
@@ -25,7 +25,7 @@ const useRehydrate = () => {
message: { replying: {} }, message: { replying: {} },
footprint: {}, footprint: {},
ui: {}, ui: {},
server: {}, server: {}
}; };
const tables = Object.keys(window.CACHE); const tables = Object.keys(window.CACHE);
const results = await Promise.all( const results = await Promise.all(
+4 -4
View File
@@ -7,7 +7,7 @@ export const ContentTypes = {
file: "rustchat/file", file: "rustchat/file",
formData: "multipart/form-data", formData: "multipart/form-data",
json: "application/json", json: "application/json",
archive: "rustchat/archive", archive: "rustchat/archive"
}; };
export const firebaseConfig = { export const firebaseConfig = {
apiKey: "AIzaSyDyJ6B1Ouenoha_gdGkBwIkBNStlwhlbO0", apiKey: "AIzaSyDyJ6B1Ouenoha_gdGkBwIkBNStlwhlbO0",
@@ -16,11 +16,11 @@ export const firebaseConfig = {
storageBucket: "rustchat-develop.appspot.com", storageBucket: "rustchat-develop.appspot.com",
messagingSenderId: "418687074928", messagingSenderId: "418687074928",
appId: "1:418687074928:web:753286adbf239f5af9eab5", appId: "1:418687074928:web:753286adbf239f5af9eab5",
measurementId: "G-XV476KEC8P", measurementId: "G-XV476KEC8P"
}; };
export const ChatPrefixs = { export const ChatPrefixs = {
channel: "#", channel: "#",
user: "@", user: "@"
}; };
export const vapidKey = `BGXCn-5YRXSFw38Q9lUKJ5bibL212-yIQn1pCvthGhp6_KwA29FO1Ax_d_7if1vfC2a5wTSVO8AcZrc-Hm1aS0Y`; export const vapidKey = `BGXCn-5YRXSFw38Q9lUKJ5bibL212-yIQn1pCvthGhp6_KwA29FO1Ax_d_7if1vfC2a5wTSVO8AcZrc-Hm1aS0Y`;
// "840319286941-6ds7lbvk55eq8mjortf68cb2ll65lprt.apps.googleusercontent.com"; // "840319286941-6ds7lbvk55eq8mjortf68cb2ll65lprt.apps.googleusercontent.com";
@@ -38,6 +38,6 @@ export const KEY_PWA_INSTALLED = "RUSTCHAT_PWA_INSTALLED";
export const Emojis = ["👍", "❤️", "😄", "👀", "👎", "🎉", "🙁", "🚀"]; export const Emojis = ["👍", "❤️", "😄", "👀", "👎", "🎉", "🙁", "🚀"];
export const Views = { export const Views = {
item: "item", item: "item",
grid: "grid", grid: "grid"
}; };
export default BASE_URL; export default BASE_URL;
+13 -13
View File
@@ -21,7 +21,7 @@ const operations = [
"message", "message",
"ui", "ui",
"footprint", "footprint",
"server", "server"
]; ];
// Create the middleware instance and methods // Create the middleware instance and methods
@@ -51,7 +51,7 @@ listenerMiddleware.startListening({
rtkqHandler({ rtkqHandler({
operation, operation,
payload, payload,
dispatch: listenerApi.dispatch, dispatch: listenerApi.dispatch
}); });
} }
break; break;
@@ -60,7 +60,7 @@ listenerMiddleware.startListening({
await channelsHandler({ await channelsHandler({
operation, operation,
payload, payload,
data: state, data: state
}); });
} }
break; break;
@@ -69,7 +69,7 @@ listenerMiddleware.startListening({
await contactsHandler({ await contactsHandler({
operation, operation,
payload, payload,
data: state, data: state
}); });
} }
break; break;
@@ -78,7 +78,7 @@ listenerMiddleware.startListening({
await channelMsgHandler({ await channelMsgHandler({
operation, operation,
payload, payload,
data: state, data: state
}); });
} }
break; break;
@@ -87,7 +87,7 @@ listenerMiddleware.startListening({
await dmMsgHandler({ await dmMsgHandler({
operation, operation,
payload, payload,
data: state, data: state
}); });
} }
break; break;
@@ -96,7 +96,7 @@ listenerMiddleware.startListening({
await fileMessageHandler({ await fileMessageHandler({
operation, operation,
// payload, // payload,
data: state, data: state
}); });
} }
break; break;
@@ -105,7 +105,7 @@ listenerMiddleware.startListening({
await messageHandler({ await messageHandler({
operation, operation,
payload, payload,
data: state, data: state
}); });
} }
break; break;
@@ -114,7 +114,7 @@ listenerMiddleware.startListening({
await reactionHandler({ await reactionHandler({
operation, operation,
payload, payload,
data: state, data: state
}); });
} }
break; break;
@@ -123,7 +123,7 @@ listenerMiddleware.startListening({
await footprintHandler({ await footprintHandler({
operation, operation,
payload, payload,
data: state, data: state
}); });
} }
break; break;
@@ -132,7 +132,7 @@ listenerMiddleware.startListening({
await UIHandler({ await UIHandler({
operation, operation,
payload, payload,
data: state, data: state
}); });
} }
break; break;
@@ -141,7 +141,7 @@ listenerMiddleware.startListening({
await serverHandler({ await serverHandler({
operation, operation,
payload, payload,
data: state, data: state
}); });
} }
break; break;
@@ -149,7 +149,7 @@ listenerMiddleware.startListening({
default: default:
break; break;
} }
}, }
}); });
export default listenerMiddleware; 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 { setAuthData, updateToken, resetAuthData, updateInitialized } from "../slices/auth.data";
import BASE_URL, { KEY_DEVICE_KEY } from "../config"; import BASE_URL, { KEY_DEVICE_KEY } from "../config";
const getDeviceId = () => { const getDeviceId = () => {
let d = localStorage.getItem(KEY_DEVICE_KEY); let d = localStorage.getItem(KEY_DEVICE_KEY);
if (!d) { if (!d) {
d = `web:${nanoid()}`; d = `web:${nanoid()}`;
localStorage.setItem(KEY_DEVICE_KEY, d); localStorage.setItem(KEY_DEVICE_KEY, d);
} }
return d; return d;
}; };
export const authApi = createApi({ export const authApi = createApi({
reducerPath: "authApi", reducerPath: "authApi",
baseQuery, baseQuery,
endpoints: (builder) => ({ endpoints: (builder) => ({
login: builder.mutation({ login: builder.mutation({
query: (credentials) => ({ query: (credentials) => ({
url: "token/login", url: "token/login",
method: "POST", method: "POST",
body: { body: {
credential: credentials, credential: credentials,
device: getDeviceId(), device: getDeviceId(),
device_token: "test" device_token: "test"
} }
}), }),
transformResponse: (data) => { transformResponse: (data) => {
const { avatar_updated_at } = data.user; const { avatar_updated_at } = data.user;
data.user.avatar = data.user.avatar =
avatar_updated_at == 0 avatar_updated_at == 0
? "" ? ""
: `${BASE_URL}/resource/avatar?uid=${data.user.uid}&t=${avatar_updated_at}`; : `${BASE_URL}/resource/avatar?uid=${data.user.uid}&t=${avatar_updated_at}`;
return data; return data;
}, },
async onQueryStarted(params, { dispatch, queryFulfilled }) { async onQueryStarted(params, { dispatch, queryFulfilled }) {
try { try {
const { data } = await queryFulfilled; const { data } = await queryFulfilled;
if (data) { if (data) {
console.log("login resp", data); console.log("login resp", data);
dispatch(setAuthData(data)); dispatch(setAuthData(data));
} }
} catch { } catch {
console.log("login error"); console.log("login error");
} }
} }
}), }),
// 更新token // 更新token
renew: builder.mutation({ renew: builder.mutation({
query: ({ token, refreshToken }) => ({ query: ({ token, refreshToken }) => ({
url: "/token/renew", url: "/token/renew",
method: "POST", method: "POST",
body: { body: {
token, token,
refresh_token: refreshToken refresh_token: refreshToken
} }
}), }),
async onQueryStarted(params, { dispatch, queryFulfilled }) { async onQueryStarted(params, { dispatch, queryFulfilled }) {
try { try {
const { data } = await queryFulfilled; const { data } = await queryFulfilled;
dispatch(updateToken(data)); dispatch(updateToken(data));
} catch { } catch {
dispatch(resetAuthData()); dispatch(resetAuthData());
console.log("renew token error"); console.log("renew token error");
} }
} }
}), }),
// 更新 device token // 更新 device token
updateDeviceToken: builder.mutation({ updateDeviceToken: builder.mutation({
query: (device_token) => ({ query: (device_token) => ({
url: "/token/device_token", url: "/token/device_token",
method: "PUT", method: "PUT",
body: { body: {
device_token device_token
} }
}) })
}), }),
// 获取openid // 获取openid
getOpenid: builder.mutation({ getOpenid: builder.mutation({
query: ({ issuer, redirect_uri }) => ({ query: ({ issuer, redirect_uri }) => ({
url: "/token/openid/authorize", url: "/token/openid/authorize",
method: "POST", method: "POST",
body: { body: {
issuer, issuer,
redirect_uri redirect_uri
} }
}) })
}), }),
checkInviteTokenValid: builder.mutation({ checkInviteTokenValid: builder.mutation({
query: (token) => ({ query: (token) => ({
url: "user/check_invite_magic_token", url: "user/check_invite_magic_token",
method: "POST", method: "POST",
body: { magic_token: token } body: { magic_token: token }
}) })
}), }),
updatePassword: builder.mutation({ updatePassword: builder.mutation({
query: ({ old_password, new_password }) => ({ query: ({ old_password, new_password }) => ({
url: "user/change_password", url: "user/change_password",
method: "POST", method: "POST",
body: { old_password, new_password } body: { old_password, new_password }
}) })
}), }),
sendMagicLink: builder.mutation({ sendMagicLink: builder.mutation({
query: (email) => ({ query: (email) => ({
url: "token/send_magic_link", url: "token/send_magic_link",
method: "POST", method: "POST",
body: { email } body: { email }
}) })
}), }),
getMetamaskNonce: builder.query({ getMetamaskNonce: builder.query({
query: (address) => ({ query: (address) => ({
url: `/token/metamask/nonce?public_address=${address}` url: `/token/metamask/nonce?public_address=${address}`
}) })
}), }),
getCredentials: builder.query({ getCredentials: builder.query({
query: () => ({ query: () => ({
url: `/token/credentials` url: `/token/credentials`
}) })
}), }),
logout: builder.query({ logout: builder.query({
query: () => ({ url: `token/logout` }), query: () => ({ url: `token/logout` }),
async onQueryStarted(params, { dispatch, queryFulfilled }) { async onQueryStarted(params, { dispatch, queryFulfilled }) {
try { try {
await queryFulfilled; await queryFulfilled;
dispatch(resetAuthData()); dispatch(resetAuthData());
} catch { } catch {
console.log("logout error"); console.log("logout error");
} }
} }
}), }),
getInitialized: builder.query({ getInitialized: builder.query({
query: () => ({ query: () => ({
url: `/admin/system/initialized` url: `/admin/system/initialized`
}), }),
async onQueryStarted(params, { dispatch, queryFulfilled }) { async onQueryStarted(params, { dispatch, queryFulfilled }) {
try { try {
const { data: isInitialized } = await queryFulfilled; const { data: isInitialized } = await queryFulfilled;
dispatch(updateInitialized(isInitialized)); dispatch(updateInitialized(isInitialized));
} catch { } catch {
console.log("api initialized error"); console.log("api initialized error");
} }
} }
})
}) })
})
}); });
export const { export const {
useGetInitializedQuery, useGetInitializedQuery,
useSendMagicLinkMutation, useSendMagicLinkMutation,
useGetCredentialsQuery, useGetCredentialsQuery,
useUpdateDeviceTokenMutation, useUpdateDeviceTokenMutation,
useGetOpenidMutation, useGetOpenidMutation,
useRenewMutation, useRenewMutation,
useLazyGetMetamaskNonceQuery, useLazyGetMetamaskNonceQuery,
useLoginMutation, useLoginMutation,
useLazyLogoutQuery, useLazyLogoutQuery,
useCheckInviteTokenValidMutation, useCheckInviteTokenValidMutation,
useUpdatePasswordMutation useUpdatePasswordMutation
} = authApi; } = authApi;
+6 -13
View File
@@ -18,7 +18,7 @@ const whiteList = [
"getMetamaskNonce", "getMetamaskNonce",
"renew", "renew",
"getInitialized", "getInitialized",
"createAdmin", "createAdmin"
]; ];
const baseQuery = fetchBaseQuery({ const baseQuery = fetchBaseQuery({
baseUrl: BASE_URL, baseUrl: BASE_URL,
@@ -29,7 +29,7 @@ const baseQuery = fetchBaseQuery({
headers.set(tokenHeader, token); headers.set(tokenHeader, token);
} }
return headers; return headers;
}, }
}); });
let waitingForRenew = null; let waitingForRenew = null;
const baseQueryWithTokenCheck = async (args, api, extraOptions) => { const baseQueryWithTokenCheck = async (args, api, extraOptions) => {
@@ -37,16 +37,9 @@ const baseQueryWithTokenCheck = async (args, api, extraOptions) => {
await waitingForRenew; await waitingForRenew;
} }
// 先检查token是否过期,过期则renew // 先检查token是否过期,过期则renew
const { const { token, refreshToken, expireTime = +new Date() } = api.getState().authData;
token,
refreshToken,
expireTime = +new Date(),
} = api.getState().authData;
let result = null; let result = null;
if ( if (!whiteList.includes(api.endpoint) && dayjs().isAfter(new Date(expireTime - 20 * 1000))) {
!whiteList.includes(api.endpoint) &&
dayjs().isAfter(new Date(expireTime - 20 * 1000))
) {
// 快过期了,renew // 快过期了,renew
waitingForRenew = baseQuery( waitingForRenew = baseQuery(
{ {
@@ -54,8 +47,8 @@ const baseQueryWithTokenCheck = async (args, api, extraOptions) => {
method: "POST", method: "POST",
body: { body: {
token, token,
refresh_token: refreshToken, refresh_token: refreshToken
}, }
}, },
api, api,
extraOptions extraOptions
+31 -34
View File
@@ -15,10 +15,10 @@ export const channelApi = createApi({
refetchOnFocus: true, refetchOnFocus: true,
endpoints: (builder) => ({ endpoints: (builder) => ({
getChannels: builder.query({ getChannels: builder.query({
query: () => ({ url: `group` }), query: () => ({ url: `group` })
}), }),
getChannel: builder.query({ getChannel: builder.query({
query: (id) => ({ url: `group/${id}` }), query: (id) => ({ url: `group/${id}` })
}), }),
leaveChannel: builder.query({ leaveChannel: builder.query({
query: (id) => ({ url: `group/${id}/leave` }), query: (id) => ({ url: `group/${id}/leave` }),
@@ -29,25 +29,22 @@ export const channelApi = createApi({
} catch { } catch {
console.log("channel update failed"); console.log("channel update failed");
} }
}, }
}), }),
createChannel: builder.mutation({ createChannel: builder.mutation({
query: (data) => ({ query: (data) => ({
url: "group", url: "group",
method: "POST", method: "POST",
body: data, body: data
}), })
}), }),
updateChannel: builder.mutation({ updateChannel: builder.mutation({
query: ({ id, ...data }) => ({ query: ({ id, ...data }) => ({
url: `group/${id}`, url: `group/${id}`,
method: "PUT", method: "PUT",
body: data, body: data
}), }),
async onQueryStarted( async onQueryStarted({ id, name, description }, { dispatch, queryFulfilled }) {
{ id, name, description },
{ dispatch, queryFulfilled }
) {
// id: who send to ,from_uid: who sent // id: who send to ,from_uid: who sent
const patchResult = dispatch(updateChannel({ id, name, description })); const patchResult = dispatch(updateChannel({ id, name, description }));
try { try {
@@ -56,13 +53,13 @@ export const channelApi = createApi({
console.log("channel update failed"); console.log("channel update failed");
patchResult.undo(); patchResult.undo();
} }
}, }
}), }),
getHistoryMessages: builder.query({ getHistoryMessages: builder.query({
query: ({ id, mid = null, limit = 100 }) => ({ query: ({ id, mid = null, limit = 100 }) => ({
url: mid url: mid
? `/group/${id}/history?before=${mid}&limit=${limit}` ? `/group/${id}/history?before=${mid}&limit=${limit}`
: `/group/${id}/history?limit=${limit}`, : `/group/${id}/history?limit=${limit}`
}), }),
async onQueryStarted(params, { dispatch, getState, queryFulfilled }) { async onQueryStarted(params, { dispatch, getState, queryFulfilled }) {
const { data: messages } = await queryFulfilled; const { data: messages } = await queryFulfilled;
@@ -71,34 +68,34 @@ export const channelApi = createApi({
handleChatMessage(msg, dispatch, getState()); handleChatMessage(msg, dispatch, getState());
}); });
} }
}, }
}), }),
createInviteLink: builder.query({ createInviteLink: builder.query({
query: (gid) => ({ query: (gid) => ({
headers: { headers: {
"content-type": "text/plain", "content-type": "text/plain",
accept: "text/plain", accept: "text/plain"
}, },
url: `/group/${gid}/create_invite_link`, url: `/group/${gid}/create_invite_link`,
responseHandler: (response) => response.text(), responseHandler: (response) => response.text()
}), }),
transformResponse: (link) => { transformResponse: (link) => {
// 替换掉域名 // 替换掉域名
const invite = new URL(link); const invite = new URL(link);
return `${location.origin}${invite.pathname}${invite.search}${invite.hash}`; return `${location.origin}${invite.pathname}${invite.search}${invite.hash}`;
}, }
}), }),
removeChannel: builder.query({ removeChannel: builder.query({
query: (id) => ({ query: (id) => ({
url: `group/${id}`, url: `group/${id}`,
method: "DELETE", method: "DELETE"
}), }),
async onQueryStarted(id, { dispatch, getState, queryFulfilled }) { async onQueryStarted(id, { dispatch, getState, queryFulfilled }) {
const { const {
channelMessage, channelMessage,
ui: { ui: {
remeberedNavs: { chat: remeberedPath }, remeberedNavs: { chat: remeberedPath }
}, }
} = getState(); } = getState();
try { try {
await queryFulfilled; await queryFulfilled;
@@ -115,7 +112,7 @@ export const channelApi = createApi({
} catch { } catch {
console.log("remove channel error"); console.log("remove channel error");
} }
}, }
}), }),
sendChannelMsg: builder.mutation({ sendChannelMsg: builder.mutation({
query: ({ id, content, type = "text", properties = "" }) => ({ query: ({ id, content, type = "text", properties = "" }) => ({
@@ -123,38 +120,38 @@ export const channelApi = createApi({
"content-type": ContentTypes[type], "content-type": ContentTypes[type],
"X-Properties": properties "X-Properties": properties
? btoa(unescape(encodeURIComponent(JSON.stringify(properties)))) ? btoa(unescape(encodeURIComponent(JSON.stringify(properties))))
: "", : ""
}, },
url: `group/${id}/send`, url: `group/${id}/send`,
method: "POST", method: "POST",
body: type == "file" ? JSON.stringify(content) : content, body: type == "file" ? JSON.stringify(content) : content
}), }),
async onQueryStarted(param1, param2) { async onQueryStarted(param1, param2) {
await onMessageSendStarted.call(this, param1, param2, "channel"); await onMessageSendStarted.call(this, param1, param2, "channel");
}, }
}), }),
addMembers: builder.mutation({ addMembers: builder.mutation({
query: ({ id, members }) => ({ query: ({ id, members }) => ({
url: `group/${id}/members/add`, url: `group/${id}/members/add`,
method: "POST", method: "POST",
body: members, body: members
}), })
}), }),
removeMembers: builder.mutation({ removeMembers: builder.mutation({
query: ({ id, members }) => ({ query: ({ id, members }) => ({
url: `group/${id}/members/remove`, url: `group/${id}/members/remove`,
method: "POST", method: "POST",
body: members, body: members
}), })
}), }),
updateIcon: builder.mutation({ updateIcon: builder.mutation({
query: ({ gid, image }) => ({ query: ({ gid, image }) => ({
headers: { headers: {
"content-type": "image/png", "content-type": "image/png"
}, },
url: `/group/${gid}/avatar`, url: `/group/${gid}/avatar`,
method: "POST", method: "POST",
body: image, body: image
}), }),
async onQueryStarted({ gid }, { dispatch, queryFulfilled }) { async onQueryStarted({ gid }, { dispatch, queryFulfilled }) {
try { try {
@@ -162,15 +159,15 @@ export const channelApi = createApi({
dispatch( dispatch(
updateChannel({ updateChannel({
id: gid, 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) { } catch (error) {
console.log("err", error); console.log("err", error);
} }
}, }
}), })
}), })
}); });
export const { export const {
@@ -186,5 +183,5 @@ export const {
useSendChannelMsgMutation, useSendChannelMsgMutation,
useAddMembersMutation, useAddMembersMutation,
useRemoveMembersMutation, useRemoveMembersMutation,
useUpdateIconMutation, useUpdateIconMutation
} = channelApi; } = channelApi;
+21 -21
View File
@@ -45,23 +45,23 @@ export const contactApi = createApi({
} catch { } catch {
console.log("get contact list error"); console.log("get contact list error");
} }
}, }
}), }),
deleteContact: builder.query({ deleteContact: builder.query({
query: (uid) => ({ url: `/admin/user/${uid}`, method: "DELETE" }), query: (uid) => ({ url: `/admin/user/${uid}`, method: "DELETE" })
}), }),
updateContact: builder.mutation({ updateContact: builder.mutation({
query: ({ id, ...rest }) => ({ query: ({ id, ...rest }) => ({
url: `/admin/user/${id}`, url: `/admin/user/${id}`,
body: rest, body: rest,
method: "PUT", method: "PUT"
}), })
}), }),
updateMuteSetting: builder.mutation({ updateMuteSetting: builder.mutation({
query: (data) => ({ query: (data) => ({
url: `/user/mute`, url: `/user/mute`,
method: "POST", method: "POST",
body: data, body: data
}), }),
async onQueryStarted(data, { dispatch, queryFulfilled }) { async onQueryStarted(data, { dispatch, queryFulfilled }) {
try { try {
@@ -70,31 +70,31 @@ export const contactApi = createApi({
} catch (error) { } catch (error) {
console.log("update mute failed", error); console.log("update mute failed", error);
} }
}, }
}), }),
updateAvatar: builder.mutation({ updateAvatar: builder.mutation({
query: (data) => ({ query: (data) => ({
headers: { headers: {
"content-type": "image/png", "content-type": "image/png"
}, },
url: `user/avatar`, url: `user/avatar`,
method: "POST", method: "POST",
body: data, body: data
}), })
}), }),
updateInfo: builder.mutation({ updateInfo: builder.mutation({
query: (data) => ({ query: (data) => ({
url: `user`, url: `user`,
method: "PUT", method: "PUT",
body: data, body: data
}), })
}), }),
register: builder.mutation({ register: builder.mutation({
query: (data) => ({ query: (data) => ({
url: `user/register`, url: `user/register`,
method: "POST", method: "POST",
body: data, body: data
}), })
}), }),
sendMsg: builder.mutation({ sendMsg: builder.mutation({
query: ({ id, content, type = "text", properties = "" }) => ({ query: ({ id, content, type = "text", properties = "" }) => ({
@@ -102,21 +102,21 @@ export const contactApi = createApi({
"content-type": ContentTypes[type], "content-type": ContentTypes[type],
"X-Properties": properties "X-Properties": properties
? btoa(unescape(encodeURIComponent(JSON.stringify(properties)))) ? btoa(unescape(encodeURIComponent(JSON.stringify(properties))))
: "", : ""
}, },
url: `user/${id}/send`, url: `user/${id}/send`,
method: "POST", method: "POST",
body: type == "file" ? JSON.stringify(content) : content, body: type == "file" ? JSON.stringify(content) : content
}), }),
async onQueryStarted(param1, param2) { async onQueryStarted(param1, param2) {
await onMessageSendStarted.call(this, param1, param2, "user"); await onMessageSendStarted.call(this, param1, param2, "user");
}, }
}), }),
getHistoryMessages: builder.query({ getHistoryMessages: builder.query({
query: ({ id, mid = null, limit = 100 }) => ({ query: ({ id, mid = null, limit = 100 }) => ({
url: mid url: mid
? `/user/${id}/history?before=${mid}&limit=${limit}` ? `/user/${id}/history?before=${mid}&limit=${limit}`
: `/user/${id}/history?limit=${limit}`, : `/user/${id}/history?limit=${limit}`
}), }),
async onQueryStarted(params, { dispatch, getState, queryFulfilled }) { async onQueryStarted(params, { dispatch, getState, queryFulfilled }) {
const { data: messages } = await queryFulfilled; const { data: messages } = await queryFulfilled;
@@ -125,9 +125,9 @@ export const contactApi = createApi({
handleChatMessage(msg, dispatch, getState()); handleChatMessage(msg, dispatch, getState());
}); });
} }
}, }
}), })
}), })
}); });
export const { export const {
@@ -140,5 +140,5 @@ export const {
useGetContactsQuery, useGetContactsQuery,
useLazyGetContactsQuery, useLazyGetContactsQuery,
useSendMsgMutation, useSendMsgMutation,
useRegisterMutation, useRegisterMutation
} = contactApi; } = contactApi;
+3 -4
View File
@@ -12,7 +12,7 @@ export const onMessageSendStarted = async (
type = "text", type = "text",
from_uid, from_uid,
reply_mid = null, reply_mid = null,
properties = { local_id: +new Date() }, properties = { local_id: +new Date() }
}, },
{ dispatch, queryFulfilled }, { dispatch, queryFulfilled },
from = "channel" from = "channel"
@@ -42,11 +42,10 @@ export const onMessageSendStarted = async (
properties, properties,
from_uid, from_uid,
reply_mid, reply_mid,
sending: true, sending: true
}; };
const addContextMessage = from == "channel" ? addChannelMsg : addUserMsg; const addContextMessage = from == "channel" ? addChannelMsg : addUserMsg;
const removeContextMessage = const removeContextMessage = from == "channel" ? removeChannelMsg : removeUserMsg;
from == "channel" ? removeChannelMsg : removeUserMsg;
if (!ignoreLocal) { if (!ignoreLocal) {
batch(() => { batch(() => {
dispatch(addMessage({ mid: ts, ...tmpMsg })); dispatch(addMessage({ mid: ts, ...tmpMsg }));
+205 -205
View File
@@ -1,214 +1,214 @@
import { createApi } from '@reduxjs/toolkit/query/react'; import { createApi } from "@reduxjs/toolkit/query/react";
import { ContentTypes } from '../config'; import { ContentTypes } from "../config";
import { updateReadChannels, updateReadUsers } from '../slices/footprint'; import { updateReadChannels, updateReadUsers } from "../slices/footprint";
import { import {
fullfillFavorites, fullfillFavorites,
populateFavorite, populateFavorite,
addFavorite, addFavorite,
deleteFavorite deleteFavorite
} from '../slices/favorites'; } from "../slices/favorites";
import { onMessageSendStarted } from './handlers'; import { onMessageSendStarted } from "./handlers";
import { normalizeArchiveData } from '../../common/utils'; import { normalizeArchiveData } from "../../common/utils";
// import { updateMessage } from "../slices/message"; // import { updateMessage } from "../slices/message";
import baseQuery from './base.query'; import baseQuery from "./base.query";
export const messageApi = createApi({ export const messageApi = createApi({
reducerPath: 'messageApi', reducerPath: "messageApi",
baseQuery, baseQuery,
endpoints: (builder) => ({ endpoints: (builder) => ({
editMessage: builder.mutation({ editMessage: builder.mutation({
query: ({ mid, content, type = 'text' }) => ({ query: ({ mid, content, type = "text" }) => ({
headers: { headers: {
'content-type': ContentTypes[type] "content-type": ContentTypes[type]
}, },
url: `/message/${mid}/edit`, url: `/message/${mid}/edit`,
method: 'PUT', method: "PUT",
body: content body: content
}) })
// async onQueryStarted({mid,content},{dispatch}){ // async onQueryStarted({mid,content},{dispatch}){
// dispatch() // dispatch()
// } // }
}), }),
reactMessage: builder.mutation({ reactMessage: builder.mutation({
query: ({ mid, action }) => ({ query: ({ mid, action }) => ({
url: `/message/${mid}/like`, url: `/message/${mid}/like`,
method: 'PUT', method: "PUT",
body: { action } body: { action }
}) })
}), }),
deleteMessage: builder.query({ deleteMessage: builder.query({
query: (mid) => ({ query: (mid) => ({
url: `/message/${mid}`, url: `/message/${mid}`,
method: 'DELETE' method: "DELETE"
}) })
}), }),
prepareUploadFile: builder.mutation({ prepareUploadFile: builder.mutation({
query: (meta = {}) => ({ query: (meta = {}) => ({
url: `/resource/file/prepare`, url: `/resource/file/prepare`,
method: 'POST', method: "POST",
body: meta body: meta
}) })
}), }),
createArchive: builder.mutation({ createArchive: builder.mutation({
query: (mids = []) => ({ query: (mids = []) => ({
url: `/resource/archive`, url: `/resource/archive`,
method: 'POST', method: "POST",
body: { mid_list: mids } body: { mid_list: mids }
}) })
}), }),
uploadFile: builder.mutation({ uploadFile: builder.mutation({
query: (formData) => ({ query: (formData) => ({
// headers: { // headers: {
// "content-type": ContentTypes.formData, // "content-type": ContentTypes.formData,
// }, // },
url: `/resource/file/upload`, url: `/resource/file/upload`,
method: 'POST', method: "POST",
body: formData body: formData
}), }),
transformResponse: (data) => { transformResponse: (data) => {
console.log('upload file response', data); console.log("upload file response", data);
return data ? data : {}; return data ? data : {};
} }
}), }),
getOGInfo: builder.query({ getOGInfo: builder.query({
query: (url) => ({ query: (url) => ({
url: `/resource/open_graphic_parse?url=${encodeURIComponent(url)}` url: `/resource/open_graphic_parse?url=${encodeURIComponent(url)}`
}) })
}), }),
getArchiveMessage: builder.query({ getArchiveMessage: builder.query({
query: (file_path) => ({ query: (file_path) => ({
url: `/resource/archive?file_path=${encodeURIComponent(file_path)}` url: `/resource/archive?file_path=${encodeURIComponent(file_path)}`
}) })
}), }),
pinMessage: builder.mutation({ pinMessage: builder.mutation({
query: ({ gid, mid }) => ({ query: ({ gid, mid }) => ({
url: `/group/${gid}/pin`, url: `/group/${gid}/pin`,
method: 'POST', method: "POST",
body: { mid } body: { mid }
}) })
}), }),
unpinMessage: builder.mutation({ unpinMessage: builder.mutation({
query: ({ gid, mid }) => ({ query: ({ gid, mid }) => ({
url: `/group/${gid}/unpin`, url: `/group/${gid}/unpin`,
method: 'POST', method: "POST",
body: { mid } body: { mid }
}) })
}), }),
favoriteMessage: builder.mutation({ favoriteMessage: builder.mutation({
query: (mids) => ({ query: (mids) => ({
url: `/favorite`, url: `/favorite`,
method: 'POST', method: "POST",
body: { mid_list: mids } body: { mid_list: mids }
}), }),
async onQueryStarted(mids, { dispatch, queryFulfilled }) { async onQueryStarted(mids, { dispatch, queryFulfilled }) {
try { try {
const { data } = await queryFulfilled; const { data } = await queryFulfilled;
const { created_at, id } = data; const { created_at, id } = data;
dispatch(addFavorite({ id, created_at })); dispatch(addFavorite({ id, created_at }));
dispatch(messageApi.endpoints.getFavoriteDetails.initiate(id)); dispatch(messageApi.endpoints.getFavoriteDetails.initiate(id));
} catch (err) { } catch (err) {
console.log('get favorite list error', err); console.log("get favorite list error", err);
} }
} }
}), }),
removeFavorite: builder.query({ removeFavorite: builder.query({
query: (id) => ({ query: (id) => ({
url: `/favorite/${id}`, url: `/favorite/${id}`,
method: 'DELETE' method: "DELETE"
}), }),
async onQueryStarted(id, { dispatch, queryFulfilled }) { async onQueryStarted(id, { dispatch, queryFulfilled }) {
try { try {
await queryFulfilled; await queryFulfilled;
dispatch(deleteFavorite(id)); dispatch(deleteFavorite(id));
} catch (err) { } catch (err) {
console.log('get favorite list error', err); console.log("get favorite list error", err);
} }
} }
}), }),
getFavoriteDetails: builder.query({ getFavoriteDetails: builder.query({
query: (id) => ({ query: (id) => ({
url: `/favorite/${id}` url: `/favorite/${id}`
}), }),
async onQueryStarted(id, { dispatch, queryFulfilled, getState }) { async onQueryStarted(id, { dispatch, queryFulfilled, getState }) {
try { try {
const { data } = await queryFulfilled; const { data } = await queryFulfilled;
const loginUid = getState().authData.uid; const loginUid = getState().authData.uid;
const messages = normalizeArchiveData(data, id, loginUid); const messages = normalizeArchiveData(data, id, loginUid);
dispatch(populateFavorite({ id, messages })); dispatch(populateFavorite({ id, messages }));
} catch (err) { } catch (err) {
console.log('get favorite list error', err); console.log("get favorite list error", err);
} }
} }
}), }),
getFavorites: builder.query({ getFavorites: builder.query({
query: () => ({ query: () => ({
url: `/favorite` url: `/favorite`
}), }),
async onQueryStarted(data, { dispatch, queryFulfilled }) { async onQueryStarted(data, { dispatch, queryFulfilled }) {
try { try {
const { data: favorites } = await queryFulfilled; const { data: favorites } = await queryFulfilled;
dispatch(fullfillFavorites(favorites)); dispatch(fullfillFavorites(favorites));
for (const fav of favorites) { for (const fav of favorites) {
const { id } = fav; const { id } = fav;
dispatch(messageApi.endpoints.getFavoriteDetails.initiate(id)); dispatch(messageApi.endpoints.getFavoriteDetails.initiate(id));
} }
} catch (err) { } catch (err) {
console.log('get favorite list error', err); console.log("get favorite list error", err);
} }
} }
}), }),
replyMessage: builder.mutation({ replyMessage: builder.mutation({
query: ({ reply_mid, content, type = 'text' }) => ({ query: ({ reply_mid, content, type = "text" }) => ({
headers: { headers: {
'content-type': ContentTypes[type] "content-type": ContentTypes[type]
}, },
url: `/message/${reply_mid}/reply`, url: `/message/${reply_mid}/reply`,
method: 'POST', method: "POST",
body: content body: content
}), }),
async onQueryStarted(param1, param2) { async onQueryStarted(param1, param2) {
await onMessageSendStarted.call(this, param1, param2, param1.context); await onMessageSendStarted.call(this, param1, param2, param1.context);
} }
}), }),
readMessage: builder.mutation({ readMessage: builder.mutation({
query: (data) => ({ query: (data) => ({
url: `/user/read-index`, url: `/user/read-index`,
method: 'POST', method: "POST",
body: data body: data
}), }),
async onQueryStarted(data, { dispatch, queryFulfilled }) { async onQueryStarted(data, { dispatch, queryFulfilled }) {
const { users = null, groups = null } = data; const { users = null, groups = null } = data;
if (users) { if (users) {
dispatch(updateReadUsers(users)); dispatch(updateReadUsers(users));
} }
if (groups) { if (groups) {
dispatch(updateReadChannels(groups)); dispatch(updateReadChannels(groups));
} }
try { try {
await queryFulfilled; await queryFulfilled;
} catch { } catch {
// todo // todo
} }
} }
})
}) })
})
}); });
export const { export const {
useLazyRemoveFavoriteQuery, useLazyRemoveFavoriteQuery,
useUnpinMessageMutation, useUnpinMessageMutation,
useLazyGetFavoritesQuery, useLazyGetFavoritesQuery,
useFavoriteMessageMutation, useFavoriteMessageMutation,
usePinMessageMutation, usePinMessageMutation,
useLazyGetArchiveMessageQuery, useLazyGetArchiveMessageQuery,
useGetArchiveMessageQuery, useGetArchiveMessageQuery,
useLazyGetOGInfoQuery, useLazyGetOGInfoQuery,
usePrepareUploadFileMutation, usePrepareUploadFileMutation,
useUploadFileMutation, useUploadFileMutation,
useEditMessageMutation, useEditMessageMutation,
useReactMessageMutation, useReactMessageMutation,
useReplyMessageMutation, useReplyMessageMutation,
useLazyDeleteMessageQuery, useLazyDeleteMessageQuery,
useReadMessageMutation, useReadMessageMutation,
useCreateArchiveMutation useCreateArchiveMutation
} = messageApi; } = messageApi;
+46 -46
View File
@@ -20,7 +20,7 @@ export const serverApi = createApi({
} catch { } catch {
console.log("get server info error"); console.log("get server info error");
} }
}, }
}), }),
getThirdPartySecret: builder.query({ getThirdPartySecret: builder.query({
query: () => ({ query: () => ({
@@ -29,142 +29,142 @@ export const serverApi = createApi({
// accept: "text/plain", // accept: "text/plain",
// }, // },
url: `/admin/system/third_party_secret`, url: `/admin/system/third_party_secret`,
responseHandler: (response) => response.text(), responseHandler: (response) => response.text()
}), }),
keepUnusedDataFor: 0, keepUnusedDataFor: 0
}), }),
updateThirdPartySecret: builder.mutation({ updateThirdPartySecret: builder.mutation({
query: () => ({ query: () => ({
url: `/admin/system/third_party_secret`, url: `/admin/system/third_party_secret`,
method: "POST", method: "POST",
responseHandler: (response) => response.text(), responseHandler: (response) => response.text()
}), })
}), }),
getMetrics: builder.query({ getMetrics: builder.query({
query: () => ({ url: `/admin/system/metrics` }), query: () => ({ url: `/admin/system/metrics` })
}), }),
getServerVersion: builder.query({ getServerVersion: builder.query({
query: () => ({ query: () => ({
headers: { headers: {
// "content-type": "text/plain", // "content-type": "text/plain",
accept: "text/plain", accept: "text/plain"
}, },
url: `/admin/system/version`, url: `/admin/system/version`,
responseHandler: (response) => response.text(), responseHandler: (response) => response.text()
}), })
}), }),
getFirebaseConfig: builder.query({ getFirebaseConfig: builder.query({
query: () => ({ url: `admin/fcm/config` }), query: () => ({ url: `admin/fcm/config` })
}), }),
getGoogleAuthConfig: builder.query({ getGoogleAuthConfig: builder.query({
query: () => ({ url: `admin/google_auth/config` }), query: () => ({ url: `admin/google_auth/config` })
}), }),
updateGoogleAuthConfig: builder.mutation({ updateGoogleAuthConfig: builder.mutation({
query: (data) => ({ query: (data) => ({
url: `admin/google_auth/config`, url: `admin/google_auth/config`,
method: "POST", method: "POST",
body: data, body: data
}), })
}), }),
getGithubAuthConfig: builder.query({ getGithubAuthConfig: builder.query({
query: () => ({ url: `admin/github_auth/config` }), query: () => ({ url: `admin/github_auth/config` })
}), }),
updateGithubAuthConfig: builder.mutation({ updateGithubAuthConfig: builder.mutation({
query: (data) => ({ query: (data) => ({
url: `admin/github_auth/config`, url: `admin/github_auth/config`,
method: "POST", method: "POST",
body: data, body: data
}), })
}), }),
sendTestEmail: builder.mutation({ sendTestEmail: builder.mutation({
query: (data) => ({ query: (data) => ({
url: `/admin/system/send_mail`, url: `/admin/system/send_mail`,
method: "POST", method: "POST",
body: data, body: data
}), })
}), }),
updateFirebaseConfig: builder.mutation({ updateFirebaseConfig: builder.mutation({
query: (data) => ({ query: (data) => ({
url: `admin/fcm/config`, url: `admin/fcm/config`,
method: "POST", method: "POST",
body: data, body: data
}), })
}), }),
getAgoraConfig: builder.query({ getAgoraConfig: builder.query({
query: () => ({ url: `admin/agora/config` }), query: () => ({ url: `admin/agora/config` })
}), }),
updateAgoraConfig: builder.mutation({ updateAgoraConfig: builder.mutation({
query: (data) => ({ query: (data) => ({
url: `admin/agora/config`, url: `admin/agora/config`,
method: "POST", method: "POST",
body: data, body: data
}), })
}), }),
getSMTPConfig: builder.query({ getSMTPConfig: builder.query({
query: () => ({ url: `admin/smtp/config` }), query: () => ({ url: `admin/smtp/config` })
}), }),
getSMTPStatus: builder.query({ getSMTPStatus: builder.query({
query: () => ({ url: `/admin/smtp/enabled` }), query: () => ({ url: `/admin/smtp/enabled` })
}), }),
updateSMTPConfig: builder.mutation({ updateSMTPConfig: builder.mutation({
query: (data) => ({ query: (data) => ({
url: `admin/smtp/config`, url: `admin/smtp/config`,
method: "POST", method: "POST",
body: data, body: data
}), })
}), }),
getLoginConfig: builder.query({ getLoginConfig: builder.query({
query: () => ({ url: `admin/login/config` }), query: () => ({ url: `admin/login/config` })
}), }),
updateLoginConfig: builder.mutation({ updateLoginConfig: builder.mutation({
query: (data) => ({ query: (data) => ({
url: `admin/login/config`, url: `admin/login/config`,
method: "POST", method: "POST",
body: data, body: data
}), })
}), }),
updateLogo: builder.mutation({ updateLogo: builder.mutation({
query: (data) => ({ query: (data) => ({
headers: { headers: {
"content-type": "image/png", "content-type": "image/png"
}, },
url: `admin/system/organization/logo`, url: `admin/system/organization/logo`,
method: "POST", method: "POST",
body: data, body: data
}), }),
async onQueryStarted(data, { dispatch, queryFulfilled }) { async onQueryStarted(data, { dispatch, queryFulfilled }) {
try { try {
await queryFulfilled; await queryFulfilled;
dispatch( dispatch(
updateInfo({ updateInfo({
logo: `${BASE_URL}/resource/organization/logo?t=${+new Date()}`, logo: `${BASE_URL}/resource/organization/logo?t=${+new Date()}`
}) })
); );
} catch { } catch {
console.log("update server logo error"); console.log("update server logo error");
} }
}, }
}), }),
createInviteLink: builder.query({ createInviteLink: builder.query({
query: (expired_in = defaultExpireDuration) => ({ query: (expired_in = defaultExpireDuration) => ({
headers: { headers: {
"content-type": "text/plain", "content-type": "text/plain",
accept: "text/plain", accept: "text/plain"
}, },
url: `/admin/system/create_invite_link?expired_in=${expired_in}`, url: `/admin/system/create_invite_link?expired_in=${expired_in}`,
responseHandler: (response) => response.text(), responseHandler: (response) => response.text()
}), }),
transformResponse: (link) => { transformResponse: (link) => {
// 替换掉域名 // 替换掉域名
const invite = new URL(link); const invite = new URL(link);
return `${location.origin}${invite.pathname}${invite.search}${invite.hash}`; return `${location.origin}${invite.pathname}${invite.search}${invite.hash}`;
}, }
}), }),
updateServer: builder.mutation({ updateServer: builder.mutation({
query: (data) => ({ query: (data) => ({
url: `admin/system/organization`, url: `admin/system/organization`,
method: "POST", method: "POST",
body: data, body: data
}), }),
async onQueryStarted(data, { dispatch, queryFulfilled, getState }) { async onQueryStarted(data, { dispatch, queryFulfilled, getState }) {
const { name: prevName, description: prevDesc } = getState().server; const { name: prevName, description: prevDesc } = getState().server;
@@ -174,21 +174,21 @@ export const serverApi = createApi({
} catch { } catch {
dispatch(updateInfo({ name: prevName, description: prevDesc })); dispatch(updateInfo({ name: prevName, description: prevDesc }));
} }
}, }
}), }),
createAdmin: builder.mutation({ createAdmin: builder.mutation({
query: (data) => ({ query: (data) => ({
url: `/admin/system/create_admin`, url: `/admin/system/create_admin`,
method: "POST", method: "POST",
body: data, body: data
}), })
}), }),
getInitialized: builder.query({ getInitialized: builder.query({
query: () => ({ query: () => ({
url: `/admin/system/initialized`, url: `/admin/system/initialized`
}), })
}), })
}), })
}); });
export const { export const {
@@ -217,5 +217,5 @@ export const {
useGetThirdPartySecretQuery, useGetThirdPartySecretQuery,
useUpdateThirdPartySecretMutation, useUpdateThirdPartySecretMutation,
useCreateAdminMutation, useCreateAdminMutation,
useGetInitializedQuery, useGetInitializedQuery
} = serverApi; } = serverApi;
+8 -19
View File
@@ -1,24 +1,18 @@
import { createSlice } from "@reduxjs/toolkit"; import { createSlice } from "@reduxjs/toolkit";
import { import { KEY_PWA_INSTALLED, KEY_REFRESH_TOKEN, KEY_TOKEN, KEY_UID, KEY_EXPIRE } from "../config";
KEY_PWA_INSTALLED,
KEY_REFRESH_TOKEN,
KEY_TOKEN,
KEY_UID,
KEY_EXPIRE,
} from "../config";
const initialState = { const initialState = {
initialized: true, initialized: true,
uid: null, uid: null,
token: localStorage.getItem(KEY_TOKEN), token: localStorage.getItem(KEY_TOKEN),
expireTime: localStorage.getItem(KEY_EXPIRE) || +new Date(), expireTime: localStorage.getItem(KEY_EXPIRE) || +new Date(),
refreshToken: localStorage.getItem(KEY_REFRESH_TOKEN), refreshToken: localStorage.getItem(KEY_REFRESH_TOKEN)
}; };
const emptyState = { const emptyState = {
initialized: true, initialized: true,
uid: null, uid: null,
token: null, token: null,
expireTime: +new Date(), expireTime: +new Date(),
refreshToken: null, refreshToken: null
}; };
const authDataSlice = createSlice({ const authDataSlice = createSlice({
name: "authData", name: "authData",
@@ -30,7 +24,7 @@ const authDataSlice = createSlice({
user: { uid }, user: { uid },
token, token,
refresh_token, refresh_token,
expired_in = 0, expired_in = 0
} = action.payload; } = action.payload;
state.initialized = initialized; state.initialized = initialized;
state.uid = uid; state.uid = uid;
@@ -76,14 +70,9 @@ const authDataSlice = createSlice({
localStorage.setItem(KEY_EXPIRE, et); localStorage.setItem(KEY_EXPIRE, et);
localStorage.setItem(KEY_TOKEN, token); localStorage.setItem(KEY_TOKEN, token);
localStorage.setItem(KEY_REFRESH_TOKEN, refresh_token); localStorage.setItem(KEY_REFRESH_TOKEN, refresh_token);
}, }
}, }
}); });
export const { export const { updateInitialized, setAuthData, resetAuthData, setUid, updateToken } =
updateInitialized, authDataSlice.actions;
setAuthData,
resetAuthData,
setUid,
updateToken,
} = authDataSlice.actions;
export default authDataSlice.reducer; export default authDataSlice.reducer;
+6 -10
View File
@@ -3,7 +3,7 @@ import BASE_URL from "../config";
import { getNonNullValues } from "../../common/utils"; import { getNonNullValues } from "../../common/utils";
const initialState = { const initialState = {
ids: [], ids: [],
byId: {}, byId: {}
}; };
const channelsSlice = createSlice({ const channelsSlice = createSlice({
name: `channels`, name: `channels`,
@@ -36,18 +36,14 @@ const channelsSlice = createSlice({
icon: icon:
avatar_updated_at == 0 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) { updateChannel(state, action) {
const ignoreInPublic = ["add_member", "remove_member"]; const ignoreInPublic = ["add_member", "remove_member"];
const { id, operation, members = [], ...rest } = action.payload; const { id, operation, members = [], ...rest } = action.payload;
const currChannel = state.byId[id]; const currChannel = state.byId[id];
if ( if (!currChannel || (currChannel.is_public && ignoreInPublic.includes(operation))) return;
!currChannel ||
(currChannel.is_public && ignoreInPublic.includes(operation))
)
return;
switch (operation) { switch (operation) {
case "remove_member": case "remove_member":
{ {
@@ -99,8 +95,8 @@ const channelsSlice = createSlice({
state.ids.splice(idx, 1); state.ids.splice(idx, 1);
delete state.byId[gid]; delete state.byId[gid];
} }
}, }
}, }
}); });
export const { export const {
updatePinMessage, updatePinMessage,
@@ -108,6 +104,6 @@ export const {
fullfillChannels, fullfillChannels,
addChannel, addChannel,
updateChannel, updateChannel,
removeChannel, removeChannel
} = channelsSlice.actions; } = channelsSlice.actions;
export default channelsSlice.reducer; export default channelsSlice.reducer;
+6 -12
View File
@@ -3,7 +3,7 @@ import { getNonNullValues } from "../../common/utils";
import BASE_URL from "../config"; import BASE_URL from "../config";
const initialState = { const initialState = {
ids: [], ids: [],
byId: {}, byId: {}
}; };
const contactsSlice = createSlice({ const contactsSlice = createSlice({
name: `contacts`, name: `contacts`,
@@ -39,9 +39,7 @@ const contactsSlice = createSlice({
Object.keys(vals).forEach((k) => { Object.keys(vals).forEach((k) => {
state.byId[uid][k] = vals[k]; state.byId[uid][k] = vals[k];
if (k == "avatar_updated_at") { if (k == "avatar_updated_at") {
state.byId[ state.byId[uid].avatar = `${BASE_URL}/resource/avatar?uid=${uid}&t=${vals[k]}`;
uid
].avatar = `${BASE_URL}/resource/avatar?uid=${uid}&t=${vals[k]}`;
} }
}); });
} }
@@ -80,13 +78,9 @@ const contactsSlice = createSlice({
state.byId[uid].online = online; state.byId[uid].online = online;
} }
}); });
}, }
}, }
}); });
export const { export const { resetContacts, fullfillContacts, updateUsersByLogs, updateUsersStatus } =
resetContacts, contactsSlice.actions;
fullfillContacts,
updateUsersByLogs,
updateUsersStatus,
} = contactsSlice.actions;
export default contactsSlice.reducer; export default contactsSlice.reducer;
+4 -8
View File
@@ -24,13 +24,9 @@ const favoritesSlice = createSlice({
if (idx > -1) { if (idx > -1) {
state[idx].messages = messages; state[idx].messages = messages;
} }
}, }
}, }
}); });
export const { export const { addFavorite, deleteFavorite, fullfillFavorites, populateFavorite } =
addFavorite, favoritesSlice.actions;
deleteFavorite,
fullfillFavorites,
populateFavorite,
} = favoritesSlice.actions;
export default favoritesSlice.reducer; export default favoritesSlice.reducer;
+6 -6
View File
@@ -5,7 +5,7 @@ const initialState = {
readUsers: {}, readUsers: {},
readChannels: {}, readChannels: {},
muteUsers: {}, muteUsers: {},
muteChannels: {}, muteChannels: {}
}; };
const footprintSlice = createSlice({ const footprintSlice = createSlice({
name: "footprint", name: "footprint",
@@ -21,7 +21,7 @@ const footprintSlice = createSlice({
readUsers = {}, readUsers = {},
readChannels = {}, readChannels = {},
muteUsers = {}, muteUsers = {},
muteChannels = {}, muteChannels = {}
} = action.payload; } = action.payload;
return { return {
usersVersion, usersVersion,
@@ -29,7 +29,7 @@ const footprintSlice = createSlice({
readUsers, readUsers,
readChannels, readChannels,
muteUsers, muteUsers,
muteChannels, muteChannels
}; };
}, },
updateUsersVersion(state, action) { updateUsersVersion(state, action) {
@@ -94,8 +94,8 @@ const footprintSlice = createSlice({
reads.forEach(({ gid, mid }) => { reads.forEach(({ gid, mid }) => {
state.readChannels[gid] = mid; state.readChannels[gid] = mid;
}); });
}, }
}, }
}); });
export const { export const {
resetFootprint, resetFootprint,
@@ -104,6 +104,6 @@ export const {
updateUsersVersion, updateUsersVersion,
updateReadChannels, updateReadChannels,
updateReadUsers, updateReadUsers,
updateMute, updateMute
} = footprintSlice.actions; } = footprintSlice.actions;
export default footprintSlice.reducer; export default footprintSlice.reducer;
+5 -8
View File
@@ -15,8 +15,7 @@ const channelMsgSlice = createSlice({
const { id, mid, local_id = null } = action.payload; const { id, mid, local_id = null } = action.payload;
if (state[id]) { if (state[id]) {
const midExsited = state[id].findIndex((id) => id == mid) > -1; const midExsited = state[id].findIndex((id) => id == mid) > -1;
const localMsgExsited = const localMsgExsited = state[id].findIndex((id) => id == local_id) > -1;
state[id].findIndex((id) => id == local_id) > -1;
if (midExsited || localMsgExsited) return; if (midExsited || localMsgExsited) return;
state[id].push(+mid); state[id].push(+mid);
} else { } else {
@@ -44,14 +43,12 @@ const channelMsgSlice = createSlice({
} }
}, },
removeChannelSession(state, action) { removeChannelSession(state, action) {
const ids = Array.isArray(action.payload) const ids = Array.isArray(action.payload) ? action.payload : [action.payload];
? action.payload
: [action.payload];
ids.forEach((id) => { ids.forEach((id) => {
delete state[id]; delete state[id];
}); });
}, }
}, }
}); });
export const { export const {
removeChannelSession, removeChannelSession,
@@ -59,6 +56,6 @@ export const {
fullfillChannelMsg, fullfillChannelMsg,
addChannelMsg, addChannelMsg,
removeChannelMsg, removeChannelMsg,
replaceChannelMsg, replaceChannelMsg
} = channelMsgSlice.actions; } = channelMsgSlice.actions;
export default channelMsgSlice.reducer; export default channelMsgSlice.reducer;
+5 -11
View File
@@ -20,9 +20,7 @@ const fileMessageSlice = createSlice({
} }
}, },
removeFileMessage(state, action) { removeFileMessage(state, action) {
const mids = Array.isArray(action.payload) const mids = Array.isArray(action.payload) ? action.payload : [action.payload];
? action.payload
: [action.payload];
mids.forEach((id) => { mids.forEach((id) => {
// 从file message 列表删掉 // 从file message 列表删掉
const fidIdx = state.findIndex((fid) => fid == id); const fidIdx = state.findIndex((fid) => fid == id);
@@ -30,13 +28,9 @@ const fileMessageSlice = createSlice({
state.splice(fidIdx, 1); state.splice(fidIdx, 1);
} }
}); });
}, }
}, }
}); });
export const { export const { removeFileMessage, resetFileMessage, fullfillFileMessage, addFileMessage } =
removeFileMessage, fileMessageSlice.actions;
resetFileMessage,
fullfillFileMessage,
addFileMessage,
} = fileMessageSlice.actions;
export default fileMessageSlice.reducer; export default fileMessageSlice.reducer;
+5 -7
View File
@@ -2,7 +2,7 @@ import { createSlice } from "@reduxjs/toolkit";
import BASE_URL, { ContentTypes } from "../config"; import BASE_URL, { ContentTypes } from "../config";
import { isImage } from "../../common/utils"; import { isImage } from "../../common/utils";
const initialState = { const initialState = {
replying: {}, replying: {}
}; };
const messageSlice = createSlice({ const messageSlice = createSlice({
name: "message", name: "message",
@@ -48,9 +48,7 @@ const messageSlice = createSlice({
state[mid] = data; state[mid] = data;
}, },
removeMessage(state, action) { removeMessage(state, action) {
const mids = Array.isArray(action.payload) const mids = Array.isArray(action.payload) ? action.payload : [action.payload];
? action.payload
: [action.payload];
mids.forEach((id) => { mids.forEach((id) => {
delete state[id]; delete state[id];
}); });
@@ -65,8 +63,8 @@ const messageSlice = createSlice({
if (state.replying[key]) { if (state.replying[key]) {
delete state.replying[key]; delete state.replying[key];
} }
}, }
}, }
}); });
export const { export const {
resetMessage, resetMessage,
@@ -76,6 +74,6 @@ export const {
addMessage, addMessage,
removeMessage, removeMessage,
addReplyingMessage, addReplyingMessage,
removeReplyingMessage, removeReplyingMessage
} = messageSlice.actions; } = messageSlice.actions;
export default messageSlice.reducer; export default messageSlice.reducer;
+4 -6
View File
@@ -12,9 +12,7 @@ const reactionMessageSlice = createSlice({
return action.payload; return action.payload;
}, },
removeReactionMessage(state, action) { removeReactionMessage(state, action) {
const mids = Array.isArray(action.payload) const mids = Array.isArray(action.payload) ? action.payload : [action.payload];
? action.payload
: [action.payload];
mids.forEach((id) => { mids.forEach((id) => {
delete state[id]; delete state[id];
}); });
@@ -48,13 +46,13 @@ const reactionMessageSlice = createSlice({
state[mid][reaction] = [from_uid]; state[mid][reaction] = [from_uid];
} }
state[rid] = true; state[rid] = true;
}, }
}, }
}); });
export const { export const {
removeReactionMessage, removeReactionMessage,
resetReactionMessage, resetReactionMessage,
fullfillReactionMessage, fullfillReactionMessage,
toggleReactionMessage, toggleReactionMessage
} = reactionMessageSlice.actions; } = reactionMessageSlice.actions;
export default reactionMessageSlice.reducer; export default reactionMessageSlice.reducer;
+6 -9
View File
@@ -1,7 +1,7 @@
import { createSlice } from "@reduxjs/toolkit"; import { createSlice } from "@reduxjs/toolkit";
const initialState = { const initialState = {
ids: [], ids: [],
byId: {}, byId: {}
}; };
const userMsgSlice = createSlice({ const userMsgSlice = createSlice({
name: "userMessage", name: "userMessage",
@@ -18,8 +18,7 @@ const userMsgSlice = createSlice({
const { id, mid, local_id } = action.payload; const { id, mid, local_id } = action.payload;
if (state.byId[id]) { if (state.byId[id]) {
const midExsited = state.byId[id].findIndex((id) => id == mid) > -1; const midExsited = state.byId[id].findIndex((id) => id == mid) > -1;
const localMsgExsited = const localMsgExsited = state.byId[id].findIndex((id) => id == local_id) > -1;
state.byId[id].findIndex((id) => id == local_id) > -1;
if (midExsited || localMsgExsited) return; if (midExsited || localMsgExsited) return;
state.byId[id].push(+mid); state.byId[id].push(+mid);
@@ -53,15 +52,13 @@ const userMsgSlice = createSlice({
} }
}, },
removeUserSession(state, action) { removeUserSession(state, action) {
const ids = Array.isArray(action.payload) const ids = Array.isArray(action.payload) ? action.payload : [action.payload];
? action.payload
: [action.payload];
state.ids = state.ids.filter((id) => ids.findIndex((i) => i == id) == -1); state.ids = state.ids.filter((id) => ids.findIndex((i) => i == id) == -1);
// ids.forEach((id) => { // ids.forEach((id) => {
// delete state.byId[id]; // delete state.byId[id];
// }); // });
}, }
}, }
}); });
export const { export const {
removeUserSession, removeUserSession,
@@ -69,6 +66,6 @@ export const {
fullfillUserMsg, fullfillUserMsg,
addUserMsg, addUserMsg,
removeUserMsg, removeUserMsg,
replaceUserMsg, replaceUserMsg
} = userMsgSlice.actions; } = userMsgSlice.actions;
export default userMsgSlice.reducer; export default userMsgSlice.reducer;
+1 -1
View File
@@ -4,4 +4,4 @@ set
add add
update update
remove remove
toggle toggle
+6 -6
View File
@@ -5,8 +5,8 @@ const initialState = {
logo: "", logo: "",
inviteLink: { inviteLink: {
link: "", link: "",
expire: 0, expire: 0
}, }
}; };
const serverSlice = createSlice({ const serverSlice = createSlice({
name: "server", name: "server",
@@ -19,10 +19,10 @@ const serverSlice = createSlice({
const { const {
inviteLink = { inviteLink = {
link: "", link: "",
expire: 0, expire: 0
}, },
name = "", name = "",
description = "", description = ""
} = action.payload || {}; } = action.payload || {};
return { name, description, inviteLink }; return { name, description, inviteLink };
}, },
@@ -31,8 +31,8 @@ const serverSlice = createSlice({
Object.keys(values).forEach((_key) => { Object.keys(values).forEach((_key) => {
state[_key] = values[_key]; state[_key] = values[_key];
}); });
}, }
}, }
}); });
export const { updateInfo, resetServer, fullfillServer } = serverSlice.actions; export const { updateInfo, resetServer, fullfillServer } = serverSlice.actions;
export default serverSlice.reducer; export default serverSlice.reducer;
+8 -18
View File
@@ -5,7 +5,7 @@ const initialState = {
ready: false, ready: false,
userGuide: { userGuide: {
visible: false, visible: false,
step: 1, step: 1
}, },
inputMode: "text", inputMode: "text",
menuExpand: false, menuExpand: false,
@@ -16,8 +16,8 @@ const initialState = {
draftMixedText: {}, draftMixedText: {},
remeberedNavs: { remeberedNavs: {
chat: null, chat: null,
contact: null, contact: null
}, }
}; };
const uiSlice = createSlice({ const uiSlice = createSlice({
name: "ui", name: "ui",
@@ -61,12 +61,7 @@ const uiSlice = createSlice({
}, },
updateUploadFiles(state, action) { updateUploadFiles(state, action) {
const { const { context = "channel", id = null, operation = "add", ...rest } = action.payload;
context = "channel",
id = null,
operation = "add",
...rest
} = action.payload;
if (!id || !context) return; if (!id || !context) return;
const _key = `${context}_${id}`; const _key = `${context}_${id}`;
let files = state.uploadFiles[_key]; let files = state.uploadFiles[_key];
@@ -126,12 +121,7 @@ const uiSlice = createSlice({
} }
}, },
updateSelectMessages(state, action) { updateSelectMessages(state, action) {
const { const { context = "channel", id = null, operation = "add", data = null } = action.payload;
context = "channel",
id = null,
operation = "add",
data = null,
} = action.payload;
let currData = state.selectMessages[`${context}_${id}`]; let currData = state.selectMessages[`${context}_${id}`];
switch (operation) { switch (operation) {
case "add": { case "add": {
@@ -151,8 +141,8 @@ const uiSlice = createSlice({
break; break;
} }
state.selectMessages[`${context}_${id}`] = currData; state.selectMessages[`${context}_${id}`] = currData;
}, }
}, }
}); });
export const { export const {
fullfillUI, fullfillUI,
@@ -166,6 +156,6 @@ export const {
updateUserGuide, updateUserGuide,
updateDraftMarkdown, updateDraftMarkdown,
updateDraftMixedText, updateDraftMixedText,
updateRemeberedNavs, updateRemeberedNavs
} = uiSlice.actions; } = uiSlice.actions;
export default uiSlice.reducer; export default uiSlice.reducer;
+34 -41
View File
@@ -36,7 +36,7 @@ const reducer = combineReducers({
[messageApi.reducerPath]: messageApi.reducer, [messageApi.reducerPath]: messageApi.reducer,
[contactApi.reducerPath]: contactApi.reducer, [contactApi.reducerPath]: contactApi.reducer,
[channelApi.reducerPath]: channelApi.reducer, [channelApi.reducerPath]: channelApi.reducer,
[serverApi.reducerPath]: serverApi.reducer, [serverApi.reducerPath]: serverApi.reducer
}); });
const store = configureStore({ const store = configureStore({
@@ -50,48 +50,41 @@ const store = configureStore({
serverApi.middleware, serverApi.middleware,
messageApi.middleware messageApi.middleware
) )
.prepend(listenerMiddleware.middleware), .prepend(listenerMiddleware.middleware)
}); });
let initialized = false; let initialized = false;
setupListeners( setupListeners(store.dispatch, (dispatch, { onOnline, onOffline, onFocus, onFocusLost }) => {
store.dispatch, const handleFocus = () => dispatch(onFocus());
(dispatch, { onOnline, onOffline, onFocus, onFocusLost }) => { const handleFocusLost = () => dispatch(onFocusLost());
const handleFocus = () => dispatch(onFocus()); const handleOnline = () => dispatch(onOnline());
const handleFocusLost = () => dispatch(onFocusLost()); const handleOffline = () => dispatch(onOffline());
const handleOnline = () => dispatch(onOnline()); const handleVisibilityChange = () => {
const handleOffline = () => dispatch(onOffline()); if (window.document.visibilityState === "visible") {
const handleVisibilityChange = () => { handleFocus();
if (window.document.visibilityState === "visible") { } else {
handleFocus(); handleFocusLost();
} else { }
handleFocusLost(); };
}
}; if (!initialized) {
if (typeof window !== "undefined" && window.addEventListener) {
if (!initialized) { // Handle focus events
if (typeof window !== "undefined" && window.addEventListener) { window.addEventListener("visibilitychange", handleVisibilityChange, false);
// Handle focus events window.addEventListener("focus", handleFocus, false);
window.addEventListener(
"visibilitychange", // Handle connection events
handleVisibilityChange, window.addEventListener("online", handleOnline, false);
false window.addEventListener("offline", handleOffline, false);
); initialized = true;
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; export default store;
+14 -14
View File
@@ -1,20 +1,20 @@
::-webkit-scrollbar-thumb { ::-webkit-scrollbar-thumb {
background: #ddd; background: #ddd;
border-radius: 4px; border-radius: 4px;
} }
::-webkit-scrollbar { ::-webkit-scrollbar {
background: transparent; background: transparent;
width: 5px; width: 5px;
height: 5px; height: 5px;
} }
:root{ :root {
/* border radius */ /* border radius */
--br:8px; --br: 8px;
--rustchat-navs-bg:#E5E7EB; --rustchat-navs-bg: #e5e7eb;
--rustchat-body-bg:#F5F6F7; --rustchat-body-bg: #f5f6f7;
--rustchat-chat-bg:#fff; --rustchat-chat-bg: #fff;
--rustchat-channel-text-color:#1C1C1E; --rustchat-channel-text-color: #1c1c1e;
--rustchat-msg-text-color:#374151; --rustchat-msg-text-color: #374151;
} }
/* @media (prefers-color-scheme: dark) { /* @media (prefers-color-scheme: dark) {
:root{ :root{
@@ -25,4 +25,4 @@
--rustchat-channel-text-color:#fff; --rustchat-channel-text-color:#fff;
--rustchat-msg-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() { export default function AddEntriesMenu() {
const currentUser = useSelector( const currentUser = useSelector((store) => store.contacts.byId[store.authData.uid]);
(store) => store.contacts.byId[store.authData.uid]
);
const [isPrivate, setIsPrivate] = useState(false); const [isPrivate, setIsPrivate] = useState(false);
const [inviteModalVisible, setInviteModalVisible] = useState(false); const [inviteModalVisible, setInviteModalVisible] = useState(false);
@@ -78,10 +76,7 @@ export default function AddEntriesMenu() {
<> <>
<Styled> <Styled>
{currentUser?.is_admin && ( {currentUser?.is_admin && (
<li <li className="item" onClick={handleOpenChannelModal.bind(null, false)}>
className="item"
onClick={handleOpenChannelModal.bind(null, false)}
>
<ChannelIcon className="icon" /> <ChannelIcon className="icon" />
New Channel New Channel
</li> </li>
@@ -99,15 +94,9 @@ export default function AddEntriesMenu() {
Invite People Invite People
</li> </li>
</Styled> </Styled>
{channelModalVisible && ( {channelModalVisible && <ChannelModal personal={isPrivate} closeModal={handleCloseModal} />}
<ChannelModal personal={isPrivate} closeModal={handleCloseModal} /> {contactsModalVisible && <ContactsModal closeModal={toggleContactsModalVisible} />}
)} {inviteModalVisible && <InviteModal closeModal={toggleInviteModalVisible} />}
{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({ const tmp = getInitialsAvatar({
initials: getInitials(name), initials: getInitials(name),
background: type == "channel" ? "#EAECF0" : undefined, background: type == "channel" ? "#EAECF0" : undefined,
foreground: type == "channel" ? "#475467" : undefined, foreground: type == "channel" ? "#475467" : undefined
}); });
setSrc(tmp); setSrc(tmp);
}; };
@@ -17,7 +17,7 @@ const Avatar = ({ url = "", name = "unkonw name", type = "user", ...rest }) => {
const tmp = getInitialsAvatar({ const tmp = getInitialsAvatar({
initials: getInitials(name), initials: getInitials(name),
background: type == "channel" ? "#EAECF0" : undefined, background: type == "channel" ? "#EAECF0" : undefined,
foreground: type == "channel" ? "#475467" : undefined, foreground: type == "channel" ? "#475467" : undefined
}); });
setSrc(tmp); setSrc(tmp);
} else { } else {
+2 -4
View File
@@ -64,7 +64,7 @@ export default function AvatarUploader({
name = "", name = "",
type = "user", type = "user",
uploadImage, uploadImage,
disabled = false, disabled = false
}) { }) {
const [uploading, setUploading] = useState(false); const [uploading, setUploading] = useState(false);
const handleUpload = async (evt) => { const handleUpload = async (evt) => {
@@ -80,9 +80,7 @@ export default function AvatarUploader({
<Avatar type={type} url={url} name={name} /> <Avatar type={type} url={url} name={name} />
{!disabled && ( {!disabled && (
<> <>
<div className="tip"> <div className="tip">{uploading ? `Uploading` : `Change Avatar`}</div>
{uploading ? `Uploading` : `Change Avatar`}
</div>
<input <input
multiple={false} multiple={false}
onChange={handleUpload} onChange={handleUpload}
+6 -13
View File
@@ -80,19 +80,16 @@ export default function BlankPlaceholder({ type = "chat" }) {
setInviteModalVisible((prev) => !prev); setInviteModalVisible((prev) => !prev);
}; };
const chatTip = const chatTip =
type == "chat" type == "chat" ? "Create a Channel to Start a Conversation" : "Send a Direct Message";
? "Create a Channel to Start a Conversation" const chatHanlder = type == "chat" ? toggleChannelModalVisible : toggleContactListVisible;
: "Send a Direct Message";
const chatHanlder =
type == "chat" ? toggleChannelModalVisible : toggleContactListVisible;
return ( return (
<> <>
<Styled> <Styled>
<div className="head"> <div className="head">
<h2 className="title">Welcome to {server.name} server</h2> <h2 className="title">Welcome to {server.name} server</h2>
<p className="desc"> <p className="desc">
Here are some steps to help you get started. For more, check out our Here are some steps to help you get started. For more, check out our Getting Started
Getting Started guide guide
</p> </p>
</div> </div>
<div className="boxes"> <div className="boxes">
@@ -117,12 +114,8 @@ export default function BlankPlaceholder({ type = "chat" }) {
{createChannelVisible && ( {createChannelVisible && (
<ChannelModal personal={true} closeModal={toggleChannelModalVisible} /> <ChannelModal personal={true} closeModal={toggleChannelModalVisible} />
)} )}
{contactListVisible && ( {contactListVisible && <ContactsModal closeModal={toggleContactListVisible} />}
<ContactsModal closeModal={toggleContactListVisible} /> {inviteModalVisible && <InviteModal closeModal={toggleInviteModalVisible} />}
)}
{inviteModalVisible && (
<InviteModal closeModal={toggleInviteModalVisible} />
)}
</> </>
); );
} }
+4 -12
View File
@@ -47,16 +47,11 @@ const StyledWrapper = styled.div`
} }
} }
`; `;
export default function Channel({ export default function Channel({ interactive = true, id = "", compact = false, avatarSize = 32 }) {
interactive = true,
id = "",
compact = false,
avatarSize = 32,
}) {
const { channel, totalMemberCount } = useSelector((store) => { const { channel, totalMemberCount } = useSelector((store) => {
return { return {
channel: store.channels.byId[id], channel: store.channels.byId[id],
totalMemberCount: store.contacts.ids.length, totalMemberCount: store.contacts.ids.length
}; };
}); });
console.log("channel item", id, channel); console.log("channel item", id, channel);
@@ -65,17 +60,14 @@ export default function Channel({
return ( return (
<StyledWrapper <StyledWrapper
size={avatarSize} size={avatarSize}
className={`${interactive ? "interactive" : ""} ${ className={`${interactive ? "interactive" : ""} ${compact ? "compact" : ""}`}
compact ? "compact" : ""
}`}
> >
<div className="avatar"> <div className="avatar">
<Avatar type="channel" url={avatar} name={"#"} alt="avatar" /> <Avatar type="channel" url={avatar} name={"#"} alt="avatar" />
</div> </div>
{!compact && ( {!compact && (
<div className="name"> <div className="name">
<span className="txt">{name}</span> ( <span className="txt">{name}</span> ({is_public ? totalMemberCount : members.length})
{is_public ? totalMemberCount : members.length})
</div> </div>
)} )}
</StyledWrapper> </StyledWrapper>
+1 -5
View File
@@ -8,11 +8,7 @@ const Styled = styled.div`
fill: #d0d5dd; fill: #d0d5dd;
} }
`; `;
export default function ChannelIcon({ export default function ChannelIcon({ personal = false, muted = false, className }) {
personal = false,
muted = false,
className,
}) {
return ( return (
<Styled className={`${muted ? "muted" : ""} ${className}`}> <Styled className={`${muted ? "muted" : ""} ${className}`}>
{personal ? <LockHashIcon /> : <HashIcon />} {personal ? <LockHashIcon /> : <HashIcon />}
+6 -18
View File
@@ -22,13 +22,11 @@ export default function ChannelModal({ personal = false, closeModal }) {
name: "", name: "",
dsecription: "", dsecription: "",
members: [loginUid], members: [loginUid],
is_public: !personal, is_public: !personal
}); });
const { contacts, input, updateInput } = useFilteredUsers(); const { contacts, input, updateInput } = useFilteredUsers();
const [ const [createChannel, { isSuccess, isError, isLoading, data: newChannelId }] =
createChannel, useCreateChannelMutation();
{ isSuccess, isError, isLoading, data: newChannelId },
] = useCreateChannelMutation();
const handleToggle = () => { const handleToggle = () => {
const { is_public } = data; const { is_public } = data;
@@ -72,9 +70,7 @@ export default function ChannelModal({ personal = false, closeModal }) {
const toggleCheckMember = ({ currentTarget }) => { const toggleCheckMember = ({ currentTarget }) => {
const { members } = data; const { members } = data;
const { uid } = currentTarget.dataset; const { uid } = currentTarget.dataset;
let tmp = members.includes(+uid) let tmp = members.includes(+uid) ? members.filter((m) => m != uid) : [...members, +uid];
? members.filter((m) => m != uid)
: [...members, +uid];
console.log(uid, currentTarget); console.log(uid, currentTarget);
setData((prev) => { setData((prev) => {
return { ...prev, members: tmp }; return { ...prev, members: tmp };
@@ -135,11 +131,7 @@ export default function ChannelModal({ personal = false, closeModal }) {
<div className="name"> <div className="name">
<span className="label normal">Channel Name</span> <span className="label normal">Channel Name</span>
<div className="input"> <div className="input">
<input <input onChange={handleNameInput} value={name} placeholder="new channel" />
onChange={handleNameInput}
value={name}
placeholder="new channel"
/>
<ChannelIcon personal={!is_public} className="icon" /> <ChannelIcon personal={!is_public} className="icon" />
</div> </div>
</div> </div>
@@ -155,11 +147,7 @@ export default function ChannelModal({ personal = false, closeModal }) {
<Button onClick={closeModal} className="normal cancel"> <Button onClick={closeModal} className="normal cancel">
Cancel Cancel
</Button> </Button>
<Button <Button disabled={isLoading} onClick={handleCreate} className="normal">
disabled={isLoading}
onClick={handleCreate}
className="normal"
>
Create Create
</Button> </Button>
</div> </div>
+9 -16
View File
@@ -4,14 +4,7 @@ import Tippy from "@tippyjs/react";
import useContactOperation from "../../hook/useContactOperation"; import useContactOperation from "../../hook/useContactOperation";
import ContextMenu from "../ContextMenu"; import ContextMenu from "../ContextMenu";
export default function ContactContextMenu({ export default function ContactContextMenu({ enable = false, uid, cid, visible, hide, children }) {
enable = false,
uid,
cid,
visible,
hide,
children,
}) {
const { const {
canCall, canCall,
call, call,
@@ -21,10 +14,10 @@ export default function ContactContextMenu({
canRemove, canRemove,
canRemoveFromChannel, canRemoveFromChannel,
removeFromChannel, removeFromChannel,
removeUser, removeUser
} = useContactOperation({ } = useContactOperation({
uid, uid,
cid, cid
}); });
return ( return (
<> <>
@@ -43,26 +36,26 @@ export default function ContactContextMenu({
items={[ items={[
{ {
title: "Message", title: "Message",
handler: startChat, handler: startChat
}, },
canCall && { canCall && {
title: "Call", title: "Call",
handler: call, handler: call
}, },
canCopyEmail && { canCopyEmail && {
title: "Copy Email", title: "Copy Email",
handler: copyEmail, handler: copyEmail
}, },
canRemoveFromChannel && { canRemoveFromChannel && {
danger: true, danger: true,
title: "Remove From Channel", title: "Remove From Channel",
handler: removeFromChannel, handler: removeFromChannel
}, },
canRemove && { canRemove && {
danger: true, danger: true,
title: "Remove From Server", title: "Remove From Server",
handler: removeUser, handler: removeUser
}, }
]} ]}
/> />
} }
+4 -12
View File
@@ -17,14 +17,10 @@ export default function Contact({
popover = false, popover = false,
compact = false, compact = false,
avatarSize = 32, avatarSize = 32,
enableContextMenu = false, enableContextMenu = false
}) { }) {
const navigate = useNavigate(); const navigate = useNavigate();
const { const { visible: contextMenuVisible, handleContextMenuEvent, hideContextMenu } = useContextMenu();
visible: contextMenuVisible,
handleContextMenuEvent,
hideContextMenu,
} = useContextMenu();
const curr = useSelector((store) => store.contacts.byId[uid]); const curr = useSelector((store) => store.contacts.byId[uid]);
const handleDoubleClick = () => { const handleDoubleClick = () => {
navigate(`/chat/dm/${uid}`); navigate(`/chat/dm/${uid}`);
@@ -50,15 +46,11 @@ export default function Contact({
onContextMenu={enableContextMenu ? handleContextMenuEvent : null} onContextMenu={enableContextMenu ? handleContextMenuEvent : null}
size={avatarSize} size={avatarSize}
onDoubleClick={dm ? handleDoubleClick : null} onDoubleClick={dm ? handleDoubleClick : null}
className={`${interactive ? "interactive" : ""} ${ className={`${interactive ? "interactive" : ""} ${compact ? "compact" : ""}`}
compact ? "compact" : ""
}`}
> >
<div className="avatar"> <div className="avatar">
<Avatar url={curr.avatar} name={curr.name} alt="avatar" /> <Avatar url={curr.avatar} name={curr.name} alt="avatar" />
<div <div className={`status ${curr.online ? "online" : "offline"}`}></div>
className={`status ${curr.online ? "online" : "offline"}`}
></div>
</div> </div>
{!compact && <span className="name">{curr?.name}</span>} {!compact && <span className="name">{curr?.name}</span>}
{owner && <IconOwner />} {owner && <IconOwner />}
+1 -5
View File
@@ -64,11 +64,7 @@ export default function ContactsModal({ closeModal }) {
<Modal> <Modal>
<StyledWrapper ref={wrapperRef}> <StyledWrapper ref={wrapperRef}>
<div className="search"> <div className="search">
<input <input value={input} onChange={handleSearch} placeholder="Type Username to search" />
value={input}
onChange={handleSearch}
placeholder="Type Username to search"
/>
</div> </div>
{contacts && ( {contacts && (
<ul className="users"> <ul className="users">
+2 -4
View File
@@ -16,13 +16,11 @@ export default function ContextMenu({ items = [], hideMenu = null }) {
} }
}, },
underline = false, underline = false,
danger = false, danger = false
} = item; } = item;
return ( return (
<li <li
className={`item ${underline ? "underline" : ""} ${ className={`item ${underline ? "underline" : ""} ${danger ? "danger" : ""}`}
danger ? "danger" : ""
}`}
key={title} key={title}
onClick={(evt) => { onClick={(evt) => {
evt.stopPropagation(); evt.stopPropagation();
+1 -5
View File
@@ -25,11 +25,7 @@ export default function DeleteMessageConfirmModal({ closeModal, mids = [] }) {
<Button className="cancel" onClick={closeModal.bind(null, false)}> <Button className="cancel" onClick={closeModal.bind(null, false)}>
Cancel Cancel
</Button> </Button>
<Button <Button disabled={isDeleting} onClick={handleDelete} className="danger">
disabled={isDeleting}
onClick={handleDelete}
className="danger"
>
{isDeleting ? "Deleting" : `Delete`} {isDeleting ? "Deleting" : `Delete`}
</Button> </Button>
</> </>
+1 -3
View File
@@ -13,9 +13,7 @@ export default function FAQ() {
<Styled> <Styled>
<div className="item">Client Version: {process.env.VERSION}</div> <div className="item">Client Version: {process.env.VERSION}</div>
<div className="item">Server Version: {serverVersion}</div> <div className="item">Server Version: {serverVersion}</div>
<div className="item"> <div className="item">Build Timestamp: {process.env.REACT_APP_BUILD_TIME}</div>
Build Timestamp: {process.env.REACT_APP_BUILD_TIME}
</div>
</Styled> </Styled>
); );
} }
+7 -11
View File
@@ -9,7 +9,7 @@ import {
ImagePreview, ImagePreview,
PdfPreview, PdfPreview,
CodePreview, CodePreview,
DocPreview, DocPreview
} from "./preview"; } from "./preview";
import { getFileIcon, formatBytes } from "../../utils"; import { getFileIcon, formatBytes } from "../../utils";
import IconDownload from "../../../assets/icons/download.svg"; import IconDownload from "../../../assets/icons/download.svg";
@@ -24,7 +24,7 @@ const renderPreview = (data) => {
video: /^video/gi, video: /^video/gi,
code: /(json|javascript|java|rb|c|php|xml|css|html)$/gi, code: /(json|javascript|java|rb|c|php|xml|css|html)$/gi,
doc: /^text/gi, doc: /^text/gi,
pdf: /\/pdf$/gi, pdf: /\/pdf$/gi
}; };
const _arr = name.split("."); const _arr = name.split(".");
const _type = file_type || _arr[_arr.length - 1]; const _type = file_type || _arr[_arr.length - 1];
@@ -65,7 +65,7 @@ export default function FileBox({
size, size,
created_at, created_at,
from_uid, from_uid,
content, content
}) { }) {
const fromUser = useSelector((store) => store.contacts.byId[from_uid]); const fromUser = useSelector((store) => store.contacts.byId[from_uid]);
const icon = getFileIcon(file_type, name); const icon = getFileIcon(file_type, name);
@@ -75,9 +75,9 @@ export default function FileBox({
const withPreview = preview && previewContent; const withPreview = preview && previewContent;
return ( return (
<Styled <Styled
className={`file_box ${flex ? "flex" : ""} ${ className={`file_box ${flex ? "flex" : ""} ${withPreview ? "preview" : ""} ${
withPreview ? "preview" : "" file_type.startsWith("audio") ? "audio" : ""
} ${file_type.startsWith("audio") ? "audio" : ""}`} }`}
> >
<div className="basic"> <div className="basic">
{icon} {icon}
@@ -91,11 +91,7 @@ export default function FileBox({
</i> </i>
</span> </span>
</div> </div>
<a <a className="download" download={name} href={`${content}&download=true`}>
className="download"
download={name}
href={`${content}&download=true`}
>
<IconDownload /> <IconDownload />
</a> </a>
</div> </div>
@@ -5,11 +5,4 @@ import PdfPreview from "./Pdf";
import CodePreview from "./Code"; import CodePreview from "./Code";
import DocPreview from "./Doc"; import DocPreview from "./Doc";
export { export { VideoPreview, AudioPreview, ImagePreview, PdfPreview, CodePreview, DocPreview };
VideoPreview,
AudioPreview,
ImagePreview,
PdfPreview,
CodePreview,
DocPreview,
};
@@ -37,7 +37,7 @@ export default function ImageMessage({
thumbnail, thumbnail,
download, download,
content, content,
properties = {}, properties = {}
}) { }) {
const [url, setUrl] = useState(thumbnail); const [url, setUrl] = useState(thumbnail);
const { width = 0, height = 0 } = getDefaultSize(properties); const { width = 0, height = 0 } = getDefaultSize(properties);
@@ -64,7 +64,7 @@ export default function ImageMessage({
strokeWidth={50} strokeWidth={50}
styles={buildStyles({ styles={buildStyles({
storke: "#000", storke: "#000",
strokeLinecap: "butt", strokeLinecap: "butt"
})} })}
/> />
</div> </div>
@@ -74,7 +74,7 @@ export default function ImageMessage({
className="img preview" className="img preview"
style={{ style={{
width: width ? `${width}px` : "", width: width ? `${width}px` : "",
height: height ? `${height}px` : "", height: height ? `${height}px` : ""
}} }}
data-meta={JSON.stringify(properties)} data-meta={JSON.stringify(properties)}
data-origin={content} data-origin={content}
+7 -23
View File
@@ -23,7 +23,7 @@ export default function FileMessage({
content = "", content = "",
download = "", download = "",
thumbnail = "", 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 [imageSize, setImageSize] = useState(null);
const [uploadingFile, setUploadingFile] = useState(false); const [uploadingFile, setUploadingFile] = useState(false);
@@ -31,19 +31,13 @@ export default function FileMessage({
const { const {
sendMessage, sendMessage,
isSuccess: sendMessageSuccess, isSuccess: sendMessageSuccess,
isSending, isSending
} = useSendMessage({ } = useSendMessage({
context, context,
from: from_uid, from: from_uid,
to, to
}); });
const { const { stopUploading, data, uploadFile, progress, isSuccess: uploadSuccess } = useUploadFile();
stopUploading,
data,
uploadFile,
progress,
isSuccess: uploadSuccess,
} = useUploadFile();
const fromUser = useSelector((store) => store.contacts.byId[from_uid]); const fromUser = useSelector((store) => store.contacts.byId[from_uid]);
const { size, name, content_type } = properties ?? {}; const { size, name, content_type } = properties ?? {};
useEffect(() => { useEffect(() => {
@@ -75,20 +69,14 @@ export default function FileMessage({
const propsV2 = imageSize ? { ...props, ...imageSize } : props; const propsV2 = imageSize ? { ...props, ...imageSize } : props;
// 本地文件 并且上传成功 // 本地文件 并且上传成功
if (uploadSuccess && isLocalFile(content)) { if (uploadSuccess && isLocalFile(content)) {
console.log( console.log("send local file message", uploadSuccess, propsV2, data, content);
"send local file message",
uploadSuccess,
propsV2,
data,
content
);
// 把已经上传的东西当做消息发出去 // 把已经上传的东西当做消息发出去
const { path } = data; const { path } = data;
sendMessage({ sendMessage({
ignoreLocal: true, ignoreLocal: true,
type: "file", type: "file",
content: { path }, content: { path },
properties: propsV2, properties: propsV2
}); });
} }
}, [uploadSuccess, data, properties, content]); }, [uploadSuccess, data, properties, content]);
@@ -151,11 +139,7 @@ export default function FileMessage({
{sending ? ( {sending ? (
<IconClose className="cancel" onClick={handleCancel} /> <IconClose className="cancel" onClick={handleCancel} />
) : ( ) : (
<a <a className="download" download={name} href={`${content}&download=true`}>
className="download"
download={name}
href={`${content}&download=true`}
>
<IconDownload /> <IconDownload />
</a> </a>
)} )}
+8 -26
View File
@@ -25,7 +25,7 @@ export default function ForwardModal({ mids, closeModal }) {
const { const {
channels, channels,
// input: channelInput, // input: channelInput,
updateInput: updateChannelInput, updateInput: updateChannelInput
} = useFilteredChannels(); } = useFilteredChannels();
const { contacts, input, updateInput } = useFilteredUsers(); const { contacts, input, updateInput } = useFilteredUsers();
// const { conactsData, loginUid } = useSelector((store) => { // const { conactsData, loginUid } = useSelector((store) => {
@@ -34,8 +34,7 @@ export default function ForwardModal({ mids, closeModal }) {
const toggleCheck = ({ currentTarget }) => { const toggleCheck = ({ currentTarget }) => {
const { id, type = "user" } = currentTarget.dataset; const { id, type = "user" } = currentTarget.dataset;
const ids = type == "user" ? selectedMembers : selectedChannels; const ids = type == "user" ? selectedMembers : selectedChannels;
const updateState = const updateState = type == "user" ? setSelectedMembers : setSelectedChannels;
type == "user" ? setSelectedMembers : setSelectedChannels;
let tmp = ids.includes(+id) ? ids.filter((m) => m != id) : [...ids, +id]; let tmp = ids.includes(+id) ? ids.filter((m) => m != id) : [...ids, +id];
console.log(id, currentTarget); console.log(id, currentTarget);
updateState(tmp); updateState(tmp);
@@ -47,13 +46,13 @@ export default function ForwardModal({ mids, closeModal }) {
await forwardMessage({ await forwardMessage({
mids: mids.map((mid) => +mid), mids: mids.map((mid) => +mid),
users: selectedMembers, users: selectedMembers,
channels: selectedChannels, channels: selectedChannels
}); });
if (appendText.trim()) { if (appendText.trim()) {
await sendMessages({ await sendMessages({
content: appendText, content: appendText,
users: selectedMembers, users: selectedMembers,
channels: selectedChannels, channels: selectedChannels
}); });
} }
toast.success("Forward Message Successfully"); toast.success("Forward Message Successfully");
@@ -99,12 +98,7 @@ export default function ForwardModal({ mids, closeModal }) {
className="user channel" className="user channel"
onClick={toggleCheck} onClick={toggleCheck}
> >
<StyledCheckbox <StyledCheckbox readOnly checked={checked} name="cb" id="cb" />
readOnly
checked={checked}
name="cb"
id="cb"
/>
<Channel id={gid} interactive={false} /> <Channel id={gid} interactive={false} />
</li> </li>
); );
@@ -122,12 +116,7 @@ export default function ForwardModal({ mids, closeModal }) {
className="user" className="user"
onClick={toggleCheck} onClick={toggleCheck}
> >
<StyledCheckbox <StyledCheckbox readOnly checked={checked} name="cb" id="cb" />
readOnly
checked={checked}
name="cb"
id="cb"
/>
<Contact uid={uid} interactive={false} /> <Contact uid={uid} interactive={false} />
</li> </li>
); );
@@ -162,10 +151,7 @@ export default function ForwardModal({ mids, closeModal }) {
interactive={false} interactive={false}
// avatarSize={40} // avatarSize={40}
/> />
<CloseIcon <CloseIcon className="remove" onClick={removeSelected.bind(null, uid, "user")} />
className="remove"
onClick={removeSelected.bind(null, uid, "user")}
/>
</li> </li>
); );
})} })}
@@ -185,11 +171,7 @@ export default function ForwardModal({ mids, closeModal }) {
<Button onClick={closeModal} className="normal cancel"> <Button onClick={closeModal} className="normal cancel">
Cancel Cancel
</Button> </Button>
<Button <Button className="normal" disabled={sendButtonDisabled} onClick={handleForward}>
className="normal"
disabled={sendButtonDisabled}
onClick={handleForward}
>
Send To {selectedCount == 0 ? null : `(${selectedCount})`} Send To {selectedCount == 0 ? null : `(${selectedCount})`}
</Button> </Button>
</div> </div>
+1 -5
View File
@@ -58,11 +58,7 @@ const StyledWrapper = styled.div`
} }
} }
`; `;
export default function ImagePreviewModal({ export default function ImagePreviewModal({ download = true, data, closeModal }) {
download = true,
data,
closeModal,
}) {
const [url, setUrl] = useState(data?.thumbnail); const [url, setUrl] = useState(data?.thumbnail);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const wrapperRef = useRef(); const wrapperRef = useRef();
+3 -16
View File
@@ -38,28 +38,15 @@ const StyledWrapper = styled.div`
} }
`; `;
export default function InviteLink() { export default function InviteLink() {
const { const { generating, link, linkCopied, copyLink, generateNewLink } = useInviteLink();
generating,
link,
linkCopied,
copyLink,
generateNewLink,
} = useInviteLink();
const handleNewLink = () => { const handleNewLink = () => {
generateNewLink(); generateNewLink();
}; };
return ( return (
<StyledWrapper> <StyledWrapper>
<span className="tip"> <span className="tip">Share this link to invite people to this server.</span>
Share this link to invite people to this server.
</span>
<div className="link"> <div className="link">
<Input <Input readOnly className={"large"} placeholder="Generating" value={link} />
readOnly
className={"large"}
placeholder="Generating"
value={link}
/>
<Button onClick={copyLink} className="ghost small border_less"> <Button onClick={copyLink} className="ghost small border_less">
{linkCopied ? "Copied" : `Copy`} {linkCopied ? "Copied" : `Copy`}
</Button> </Button>
+4 -15
View File
@@ -97,15 +97,12 @@ const Styled = styled.div`
} }
`; `;
export default function AddMembers({ cid = null, closeModal }) { export default function AddMembers({ cid = null, closeModal }) {
const [ const [addMembers, { isLoading: isAdding, isSuccess }] = useAddMembersMutation();
addMembers,
{ isLoading: isAdding, isSuccess },
] = useAddMembersMutation();
const [selects, setSelects] = useState([]); const [selects, setSelects] = useState([]);
const { channel, contactData } = useSelector((store) => { const { channel, contactData } = useSelector((store) => {
return { return {
channel: store.channels.byId[cid], channel: store.channels.byId[cid],
contactData: store.contacts.byId, contactData: store.contacts.byId
}; };
}); });
useEffect(() => { useEffect(() => {
@@ -145,11 +142,7 @@ export default function AddMembers({ cid = null, closeModal }) {
return ( return (
<li className="select" key={uid}> <li className="select" key={uid}>
{contactData[uid].name} {contactData[uid].name}
<CloseIcon <CloseIcon data-uid={uid} onClick={toggleCheckMember} className="close" />
data-uid={uid}
onClick={toggleCheckMember}
className="close"
/>
</li> </li>
); );
})} })}
@@ -185,11 +178,7 @@ export default function AddMembers({ cid = null, closeModal }) {
); );
})} })}
</ul> </ul>
<Button <Button disabled={selects.length == 0 || isAdding} className="btn" onClick={handleAddMembers}>
disabled={selects.length == 0 || isAdding}
className="btn"
onClick={handleAddMembers}
>
{isAdding ? `Adding` : "Add"} to #{channel.name} {isAdding ? `Adding` : "Add"} to #{channel.name}
</Button> </Button>
</Styled> </Styled>
@@ -68,14 +68,8 @@ import Button from "../styled/Button";
import Input from "../styled/Input"; import Input from "../styled/Input";
export default function InviteByEmail({ cid = null }) { export default function InviteByEmail({ cid = null }) {
const [email, setEmail] = useState(""); const [email, setEmail] = useState("");
const { const { enableSMTP, linkCopied, link, copyLink, generateNewLink, generating } =
enableSMTP, useInviteLink(cid);
linkCopied,
link,
copyLink,
generateNewLink,
generating,
} = useInviteLink(cid);
useEffect(() => { useEffect(() => {
if (linkCopied) { if (linkCopied) {
toast.success("Invite Link Copied!"); toast.success("Invite Link Copied!");
@@ -104,12 +98,7 @@ export default function InviteByEmail({ cid = null }) {
<div className="link"> <div className="link">
<label htmlFor="">Or Send invite link to your friends</label> <label htmlFor="">Or Send invite link to your friends</label>
<div className="input"> <div className="input">
<Input <Input readOnly className="invite" placeholder="Generating" value={link} />
readOnly
className="invite"
placeholder="Generating"
value={link}
/>
<button className="copy" onClick={copyLink}> <button className="copy" onClick={copyLink}>
Copy Copy
</button> </button>
+4 -12
View File
@@ -28,20 +28,14 @@ const Styled = styled.div`
`; `;
import Modal from "../Modal"; import Modal from "../Modal";
// type: server,channel // type: server,channel
export default function InviteModal({ export default function InviteModal({ type = "server", cid = null, title = "", closeModal }) {
type = "server",
cid = null,
title = "",
closeModal,
}) {
const { channel, server } = useSelector((store) => { const { channel, server } = useSelector((store) => {
return { return {
channel: store.channels.byId[cid], channel: store.channels.byId[cid],
server: store.server, server: store.server
}; };
}); });
const finalTitle = const finalTitle = type == "server" ? server.name : `#${title || channel?.name}`;
type == "server" ? server.name : `#${title || channel?.name}`;
return ( return (
<Modal> <Modal>
<Styled> <Styled>
@@ -49,9 +43,7 @@ export default function InviteModal({
Add friends to {finalTitle} Add friends to {finalTitle}
<CloseIcon className="close" onClick={closeModal} /> <CloseIcon className="close" onClick={closeModal} />
</h2> </h2>
{!channel?.is_public && ( {!channel?.is_public && <AddMembers cid={cid} closeModal={closeModal} />}
<AddMembers cid={cid} closeModal={closeModal} />
)}
<InviteByEmail cid={channel?.is_public ? null : cid} /> <InviteByEmail cid={channel?.is_public ? null : cid} />
</Styled> </Styled>
</Modal> </Modal>
@@ -30,10 +30,7 @@ export default function LeaveConfirmModal({ id, closeModal, handleNextStep }) {
} }
buttons={ buttons={
<> <>
<Button <Button onClick={closeModal.bind(null, undefined)} className="cancel">
onClick={closeModal.bind(null, undefined)}
className="cancel"
>
Cancel Cancel
</Button> </Button>
{isOwner ? ( {isOwner ? (
@@ -28,11 +28,7 @@ const UserList = styled.ul`
} }
} }
`; `;
export default function TransferOwnerModal({ export default function TransferOwnerModal({ id, closeModal, withLeave = true }) {
id,
closeModal,
withLeave = true,
}) {
const { const {
transferOwner, transferOwner,
otherMembers, otherMembers,
@@ -40,7 +36,7 @@ export default function TransferOwnerModal({
leaveChannel, leaveChannel,
leaveSuccess, leaveSuccess,
transferSuccess, transferSuccess,
transfering, transfering
} = useLeaveChannel(id); } = useLeaveChannel(id);
const [uid, setUid] = useState(null); const [uid, setUid] = useState(null);
@@ -75,17 +71,10 @@ export default function TransferOwnerModal({
description={"This cannot be undone."} description={"This cannot be undone."}
buttons={ buttons={
<> <>
<Button <Button onClick={closeModal.bind(null, undefined)} className="cancel">
onClick={closeModal.bind(null, undefined)}
className="cancel"
>
Cancel Cancel
</Button> </Button>
<Button <Button disabled={!uid} onClick={handleTransferAndLeave} className="danger">
disabled={!uid}
onClick={handleTransferAndLeave}
className="danger"
>
{operating ? "Assigning" : `Assign and Leave`} {operating ? "Assigning" : `Assign and Leave`}
</Button> </Button>
</> </>
+3 -14
View File
@@ -2,22 +2,11 @@ import { useState } from "react";
// import styled from "styled-components"; // import styled from "styled-components";
import TransferOwnerModal from "./TransferOwnerModal"; import TransferOwnerModal from "./TransferOwnerModal";
import LeaveConfirmModal from "./LeaveConfirmModal"; import LeaveConfirmModal from "./LeaveConfirmModal";
export default function LeaveChannel({ export default function LeaveChannel({ id = null, isOwner = false, closeModal }) {
id = null,
isOwner = false,
closeModal,
}) {
const [transferOwner, setTransferOwner] = useState(isOwner); const [transferOwner, setTransferOwner] = useState(isOwner);
const handleNextStep = () => { const handleNextStep = () => {
setTransferOwner(true); setTransferOwner(true);
}; };
if (transferOwner) if (transferOwner) return <TransferOwnerModal id={id} closeModal={closeModal} />;
return <TransferOwnerModal id={id} closeModal={closeModal} />; return <LeaveConfirmModal id={id} closeModal={closeModal} handleNextStep={handleNextStep} />;
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 ( return (
<StyledWrapper className={fullscreen ? "fullscreen" : ""}> <StyledWrapper className={fullscreen ? "fullscreen" : ""}>
<Ring <Ring className="loading" size={40} lineWeight={5} speed={2} color="black" />
className="loading" <Button className={`reload danger ${reload ? "visible" : ""}`} onClick={handleReload}>
size={40}
lineWeight={5}
speed={2}
color="black"
/>
<Button
className={`reload danger ${reload ? "visible" : ""}`}
onClick={handleReload}
>
Reload Reload
</Button> </Button>
</StyledWrapper> </StyledWrapper>
+11 -33
View File
@@ -119,20 +119,12 @@ export default function ManageMembers({ cid = null }) {
return { return {
contacts: store.contacts, contacts: store.contacts,
channels: store.channels, channels: store.channels,
loginUser: store.contacts.byId[store.authData.uid], loginUser: store.contacts.byId[store.authData.uid]
}; };
}); });
const { const { copyEmail, removeFromChannel, removeUser, canRemove, canRemoveFromChannel } =
copyEmail, useContactOperation({ cid });
removeFromChannel, const [updateContact, { isSuccess: updateSuccess }] = useUpdateContactMutation();
removeUser,
canRemove,
canRemoveFromChannel,
} = useContactOperation({ cid });
const [
updateContact,
{ isSuccess: updateSuccess },
] = useUpdateContactMutation();
useEffect(() => { useEffect(() => {
if (updateSuccess) { if (updateSuccess) {
@@ -146,11 +138,7 @@ export default function ManageMembers({ cid = null }) {
updateContact({ id: uid, is_admin: isAdmin }); updateContact({ id: uid, is_admin: isAdmin });
}; };
const channel = channels.byId[cid] ?? null; const channel = channels.byId[cid] ?? null;
const uids = channel const uids = channel ? (channel.is_public ? contacts.ids : channel.members) : contacts.ids;
? channel.is_public
? contacts.ids
: channel.members
: contacts.ids;
return ( return (
<StyledWrapper> <StyledWrapper>
@@ -158,8 +146,7 @@ export default function ManageMembers({ cid = null }) {
<div className="intro"> <div className="intro">
<h4 className="title">Manage Members</h4> <h4 className="title">Manage Members</h4>
<p className="desc"> <p className="desc">
Disabling your account means you can recover it at any time after Disabling your account means you can recover it at any time after taking this action.
taking this action.
</p> </p>
</div> </div>
<ul className="members"> <ul className="members">
@@ -194,7 +181,7 @@ export default function ManageMembers({ cid = null }) {
onClick={handleToggleRole.bind(null, { onClick={handleToggleRole.bind(null, {
ignore: is_admin, ignore: is_admin,
uid, uid,
isAdmin: true, isAdmin: true
})} })}
> >
Admin Admin
@@ -205,7 +192,7 @@ export default function ManageMembers({ cid = null }) {
onClick={handleToggleRole.bind(null, { onClick={handleToggleRole.bind(null, {
ignore: !is_admin, ignore: !is_admin,
uid, uid,
isAdmin: false, isAdmin: false
})} })}
> >
User User
@@ -226,10 +213,7 @@ export default function ManageMembers({ cid = null }) {
content={ content={
<StyledMenu className="menu"> <StyledMenu className="menu">
{email && ( {email && (
<li <li className="item" onClick={copyEmail.bind(null, email)}>
className="item"
onClick={copyEmail.bind(null, email)}
>
Copy Email Copy Email
</li> </li>
)} )}
@@ -237,18 +221,12 @@ export default function ManageMembers({ cid = null }) {
{/* <li className="item underline">Change Nickname</li> */} {/* <li className="item underline">Change Nickname</li> */}
{/* <li className="item danger">Ban</li> */} {/* <li className="item danger">Ban</li> */}
{canRemoveFromChannel && ( {canRemoveFromChannel && (
<li <li className="item danger" onClick={removeFromChannel.bind(null, uid)}>
className="item danger"
onClick={removeFromChannel.bind(null, uid)}
>
Remove From Channel Remove From Channel
</li> </li>
)} )}
{canRemove && !cid && ( {canRemove && !cid && (
<li <li className="item danger" onClick={removeUser.bind(null, uid)}>
className="item danger"
onClick={removeUser.bind(null, uid)}
>
Remove From Server Remove From Server
</li> </li>
)} )}
+1 -1
View File
@@ -11,6 +11,6 @@ export default function usePrompt() {
return { return {
setCanneled: setPrompt, setCanneled: setPrompt,
prompted: !!localStorage.getItem(KEY_PWA_INSTALLED), prompted: !!localStorage.getItem(KEY_PWA_INSTALLED),
resetPrompt, resetPrompt
}; };
} }
+1 -1
View File
@@ -16,7 +16,7 @@ function MarkdownEditor({
height = "50vh", height = "50vh",
placeholder, placeholder,
sendMarkdown, sendMarkdown,
setEditorInstance, setEditorInstance
}) { }) {
const editorRef = useRef(undefined); const editorRef = useRef(undefined);
const { uploadFile } = useUploadFile(); const { uploadFile } = useUploadFile();
+9 -17
View File
@@ -62,12 +62,7 @@ const StyledCmds = styled.ul`
transform: translateX(-100%); transform: translateX(-100%);
} }
`; `;
export default function Commands({ export default function Commands({ context = "user", contextId = 0, mid = 0, toggleEditMessage }) {
context = "user",
contextId = 0,
mid = 0,
toggleEditMessage,
}) {
const { const {
canDelete, canDelete,
canReply, canReply,
@@ -80,11 +75,11 @@ export default function Commands({
togglePinModal, togglePinModal,
PinModal, PinModal,
DeleteModal, DeleteModal,
ForwardModal, ForwardModal
} = useMessageOperation({ mid, context, contextId }); } = useMessageOperation({ mid, context, contextId });
const { setReplying } = useSendMessage({ context, to: contextId }); const { setReplying } = useSendMessage({ context, to: contextId });
const { addFavorite, isFavorited } = useFavMessage({ const { addFavorite, isFavorited } = useFavMessage({
cid: context == "channel" ? contextId : null, cid: context == "channel" ? contextId : null
}); });
const dispatch = useDispatch(); const dispatch = useDispatch();
const [tippyVisible, setTippyVisible] = useState(false); const [tippyVisible, setTippyVisible] = useState(false);
@@ -126,10 +121,7 @@ export default function Commands({
return ( return (
<> <>
<StyledCmds <StyledCmds ref={cmdsRef} className={`cmds ${tippyVisible ? "visible" : ""}`}>
ref={cmdsRef}
className={`cmds ${tippyVisible ? "visible" : ""}`}
>
<Tippy <Tippy
onShow={handleTippyVisible.bind(null, true)} onShow={handleTippyVisible.bind(null, true)}
onHide={handleTippyVisible.bind(null, false)} onHide={handleTippyVisible.bind(null, false)}
@@ -175,25 +167,25 @@ export default function Commands({
canPin && { canPin && {
title: pinned ? `Unpin Message` : `Pin Message`, title: pinned ? `Unpin Message` : `Pin Message`,
icon: <IconPin className="icon" />, icon: <IconPin className="icon" />,
handler: pinned ? handleUnpin : togglePinModal, handler: pinned ? handleUnpin : togglePinModal
}, },
{ {
title: "Forward", title: "Forward",
icon: <IconForward className="icon" />, icon: <IconForward className="icon" />,
handler: toggleForwardModal, handler: toggleForwardModal
}, },
{ {
title: "Select", title: "Select",
icon: <IconSelect className="icon" />, icon: <IconSelect className="icon" />,
handler: handleSelect.bind(null, mid), handler: handleSelect.bind(null, mid)
}, },
canDelete && { canDelete && {
title: " Delete", title: " Delete",
danger: true, danger: true,
icon: <IconDelete className="icon" />, icon: <IconDelete className="icon" />,
handler: toggleDeleteModal, handler: toggleDeleteModal
}, }
]} ]}
/> />
} }
+10 -10
View File
@@ -20,7 +20,7 @@ export default function MessageContextMenu({
visible, visible,
hide, hide,
editMessage, editMessage,
children, children
}) { }) {
const { const {
copyContent, copyContent,
@@ -37,7 +37,7 @@ export default function MessageContextMenu({
togglePinModal, togglePinModal,
PinModal, PinModal,
ForwardModal, ForwardModal,
DeleteModal, DeleteModal
} = useMessageOperation({ mid, contextId, context }); } = useMessageOperation({ mid, contextId, context });
const dispatch = useDispatch(); const dispatch = useDispatch();
const { setReplying } = useSendMessage({ context, to: contextId }); const { setReplying } = useSendMessage({ context, to: contextId });
@@ -71,39 +71,39 @@ export default function MessageContextMenu({
canEdit && { canEdit && {
title: "Edit Message", title: "Edit Message",
icon: <IconEdit className="icon" />, icon: <IconEdit className="icon" />,
handler: editMessage, handler: editMessage
}, },
canReply && { canReply && {
title: "Reply", title: "Reply",
icon: <IconReply className="icon" />, icon: <IconReply className="icon" />,
handler: handleReply, handler: handleReply
}, },
canCopy && { canCopy && {
title: "Copy", title: "Copy",
icon: <IconCopy className="icon" />, icon: <IconCopy className="icon" />,
handler: copyContent, handler: copyContent
}, },
canPin && { canPin && {
title: pinned ? "Unpin" : "Pin", title: pinned ? "Unpin" : "Pin",
icon: <IconPin className="icon" />, icon: <IconPin className="icon" />,
handler: pinned ? unPin.bind(null, mid) : togglePinModal, handler: pinned ? unPin.bind(null, mid) : togglePinModal
}, },
{ {
title: "Forward", title: "Forward",
icon: <IconForward className="icon" />, icon: <IconForward className="icon" />,
handler: toggleForwardModal, handler: toggleForwardModal
}, },
{ {
title: "Select", title: "Select",
icon: <IconSelect className="icon" />, icon: <IconSelect className="icon" />,
handler: handleSelect, handler: handleSelect
}, },
canDelete && { canDelete && {
title: "Delete", title: "Delete",
danger: true, danger: true,
icon: <IconDelete className="icon" />, icon: <IconDelete className="icon" />,
handler: toggleDeleteModal, handler: toggleDeleteModal
}, }
]} ]}
/> />
} }
+2 -3
View File
@@ -91,7 +91,7 @@ export default function EditMessage({ mid, cancelEdit }) {
edit({ edit({
mid, mid,
content: currMsg, content: currMsg,
type: msg.content_type == ContentTypes.markdown ? "markdown" : "text", type: msg.content_type == ContentTypes.markdown ? "markdown" : "text"
}); });
}; };
if (!msg) return null; if (!msg) return null;
@@ -121,8 +121,7 @@ export default function EditMessage({ mid, cancelEdit }) {
esc to <button onClick={cancelEdit}>cancel</button> esc to <button onClick={cancelEdit}>cancel</button>
</span> </span>
<span className="opt"> <span className="opt">
enter to{" "} enter to <button onClick={handleSave}>{isEditing ? "saving" : `save`}</button>
<button onClick={handleSave}>{isEditing ? "saving" : `save`}</button>
</span> </span>
</div> </div>
</StyledWrapper> </StyledWrapper>
@@ -23,20 +23,10 @@ const FavoritedMessage = ({ id }) => {
const favorite_mids = messages.map(({ from_mid }) => from_mid) || []; const favorite_mids = messages.map(({ from_mid }) => from_mid) || [];
setMsgs( setMsgs(
<StyledFav <StyledFav data-favorite-mids={favorite_mids.join(",")} className="favorite">
data-favorite-mids={favorite_mids.join(",")}
className="favorite"
>
<div className="list"> <div className="list">
{messages.map((msg, idx) => { {messages.map((msg, idx) => {
const { const { user = {}, download, content, content_type, properties, thumbnail } = msg;
user = {},
download,
content,
content_type,
properties,
thumbnail,
} = msg;
return ( return (
<StyledMsg className="archive" key={idx}> <StyledMsg className="archive" key={idx}>
{user && ( {user && (
@@ -54,7 +44,7 @@ const FavoritedMessage = ({ id }) => {
content, content,
content_type, content_type,
properties, properties,
thumbnail, thumbnail
})} })}
</div> </div>
</div> </div>
@@ -56,7 +56,7 @@ const ForwardedMessage = ({ context, to, from_uid, id }) => {
content, content,
content_type, content_type,
properties, properties,
thumbnail, thumbnail
} = msg; } = msg;
return ( return (
<StyledMsg className="archive" key={idx}> <StyledMsg className="archive" key={idx}>
@@ -78,7 +78,7 @@ const ForwardedMessage = ({ context, to, from_uid, id }) => {
content, content,
content_type, content_type,
properties, properties,
thumbnail, thumbnail
})} })}
</div> </div>
</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 }; return { msg: store.message[mid], contactsData: store.contacts.byId };
}); });
if (!msg) return null; if (!msg) return null;
const { const { from_uid, created_at, content_type, content, thumbnail, properties } = msg;
from_uid,
created_at,
content_type,
content,
thumbnail,
properties,
} = msg;
const { name, avatar } = contactsData[from_uid]; const { name, avatar } = contactsData[from_uid];
return ( return (
<StyledWrapper className={`preview`}> <StyledWrapper className={`preview`}>
@@ -26,9 +19,7 @@ export default function PreviewMessage({ mid = 0 }) {
<div className="details"> <div className="details">
<div className="up"> <div className="up">
<span className="name">{name}</span> <span className="name">{name}</span>
<i className="time"> <i className="time">{dayjs(created_at).format("YYYY-MM-DD h:mm:ss A")}</i>
{dayjs(created_at).format("YYYY-MM-DD h:mm:ss A")}
</i>
</div> </div>
<div className={`down`}> <div className={`down`}>
{renderContent({ {renderContent({
@@ -36,7 +27,7 @@ export default function PreviewMessage({ mid = 0 }) {
content, content,
thumbnail, thumbnail,
from_uid, from_uid,
properties, properties
})} })}
</div> </div>
</div> </div>
+4 -9
View File
@@ -70,8 +70,7 @@ const StyledDetails = styled.div`
position: relative; position: relative;
background: #ffffff; background: #ffffff;
border-radius: var(--br); border-radius: var(--br);
box-shadow: 0px 12px 16px -4px rgba(16, 24, 40, 0.08), box-shadow: 0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03);
0px 4px 6px -2px rgba(16, 24, 40, 0.03);
display: flex; display: flex;
align-items: flex-start; align-items: flex-start;
gap: 8px; gap: 8px;
@@ -132,7 +131,7 @@ export default function Reaction({ mid, reactions = null }) {
const [reactWithEmoji] = useReactMessageMutation(); const [reactWithEmoji] = useReactMessageMutation();
const { currUid } = useSelector((store) => { const { currUid } = useSelector((store) => {
return { return {
currUid: store.authData.uid, currUid: store.authData.uid
}; };
}); });
const handleReact = (emoji) => { const handleReact = (emoji) => {
@@ -156,18 +155,14 @@ export default function Reaction({ mid, reactions = null }) {
// visible={true} // visible={true}
interactive interactive
placement="top" placement="top"
content={ content={<ReactionDetails uids={uids} emoji={reaction} index={idx} />}
<ReactionDetails uids={uids} emoji={reaction} index={idx} />
}
> >
<i className="emoji"> <i className="emoji">
<ReactionItem native={reaction} /> <ReactionItem native={reaction} />
</i> </i>
</Tippy> </Tippy>
{uids.length > 1 ? ( {uids.length > 1 ? <em className="count">{`${uids.length}`} </em> : null}
<em className="count">{`${uids.length}`} </em>
) : null}
</span> </span>
) : null; ) : null;
})} })}
@@ -41,7 +41,7 @@ export default function ReactionPicker({ mid, hidePicker }) {
const { reactionData, currUid } = useSelector((store) => { const { reactionData, currUid } = useSelector((store) => {
return { return {
reactionData: store.reactionMessage[mid] || {}, reactionData: store.reactionMessage[mid] || {},
currUid: store.authData.uid, currUid: store.authData.uid
}; };
}); });
// useOutsideClick(wrapperRef, hidePicker); // useOutsideClick(wrapperRef, hidePicker);
@@ -55,8 +55,7 @@ export default function ReactionPicker({ mid, hidePicker }) {
<ul className={`emojis ${isLoading ? "reacting" : ""}`}> <ul className={`emojis ${isLoading ? "reacting" : ""}`}>
{Emojis.map((emoji) => { {Emojis.map((emoji) => {
let reacted = let reacted =
reactionData[emoji] && reactionData[emoji] && reactionData[emoji].findIndex((id) => id == currUid) > -1;
reactionData[emoji].findIndex((id) => id == currUid) > -1;
return ( return (
<li <li
+1 -5
View File
@@ -66,11 +66,7 @@ const Styled = styled.div`
width: 100%; width: 100%;
height: 100%; height: 100%;
content: ""; content: "";
background: linear-gradient( background: linear-gradient(180deg, rgba(255, 255, 255, 0) 63.54%, #e5e7eb 93.09%);
180deg,
rgba(255, 255, 255, 0) 63.54%,
#e5e7eb 93.09%
);
} }
} }
.pic { .pic {
+19 -25
View File
@@ -26,26 +26,24 @@ function Message({
mid = "", mid = "",
context = "user", context = "user",
updateReadIndex, updateReadIndex,
read = true, read = true
}) { }) {
const { const { visible: contextMenuVisible, handleContextMenuEvent, hideContextMenu } = useContextMenu();
visible: contextMenuVisible,
handleContextMenuEvent,
hideContextMenu,
} = useContextMenu();
const inviewRef = useInView(); const inviewRef = useInView();
const [edit, setEdit] = useState(false); const [edit, setEdit] = useState(false);
const avatarRef = useRef(null); const avatarRef = useRef(null);
const { getPinInfo } = usePinMessage(context == "channel" ? contextId : null); const { getPinInfo } = usePinMessage(context == "channel" ? contextId : null);
const { message = {}, reactionMessageData, contactsData } = useSelector( const {
(store) => { message = {},
return { reactionMessageData,
reactionMessageData: store.reactionMessage, contactsData
message: store.message[mid] || {}, } = useSelector((store) => {
contactsData: store.contacts.byId, return {
}; reactionMessageData: store.reactionMessage,
} message: store.message[mid] || {},
); contactsData: store.contacts.byId
};
});
const toggleEditMessage = () => { const toggleEditMessage = () => {
setEdit((prev) => !prev); setEdit((prev) => !prev);
@@ -61,7 +59,7 @@ function Message({
download, download,
content_type = "text/plain", content_type = "text/plain",
edited, edited,
properties, properties
} = message; } = message;
useEffect(() => { useEffect(() => {
@@ -79,11 +77,7 @@ function Message({
// if (!message) return null; // if (!message) return null;
let timePrefix = null; let timePrefix = null;
const dayjsTime = dayjs(time); const dayjsTime = dayjs(time);
timePrefix = dayjsTime.isToday() timePrefix = dayjsTime.isToday() ? "Today" : dayjsTime.isYesterday() ? "Yesterday" : null;
? "Today"
: dayjsTime.isYesterday()
? "Yesterday"
: null;
const pinInfo = getPinInfo(mid); const pinInfo = getPinInfo(mid);
// return null; // return null;
@@ -94,9 +88,9 @@ function Message({
onContextMenu={handleContextMenuEvent} onContextMenu={handleContextMenuEvent}
data-msg-mid={mid} data-msg-mid={mid}
ref={inviewRef} ref={inviewRef}
className={`message ${readOnly ? "readonly" : ""} ${ className={`message ${readOnly ? "readonly" : ""} ${pinInfo ? "pinned" : ""} ${
pinInfo ? "pinned" : "" contextMenuVisible ? "contextVisible" : ""
} ${contextMenuVisible ? "contextVisible" : ""} `} } `}
> >
<Tippy <Tippy
key={_key} key={_key}
@@ -153,7 +147,7 @@ function Message({
content, content,
thumbnail, thumbnail,
download, download,
edited, edited
}) })
)} )}
{reactions && <Reaction mid={mid} reactions={reactions} />} {reactions && <Reaction mid={mid} reactions={reactions} />}
+9 -24
View File
@@ -20,7 +20,7 @@ const renderContent = ({
content, content,
download, download,
thumbnail, thumbnail,
edited = false, edited = false
}) => { }) => {
let ctn = null; let ctn = null;
switch (content_type) { switch (content_type) {
@@ -30,38 +30,23 @@ const renderContent = ({
<Linkit <Linkit
componentDecorator={(decoratedHref, decoratedText, key) => ( componentDecorator={(decoratedHref, decoratedText, key) => (
<React.Fragment key={key}> <React.Fragment key={key}>
<a <a className="link" target="_blank" href={decoratedHref} key={key} rel="noreferrer">
className="link"
target="_blank"
href={decoratedHref}
key={key}
rel="noreferrer"
>
{decoratedText} {decoratedText}
</a> </a>
{!decoratedHref.startsWith("mailto") && ( {!decoratedHref.startsWith("mailto") && <URLPreview url={decoratedHref} />}
<URLPreview url={decoratedHref} />
)}
</React.Fragment> </React.Fragment>
)} )}
> >
{reactStringReplace( {reactStringReplace(content, /(\s{1}@[0-9]+\s{1})/g, (match, idx) => {
content, console.log("match", match);
/(\s{1}@[0-9]+\s{1})/g, const uid = match.trim().slice(1);
(match, idx) => { return <Mention key={idx} uid={uid} cid={to} />;
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,)} */} {/* {content.replace(/\s{1}\@[1-9]+\s{1}/g,)} */}
{/* {new RegExp(/\s{1}\@[1-9]+\s{1}/g).exec(content)} */} {/* {new RegExp(/\s{1}\@[1-9]+\s{1}/g).exec(content)} */}
</Linkit> </Linkit>
{edited && ( {edited && (
<span <span className="edited" title={dayjs(edited).format("YYYY-MM-DD h:mm:ss A")}>
className="edited"
title={dayjs(edited).format("YYYY-MM-DD h:mm:ss A")}
>
(edited) (edited)
</span> </span>
)} )}
@@ -10,17 +10,15 @@ import usePinMessage from "../../hook/usePinMessage";
export default function useMessageOperation({ mid, context, contextId }) { export default function useMessageOperation({ mid, context, contextId }) {
const { copy } = useCopy(); const { copy } = useCopy();
const { content_type, properties, currUid, from_uid, content } = useSelector( const { content_type, properties, currUid, from_uid, content } = useSelector((store) => {
(store) => { return {
return { content: store.message[mid]?.content,
content: store.message[mid]?.content, from_uid: store.message[mid]?.from_uid,
from_uid: store.message[mid]?.from_uid, content_type: store.message[mid]?.content_type,
content_type: store.message[mid]?.content_type, properties: store.message[mid]?.properties,
properties: store.message[mid]?.properties, currUid: store.authData.uid
currUid: store.authData.uid, };
}; });
}
);
const { canPin, pins, unpinMessage, isUnpinSuccess } = usePinMessage( const { canPin, pins, unpinMessage, isUnpinSuccess } = usePinMessage(
context == "channel" ? contextId : undefined context == "channel" ? contextId : undefined
); );
@@ -71,16 +69,11 @@ export default function useMessageOperation({ mid, context, contextId }) {
properties?.content_type && properties?.content_type &&
properties?.content_type.startsWith("image"); properties?.content_type.startsWith("image");
const enableEdit = const enableEdit =
currUid == from_uid && currUid == from_uid && [ContentTypes.text, ContentTypes.markdown].includes(content_type);
[ContentTypes.text, ContentTypes.markdown].includes(content_type);
const canDelete = currUid == from_uid; const canDelete = currUid == from_uid;
const canCopy = const canCopy = [ContentTypes.text, ContentTypes.markdown].includes(content_type) || isImage;
[ContentTypes.text, ContentTypes.markdown].includes(content_type) ||
isImage;
return { return {
copyContent: isImage copyContent: isImage ? copyContent.bind(null, true) : copyContent.bind(null, false),
? copyContent.bind(null, true)
: copyContent.bind(null, false),
canCopy, canCopy,
isImage, isImage,
isMarkdown: content_type == ContentTypes.markdown, isMarkdown: content_type == ContentTypes.markdown,
@@ -101,6 +94,6 @@ export default function useMessageOperation({ mid, context, contextId }) {
) : null, ) : null,
PinModal: pinModalVisible ? ( PinModal: pinModalVisible ? (
<PinMessageModal mid={mid} gid={contextId} closeModal={togglePinModal} /> <PinMessageModal mid={mid} gid={contextId} closeModal={togglePinModal} />
) : null, ) : null
}; };
} }
+15 -15
View File
@@ -4,7 +4,7 @@ export const CONFIG = {
editableProps: { editableProps: {
spellCheck: false, spellCheck: false,
autoFocus: true, autoFocus: true,
placeholder: "Type…", placeholder: "Type…"
}, },
trailingBlock: { type: ELEMENT_PARAGRAPH }, trailingBlock: { type: ELEMENT_PARAGRAPH },
softBreak: { softBreak: {
@@ -13,11 +13,11 @@ export const CONFIG = {
{ {
hotkey: "shift+enter", hotkey: "shift+enter",
query: { query: {
allow: [ELEMENT_IMAGE, ELEMENT_PARAGRAPH], allow: [ELEMENT_IMAGE, ELEMENT_PARAGRAPH]
}, }
}, }
], ]
}, }
}, },
exitBreak: { exitBreak: {
options: { options: {
@@ -25,9 +25,9 @@ export const CONFIG = {
{ {
hotkey: "mod+enter", hotkey: "mod+enter",
query: { query: {
allow: [ELEMENT_IMAGE, ELEMENT_PARAGRAPH], allow: [ELEMENT_IMAGE, ELEMENT_PARAGRAPH]
}, }
}, }
// { // {
// hotkey: "mod+shift+enter", // hotkey: "mod+shift+enter",
// before: true, // before: true,
@@ -40,14 +40,14 @@ export const CONFIG = {
// allow: KEYS_HEADING, // allow: KEYS_HEADING,
// }, // },
// }, // },
], ]
}, }
}, },
selectOnBackspace: { selectOnBackspace: {
options: { options: {
query: { query: {
allow: [ELEMENT_IMAGE], allow: [ELEMENT_IMAGE]
}, }
}, }
}, }
}; };
+23 -39
View File
@@ -23,7 +23,7 @@ import {
// usePlateEditorRef, // usePlateEditorRef,
// ELEMENT_IMAGE, // ELEMENT_IMAGE,
// useComboboxControls, // useComboboxControls,
MentionCombobox, MentionCombobox
} from "@udecode/plate"; } from "@udecode/plate";
import { createComboboxPlugin } from "@udecode/plate-combobox"; import { createComboboxPlugin } from "@udecode/plate-combobox";
import { ReactEditor } from "slate-react"; import { ReactEditor } from "slate-react";
@@ -44,7 +44,7 @@ const Plugins = ({
id = "", id = "",
placeholder = "Write some markdown...", placeholder = "Write some markdown...",
sendMessages, sendMessages,
members = [], members = []
}) => { }) => {
// const { getMenuProps, getItemProps } = useComboboxControls(); // const { getMenuProps, getItemProps } = useComboboxControls();
const [context, to] = id.split("_"); const [context, to] = id.split("_");
@@ -57,7 +57,7 @@ const Plugins = ({
const initialProps = { const initialProps = {
...CONFIG.editableProps, ...CONFIG.editableProps,
className: "box", className: "box",
placeholder, placeholder
}; };
const plateEditor = getPlateEditorRef(`${TEXT_EDITOR_PREFIX}_${id}`); const plateEditor = getPlateEditorRef(`${TEXT_EDITOR_PREFIX}_${id}`);
useEffect(() => { useEffect(() => {
@@ -91,13 +91,7 @@ const Plugins = ({
(evt) => { (evt) => {
// 是否在at操作 // 是否在at操作
const mentionInputs = findMentionInput(plateEditor); const mentionInputs = findMentionInput(plateEditor);
if ( if (mentionInputs || evt.shiftKey || evt.ctrlKey || evt.altKey || evt.isComposing) {
mentionInputs ||
evt.shiftKey ||
evt.ctrlKey ||
evt.altKey ||
evt.isComposing
) {
return true; return true;
} }
evt.preventDefault(); evt.preventDefault();
@@ -107,14 +101,14 @@ const Plugins = ({
Transforms.delete(plateEditor, { Transforms.delete(plateEditor, {
at: { at: {
anchor: Editor.start(plateEditor, []), anchor: Editor.start(plateEditor, []),
focus: Editor.end(plateEditor, []), focus: Editor.end(plateEditor, [])
}, }
}); });
}, },
{ {
// eventTypes: ["keydown"], // eventTypes: ["keydown"],
target: editableRef, target: editableRef,
when: !cmdKey, when: !cmdKey
} }
); );
useKey( useKey(
@@ -125,7 +119,7 @@ const Plugins = ({
}, },
{ {
eventTypes: ["keydown", "keyup"], eventTypes: ["keydown", "keyup"],
target: editableRef, target: editableRef
} }
); );
const pluginArr = [ const pluginArr = [
@@ -133,7 +127,7 @@ const Plugins = ({
createNodeIdPlugin(), createNodeIdPlugin(),
createSoftBreakPlugin(CONFIG.softBreak), createSoftBreakPlugin(CONFIG.softBreak),
createTrailingBlockPlugin(CONFIG.trailingBlock), createTrailingBlockPlugin(CONFIG.trailingBlock),
createExitBreakPlugin(CONFIG.exitBreak), createExitBreakPlugin(CONFIG.exitBreak)
]; ];
const plugins = createPlugins( const plugins = createPlugins(
enableMentions enableMentions
@@ -145,17 +139,17 @@ const Plugins = ({
console.log("mention", item); console.log("mention", item);
const { const {
text, text,
data: { uid }, data: { uid }
} = item; } = item;
return { value: `@${text}`, uid }; return { value: `@${text}`, uid };
}, },
insertSpaceAfterMention: true, insertSpaceAfterMention: true
}, }
}), })
]) ])
: pluginArr, : pluginArr,
{ {
components, components
} }
); );
@@ -179,20 +173,16 @@ const Plugins = ({
const { value, mentions } = getMixedText(v.children); const { value, mentions } = getMixedText(v.children);
const prev = tmps[tmps.length - 1]; const prev = tmps[tmps.length - 1];
if (!prev) { if (!prev) {
tmps.push([ tmps.push([{ type: "text", content: value, properties: { mentions } }]);
{ type: "text", content: value, properties: { mentions } },
]);
} else { } else {
if (Array.isArray(prev)) { if (Array.isArray(prev)) {
tmps[tmps.length - 1].push({ tmps[tmps.length - 1].push({
type: "text", type: "text",
content: value, content: value,
properties: { mentions }, properties: { mentions }
}); });
} else { } else {
tmps.push([ tmps.push([{ type: "text", content: value, properties: { mentions } }]);
{ type: "text", content: value, properties: { mentions } },
]);
} }
} }
} }
@@ -202,8 +192,8 @@ const Plugins = ({
type: "text", type: "text",
content: tmp.map((t) => t.content).join("\n"), content: tmp.map((t) => t.content).join("\n"),
properties: { properties: {
mentions: tmp.map((t) => t.properties?.mentions || []).flat(), mentions: tmp.map((t) => t.properties?.mentions || []).flat()
}, }
} }
: tmp; : tmp;
}); });
@@ -229,13 +219,7 @@ const Plugins = ({
// component={StyledCombobox} // component={StyledCombobox}
onRenderItem={({ item }) => { onRenderItem={({ item }) => {
console.log("wtf", item); console.log("wtf", item);
return ( return <Contact key={item.data.uid} uid={item.data.uid} interactive={false} />;
<Contact
key={item.data.uid}
uid={item.data.uid}
interactive={false}
/>
);
}} }}
items={members.map((id) => { items={members.map((id) => {
const data = contactData[id]; const data = contactData[id];
@@ -246,8 +230,8 @@ const Plugins = ({
text: name, text: name,
data: { data: {
uid, uid,
...rest, ...rest
}, }
}; };
})} })}
/> />
@@ -273,7 +257,7 @@ export const useMixedEditor = (key) => {
}; };
return { return {
focus, focus,
insertText, insertText
}; };
}; };
export default Plugins; export default Plugins;
+4 -4
View File
@@ -2,12 +2,12 @@ import {
createBasicElementsPlugin, createBasicElementsPlugin,
createImagePlugin, createImagePlugin,
createParagraphPlugin, createParagraphPlugin,
createSelectOnBackspacePlugin, createSelectOnBackspacePlugin
} from "@udecode/plate"; } from "@udecode/plate";
import { CONFIG } from "./config"; import { CONFIG } from "./config";
const basicElements = [ const basicElements = [
createParagraphPlugin(), // paragraph element createParagraphPlugin() // paragraph element
]; ];
export const PLUGINS = { export const PLUGINS = {
@@ -16,6 +16,6 @@ export const PLUGINS = {
image: [ image: [
createBasicElementsPlugin(), createBasicElementsPlugin(),
createImagePlugin(), createImagePlugin(),
createSelectOnBackspacePlugin(CONFIG.selectOnBackspace), createSelectOnBackspacePlugin(CONFIG.selectOnBackspace)
], ]
}; };
@@ -1,6 +1,6 @@
const nodeTypes = { const nodeTypes = {
paragraph: "p", paragraph: "p",
image: "img", image: "img"
}; };
export default nodeTypes; export default nodeTypes;
@@ -1,13 +1,10 @@
import { import {
ELEMENT_PARAGRAPH, ELEMENT_PARAGRAPH
// TElement, // TElement,
} from "@udecode/plate"; } from "@udecode/plate";
// import { Text } from 'slate' // import { Text } from 'slate'
export const createElement = ( export const createElement = (text = "", { type = ELEMENT_PARAGRAPH, mark } = {}) => {
text = "",
{ type = ELEMENT_PARAGRAPH, mark } = {}
) => {
const leaf = { text }; const leaf = { text };
if (mark) { if (mark) {
leaf[mark] = true; leaf[mark] = true;
@@ -15,7 +12,7 @@ export const createElement = (
return { return {
type, type,
children: [leaf], children: [leaf]
}; };
}; };
+4 -8
View File
@@ -24,11 +24,11 @@ const Styled = styled.div`
} }
`; `;
export default function MrakdownRender({ content }) { export default function MrakdownRender({ content }) {
const mdContainer = useRef(null); const mdContainer = useRef(undefined);
const [previewImage, setPreviewImage] = useState(null); const [previewImage, setPreviewImage] = useState(null);
useEffect(() => { useEffect(() => {
if (mdContainer) { const container = mdContainer?.current;
const container = mdContainer.current; if (container) {
// 点击查看大图 // 点击查看大图
container.addEventListener( container.addEventListener(
"click", "click",
@@ -56,11 +56,7 @@ export default function MrakdownRender({ content }) {
return ( return (
<> <>
{previewImage && ( {previewImage && (
<ImagePreviewModal <ImagePreviewModal download={false} data={previewImage} closeModal={closePreviewModal} />
download={false}
data={previewImage}
closeModal={closePreviewModal}
/>
)} )}
<Styled ref={mdContainer}> <Styled ref={mdContainer}>
<Viewer <Viewer
+2 -8
View File
@@ -21,15 +21,9 @@ const Notification = () => {
navigateTo(newPath); navigateTo(newPath);
}; };
// https only // https only
navigator.serviceWorker?.addEventListener( navigator.serviceWorker?.addEventListener("message", handleServiceworkerMessage);
"message",
handleServiceworkerMessage
);
return () => { return () => {
navigator.serviceWorker?.removeEventListener( navigator.serviceWorker?.removeEventListener("message", handleServiceworkerMessage);
"message",
handleServiceworkerMessage
);
}; };
}, []); }, []);
@@ -9,7 +9,7 @@ const useDeviceToken = (vapidKey) => {
const messaging = getMessaging(initializeApp(firebaseConfig)); const messaging = getMessaging(initializeApp(firebaseConfig));
getToken(messaging, { getToken(messaging, {
vapidKey, vapidKey
}) })
.then((currentToken) => { .then((currentToken) => {
if (currentToken) { if (currentToken) {
@@ -19,9 +19,7 @@ const useDeviceToken = (vapidKey) => {
// Perform any other neccessary action with the token // Perform any other neccessary action with the token
} else { } else {
// Show permission request UI // Show permission request UI
console.log( console.log("No registration token available. Request permission to generate one.");
"No registration token available. Request permission to generate one."
);
} }
}) })
.catch((err) => { .catch((err) => {
+4 -5
View File
@@ -21,12 +21,12 @@ export default function Profile({ uid = null, type = "embed", cid = null }) {
removeFromChannel, removeFromChannel,
canRemoveFromChannel, canRemoveFromChannel,
canRemove, canRemove,
removeUser, removeUser
} = useContactOperation({ uid, cid }); } = useContactOperation({ uid, cid });
const { data } = useSelector((store) => { const { data } = useSelector((store) => {
return { 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 { const {
name, name,
email, email,
avatar, avatar
// introduction = "This guy has nothing to introduce", // introduction = "This guy has nothing to introduce",
} = data; } = data;
const enableCall = type == "card" && canCall; const enableCall = type == "card" && canCall;
const canRemoveFromServer = type == "embed" && canRemove; const canRemoveFromServer = type == "embed" && canRemove;
const hasMore = const hasMore = enableCall || email || canRemoveFromChannel || canRemoveFromServer;
enableCall || email || canRemoveFromChannel || canRemoveFromServer;
return ( return (
<StyledWrapper className={type}> <StyledWrapper className={type}>
<Avatar className="avatar" url={avatar} name={name} /> <Avatar className="avatar" url={avatar} name={name} />
+1 -2
View File
@@ -86,8 +86,7 @@ const StyledWrapper = styled.div`
padding: 16px; padding: 16px;
width: 280px; width: 280px;
background: #ffffff; background: #ffffff;
box-shadow: 0px 20px 25px 20px rgba(31, 41, 55, 0.1), box-shadow: 0px 20px 25px 20px rgba(31, 41, 55, 0.1), 0px 10px 10px rgba(31, 41, 55, 0.04);
0px 10px 10px rgba(31, 41, 55, 0.04);
border-radius: 6px; border-radius: 6px;
.icons { .icons {
padding-bottom: 2px; padding-bottom: 2px;
+1 -1
View File
@@ -15,7 +15,7 @@ const Emojis = {
"🚀": <EmojiRocket className="emoji" />, "🚀": <EmojiRocket className="emoji" />,
"❤️": <EmojiHeart className="emoji" />, "❤️": <EmojiHeart className="emoji" />,
"🙁": <EmojiUnhappy className="emoji" />, "🙁": <EmojiUnhappy className="emoji" />,
"🎉": <EmojiCelebrate className="emoji" />, "🎉": <EmojiCelebrate className="emoji" />
}; };
export default function ReactionItem({ native = "" }) { export default function ReactionItem({ native = "" }) {
+1 -2
View File
@@ -13,8 +13,7 @@ const StyledWrapper = styled.div`
background: #fff; background: #fff;
/* gap: 20px; */ /* gap: 20px; */
border: 1px solid #e5e7eb; border: 1px solid #e5e7eb;
box-shadow: 0px 4px 8px -2px rgba(16, 24, 40, 0.1), box-shadow: 0px 4px 8px -2px rgba(16, 24, 40, 0.1), 0px 2px 4px -2px rgba(16, 24, 40, 0.06);
0px 2px 4px -2px rgba(16, 24, 40, 0.06);
border-radius: 25px; border-radius: 25px;
.txt { .txt {
padding: 8px; padding: 8px;
+1 -6
View File
@@ -44,12 +44,7 @@ export default function Search() {
<input placeholder="Search..." className="input" /> <input placeholder="Search..." className="input" />
</div> </div>
<Tooltip tip="More" placement="bottom"> <Tooltip tip="More" placement="bottom">
<Tippy <Tippy interactive placement="bottom-end" trigger="click" content={<AddEntriesMenu />}>
interactive
placement="bottom-end"
trigger="click"
content={<AddEntriesMenu />}
>
<img src={addIcon} alt="add icon" className="add" /> <img src={addIcon} alt="add icon" className="add" />
</Tippy> </Tippy>
</Tooltip> </Tooltip>
+2 -7
View File
@@ -45,8 +45,7 @@ export default function EmojiPicker({ selectEmoji }) {
const clickEle = evt.target; const clickEle = evt.target;
const ignore = const ignore =
(clickEle.nodeName == "svg" && clickEle.dataset.emoji == "toggler") || (clickEle.nodeName == "svg" && clickEle.dataset.emoji == "toggler") ||
(clickEle.nodeName == "path" && (clickEle.nodeName == "path" && clickEle.parentElement.dataset.emoji == "toggler");
clickEle.parentElement.dataset.emoji == "toggler");
// console.log("outside click", clickEle, clickEle.parentElement, ignore); // console.log("outside click", clickEle, clickEle.parentElement, ignore);
if (ignore) return; if (ignore) return;
// if(clickEle) // if(clickEle)
@@ -60,11 +59,7 @@ export default function EmojiPicker({ selectEmoji }) {
<div ref={ref} className={`picker ${visible ? "visible" : ""}`}> <div ref={ref} className={`picker ${visible ? "visible" : ""}`}>
<Picker onSelect={handleSelect} /> <Picker onSelect={handleSelect} />
</div> </div>
<SmileIcon <SmileIcon data-emoji="toggler" className="emoji" onClick={togglePickerVisible} />
data-emoji="toggler"
className="emoji"
onClick={togglePickerVisible}
/>
</Styled> </Styled>
</Tooltip> </Tooltip>
); );
+6 -14
View File
@@ -57,11 +57,7 @@ const Styled = styled.div`
width: 100%; width: 100%;
height: 100%; height: 100%;
content: ""; content: "";
background: linear-gradient( background: linear-gradient(180deg, rgba(255, 255, 255, 0) 63.54%, #f3f4f6 93.09%);
180deg,
rgba(255, 255, 255, 0) 63.54%,
#f3f4f6 93.09%
);
} }
} }
.icon { .icon {
@@ -87,15 +83,11 @@ const renderContent = (data) => {
let res = null; let res = null;
switch (content_type) { switch (content_type) {
case ContentTypes.text: case ContentTypes.text:
res = reactStringReplace( res = reactStringReplace(content, /(\s{1}@[0-9]+\s{1})/g, (match, idx) => {
content, console.log("match", match);
/(\s{1}@[0-9]+\s{1})/g, const uid = match.trim().slice(1);
(match, idx) => { return <Mention popover={false} key={idx} uid={uid} />;
console.log("match", match); });
const uid = match.trim().slice(1);
return <Mention popover={false} key={idx} uid={uid} />;
}
);
break; break;
case ContentTypes.markdown: case ContentTypes.markdown:
res = ( res = (
+1 -1
View File
@@ -47,7 +47,7 @@ export default function Toolbar({
toggleMode, toggleMode,
mode, mode,
to, to,
context, context
}) { }) {
const { addStageFile } = useUploadFile({ context, id: to }); const { addStageFile } = useUploadFile({ context, id: to });
const fileInputRef = useRef(null); const fileInputRef = useRef(null);
@@ -13,7 +13,7 @@ export default function UploadFileList({ context = "", id = null }) {
const [editInfo, setEditInfo] = useState(null); const [editInfo, setEditInfo] = useState(null);
const { stageFiles, updateStageFile, removeStageFile } = useUploadFile({ const { stageFiles, updateStageFile, removeStageFile } = useUploadFile({
context, context,
id, id
}); });
const toggleModalVisible = (info) => { const toggleModalVisible = (info) => {
setEditInfo((prev) => (prev ? null : info)); setEditInfo((prev) => (prev ? null : info));
@@ -50,19 +50,12 @@ export default function UploadFileList({ context = "", id = null }) {
return ( return (
<li className="file" key={url}> <li className="file" key={url}>
<div className="preview"> <div className="preview">
{type.startsWith("image") ? ( {type.startsWith("image") ? <img src={url} alt="image" /> : getFileIcon(type, name)}
<img src={url} alt="image" />
) : (
getFileIcon(type, name)
)}
</div> </div>
<h4 className="name">{name}</h4> <h4 className="name">{name}</h4>
<span className="size">{formatBytes(size)}</span> <span className="size">{formatBytes(size)}</span>
<ul className="opts"> <ul className="opts">
<li <li className="opt edit" onClick={handleOpenEditModal.bind(null, idx)}>
className="opt edit"
onClick={handleOpenEditModal.bind(null, idx)}
>
<EditIcon /> <EditIcon />
</li> </li>
<li <li

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