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

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