chore: update prettier setting
This commit is contained in:
+1
-1
@@ -1 +1 @@
|
||||
module.exports = {extends: ['@commitlint/config-conventional']};
|
||||
module.exports = { extends: ["@commitlint/config-conventional"] };
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"singleQuote": false,
|
||||
"bracketSpacing": true,
|
||||
"jsxBracketSameLine": false,
|
||||
"tabWidth": 1,
|
||||
"tabWidth": 2,
|
||||
"semi": true,
|
||||
"trailingComma": "none"
|
||||
}
|
||||
Vendored
+8
-5
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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");
|
||||
};
|
||||
|
||||
@@ -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));
|
||||
},
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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
@@ -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
@@ -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);
|
||||
});
|
||||
|
||||
Vendored
+12
-12
@@ -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
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
Vendored
+1
-1
@@ -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
@@ -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;
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -4,4 +4,4 @@ set
|
||||
add
|
||||
update
|
||||
remove
|
||||
toggle
|
||||
toggle
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
}
|
||||
} */
|
||||
} */
|
||||
|
||||
+151
-1294
File diff suppressed because it is too large
Load Diff
@@ -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} />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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} />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 />}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
}
|
||||
]}
|
||||
/>
|
||||
}
|
||||
|
||||
@@ -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 />}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
|
||||
@@ -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} />;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -11,6 +11,6 @@ export default function usePrompt() {
|
||||
return {
|
||||
setCanneled: setPrompt,
|
||||
prompted: !!localStorage.getItem(KEY_PWA_INSTALLED),
|
||||
resetPrompt,
|
||||
resetPrompt
|
||||
};
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ function MarkdownEditor({
|
||||
height = "50vh",
|
||||
placeholder,
|
||||
sendMarkdown,
|
||||
setEditorInstance,
|
||||
setEditorInstance
|
||||
}) {
|
||||
const editorRef = useRef(undefined);
|
||||
const { uploadFile } = useUploadFile();
|
||||
|
||||
@@ -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
|
||||
}
|
||||
]}
|
||||
/>
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
]}
|
||||
/>
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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} />}
|
||||
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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,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;
|
||||
|
||||
@@ -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]
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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} />
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 = "" }) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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 = (
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user