mirror of
https://github.com/LemmyNet/lemmy-ui.git
synced 2024-11-24 23:31:30 +00:00
Merge branch 'main' into federation_disclaimer
This commit is contained in:
commit
7b8ce16407
|
@ -20,10 +20,11 @@
|
||||||
"@typescript-eslint/no-explicit-any": 0,
|
"@typescript-eslint/no-explicit-any": 0,
|
||||||
"@typescript-eslint/explicit-module-boundary-types": 0,
|
"@typescript-eslint/explicit-module-boundary-types": 0,
|
||||||
"@typescript-eslint/no-empty-function": 0,
|
"@typescript-eslint/no-empty-function": 0,
|
||||||
|
"@typescript-eslint/no-non-null-assertion": 0,
|
||||||
"arrow-body-style": 0,
|
"arrow-body-style": 0,
|
||||||
"curly": 0,
|
"curly": 0,
|
||||||
"eol-last": 0,
|
"eol-last": 0,
|
||||||
"eqeqeq": 0,
|
"eqeqeq": "error",
|
||||||
"func-style": 0,
|
"func-style": 0,
|
||||||
"import/no-duplicates": 0,
|
"import/no-duplicates": 0,
|
||||||
"max-statements": 0,
|
"max-statements": 0,
|
||||||
|
@ -39,7 +40,7 @@
|
||||||
"no-useless-constructor": 0,
|
"no-useless-constructor": 0,
|
||||||
"no-useless-escape": 0,
|
"no-useless-escape": 0,
|
||||||
"no-var": 0,
|
"no-var": 0,
|
||||||
"prefer-const": 1,
|
"prefer-const": "error",
|
||||||
"prefer-rest-params": 0,
|
"prefer-rest-params": 0,
|
||||||
"prettier/prettier": "error",
|
"prettier/prettier": "error",
|
||||||
"quote-props": 0,
|
"quote-props": 0,
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
src/shared/translations
|
src/shared/translations
|
||||||
lemmy-translations
|
lemmy-translations
|
||||||
src/assets/css/themes/*.css
|
src/assets/css/themes/*.css
|
||||||
|
src/assets/css/code-themes/*.css
|
||||||
stats.json
|
stats.json
|
||||||
dist
|
dist
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
pipeline:
|
steps:
|
||||||
fetch_git_submodules:
|
fetch_git_submodules:
|
||||||
image: node:alpine
|
image: node:20-alpine
|
||||||
commands:
|
commands:
|
||||||
- apk add git
|
- apk add git
|
||||||
- git submodule init
|
- git submodule init
|
||||||
|
@ -8,17 +8,17 @@ pipeline:
|
||||||
# - git fetch --tags
|
# - git fetch --tags
|
||||||
|
|
||||||
yarn:
|
yarn:
|
||||||
image: node:alpine
|
image: node:20-alpine
|
||||||
commands:
|
commands:
|
||||||
- yarn
|
- yarn
|
||||||
|
|
||||||
yarn_lint:
|
yarn_lint:
|
||||||
image: node:alpine
|
image: node:20-alpine
|
||||||
commands:
|
commands:
|
||||||
- yarn lint
|
- yarn lint
|
||||||
|
|
||||||
yarn_build_dev:
|
yarn_build_dev:
|
||||||
image: node:alpine
|
image: node:20-alpine
|
||||||
commands:
|
commands:
|
||||||
- yarn build:dev
|
- yarn build:dev
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ pipeline:
|
||||||
repo: dessalines/lemmy-ui
|
repo: dessalines/lemmy-ui
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
platforms: linux/amd64
|
platforms: linux/amd64
|
||||||
auto_tag: true
|
tag: ${CI_COMMIT_TAG}
|
||||||
when:
|
when:
|
||||||
event: tag
|
event: tag
|
||||||
|
|
||||||
|
@ -43,3 +43,19 @@ pipeline:
|
||||||
tag: dev
|
tag: dev
|
||||||
when:
|
when:
|
||||||
event: cron
|
event: cron
|
||||||
|
|
||||||
|
notify_on_failure:
|
||||||
|
image: alpine:3
|
||||||
|
commands:
|
||||||
|
- apk add curl
|
||||||
|
- "curl -d'Lemmy-UI CI build failed: ${CI_PIPELINE_URL}' ntfy.sh/lemmy_drone_ci"
|
||||||
|
when:
|
||||||
|
status: [failure]
|
||||||
|
|
||||||
|
notify_on_tag_deploy:
|
||||||
|
image: alpine:3
|
||||||
|
commands:
|
||||||
|
- apk add curl
|
||||||
|
- "curl -d'lemmy-ui:${CI_COMMIT_TAG} deployed' ntfy.sh/lemmy_drone_ci"
|
||||||
|
when:
|
||||||
|
event: tag
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
FROM node:20.2-alpine as builder
|
FROM node:20-alpine as builder
|
||||||
RUN apk update && apk add curl yarn python3 build-base gcc wget git --no-cache
|
RUN apk update && apk add curl yarn python3 build-base gcc wget git --no-cache
|
||||||
RUN curl -sf https://gobinaries.com/tj/node-prune | sh
|
RUN curl -sf https://gobinaries.com/tj/node-prune | sh
|
||||||
|
|
||||||
|
@ -38,10 +38,14 @@ RUN rm -rf ./node_modules/npm
|
||||||
|
|
||||||
RUN du -sh ./node_modules/* | sort -nr | grep '\dM.*'
|
RUN du -sh ./node_modules/* | sort -nr | grep '\dM.*'
|
||||||
|
|
||||||
FROM node:alpine as runner
|
FROM node:20-alpine as runner
|
||||||
|
RUN apk update && apk add curl --no-cache
|
||||||
COPY --from=builder /usr/src/app/dist /app/dist
|
COPY --from=builder /usr/src/app/dist /app/dist
|
||||||
COPY --from=builder /usr/src/app/node_modules /app/node_modules
|
COPY --from=builder /usr/src/app/node_modules /app/node_modules
|
||||||
|
|
||||||
|
RUN chown -R node:node /app
|
||||||
|
|
||||||
|
USER node
|
||||||
EXPOSE 1234
|
EXPOSE 1234
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
CMD node dist/js/server.js
|
CMD node dist/js/server.js
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
FROM node:20.2-alpine as builder
|
FROM node:20-alpine as builder
|
||||||
RUN apk update && apk add curl yarn python3 build-base gcc wget git --no-cache
|
RUN apk update && apk add curl yarn python3 build-base gcc wget git --no-cache
|
||||||
|
|
||||||
WORKDIR /usr/src/app
|
WORKDIR /usr/src/app
|
||||||
|
@ -28,7 +28,7 @@ RUN echo "export const VERSION = 'dev';" > "src/shared/version.ts"
|
||||||
RUN yarn --prefer-offline
|
RUN yarn --prefer-offline
|
||||||
RUN yarn build:dev
|
RUN yarn build:dev
|
||||||
|
|
||||||
FROM node:alpine as runner
|
FROM node:20-alpine as runner
|
||||||
COPY --from=builder /usr/src/app/dist /app/dist
|
COPY --from=builder /usr/src/app/dist /app/dist
|
||||||
COPY --from=builder /usr/src/app/node_modules /app/node_modules
|
COPY --from=builder /usr/src/app/node_modules /app/node_modules
|
||||||
|
|
||||||
|
|
|
@ -8,12 +8,12 @@ fs.readdir(translationDir, (_err, files) => {
|
||||||
const lang = filename.split(".")[0];
|
const lang = filename.split(".")[0];
|
||||||
try {
|
try {
|
||||||
const json = JSON.parse(
|
const json = JSON.parse(
|
||||||
fs.readFileSync(translationDir + filename, "utf8")
|
fs.readFileSync(translationDir + filename, "utf8"),
|
||||||
);
|
);
|
||||||
let data = `export const ${lang} = {\n translation: {`;
|
let data = `export const ${lang} = {\n translation: {`;
|
||||||
for (const key in json) {
|
for (const key in json) {
|
||||||
if (key in json) {
|
if (key in json) {
|
||||||
const value = json[key].replace(/"/g, '\\"');
|
const value = json[key].replace(/"/g, '\\"').replace("\n", "\\n");
|
||||||
data += `\n ${key}: "${value}",`;
|
data += `\n ${key}: "${value}",`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -67,14 +67,14 @@ ${optionKeys.map(key => `${indent}| "${key}"`).join("\n")};
|
||||||
export type I18nKeys = NoOptionI18nKeys | OptionI18nKeys;
|
export type I18nKeys = NoOptionI18nKeys | OptionI18nKeys;
|
||||||
|
|
||||||
export type TTypedOptions<TKey extends OptionI18nKeys> =${Array.from(
|
export type TTypedOptions<TKey extends OptionI18nKeys> =${Array.from(
|
||||||
optionMap.entries()
|
optionMap.entries(),
|
||||||
).reduce(
|
).reduce(
|
||||||
(acc, [key, options]) =>
|
(acc, [key, options]) =>
|
||||||
`${acc} TKey extends \"${key}\" ? ${
|
`${acc} TKey extends \"${key}\" ? ${
|
||||||
options.reduce((acc, cur) => acc + `${cur}: string | number; `, "{ ") +
|
options.reduce((acc, cur) => acc + `${cur}: string | number; `, "{ ") +
|
||||||
"}"
|
"}"
|
||||||
} :\n${indent}`,
|
} :\n${indent}`,
|
||||||
""
|
"",
|
||||||
)} (Record<string, unknown> | string);
|
)} (Record<string, unknown> | string);
|
||||||
|
|
||||||
export interface TFunctionTyped {
|
export interface TFunctionTyped {
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit a241fe1255a6363c7ae1ec5a09520c066745e6ce
|
Subproject commit 6fbc86932a03c4d40829ee4a3395259b2a7660e5
|
77
package.json
77
package.json
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "lemmy-ui",
|
"name": "lemmy-ui",
|
||||||
"version": "0.18.1-rc.11",
|
"version": "0.19.0-rc.3",
|
||||||
"description": "An isomorphic UI for lemmy",
|
"description": "An isomorphic UI for lemmy",
|
||||||
"repository": "https://github.com/LemmyNet/lemmy-ui",
|
"repository": "https://github.com/LemmyNet/lemmy-ui",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
|
@ -15,6 +15,7 @@
|
||||||
"dev": "yarn build:dev --watch",
|
"dev": "yarn build:dev --watch",
|
||||||
"lint": "yarn translations:generate && tsc --noEmit && eslint --report-unused-disable-directives --ext .js,.ts,.tsx \"src/**\" && prettier --check \"src/**/*.{ts,tsx,js,css,scss}\"",
|
"lint": "yarn translations:generate && tsc --noEmit && eslint --report-unused-disable-directives --ext .js,.ts,.tsx \"src/**\" && prettier --check \"src/**/*.{ts,tsx,js,css,scss}\"",
|
||||||
"prepare": "husky install",
|
"prepare": "husky install",
|
||||||
|
"postinstall": "husky install",
|
||||||
"themes:build": "sass src/assets/css/themes/:src/assets/css/themes",
|
"themes:build": "sass src/assets/css/themes/:src/assets/css/themes",
|
||||||
"themes:watch": "sass --watch src/assets/css/themes/:src/assets/css/themes",
|
"themes:watch": "sass --watch src/assets/css/themes/:src/assets/css/themes",
|
||||||
"translations:generate": "node generate_translations.js",
|
"translations:generate": "node generate_translations.js",
|
||||||
|
@ -34,23 +35,24 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/plugin-proposal-decorators": "^7.21.0",
|
"@babel/plugin-proposal-decorators": "^7.21.5",
|
||||||
"@babel/plugin-transform-runtime": "^7.21.4",
|
"@babel/plugin-transform-runtime": "^7.21.5",
|
||||||
"@babel/plugin-transform-typescript": "^7.21.3",
|
"@babel/plugin-transform-typescript": "^7.21.5",
|
||||||
"@babel/preset-env": "7.21.5",
|
"@babel/preset-env": "7.21.5",
|
||||||
"@babel/preset-typescript": "^7.21.5",
|
"@babel/preset-typescript": "^7.21.5",
|
||||||
"@babel/runtime": "^7.21.5",
|
"@babel/runtime": "^7.21.5",
|
||||||
"@emoji-mart/data": "^1.1.0",
|
"@emoji-mart/data": "^1.1.0",
|
||||||
|
"@shortcm/qr-image": "^9.0.2",
|
||||||
"autosize": "^6.0.1",
|
"autosize": "^6.0.1",
|
||||||
"babel-loader": "^9.1.2",
|
"babel-loader": "^9.1.2",
|
||||||
"babel-plugin-inferno": "^6.6.0",
|
"babel-plugin-inferno": "^6.6.0",
|
||||||
"bootstrap": "^5.2.3",
|
"bootstrap": "^5.3.1",
|
||||||
"check-password-strength": "^2.0.7",
|
"check-password-strength": "^2.0.7",
|
||||||
"classnames": "^2.3.1",
|
"classnames": "^2.3.1",
|
||||||
"clean-webpack-plugin": "^4.0.0",
|
"clean-webpack-plugin": "^4.0.0",
|
||||||
"cookie": "^0.5.0",
|
"cookie": "^0.5.0",
|
||||||
"copy-webpack-plugin": "^11.0.0",
|
"copy-webpack-plugin": "^11.0.0",
|
||||||
"cross-fetch": "^3.1.5",
|
"cross-fetch": "^4.0.0",
|
||||||
"css-loader": "^6.7.3",
|
"css-loader": "^6.7.3",
|
||||||
"date-fns": "^2.30.0",
|
"date-fns": "^2.30.0",
|
||||||
"emoji-mart": "^5.4.0",
|
"emoji-mart": "^5.4.0",
|
||||||
|
@ -58,22 +60,24 @@
|
||||||
"express": "~4.18.2",
|
"express": "~4.18.2",
|
||||||
"history": "^5.3.0",
|
"history": "^5.3.0",
|
||||||
"html-to-text": "^9.0.5",
|
"html-to-text": "^9.0.5",
|
||||||
"i18next": "^22.4.15",
|
"husky": "^8.0.3",
|
||||||
"inferno": "^8.1.1",
|
"i18next": "^23.3.0",
|
||||||
"inferno-create-element": "^8.1.1",
|
"inferno": "^8.2.2",
|
||||||
|
"inferno-create-element": "^8.2.2",
|
||||||
"inferno-helmet": "^5.2.1",
|
"inferno-helmet": "^5.2.1",
|
||||||
"inferno-hydrate": "^8.1.1",
|
"inferno-hydrate": "^8.2.2",
|
||||||
"inferno-i18next-dess": "0.0.2",
|
"inferno-i18next-dess": "0.0.2",
|
||||||
"inferno-router": "^8.1.1",
|
"inferno-router": "^8.2.2",
|
||||||
"inferno-server": "^8.1.1",
|
"inferno-server": "^8.2.2",
|
||||||
"jwt-decode": "^3.1.2",
|
"jwt-decode": "^3.1.2",
|
||||||
"lemmy-js-client": "0.18.0-rc.2",
|
"lemmy-js-client": "0.19.0-rc.14",
|
||||||
"lodash.isequal": "^4.5.0",
|
"lodash.isequal": "^4.5.0",
|
||||||
"lodash.merge": "^4.6.2",
|
|
||||||
"markdown-it": "^13.0.1",
|
"markdown-it": "^13.0.1",
|
||||||
|
"markdown-it-bidi": "^0.1.0",
|
||||||
"markdown-it-container": "^3.0.0",
|
"markdown-it-container": "^3.0.0",
|
||||||
"markdown-it-emoji": "^2.0.2",
|
"markdown-it-emoji": "^2.0.2",
|
||||||
"markdown-it-footnote": "^3.0.3",
|
"markdown-it-footnote": "^3.0.3",
|
||||||
|
"markdown-it-highlightjs": "^4.0.1",
|
||||||
"markdown-it-html5-embed": "^1.0.0",
|
"markdown-it-html5-embed": "^1.0.0",
|
||||||
"markdown-it-ruby": "^0.1.1",
|
"markdown-it-ruby": "^0.1.1",
|
||||||
"markdown-it-sub": "^1.0.0",
|
"markdown-it-sub": "^1.0.0",
|
||||||
|
@ -81,21 +85,23 @@
|
||||||
"mini-css-extract-plugin": "^2.7.5",
|
"mini-css-extract-plugin": "^2.7.5",
|
||||||
"register-service-worker": "^1.7.2",
|
"register-service-worker": "^1.7.2",
|
||||||
"run-node-webpack-plugin": "^1.3.0",
|
"run-node-webpack-plugin": "^1.3.0",
|
||||||
"sanitize-html": "^2.10.0",
|
"rxjs": "^7.8.1",
|
||||||
"sass": "^1.62.1",
|
"sanitize-html": "^2.11.0",
|
||||||
"sass-loader": "^13.2.2",
|
"sass": "^1.64.1",
|
||||||
|
"sass-loader": "^13.3.2",
|
||||||
"serialize-javascript": "^6.0.1",
|
"serialize-javascript": "^6.0.1",
|
||||||
"service-worker-webpack": "^1.0.0",
|
"service-worker-webpack": "^1.0.0",
|
||||||
"sharp": "^0.32.1",
|
"sharp": "^0.32.4",
|
||||||
"tippy.js": "^6.3.7",
|
"tippy.js": "^6.3.7",
|
||||||
"toastify-js": "^1.12.0",
|
"toastify-js": "^1.12.0",
|
||||||
"tributejs": "^5.1.3",
|
"tributejs": "^5.1.3",
|
||||||
"webpack": "5.82.1",
|
"webpack": "5.88.2",
|
||||||
"webpack-cli": "^5.1.1",
|
"webpack-cli": "^5.1.4",
|
||||||
"webpack-node-externals": "^3.0.0"
|
"webpack-node-externals": "^3.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.21.8",
|
"@babel/core": "^7.21.5",
|
||||||
|
"@babel/plugin-proposal-class-properties": "^7.18.6",
|
||||||
"@types/autosize": "^4.0.0",
|
"@types/autosize": "^4.0.0",
|
||||||
"@types/bootstrap": "^5.2.6",
|
"@types/bootstrap": "^5.2.6",
|
||||||
"@types/cookie": "^0.5.1",
|
"@types/cookie": "^0.5.1",
|
||||||
|
@ -103,33 +109,32 @@
|
||||||
"@types/html-to-text": "^9.0.0",
|
"@types/html-to-text": "^9.0.0",
|
||||||
"@types/lodash.isequal": "^4.5.6",
|
"@types/lodash.isequal": "^4.5.6",
|
||||||
"@types/markdown-it": "^12.2.3",
|
"@types/markdown-it": "^12.2.3",
|
||||||
"@types/markdown-it-container": "^2.0.5",
|
"@types/markdown-it-container": "^2.0.6",
|
||||||
"@types/node": "^20.1.2",
|
"@types/node": "^20.4.5",
|
||||||
"@types/path-browserify": "^1.0.0",
|
"@types/path-browserify": "^1.0.0",
|
||||||
"@types/sanitize-html": "^2.9.0",
|
"@types/sanitize-html": "^2.9.0",
|
||||||
"@types/serialize-javascript": "^5.0.1",
|
"@types/serialize-javascript": "^5.0.1",
|
||||||
"@types/toastify-js": "^1.11.1",
|
"@types/toastify-js": "^1.12.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.59.5",
|
"@typescript-eslint/eslint-plugin": "^6.2.0",
|
||||||
"@typescript-eslint/parser": "^5.59.5",
|
"@typescript-eslint/parser": "^6.2.0",
|
||||||
"eslint": "^8.40.0",
|
"eslint": "^8.45.0",
|
||||||
"eslint-plugin-inferno": "^7.32.2",
|
"eslint-plugin-inferno": "^7.32.2",
|
||||||
"eslint-plugin-jsx-a11y": "^6.7.1",
|
"eslint-plugin-jsx-a11y": "^6.7.1",
|
||||||
"eslint-plugin-prettier": "^4.2.1",
|
"eslint-plugin-prettier": "^5.0.0",
|
||||||
"husky": "^8.0.3",
|
|
||||||
"import-sort-style-module": "^6.0.0",
|
"import-sort-style-module": "^6.0.0",
|
||||||
"lint-staged": "^13.2.2",
|
"lint-staged": "^13.2.3",
|
||||||
"prettier": "^2.8.8",
|
"prettier": "^3.0.0",
|
||||||
"prettier-plugin-import-sort": "^0.0.7",
|
"prettier-plugin-import-sort": "^0.0.7",
|
||||||
"prettier-plugin-organize-imports": "^3.2.2",
|
"prettier-plugin-organize-imports": "^3.2.3",
|
||||||
"prettier-plugin-packagejson": "^2.4.3",
|
"prettier-plugin-packagejson": "^2.4.5",
|
||||||
"rimraf": "^5.0.0",
|
"rimraf": "^5.0.0",
|
||||||
"sortpack": "^2.3.4",
|
"sortpack": "^2.3.4",
|
||||||
"style-loader": "^3.3.2",
|
"style-loader": "^3.3.2",
|
||||||
"terser": "^5.17.3",
|
"terser": "^5.19.2",
|
||||||
"typescript": "^5.0.4",
|
"typescript": "^5.1.6",
|
||||||
"typescript-language-server": "^3.3.2",
|
"typescript-language-server": "^3.3.2",
|
||||||
"webpack-bundle-analyzer": "^4.9.0",
|
"webpack-bundle-analyzer": "^4.9.0",
|
||||||
"webpack-dev-server": "4.15.0"
|
"webpack-dev-server": "4.15.1"
|
||||||
},
|
},
|
||||||
"packageManager": "yarn@1.22.19",
|
"packageManager": "yarn@1.22.19",
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|
1
src/assets/css/code-themes/atom-one-dark.css
Normal file
1
src/assets/css/code-themes/atom-one-dark.css
Normal file
|
@ -0,0 +1 @@
|
||||||
|
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#abb2bf;background:#282c34}.hljs-comment,.hljs-quote{color:#5c6370;font-style:italic}.hljs-doctag,.hljs-formula,.hljs-keyword{color:#c678dd}.hljs-deletion,.hljs-name,.hljs-section,.hljs-selector-tag,.hljs-subst{color:#e06c75}.hljs-literal{color:#56b6c2}.hljs-addition,.hljs-attribute,.hljs-meta .hljs-string,.hljs-regexp,.hljs-string{color:#98c379}.hljs-attr,.hljs-number,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-pseudo,.hljs-template-variable,.hljs-type,.hljs-variable{color:#d19a66}.hljs-bullet,.hljs-link,.hljs-meta,.hljs-selector-id,.hljs-symbol,.hljs-title{color:#61aeee}.hljs-built_in,.hljs-class .hljs-title,.hljs-title.class_{color:#e6c07b}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}.hljs-link{text-decoration:underline}
|
1
src/assets/css/code-themes/atom-one-light.css
Normal file
1
src/assets/css/code-themes/atom-one-light.css
Normal file
|
@ -0,0 +1 @@
|
||||||
|
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#383a42;background:#fafafa}.hljs-comment,.hljs-quote{color:#a0a1a7;font-style:italic}.hljs-doctag,.hljs-formula,.hljs-keyword{color:#a626a4}.hljs-deletion,.hljs-name,.hljs-section,.hljs-selector-tag,.hljs-subst{color:#e45649}.hljs-literal{color:#0184bb}.hljs-addition,.hljs-attribute,.hljs-meta .hljs-string,.hljs-regexp,.hljs-string{color:#50a14f}.hljs-attr,.hljs-number,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-pseudo,.hljs-template-variable,.hljs-type,.hljs-variable{color:#986801}.hljs-bullet,.hljs-link,.hljs-meta,.hljs-selector-id,.hljs-symbol,.hljs-title{color:#4078f2}.hljs-built_in,.hljs-class .hljs-title,.hljs-title.class_{color:#c18401}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}.hljs-link{text-decoration:underline}
|
|
@ -251,7 +251,7 @@ hr {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.img-blur {
|
.img-blur-thumb {
|
||||||
filter: blur(10px);
|
filter: blur(10px);
|
||||||
-webkit-filter: blur(10px);
|
-webkit-filter: blur(10px);
|
||||||
-moz-filter: blur(10px);
|
-moz-filter: blur(10px);
|
||||||
|
@ -259,6 +259,18 @@ hr {
|
||||||
-ms-filter: blur(10px);
|
-ms-filter: blur(10px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.img-blur-icon {
|
||||||
|
filter: blur(3px);
|
||||||
|
-webkit-filter: blur(3px);
|
||||||
|
-moz-filter: blur(3px);
|
||||||
|
-o-filter: blur(3px);
|
||||||
|
-ms-filter: blur(3px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.img-cover {
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
.img-expanded {
|
.img-expanded {
|
||||||
max-height: 90vh;
|
max-height: 90vh;
|
||||||
}
|
}
|
||||||
|
@ -436,3 +448,7 @@ br.big {
|
||||||
.skip-link:focus {
|
.skip-link:focus {
|
||||||
top: 0;
|
top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.totp-link {
|
||||||
|
width: fit-content;
|
||||||
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ $green: #00bc8c;
|
||||||
$cyan: #3498db;
|
$cyan: #3498db;
|
||||||
|
|
||||||
$primary: $green;
|
$primary: $green;
|
||||||
$secondary: $gray-700;
|
$secondary: $gray-600;
|
||||||
$success: $green;
|
$success: $green;
|
||||||
$dark: $gray-300;
|
$dark: $gray-300;
|
||||||
|
|
||||||
|
@ -30,9 +30,18 @@ $mark-bg: $gray-900;
|
||||||
$text-muted: $gray-600;
|
$text-muted: $gray-600;
|
||||||
$yiq-contrasted-threshold: 175;
|
$yiq-contrasted-threshold: 175;
|
||||||
|
|
||||||
$font-family-sans-serif: "Lato", -apple-system, BlinkMacSystemFont, "Segoe UI",
|
$font-family-sans-serif:
|
||||||
Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji",
|
"Lato",
|
||||||
"Segoe UI Emoji", "Segoe UI Symbol";
|
-apple-system,
|
||||||
|
BlinkMacSystemFont,
|
||||||
|
"Segoe UI",
|
||||||
|
Roboto,
|
||||||
|
"Helvetica Neue",
|
||||||
|
Arial,
|
||||||
|
sans-serif,
|
||||||
|
"Apple Color Emoji",
|
||||||
|
"Segoe UI Emoji",
|
||||||
|
"Segoe UI Symbol";
|
||||||
$font-size-base: 0.9375rem;
|
$font-size-base: 0.9375rem;
|
||||||
$h1-font-size: 3rem;
|
$h1-font-size: 3rem;
|
||||||
$h2-font-size: 2.5rem;
|
$h2-font-size: 2.5rem;
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
@import "variables.darkly";
|
@import "variables.darkly";
|
||||||
|
|
||||||
$primary: $blue;
|
$primary: $blue;
|
||||||
$secondary: #444;
|
|
||||||
$light: $gray-800;
|
$light: $gray-800;
|
||||||
|
|
||||||
$link-color: $red;
|
$link-color: $red;
|
||||||
|
|
|
@ -17,7 +17,7 @@ $green: #00bc8c;
|
||||||
$cyan: #3498db;
|
$cyan: #3498db;
|
||||||
|
|
||||||
$primary: $green;
|
$primary: $green;
|
||||||
$secondary: $gray-700;
|
$secondary: $gray-500;
|
||||||
$success: $green;
|
$success: $green;
|
||||||
$dark: $gray-300;
|
$dark: $gray-300;
|
||||||
|
|
||||||
|
@ -29,9 +29,18 @@ $mark-bg: #333;
|
||||||
$text-muted: $gray-600;
|
$text-muted: $gray-600;
|
||||||
$yiq-contrasted-threshold: 175;
|
$yiq-contrasted-threshold: 175;
|
||||||
|
|
||||||
$font-family-sans-serif: "Lato", -apple-system, BlinkMacSystemFont, "Segoe UI",
|
$font-family-sans-serif:
|
||||||
Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji",
|
"Lato",
|
||||||
"Segoe UI Emoji", "Segoe UI Symbol";
|
-apple-system,
|
||||||
|
BlinkMacSystemFont,
|
||||||
|
"Segoe UI",
|
||||||
|
Roboto,
|
||||||
|
"Helvetica Neue",
|
||||||
|
Arial,
|
||||||
|
sans-serif,
|
||||||
|
"Apple Color Emoji",
|
||||||
|
"Segoe UI Emoji",
|
||||||
|
"Segoe UI Symbol";
|
||||||
$h1-font-size: 3rem;
|
$h1-font-size: 3rem;
|
||||||
$h2-font-size: 2.5rem;
|
$h2-font-size: 2.5rem;
|
||||||
$h3-font-size: 2rem;
|
$h3-font-size: 2rem;
|
||||||
|
|
|
@ -26,7 +26,7 @@ $danger: #aa0000;
|
||||||
$info: #00aaaa;
|
$info: #00aaaa;
|
||||||
$warning: #aa00aa;
|
$warning: #aa00aa;
|
||||||
$light: $gray-800;
|
$light: $gray-800;
|
||||||
$dark: black;
|
$dark: $gray-300;
|
||||||
|
|
||||||
$body-bg: #000084;
|
$body-bg: #000084;
|
||||||
$body-color: $gray-300;
|
$body-color: $gray-300;
|
||||||
|
|
|
@ -9,6 +9,7 @@ $gray-800: #303030;
|
||||||
$gray-900: #222;
|
$gray-900: #222;
|
||||||
|
|
||||||
$light: $gray-700;
|
$light: $gray-700;
|
||||||
|
$dark: $gray-200;
|
||||||
|
|
||||||
$body-bg: $gray-900;
|
$body-bg: $gray-900;
|
||||||
$body-color: $gray-200;
|
$body-color: $gray-200;
|
||||||
|
|
|
@ -70,7 +70,7 @@ hr.my-3 {
|
||||||
--bs-gray-800: #303030;
|
--bs-gray-800: #303030;
|
||||||
--bs-gray-900: #222;
|
--bs-gray-900: #222;
|
||||||
--bs-primary: #00bc8c;
|
--bs-primary: #00bc8c;
|
||||||
--bs-secondary: #444;
|
--bs-secondary: #adb5bd;
|
||||||
--bs-success: #00bc8c;
|
--bs-success: #00bc8c;
|
||||||
--bs-info: #3498db;
|
--bs-info: #3498db;
|
||||||
--bs-warning: #f39c12;
|
--bs-warning: #f39c12;
|
||||||
|
@ -78,7 +78,7 @@ hr.my-3 {
|
||||||
--bs-light: #303030;
|
--bs-light: #303030;
|
||||||
--bs-dark: #dee2e6;
|
--bs-dark: #dee2e6;
|
||||||
--bs-primary-rgb: 0, 188, 140;
|
--bs-primary-rgb: 0, 188, 140;
|
||||||
--bs-secondary-rgb: 68, 68, 68;
|
--bs-secondary-rgb: 173, 181, 189;
|
||||||
--bs-success-rgb: 0, 188, 140;
|
--bs-success-rgb: 0, 188, 140;
|
||||||
--bs-info-rgb: 52, 152, 219;
|
--bs-info-rgb: 52, 152, 219;
|
||||||
--bs-warning-rgb: 243, 156, 18;
|
--bs-warning-rgb: 243, 156, 18;
|
||||||
|
@ -86,7 +86,7 @@ hr.my-3 {
|
||||||
--bs-light-rgb: 48, 48, 48;
|
--bs-light-rgb: 48, 48, 48;
|
||||||
--bs-dark-rgb: 222, 226, 230;
|
--bs-dark-rgb: 222, 226, 230;
|
||||||
--bs-primary-text-emphasis: #004b38;
|
--bs-primary-text-emphasis: #004b38;
|
||||||
--bs-secondary-text-emphasis: #1b1b1b;
|
--bs-secondary-text-emphasis: #45484c;
|
||||||
--bs-success-text-emphasis: #004b38;
|
--bs-success-text-emphasis: #004b38;
|
||||||
--bs-info-text-emphasis: #153d58;
|
--bs-info-text-emphasis: #153d58;
|
||||||
--bs-warning-text-emphasis: #613e07;
|
--bs-warning-text-emphasis: #613e07;
|
||||||
|
@ -94,7 +94,7 @@ hr.my-3 {
|
||||||
--bs-light-text-emphasis: #444;
|
--bs-light-text-emphasis: #444;
|
||||||
--bs-dark-text-emphasis: #444;
|
--bs-dark-text-emphasis: #444;
|
||||||
--bs-primary-bg-subtle: #ccf2e8;
|
--bs-primary-bg-subtle: #ccf2e8;
|
||||||
--bs-secondary-bg-subtle: #dadada;
|
--bs-secondary-bg-subtle: #eff0f2;
|
||||||
--bs-success-bg-subtle: #ccf2e8;
|
--bs-success-bg-subtle: #ccf2e8;
|
||||||
--bs-info-bg-subtle: #d6eaf8;
|
--bs-info-bg-subtle: #d6eaf8;
|
||||||
--bs-warning-bg-subtle: #fdebd0;
|
--bs-warning-bg-subtle: #fdebd0;
|
||||||
|
@ -102,7 +102,7 @@ hr.my-3 {
|
||||||
--bs-light-bg-subtle: #fcfcfd;
|
--bs-light-bg-subtle: #fcfcfd;
|
||||||
--bs-dark-bg-subtle: #ced4da;
|
--bs-dark-bg-subtle: #ced4da;
|
||||||
--bs-primary-border-subtle: #99e4d1;
|
--bs-primary-border-subtle: #99e4d1;
|
||||||
--bs-secondary-border-subtle: #b4b4b4;
|
--bs-secondary-border-subtle: #dee1e5;
|
||||||
--bs-success-border-subtle: #99e4d1;
|
--bs-success-border-subtle: #99e4d1;
|
||||||
--bs-info-border-subtle: #aed6f1;
|
--bs-info-border-subtle: #aed6f1;
|
||||||
--bs-warning-border-subtle: #fad7a0;
|
--bs-warning-border-subtle: #fad7a0;
|
||||||
|
@ -182,7 +182,7 @@ hr.my-3 {
|
||||||
--bs-tertiary-bg: #292929;
|
--bs-tertiary-bg: #292929;
|
||||||
--bs-tertiary-bg-rgb: 41, 41, 41;
|
--bs-tertiary-bg-rgb: 41, 41, 41;
|
||||||
--bs-primary-text-emphasis: #66d7ba;
|
--bs-primary-text-emphasis: #66d7ba;
|
||||||
--bs-secondary-text-emphasis: #8f8f8f;
|
--bs-secondary-text-emphasis: #ced3d7;
|
||||||
--bs-success-text-emphasis: #66d7ba;
|
--bs-success-text-emphasis: #66d7ba;
|
||||||
--bs-info-text-emphasis: #85c1e9;
|
--bs-info-text-emphasis: #85c1e9;
|
||||||
--bs-warning-text-emphasis: #f8c471;
|
--bs-warning-text-emphasis: #f8c471;
|
||||||
|
@ -190,7 +190,7 @@ hr.my-3 {
|
||||||
--bs-light-text-emphasis: #f8f9fa;
|
--bs-light-text-emphasis: #f8f9fa;
|
||||||
--bs-dark-text-emphasis: #dee2e6;
|
--bs-dark-text-emphasis: #dee2e6;
|
||||||
--bs-primary-bg-subtle: #00261c;
|
--bs-primary-bg-subtle: #00261c;
|
||||||
--bs-secondary-bg-subtle: #0e0e0e;
|
--bs-secondary-bg-subtle: #232426;
|
||||||
--bs-success-bg-subtle: #00261c;
|
--bs-success-bg-subtle: #00261c;
|
||||||
--bs-info-bg-subtle: #0a1e2c;
|
--bs-info-bg-subtle: #0a1e2c;
|
||||||
--bs-warning-bg-subtle: #311f04;
|
--bs-warning-bg-subtle: #311f04;
|
||||||
|
@ -198,7 +198,7 @@ hr.my-3 {
|
||||||
--bs-light-bg-subtle: #303030;
|
--bs-light-bg-subtle: #303030;
|
||||||
--bs-dark-bg-subtle: #181818;
|
--bs-dark-bg-subtle: #181818;
|
||||||
--bs-primary-border-subtle: #007154;
|
--bs-primary-border-subtle: #007154;
|
||||||
--bs-secondary-border-subtle: #292929;
|
--bs-secondary-border-subtle: #686d71;
|
||||||
--bs-success-border-subtle: #007154;
|
--bs-success-border-subtle: #007154;
|
||||||
--bs-info-border-subtle: #1f5b83;
|
--bs-info-border-subtle: #1f5b83;
|
||||||
--bs-warning-border-subtle: #925e0b;
|
--bs-warning-border-subtle: #925e0b;
|
||||||
|
@ -1961,13 +1961,13 @@ progress {
|
||||||
|
|
||||||
.table-secondary {
|
.table-secondary {
|
||||||
--bs-table-color: #000;
|
--bs-table-color: #000;
|
||||||
--bs-table-bg: #dadada;
|
--bs-table-bg: #eff0f2;
|
||||||
--bs-table-border-color: #c4c4c4;
|
--bs-table-border-color: #d7d8da;
|
||||||
--bs-table-striped-bg: #cfcfcf;
|
--bs-table-striped-bg: #e3e4e6;
|
||||||
--bs-table-striped-color: #000;
|
--bs-table-striped-color: #000;
|
||||||
--bs-table-active-bg: #c4c4c4;
|
--bs-table-active-bg: #d7d8da;
|
||||||
--bs-table-active-color: #000;
|
--bs-table-active-color: #000;
|
||||||
--bs-table-hover-bg: #cacaca;
|
--bs-table-hover-bg: #dddee0;
|
||||||
--bs-table-hover-color: #000;
|
--bs-table-hover-color: #000;
|
||||||
color: var(--bs-table-color);
|
color: var(--bs-table-color);
|
||||||
border-color: var(--bs-table-border-color);
|
border-color: var(--bs-table-border-color);
|
||||||
|
@ -2994,20 +2994,20 @@ textarea.form-control-lg {
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-secondary {
|
.btn-secondary {
|
||||||
--bs-btn-color: #fff;
|
--bs-btn-color: #000;
|
||||||
--bs-btn-bg: #444;
|
--bs-btn-bg: #adb5bd;
|
||||||
--bs-btn-border-color: #444;
|
--bs-btn-border-color: #adb5bd;
|
||||||
--bs-btn-hover-color: #fff;
|
--bs-btn-hover-color: #000;
|
||||||
--bs-btn-hover-bg: #3a3a3a;
|
--bs-btn-hover-bg: #b9c0c7;
|
||||||
--bs-btn-hover-border-color: #363636;
|
--bs-btn-hover-border-color: #b5bcc4;
|
||||||
--bs-btn-focus-shadow-rgb: 96, 96, 96;
|
--bs-btn-focus-shadow-rgb: 147, 154, 161;
|
||||||
--bs-btn-active-color: #fff;
|
--bs-btn-active-color: #000;
|
||||||
--bs-btn-active-bg: #363636;
|
--bs-btn-active-bg: #bdc4ca;
|
||||||
--bs-btn-active-border-color: #333333;
|
--bs-btn-active-border-color: #b5bcc4;
|
||||||
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
|
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
|
||||||
--bs-btn-disabled-color: #fff;
|
--bs-btn-disabled-color: #000;
|
||||||
--bs-btn-disabled-bg: #444;
|
--bs-btn-disabled-bg: #adb5bd;
|
||||||
--bs-btn-disabled-border-color: #444;
|
--bs-btn-disabled-border-color: #adb5bd;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-success {
|
.btn-success {
|
||||||
|
@ -3130,19 +3130,19 @@ textarea.form-control-lg {
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-outline-secondary {
|
.btn-outline-secondary {
|
||||||
--bs-btn-color: #444;
|
--bs-btn-color: #adb5bd;
|
||||||
--bs-btn-border-color: #444;
|
--bs-btn-border-color: #adb5bd;
|
||||||
--bs-btn-hover-color: #fff;
|
--bs-btn-hover-color: #000;
|
||||||
--bs-btn-hover-bg: #444;
|
--bs-btn-hover-bg: #adb5bd;
|
||||||
--bs-btn-hover-border-color: #444;
|
--bs-btn-hover-border-color: #adb5bd;
|
||||||
--bs-btn-focus-shadow-rgb: 68, 68, 68;
|
--bs-btn-focus-shadow-rgb: 173, 181, 189;
|
||||||
--bs-btn-active-color: #fff;
|
--bs-btn-active-color: #000;
|
||||||
--bs-btn-active-bg: #444;
|
--bs-btn-active-bg: #adb5bd;
|
||||||
--bs-btn-active-border-color: #444;
|
--bs-btn-active-border-color: #adb5bd;
|
||||||
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
|
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
|
||||||
--bs-btn-disabled-color: #444;
|
--bs-btn-disabled-color: #adb5bd;
|
||||||
--bs-btn-disabled-bg: transparent;
|
--bs-btn-disabled-bg: transparent;
|
||||||
--bs-btn-disabled-border-color: #444;
|
--bs-btn-disabled-border-color: #adb5bd;
|
||||||
--bs-gradient: none;
|
--bs-gradient: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6777,8 +6777,8 @@ textarea.form-control-lg {
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-bg-secondary {
|
.text-bg-secondary {
|
||||||
color: #fff !important;
|
color: #000 !important;
|
||||||
background-color: RGBA(68, 68, 68, var(--bs-bg-opacity, 1)) !important;
|
background-color: RGBA(173, 181, 189, var(--bs-bg-opacity, 1)) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-bg-success {
|
.text-bg-success {
|
||||||
|
@ -6825,8 +6825,8 @@ textarea.form-control-lg {
|
||||||
text-decoration-color: RGBA(var(--bs-secondary-rgb), var(--bs-link-underline-opacity, 1)) !important;
|
text-decoration-color: RGBA(var(--bs-secondary-rgb), var(--bs-link-underline-opacity, 1)) !important;
|
||||||
}
|
}
|
||||||
.link-secondary:hover, .link-secondary:focus {
|
.link-secondary:hover, .link-secondary:focus {
|
||||||
color: RGBA(54, 54, 54, var(--bs-link-opacity, 1)) !important;
|
color: RGBA(189, 196, 202, var(--bs-link-opacity, 1)) !important;
|
||||||
text-decoration-color: RGBA(54, 54, 54, var(--bs-link-underline-opacity, 1)) !important;
|
text-decoration-color: RGBA(189, 196, 202, var(--bs-link-underline-opacity, 1)) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.link-success {
|
.link-success {
|
||||||
|
|
|
@ -30,7 +30,7 @@
|
||||||
--bs-gray-800: #202020;
|
--bs-gray-800: #202020;
|
||||||
--bs-gray-900: #111;
|
--bs-gray-900: #111;
|
||||||
--bs-primary: #00bc8c;
|
--bs-primary: #00bc8c;
|
||||||
--bs-secondary: #333;
|
--bs-secondary: #666;
|
||||||
--bs-success: #00bc8c;
|
--bs-success: #00bc8c;
|
||||||
--bs-info: #3498db;
|
--bs-info: #3498db;
|
||||||
--bs-warning: #f39c12;
|
--bs-warning: #f39c12;
|
||||||
|
@ -38,7 +38,7 @@
|
||||||
--bs-light: #111;
|
--bs-light: #111;
|
||||||
--bs-dark: #dee2e6;
|
--bs-dark: #dee2e6;
|
||||||
--bs-primary-rgb: 0, 188, 140;
|
--bs-primary-rgb: 0, 188, 140;
|
||||||
--bs-secondary-rgb: 51, 51, 51;
|
--bs-secondary-rgb: 102, 102, 102;
|
||||||
--bs-success-rgb: 0, 188, 140;
|
--bs-success-rgb: 0, 188, 140;
|
||||||
--bs-info-rgb: 52, 152, 219;
|
--bs-info-rgb: 52, 152, 219;
|
||||||
--bs-warning-rgb: 243, 156, 18;
|
--bs-warning-rgb: 243, 156, 18;
|
||||||
|
@ -46,7 +46,7 @@
|
||||||
--bs-light-rgb: 17, 17, 17;
|
--bs-light-rgb: 17, 17, 17;
|
||||||
--bs-dark-rgb: 222, 226, 230;
|
--bs-dark-rgb: 222, 226, 230;
|
||||||
--bs-primary-text-emphasis: #004b38;
|
--bs-primary-text-emphasis: #004b38;
|
||||||
--bs-secondary-text-emphasis: #141414;
|
--bs-secondary-text-emphasis: #292929;
|
||||||
--bs-success-text-emphasis: #004b38;
|
--bs-success-text-emphasis: #004b38;
|
||||||
--bs-info-text-emphasis: #153d58;
|
--bs-info-text-emphasis: #153d58;
|
||||||
--bs-warning-text-emphasis: #613e07;
|
--bs-warning-text-emphasis: #613e07;
|
||||||
|
@ -54,7 +54,7 @@
|
||||||
--bs-light-text-emphasis: #333;
|
--bs-light-text-emphasis: #333;
|
||||||
--bs-dark-text-emphasis: #333;
|
--bs-dark-text-emphasis: #333;
|
||||||
--bs-primary-bg-subtle: #ccf2e8;
|
--bs-primary-bg-subtle: #ccf2e8;
|
||||||
--bs-secondary-bg-subtle: #d6d6d6;
|
--bs-secondary-bg-subtle: #e0e0e0;
|
||||||
--bs-success-bg-subtle: #ccf2e8;
|
--bs-success-bg-subtle: #ccf2e8;
|
||||||
--bs-info-bg-subtle: #d6eaf8;
|
--bs-info-bg-subtle: #d6eaf8;
|
||||||
--bs-warning-bg-subtle: #fdebd0;
|
--bs-warning-bg-subtle: #fdebd0;
|
||||||
|
@ -62,7 +62,7 @@
|
||||||
--bs-light-bg-subtle: #f6f6f7;
|
--bs-light-bg-subtle: #f6f6f7;
|
||||||
--bs-dark-bg-subtle: #ced4da;
|
--bs-dark-bg-subtle: #ced4da;
|
||||||
--bs-primary-border-subtle: #99e4d1;
|
--bs-primary-border-subtle: #99e4d1;
|
||||||
--bs-secondary-border-subtle: #adadad;
|
--bs-secondary-border-subtle: #c2c2c2;
|
||||||
--bs-success-border-subtle: #99e4d1;
|
--bs-success-border-subtle: #99e4d1;
|
||||||
--bs-info-border-subtle: #aed6f1;
|
--bs-info-border-subtle: #aed6f1;
|
||||||
--bs-warning-border-subtle: #fad7a0;
|
--bs-warning-border-subtle: #fad7a0;
|
||||||
|
@ -142,7 +142,7 @@
|
||||||
--bs-tertiary-bg: #191919;
|
--bs-tertiary-bg: #191919;
|
||||||
--bs-tertiary-bg-rgb: 25, 25, 25;
|
--bs-tertiary-bg-rgb: 25, 25, 25;
|
||||||
--bs-primary-text-emphasis: #66d7ba;
|
--bs-primary-text-emphasis: #66d7ba;
|
||||||
--bs-secondary-text-emphasis: #858585;
|
--bs-secondary-text-emphasis: #a3a3a3;
|
||||||
--bs-success-text-emphasis: #66d7ba;
|
--bs-success-text-emphasis: #66d7ba;
|
||||||
--bs-info-text-emphasis: #85c1e9;
|
--bs-info-text-emphasis: #85c1e9;
|
||||||
--bs-warning-text-emphasis: #f8c471;
|
--bs-warning-text-emphasis: #f8c471;
|
||||||
|
@ -150,7 +150,7 @@
|
||||||
--bs-light-text-emphasis: #f8f9fa;
|
--bs-light-text-emphasis: #f8f9fa;
|
||||||
--bs-dark-text-emphasis: #dee2e6;
|
--bs-dark-text-emphasis: #dee2e6;
|
||||||
--bs-primary-bg-subtle: #00261c;
|
--bs-primary-bg-subtle: #00261c;
|
||||||
--bs-secondary-bg-subtle: #0a0a0a;
|
--bs-secondary-bg-subtle: #141414;
|
||||||
--bs-success-bg-subtle: #00261c;
|
--bs-success-bg-subtle: #00261c;
|
||||||
--bs-info-bg-subtle: #0a1e2c;
|
--bs-info-bg-subtle: #0a1e2c;
|
||||||
--bs-warning-bg-subtle: #311f04;
|
--bs-warning-bg-subtle: #311f04;
|
||||||
|
@ -158,7 +158,7 @@
|
||||||
--bs-light-bg-subtle: #202020;
|
--bs-light-bg-subtle: #202020;
|
||||||
--bs-dark-bg-subtle: #101010;
|
--bs-dark-bg-subtle: #101010;
|
||||||
--bs-primary-border-subtle: #007154;
|
--bs-primary-border-subtle: #007154;
|
||||||
--bs-secondary-border-subtle: #1f1f1f;
|
--bs-secondary-border-subtle: #3d3d3d;
|
||||||
--bs-success-border-subtle: #007154;
|
--bs-success-border-subtle: #007154;
|
||||||
--bs-info-border-subtle: #1f5b83;
|
--bs-info-border-subtle: #1f5b83;
|
||||||
--bs-warning-border-subtle: #925e0b;
|
--bs-warning-border-subtle: #925e0b;
|
||||||
|
@ -1945,13 +1945,13 @@ progress {
|
||||||
|
|
||||||
.table-secondary {
|
.table-secondary {
|
||||||
--bs-table-color: #000;
|
--bs-table-color: #000;
|
||||||
--bs-table-bg: #d6d6d6;
|
--bs-table-bg: #e0e0e0;
|
||||||
--bs-table-border-color: #c1c1c1;
|
--bs-table-border-color: #cacaca;
|
||||||
--bs-table-striped-bg: #cbcbcb;
|
--bs-table-striped-bg: #d5d5d5;
|
||||||
--bs-table-striped-color: #000;
|
--bs-table-striped-color: #000;
|
||||||
--bs-table-active-bg: #c1c1c1;
|
--bs-table-active-bg: #cacaca;
|
||||||
--bs-table-active-color: #000;
|
--bs-table-active-color: #000;
|
||||||
--bs-table-hover-bg: #c6c6c6;
|
--bs-table-hover-bg: #cfcfcf;
|
||||||
--bs-table-hover-color: #000;
|
--bs-table-hover-color: #000;
|
||||||
color: var(--bs-table-color);
|
color: var(--bs-table-color);
|
||||||
border-color: var(--bs-table-border-color);
|
border-color: var(--bs-table-border-color);
|
||||||
|
@ -2979,19 +2979,19 @@ textarea.form-control-lg {
|
||||||
|
|
||||||
.btn-secondary {
|
.btn-secondary {
|
||||||
--bs-btn-color: #f3f3f3;
|
--bs-btn-color: #f3f3f3;
|
||||||
--bs-btn-bg: #333;
|
--bs-btn-bg: #666;
|
||||||
--bs-btn-border-color: #333;
|
--bs-btn-border-color: #666;
|
||||||
--bs-btn-hover-color: #f3f3f3;
|
--bs-btn-hover-color: #f3f3f3;
|
||||||
--bs-btn-hover-bg: #2b2b2b;
|
--bs-btn-hover-bg: #575757;
|
||||||
--bs-btn-hover-border-color: #292929;
|
--bs-btn-hover-border-color: #525252;
|
||||||
--bs-btn-focus-shadow-rgb: 80, 80, 80;
|
--bs-btn-focus-shadow-rgb: 123, 123, 123;
|
||||||
--bs-btn-active-color: #f3f3f3;
|
--bs-btn-active-color: #f3f3f3;
|
||||||
--bs-btn-active-bg: #292929;
|
--bs-btn-active-bg: #525252;
|
||||||
--bs-btn-active-border-color: #262626;
|
--bs-btn-active-border-color: #4d4d4d;
|
||||||
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
|
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
|
||||||
--bs-btn-disabled-color: #f3f3f3;
|
--bs-btn-disabled-color: #f3f3f3;
|
||||||
--bs-btn-disabled-bg: #333;
|
--bs-btn-disabled-bg: #666;
|
||||||
--bs-btn-disabled-border-color: #333;
|
--bs-btn-disabled-border-color: #666;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-success {
|
.btn-success {
|
||||||
|
@ -3114,19 +3114,19 @@ textarea.form-control-lg {
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-outline-secondary {
|
.btn-outline-secondary {
|
||||||
--bs-btn-color: #333;
|
--bs-btn-color: #666;
|
||||||
--bs-btn-border-color: #333;
|
--bs-btn-border-color: #666;
|
||||||
--bs-btn-hover-color: #f3f3f3;
|
--bs-btn-hover-color: #f3f3f3;
|
||||||
--bs-btn-hover-bg: #333;
|
--bs-btn-hover-bg: #666;
|
||||||
--bs-btn-hover-border-color: #333;
|
--bs-btn-hover-border-color: #666;
|
||||||
--bs-btn-focus-shadow-rgb: 51, 51, 51;
|
--bs-btn-focus-shadow-rgb: 102, 102, 102;
|
||||||
--bs-btn-active-color: #f3f3f3;
|
--bs-btn-active-color: #f3f3f3;
|
||||||
--bs-btn-active-bg: #333;
|
--bs-btn-active-bg: #666;
|
||||||
--bs-btn-active-border-color: #333;
|
--bs-btn-active-border-color: #666;
|
||||||
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
|
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
|
||||||
--bs-btn-disabled-color: #333;
|
--bs-btn-disabled-color: #666;
|
||||||
--bs-btn-disabled-bg: transparent;
|
--bs-btn-disabled-bg: transparent;
|
||||||
--bs-btn-disabled-border-color: #333;
|
--bs-btn-disabled-border-color: #666;
|
||||||
--bs-gradient: none;
|
--bs-gradient: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6766,7 +6766,7 @@ textarea.form-control-lg {
|
||||||
|
|
||||||
.text-bg-secondary {
|
.text-bg-secondary {
|
||||||
color: #f3f3f3 !important;
|
color: #f3f3f3 !important;
|
||||||
background-color: RGBA(51, 51, 51, var(--bs-bg-opacity, 1)) !important;
|
background-color: RGBA(102, 102, 102, var(--bs-bg-opacity, 1)) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-bg-success {
|
.text-bg-success {
|
||||||
|
@ -6813,8 +6813,8 @@ textarea.form-control-lg {
|
||||||
text-decoration-color: RGBA(var(--bs-secondary-rgb), var(--bs-link-underline-opacity, 1)) !important;
|
text-decoration-color: RGBA(var(--bs-secondary-rgb), var(--bs-link-underline-opacity, 1)) !important;
|
||||||
}
|
}
|
||||||
.link-secondary:hover, .link-secondary:focus {
|
.link-secondary:hover, .link-secondary:focus {
|
||||||
color: RGBA(41, 41, 41, var(--bs-link-opacity, 1)) !important;
|
color: RGBA(82, 82, 82, var(--bs-link-opacity, 1)) !important;
|
||||||
text-decoration-color: RGBA(41, 41, 41, var(--bs-link-underline-opacity, 1)) !important;
|
text-decoration-color: RGBA(82, 82, 82, var(--bs-link-underline-opacity, 1)) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.link-success {
|
.link-success {
|
||||||
|
|
|
@ -30,7 +30,7 @@
|
||||||
--bs-gray-800: #303030;
|
--bs-gray-800: #303030;
|
||||||
--bs-gray-900: #222;
|
--bs-gray-900: #222;
|
||||||
--bs-primary: #375a7f;
|
--bs-primary: #375a7f;
|
||||||
--bs-secondary: #444;
|
--bs-secondary: #adb5bd;
|
||||||
--bs-success: #00bc8c;
|
--bs-success: #00bc8c;
|
||||||
--bs-info: #3498db;
|
--bs-info: #3498db;
|
||||||
--bs-warning: #f39c12;
|
--bs-warning: #f39c12;
|
||||||
|
@ -38,7 +38,7 @@
|
||||||
--bs-light: #303030;
|
--bs-light: #303030;
|
||||||
--bs-dark: #dee2e6;
|
--bs-dark: #dee2e6;
|
||||||
--bs-primary-rgb: 55, 90, 127;
|
--bs-primary-rgb: 55, 90, 127;
|
||||||
--bs-secondary-rgb: 68, 68, 68;
|
--bs-secondary-rgb: 173, 181, 189;
|
||||||
--bs-success-rgb: 0, 188, 140;
|
--bs-success-rgb: 0, 188, 140;
|
||||||
--bs-info-rgb: 52, 152, 219;
|
--bs-info-rgb: 52, 152, 219;
|
||||||
--bs-warning-rgb: 243, 156, 18;
|
--bs-warning-rgb: 243, 156, 18;
|
||||||
|
@ -46,7 +46,7 @@
|
||||||
--bs-light-rgb: 48, 48, 48;
|
--bs-light-rgb: 48, 48, 48;
|
||||||
--bs-dark-rgb: 222, 226, 230;
|
--bs-dark-rgb: 222, 226, 230;
|
||||||
--bs-primary-text-emphasis: #162433;
|
--bs-primary-text-emphasis: #162433;
|
||||||
--bs-secondary-text-emphasis: #1b1b1b;
|
--bs-secondary-text-emphasis: #45484c;
|
||||||
--bs-success-text-emphasis: #004b38;
|
--bs-success-text-emphasis: #004b38;
|
||||||
--bs-info-text-emphasis: #153d58;
|
--bs-info-text-emphasis: #153d58;
|
||||||
--bs-warning-text-emphasis: #613e07;
|
--bs-warning-text-emphasis: #613e07;
|
||||||
|
@ -54,7 +54,7 @@
|
||||||
--bs-light-text-emphasis: #444;
|
--bs-light-text-emphasis: #444;
|
||||||
--bs-dark-text-emphasis: #444;
|
--bs-dark-text-emphasis: #444;
|
||||||
--bs-primary-bg-subtle: #d7dee5;
|
--bs-primary-bg-subtle: #d7dee5;
|
||||||
--bs-secondary-bg-subtle: #dadada;
|
--bs-secondary-bg-subtle: #eff0f2;
|
||||||
--bs-success-bg-subtle: #ccf2e8;
|
--bs-success-bg-subtle: #ccf2e8;
|
||||||
--bs-info-bg-subtle: #d6eaf8;
|
--bs-info-bg-subtle: #d6eaf8;
|
||||||
--bs-warning-bg-subtle: #fdebd0;
|
--bs-warning-bg-subtle: #fdebd0;
|
||||||
|
@ -62,7 +62,7 @@
|
||||||
--bs-light-bg-subtle: #fcfcfd;
|
--bs-light-bg-subtle: #fcfcfd;
|
||||||
--bs-dark-bg-subtle: #ced4da;
|
--bs-dark-bg-subtle: #ced4da;
|
||||||
--bs-primary-border-subtle: #afbdcc;
|
--bs-primary-border-subtle: #afbdcc;
|
||||||
--bs-secondary-border-subtle: #b4b4b4;
|
--bs-secondary-border-subtle: #dee1e5;
|
||||||
--bs-success-border-subtle: #99e4d1;
|
--bs-success-border-subtle: #99e4d1;
|
||||||
--bs-info-border-subtle: #aed6f1;
|
--bs-info-border-subtle: #aed6f1;
|
||||||
--bs-warning-border-subtle: #fad7a0;
|
--bs-warning-border-subtle: #fad7a0;
|
||||||
|
@ -142,7 +142,7 @@
|
||||||
--bs-tertiary-bg: #292929;
|
--bs-tertiary-bg: #292929;
|
||||||
--bs-tertiary-bg-rgb: 41, 41, 41;
|
--bs-tertiary-bg-rgb: 41, 41, 41;
|
||||||
--bs-primary-text-emphasis: #879cb2;
|
--bs-primary-text-emphasis: #879cb2;
|
||||||
--bs-secondary-text-emphasis: #8f8f8f;
|
--bs-secondary-text-emphasis: #ced3d7;
|
||||||
--bs-success-text-emphasis: #66d7ba;
|
--bs-success-text-emphasis: #66d7ba;
|
||||||
--bs-info-text-emphasis: #85c1e9;
|
--bs-info-text-emphasis: #85c1e9;
|
||||||
--bs-warning-text-emphasis: #f8c471;
|
--bs-warning-text-emphasis: #f8c471;
|
||||||
|
@ -150,7 +150,7 @@
|
||||||
--bs-light-text-emphasis: #f8f9fa;
|
--bs-light-text-emphasis: #f8f9fa;
|
||||||
--bs-dark-text-emphasis: #dee2e6;
|
--bs-dark-text-emphasis: #dee2e6;
|
||||||
--bs-primary-bg-subtle: #0b1219;
|
--bs-primary-bg-subtle: #0b1219;
|
||||||
--bs-secondary-bg-subtle: #0e0e0e;
|
--bs-secondary-bg-subtle: #232426;
|
||||||
--bs-success-bg-subtle: #00261c;
|
--bs-success-bg-subtle: #00261c;
|
||||||
--bs-info-bg-subtle: #0a1e2c;
|
--bs-info-bg-subtle: #0a1e2c;
|
||||||
--bs-warning-bg-subtle: #311f04;
|
--bs-warning-bg-subtle: #311f04;
|
||||||
|
@ -158,7 +158,7 @@
|
||||||
--bs-light-bg-subtle: #303030;
|
--bs-light-bg-subtle: #303030;
|
||||||
--bs-dark-bg-subtle: #181818;
|
--bs-dark-bg-subtle: #181818;
|
||||||
--bs-primary-border-subtle: #21364c;
|
--bs-primary-border-subtle: #21364c;
|
||||||
--bs-secondary-border-subtle: #292929;
|
--bs-secondary-border-subtle: #686d71;
|
||||||
--bs-success-border-subtle: #007154;
|
--bs-success-border-subtle: #007154;
|
||||||
--bs-info-border-subtle: #1f5b83;
|
--bs-info-border-subtle: #1f5b83;
|
||||||
--bs-warning-border-subtle: #925e0b;
|
--bs-warning-border-subtle: #925e0b;
|
||||||
|
@ -1945,13 +1945,13 @@ progress {
|
||||||
|
|
||||||
.table-secondary {
|
.table-secondary {
|
||||||
--bs-table-color: #000;
|
--bs-table-color: #000;
|
||||||
--bs-table-bg: #dadada;
|
--bs-table-bg: #eff0f2;
|
||||||
--bs-table-border-color: #c4c4c4;
|
--bs-table-border-color: #d7d8da;
|
||||||
--bs-table-striped-bg: #cfcfcf;
|
--bs-table-striped-bg: #e3e4e6;
|
||||||
--bs-table-striped-color: #000;
|
--bs-table-striped-color: #000;
|
||||||
--bs-table-active-bg: #c4c4c4;
|
--bs-table-active-bg: #d7d8da;
|
||||||
--bs-table-active-color: #000;
|
--bs-table-active-color: #000;
|
||||||
--bs-table-hover-bg: #cacaca;
|
--bs-table-hover-bg: #dddee0;
|
||||||
--bs-table-hover-color: #000;
|
--bs-table-hover-color: #000;
|
||||||
color: var(--bs-table-color);
|
color: var(--bs-table-color);
|
||||||
border-color: var(--bs-table-border-color);
|
border-color: var(--bs-table-border-color);
|
||||||
|
@ -2978,20 +2978,20 @@ textarea.form-control-lg {
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-secondary {
|
.btn-secondary {
|
||||||
--bs-btn-color: #fff;
|
--bs-btn-color: #000;
|
||||||
--bs-btn-bg: #444;
|
--bs-btn-bg: #adb5bd;
|
||||||
--bs-btn-border-color: #444;
|
--bs-btn-border-color: #adb5bd;
|
||||||
--bs-btn-hover-color: #fff;
|
--bs-btn-hover-color: #000;
|
||||||
--bs-btn-hover-bg: #3a3a3a;
|
--bs-btn-hover-bg: #b9c0c7;
|
||||||
--bs-btn-hover-border-color: #363636;
|
--bs-btn-hover-border-color: #b5bcc4;
|
||||||
--bs-btn-focus-shadow-rgb: 96, 96, 96;
|
--bs-btn-focus-shadow-rgb: 147, 154, 161;
|
||||||
--bs-btn-active-color: #fff;
|
--bs-btn-active-color: #000;
|
||||||
--bs-btn-active-bg: #363636;
|
--bs-btn-active-bg: #bdc4ca;
|
||||||
--bs-btn-active-border-color: #333333;
|
--bs-btn-active-border-color: #b5bcc4;
|
||||||
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
|
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
|
||||||
--bs-btn-disabled-color: #fff;
|
--bs-btn-disabled-color: #000;
|
||||||
--bs-btn-disabled-bg: #444;
|
--bs-btn-disabled-bg: #adb5bd;
|
||||||
--bs-btn-disabled-border-color: #444;
|
--bs-btn-disabled-border-color: #adb5bd;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-success {
|
.btn-success {
|
||||||
|
@ -3114,19 +3114,19 @@ textarea.form-control-lg {
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-outline-secondary {
|
.btn-outline-secondary {
|
||||||
--bs-btn-color: #444;
|
--bs-btn-color: #adb5bd;
|
||||||
--bs-btn-border-color: #444;
|
--bs-btn-border-color: #adb5bd;
|
||||||
--bs-btn-hover-color: #fff;
|
--bs-btn-hover-color: #000;
|
||||||
--bs-btn-hover-bg: #444;
|
--bs-btn-hover-bg: #adb5bd;
|
||||||
--bs-btn-hover-border-color: #444;
|
--bs-btn-hover-border-color: #adb5bd;
|
||||||
--bs-btn-focus-shadow-rgb: 68, 68, 68;
|
--bs-btn-focus-shadow-rgb: 173, 181, 189;
|
||||||
--bs-btn-active-color: #fff;
|
--bs-btn-active-color: #000;
|
||||||
--bs-btn-active-bg: #444;
|
--bs-btn-active-bg: #adb5bd;
|
||||||
--bs-btn-active-border-color: #444;
|
--bs-btn-active-border-color: #adb5bd;
|
||||||
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
|
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
|
||||||
--bs-btn-disabled-color: #444;
|
--bs-btn-disabled-color: #adb5bd;
|
||||||
--bs-btn-disabled-bg: transparent;
|
--bs-btn-disabled-bg: transparent;
|
||||||
--bs-btn-disabled-border-color: #444;
|
--bs-btn-disabled-border-color: #adb5bd;
|
||||||
--bs-gradient: none;
|
--bs-gradient: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6765,8 +6765,8 @@ textarea.form-control-lg {
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-bg-secondary {
|
.text-bg-secondary {
|
||||||
color: #fff !important;
|
color: #000 !important;
|
||||||
background-color: RGBA(68, 68, 68, var(--bs-bg-opacity, 1)) !important;
|
background-color: RGBA(173, 181, 189, var(--bs-bg-opacity, 1)) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-bg-success {
|
.text-bg-success {
|
||||||
|
@ -6813,8 +6813,8 @@ textarea.form-control-lg {
|
||||||
text-decoration-color: RGBA(var(--bs-secondary-rgb), var(--bs-link-underline-opacity, 1)) !important;
|
text-decoration-color: RGBA(var(--bs-secondary-rgb), var(--bs-link-underline-opacity, 1)) !important;
|
||||||
}
|
}
|
||||||
.link-secondary:hover, .link-secondary:focus {
|
.link-secondary:hover, .link-secondary:focus {
|
||||||
color: RGBA(54, 54, 54, var(--bs-link-opacity, 1)) !important;
|
color: RGBA(189, 196, 202, var(--bs-link-opacity, 1)) !important;
|
||||||
text-decoration-color: RGBA(54, 54, 54, var(--bs-link-underline-opacity, 1)) !important;
|
text-decoration-color: RGBA(189, 196, 202, var(--bs-link-underline-opacity, 1)) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.link-success {
|
.link-success {
|
||||||
|
|
|
@ -30,7 +30,7 @@
|
||||||
--bs-gray-800: #303030;
|
--bs-gray-800: #303030;
|
||||||
--bs-gray-900: #222;
|
--bs-gray-900: #222;
|
||||||
--bs-primary: #00bc8c;
|
--bs-primary: #00bc8c;
|
||||||
--bs-secondary: #444;
|
--bs-secondary: #adb5bd;
|
||||||
--bs-success: #00bc8c;
|
--bs-success: #00bc8c;
|
||||||
--bs-info: #3498db;
|
--bs-info: #3498db;
|
||||||
--bs-warning: #f39c12;
|
--bs-warning: #f39c12;
|
||||||
|
@ -38,7 +38,7 @@
|
||||||
--bs-light: #303030;
|
--bs-light: #303030;
|
||||||
--bs-dark: #dee2e6;
|
--bs-dark: #dee2e6;
|
||||||
--bs-primary-rgb: 0, 188, 140;
|
--bs-primary-rgb: 0, 188, 140;
|
||||||
--bs-secondary-rgb: 68, 68, 68;
|
--bs-secondary-rgb: 173, 181, 189;
|
||||||
--bs-success-rgb: 0, 188, 140;
|
--bs-success-rgb: 0, 188, 140;
|
||||||
--bs-info-rgb: 52, 152, 219;
|
--bs-info-rgb: 52, 152, 219;
|
||||||
--bs-warning-rgb: 243, 156, 18;
|
--bs-warning-rgb: 243, 156, 18;
|
||||||
|
@ -46,7 +46,7 @@
|
||||||
--bs-light-rgb: 48, 48, 48;
|
--bs-light-rgb: 48, 48, 48;
|
||||||
--bs-dark-rgb: 222, 226, 230;
|
--bs-dark-rgb: 222, 226, 230;
|
||||||
--bs-primary-text-emphasis: #004b38;
|
--bs-primary-text-emphasis: #004b38;
|
||||||
--bs-secondary-text-emphasis: #1b1b1b;
|
--bs-secondary-text-emphasis: #45484c;
|
||||||
--bs-success-text-emphasis: #004b38;
|
--bs-success-text-emphasis: #004b38;
|
||||||
--bs-info-text-emphasis: #153d58;
|
--bs-info-text-emphasis: #153d58;
|
||||||
--bs-warning-text-emphasis: #613e07;
|
--bs-warning-text-emphasis: #613e07;
|
||||||
|
@ -54,7 +54,7 @@
|
||||||
--bs-light-text-emphasis: #444;
|
--bs-light-text-emphasis: #444;
|
||||||
--bs-dark-text-emphasis: #444;
|
--bs-dark-text-emphasis: #444;
|
||||||
--bs-primary-bg-subtle: #ccf2e8;
|
--bs-primary-bg-subtle: #ccf2e8;
|
||||||
--bs-secondary-bg-subtle: #dadada;
|
--bs-secondary-bg-subtle: #eff0f2;
|
||||||
--bs-success-bg-subtle: #ccf2e8;
|
--bs-success-bg-subtle: #ccf2e8;
|
||||||
--bs-info-bg-subtle: #d6eaf8;
|
--bs-info-bg-subtle: #d6eaf8;
|
||||||
--bs-warning-bg-subtle: #fdebd0;
|
--bs-warning-bg-subtle: #fdebd0;
|
||||||
|
@ -62,7 +62,7 @@
|
||||||
--bs-light-bg-subtle: #fcfcfd;
|
--bs-light-bg-subtle: #fcfcfd;
|
||||||
--bs-dark-bg-subtle: #ced4da;
|
--bs-dark-bg-subtle: #ced4da;
|
||||||
--bs-primary-border-subtle: #99e4d1;
|
--bs-primary-border-subtle: #99e4d1;
|
||||||
--bs-secondary-border-subtle: #b4b4b4;
|
--bs-secondary-border-subtle: #dee1e5;
|
||||||
--bs-success-border-subtle: #99e4d1;
|
--bs-success-border-subtle: #99e4d1;
|
||||||
--bs-info-border-subtle: #aed6f1;
|
--bs-info-border-subtle: #aed6f1;
|
||||||
--bs-warning-border-subtle: #fad7a0;
|
--bs-warning-border-subtle: #fad7a0;
|
||||||
|
@ -142,7 +142,7 @@
|
||||||
--bs-tertiary-bg: #292929;
|
--bs-tertiary-bg: #292929;
|
||||||
--bs-tertiary-bg-rgb: 41, 41, 41;
|
--bs-tertiary-bg-rgb: 41, 41, 41;
|
||||||
--bs-primary-text-emphasis: #66d7ba;
|
--bs-primary-text-emphasis: #66d7ba;
|
||||||
--bs-secondary-text-emphasis: #8f8f8f;
|
--bs-secondary-text-emphasis: #ced3d7;
|
||||||
--bs-success-text-emphasis: #66d7ba;
|
--bs-success-text-emphasis: #66d7ba;
|
||||||
--bs-info-text-emphasis: #85c1e9;
|
--bs-info-text-emphasis: #85c1e9;
|
||||||
--bs-warning-text-emphasis: #f8c471;
|
--bs-warning-text-emphasis: #f8c471;
|
||||||
|
@ -150,7 +150,7 @@
|
||||||
--bs-light-text-emphasis: #f8f9fa;
|
--bs-light-text-emphasis: #f8f9fa;
|
||||||
--bs-dark-text-emphasis: #dee2e6;
|
--bs-dark-text-emphasis: #dee2e6;
|
||||||
--bs-primary-bg-subtle: #00261c;
|
--bs-primary-bg-subtle: #00261c;
|
||||||
--bs-secondary-bg-subtle: #0e0e0e;
|
--bs-secondary-bg-subtle: #232426;
|
||||||
--bs-success-bg-subtle: #00261c;
|
--bs-success-bg-subtle: #00261c;
|
||||||
--bs-info-bg-subtle: #0a1e2c;
|
--bs-info-bg-subtle: #0a1e2c;
|
||||||
--bs-warning-bg-subtle: #311f04;
|
--bs-warning-bg-subtle: #311f04;
|
||||||
|
@ -158,7 +158,7 @@
|
||||||
--bs-light-bg-subtle: #303030;
|
--bs-light-bg-subtle: #303030;
|
||||||
--bs-dark-bg-subtle: #181818;
|
--bs-dark-bg-subtle: #181818;
|
||||||
--bs-primary-border-subtle: #007154;
|
--bs-primary-border-subtle: #007154;
|
||||||
--bs-secondary-border-subtle: #292929;
|
--bs-secondary-border-subtle: #686d71;
|
||||||
--bs-success-border-subtle: #007154;
|
--bs-success-border-subtle: #007154;
|
||||||
--bs-info-border-subtle: #1f5b83;
|
--bs-info-border-subtle: #1f5b83;
|
||||||
--bs-warning-border-subtle: #925e0b;
|
--bs-warning-border-subtle: #925e0b;
|
||||||
|
@ -1945,13 +1945,13 @@ progress {
|
||||||
|
|
||||||
.table-secondary {
|
.table-secondary {
|
||||||
--bs-table-color: #000;
|
--bs-table-color: #000;
|
||||||
--bs-table-bg: #dadada;
|
--bs-table-bg: #eff0f2;
|
||||||
--bs-table-border-color: #c4c4c4;
|
--bs-table-border-color: #d7d8da;
|
||||||
--bs-table-striped-bg: #cfcfcf;
|
--bs-table-striped-bg: #e3e4e6;
|
||||||
--bs-table-striped-color: #000;
|
--bs-table-striped-color: #000;
|
||||||
--bs-table-active-bg: #c4c4c4;
|
--bs-table-active-bg: #d7d8da;
|
||||||
--bs-table-active-color: #000;
|
--bs-table-active-color: #000;
|
||||||
--bs-table-hover-bg: #cacaca;
|
--bs-table-hover-bg: #dddee0;
|
||||||
--bs-table-hover-color: #000;
|
--bs-table-hover-color: #000;
|
||||||
color: var(--bs-table-color);
|
color: var(--bs-table-color);
|
||||||
border-color: var(--bs-table-border-color);
|
border-color: var(--bs-table-border-color);
|
||||||
|
@ -2978,20 +2978,20 @@ textarea.form-control-lg {
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-secondary {
|
.btn-secondary {
|
||||||
--bs-btn-color: #fff;
|
--bs-btn-color: #000;
|
||||||
--bs-btn-bg: #444;
|
--bs-btn-bg: #adb5bd;
|
||||||
--bs-btn-border-color: #444;
|
--bs-btn-border-color: #adb5bd;
|
||||||
--bs-btn-hover-color: #fff;
|
--bs-btn-hover-color: #000;
|
||||||
--bs-btn-hover-bg: #3a3a3a;
|
--bs-btn-hover-bg: #b9c0c7;
|
||||||
--bs-btn-hover-border-color: #363636;
|
--bs-btn-hover-border-color: #b5bcc4;
|
||||||
--bs-btn-focus-shadow-rgb: 96, 96, 96;
|
--bs-btn-focus-shadow-rgb: 147, 154, 161;
|
||||||
--bs-btn-active-color: #fff;
|
--bs-btn-active-color: #000;
|
||||||
--bs-btn-active-bg: #363636;
|
--bs-btn-active-bg: #bdc4ca;
|
||||||
--bs-btn-active-border-color: #333333;
|
--bs-btn-active-border-color: #b5bcc4;
|
||||||
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
|
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
|
||||||
--bs-btn-disabled-color: #fff;
|
--bs-btn-disabled-color: #000;
|
||||||
--bs-btn-disabled-bg: #444;
|
--bs-btn-disabled-bg: #adb5bd;
|
||||||
--bs-btn-disabled-border-color: #444;
|
--bs-btn-disabled-border-color: #adb5bd;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-success {
|
.btn-success {
|
||||||
|
@ -3114,19 +3114,19 @@ textarea.form-control-lg {
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-outline-secondary {
|
.btn-outline-secondary {
|
||||||
--bs-btn-color: #444;
|
--bs-btn-color: #adb5bd;
|
||||||
--bs-btn-border-color: #444;
|
--bs-btn-border-color: #adb5bd;
|
||||||
--bs-btn-hover-color: #fff;
|
--bs-btn-hover-color: #000;
|
||||||
--bs-btn-hover-bg: #444;
|
--bs-btn-hover-bg: #adb5bd;
|
||||||
--bs-btn-hover-border-color: #444;
|
--bs-btn-hover-border-color: #adb5bd;
|
||||||
--bs-btn-focus-shadow-rgb: 68, 68, 68;
|
--bs-btn-focus-shadow-rgb: 173, 181, 189;
|
||||||
--bs-btn-active-color: #fff;
|
--bs-btn-active-color: #000;
|
||||||
--bs-btn-active-bg: #444;
|
--bs-btn-active-bg: #adb5bd;
|
||||||
--bs-btn-active-border-color: #444;
|
--bs-btn-active-border-color: #adb5bd;
|
||||||
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
|
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
|
||||||
--bs-btn-disabled-color: #444;
|
--bs-btn-disabled-color: #adb5bd;
|
||||||
--bs-btn-disabled-bg: transparent;
|
--bs-btn-disabled-bg: transparent;
|
||||||
--bs-btn-disabled-border-color: #444;
|
--bs-btn-disabled-border-color: #adb5bd;
|
||||||
--bs-gradient: none;
|
--bs-gradient: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6765,8 +6765,8 @@ textarea.form-control-lg {
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-bg-secondary {
|
.text-bg-secondary {
|
||||||
color: #fff !important;
|
color: #000 !important;
|
||||||
background-color: RGBA(68, 68, 68, var(--bs-bg-opacity, 1)) !important;
|
background-color: RGBA(173, 181, 189, var(--bs-bg-opacity, 1)) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-bg-success {
|
.text-bg-success {
|
||||||
|
@ -6813,8 +6813,8 @@ textarea.form-control-lg {
|
||||||
text-decoration-color: RGBA(var(--bs-secondary-rgb), var(--bs-link-underline-opacity, 1)) !important;
|
text-decoration-color: RGBA(var(--bs-secondary-rgb), var(--bs-link-underline-opacity, 1)) !important;
|
||||||
}
|
}
|
||||||
.link-secondary:hover, .link-secondary:focus {
|
.link-secondary:hover, .link-secondary:focus {
|
||||||
color: RGBA(54, 54, 54, var(--bs-link-opacity, 1)) !important;
|
color: RGBA(189, 196, 202, var(--bs-link-opacity, 1)) !important;
|
||||||
text-decoration-color: RGBA(54, 54, 54, var(--bs-link-underline-opacity, 1)) !important;
|
text-decoration-color: RGBA(189, 196, 202, var(--bs-link-underline-opacity, 1)) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.link-success {
|
.link-success {
|
||||||
|
|
|
@ -26,35 +26,35 @@
|
||||||
--bs-gray-400: #ced4da;
|
--bs-gray-400: #ced4da;
|
||||||
--bs-gray-500: #adb5bd;
|
--bs-gray-500: #adb5bd;
|
||||||
--bs-gray-600: #6c757d;
|
--bs-gray-600: #6c757d;
|
||||||
--bs-gray-700: #495057;
|
--bs-gray-700: #444;
|
||||||
--bs-gray-800: #303030;
|
--bs-gray-800: #303030;
|
||||||
--bs-gray-900: #222;
|
--bs-gray-900: #2f2f2f;
|
||||||
--bs-primary: #fefe54;
|
--bs-primary: #fefe54;
|
||||||
--bs-secondary: #222;
|
--bs-secondary: #303030;
|
||||||
--bs-success: #00aa00;
|
--bs-success: #00aa00;
|
||||||
--bs-info: #00aaaa;
|
--bs-info: #00aaaa;
|
||||||
--bs-warning: #aa00aa;
|
--bs-warning: #aa00aa;
|
||||||
--bs-danger: #aa0000;
|
--bs-danger: #aa0000;
|
||||||
--bs-light: #303030;
|
--bs-light: #444;
|
||||||
--bs-dark: black;
|
--bs-dark: #bbb;
|
||||||
--bs-primary-rgb: 254, 254, 84;
|
--bs-primary-rgb: 254, 254, 84;
|
||||||
--bs-secondary-rgb: 34, 34, 34;
|
--bs-secondary-rgb: 48, 48, 48;
|
||||||
--bs-success-rgb: 0, 170, 0;
|
--bs-success-rgb: 0, 170, 0;
|
||||||
--bs-info-rgb: 0, 170, 170;
|
--bs-info-rgb: 0, 170, 170;
|
||||||
--bs-warning-rgb: 170, 0, 170;
|
--bs-warning-rgb: 170, 0, 170;
|
||||||
--bs-danger-rgb: 170, 0, 0;
|
--bs-danger-rgb: 170, 0, 0;
|
||||||
--bs-light-rgb: 48, 48, 48;
|
--bs-light-rgb: 68, 68, 68;
|
||||||
--bs-dark-rgb: 0, 0, 0;
|
--bs-dark-rgb: 187, 187, 187;
|
||||||
--bs-primary-text-emphasis: #666622;
|
--bs-primary-text-emphasis: #666622;
|
||||||
--bs-secondary-text-emphasis: #0e0e0e;
|
--bs-secondary-text-emphasis: #131313;
|
||||||
--bs-success-text-emphasis: #004400;
|
--bs-success-text-emphasis: #004400;
|
||||||
--bs-info-text-emphasis: #004444;
|
--bs-info-text-emphasis: #004444;
|
||||||
--bs-warning-text-emphasis: #440044;
|
--bs-warning-text-emphasis: #440044;
|
||||||
--bs-danger-text-emphasis: #440000;
|
--bs-danger-text-emphasis: #440000;
|
||||||
--bs-light-text-emphasis: #495057;
|
--bs-light-text-emphasis: #444;
|
||||||
--bs-dark-text-emphasis: #495057;
|
--bs-dark-text-emphasis: #444;
|
||||||
--bs-primary-bg-subtle: #ffffdd;
|
--bs-primary-bg-subtle: #ffffdd;
|
||||||
--bs-secondary-bg-subtle: lightgray;
|
--bs-secondary-bg-subtle: #d6d6d6;
|
||||||
--bs-success-bg-subtle: #cceecc;
|
--bs-success-bg-subtle: #cceecc;
|
||||||
--bs-info-bg-subtle: #cceeee;
|
--bs-info-bg-subtle: #cceeee;
|
||||||
--bs-warning-bg-subtle: #eeccee;
|
--bs-warning-bg-subtle: #eeccee;
|
||||||
|
@ -62,7 +62,7 @@
|
||||||
--bs-light-bg-subtle: #fcfcfd;
|
--bs-light-bg-subtle: #fcfcfd;
|
||||||
--bs-dark-bg-subtle: #ced4da;
|
--bs-dark-bg-subtle: #ced4da;
|
||||||
--bs-primary-border-subtle: #ffffbb;
|
--bs-primary-border-subtle: #ffffbb;
|
||||||
--bs-secondary-border-subtle: #a7a7a7;
|
--bs-secondary-border-subtle: #acacac;
|
||||||
--bs-success-border-subtle: #99dd99;
|
--bs-success-border-subtle: #99dd99;
|
||||||
--bs-info-border-subtle: #99dddd;
|
--bs-info-border-subtle: #99dddd;
|
||||||
--bs-warning-border-subtle: #dd99dd;
|
--bs-warning-border-subtle: #dd99dd;
|
||||||
|
@ -129,8 +129,8 @@
|
||||||
color-scheme: dark;
|
color-scheme: dark;
|
||||||
--bs-body-color: #adb5bd;
|
--bs-body-color: #adb5bd;
|
||||||
--bs-body-color-rgb: 173, 181, 189;
|
--bs-body-color-rgb: 173, 181, 189;
|
||||||
--bs-body-bg: #222;
|
--bs-body-bg: #2f2f2f;
|
||||||
--bs-body-bg-rgb: 34, 34, 34;
|
--bs-body-bg-rgb: 47, 47, 47;
|
||||||
--bs-emphasis-color: #fff;
|
--bs-emphasis-color: #fff;
|
||||||
--bs-emphasis-color-rgb: 255, 255, 255;
|
--bs-emphasis-color-rgb: 255, 255, 255;
|
||||||
--bs-secondary-color: rgba(173, 181, 189, 0.75);
|
--bs-secondary-color: rgba(173, 181, 189, 0.75);
|
||||||
|
@ -139,10 +139,10 @@
|
||||||
--bs-secondary-bg-rgb: 48, 48, 48;
|
--bs-secondary-bg-rgb: 48, 48, 48;
|
||||||
--bs-tertiary-color: rgba(173, 181, 189, 0.5);
|
--bs-tertiary-color: rgba(173, 181, 189, 0.5);
|
||||||
--bs-tertiary-color-rgb: 173, 181, 189;
|
--bs-tertiary-color-rgb: 173, 181, 189;
|
||||||
--bs-tertiary-bg: #292929;
|
--bs-tertiary-bg: #303030;
|
||||||
--bs-tertiary-bg-rgb: 41, 41, 41;
|
--bs-tertiary-bg-rgb: 48, 48, 48;
|
||||||
--bs-primary-text-emphasis: #fefe98;
|
--bs-primary-text-emphasis: #fefe98;
|
||||||
--bs-secondary-text-emphasis: #7a7a7a;
|
--bs-secondary-text-emphasis: #838383;
|
||||||
--bs-success-text-emphasis: #66cc66;
|
--bs-success-text-emphasis: #66cc66;
|
||||||
--bs-info-text-emphasis: #66cccc;
|
--bs-info-text-emphasis: #66cccc;
|
||||||
--bs-warning-text-emphasis: #cc66cc;
|
--bs-warning-text-emphasis: #cc66cc;
|
||||||
|
@ -150,7 +150,7 @@
|
||||||
--bs-light-text-emphasis: #f8f9fa;
|
--bs-light-text-emphasis: #f8f9fa;
|
||||||
--bs-dark-text-emphasis: #bbb;
|
--bs-dark-text-emphasis: #bbb;
|
||||||
--bs-primary-bg-subtle: #333311;
|
--bs-primary-bg-subtle: #333311;
|
||||||
--bs-secondary-bg-subtle: #070707;
|
--bs-secondary-bg-subtle: #0a0a0a;
|
||||||
--bs-success-bg-subtle: #002200;
|
--bs-success-bg-subtle: #002200;
|
||||||
--bs-info-bg-subtle: #002222;
|
--bs-info-bg-subtle: #002222;
|
||||||
--bs-warning-bg-subtle: #220022;
|
--bs-warning-bg-subtle: #220022;
|
||||||
|
@ -158,12 +158,12 @@
|
||||||
--bs-light-bg-subtle: #303030;
|
--bs-light-bg-subtle: #303030;
|
||||||
--bs-dark-bg-subtle: #181818;
|
--bs-dark-bg-subtle: #181818;
|
||||||
--bs-primary-border-subtle: #989832;
|
--bs-primary-border-subtle: #989832;
|
||||||
--bs-secondary-border-subtle: #141414;
|
--bs-secondary-border-subtle: #1d1d1d;
|
||||||
--bs-success-border-subtle: #006600;
|
--bs-success-border-subtle: #006600;
|
||||||
--bs-info-border-subtle: #006666;
|
--bs-info-border-subtle: #006666;
|
||||||
--bs-warning-border-subtle: #660066;
|
--bs-warning-border-subtle: #660066;
|
||||||
--bs-danger-border-subtle: #660000;
|
--bs-danger-border-subtle: #660000;
|
||||||
--bs-light-border-subtle: #495057;
|
--bs-light-border-subtle: #444;
|
||||||
--bs-dark-border-subtle: #303030;
|
--bs-dark-border-subtle: #303030;
|
||||||
--bs-heading-color: inherit;
|
--bs-heading-color: inherit;
|
||||||
--bs-link-color: #fefe98;
|
--bs-link-color: #fefe98;
|
||||||
|
@ -171,7 +171,7 @@
|
||||||
--bs-link-color-rgb: 254, 254, 152;
|
--bs-link-color-rgb: 254, 254, 152;
|
||||||
--bs-link-hover-color-rgb: 254, 254, 173;
|
--bs-link-hover-color-rgb: 254, 254, 173;
|
||||||
--bs-code-color: #fe98fe;
|
--bs-code-color: #fe98fe;
|
||||||
--bs-border-color: #495057;
|
--bs-border-color: #444;
|
||||||
--bs-border-color-translucent: rgba(255, 255, 255, 0.15);
|
--bs-border-color-translucent: rgba(255, 255, 255, 0.15);
|
||||||
--bs-form-valid-color: #99ff99;
|
--bs-form-valid-color: #99ff99;
|
||||||
--bs-form-valid-border-color: #99ff99;
|
--bs-form-valid-border-color: #99ff99;
|
||||||
|
@ -1942,13 +1942,13 @@ progress {
|
||||||
|
|
||||||
.table-secondary {
|
.table-secondary {
|
||||||
--bs-table-color: #000;
|
--bs-table-color: #000;
|
||||||
--bs-table-bg: lightgray;
|
--bs-table-bg: #d6d6d6;
|
||||||
--bs-table-border-color: #bebebe;
|
--bs-table-border-color: #c1c1c1;
|
||||||
--bs-table-striped-bg: #c8c8c8;
|
--bs-table-striped-bg: #cbcbcb;
|
||||||
--bs-table-striped-color: #000;
|
--bs-table-striped-color: #000;
|
||||||
--bs-table-active-bg: #bebebe;
|
--bs-table-active-bg: #c1c1c1;
|
||||||
--bs-table-active-color: #000;
|
--bs-table-active-color: #000;
|
||||||
--bs-table-hover-bg: #c3c3c3;
|
--bs-table-hover-bg: #c6c6c6;
|
||||||
--bs-table-hover-color: #000;
|
--bs-table-hover-color: #000;
|
||||||
color: var(--bs-table-color);
|
color: var(--bs-table-color);
|
||||||
border-color: var(--bs-table-border-color);
|
border-color: var(--bs-table-border-color);
|
||||||
|
@ -2012,28 +2012,28 @@ progress {
|
||||||
|
|
||||||
.table-light {
|
.table-light {
|
||||||
--bs-table-color: #fff;
|
--bs-table-color: #fff;
|
||||||
--bs-table-bg: #303030;
|
--bs-table-bg: #444;
|
||||||
--bs-table-border-color: #454545;
|
--bs-table-border-color: #575757;
|
||||||
--bs-table-striped-bg: #3a3a3a;
|
--bs-table-striped-bg: #4d4d4d;
|
||||||
--bs-table-striped-color: #fff;
|
--bs-table-striped-color: #fff;
|
||||||
--bs-table-active-bg: #454545;
|
--bs-table-active-bg: #575757;
|
||||||
--bs-table-active-color: #fff;
|
--bs-table-active-color: #fff;
|
||||||
--bs-table-hover-bg: #404040;
|
--bs-table-hover-bg: #525252;
|
||||||
--bs-table-hover-color: #fff;
|
--bs-table-hover-color: #fff;
|
||||||
color: var(--bs-table-color);
|
color: var(--bs-table-color);
|
||||||
border-color: var(--bs-table-border-color);
|
border-color: var(--bs-table-border-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-dark {
|
.table-dark {
|
||||||
--bs-table-color: #fff;
|
--bs-table-color: #000;
|
||||||
--bs-table-bg: black;
|
--bs-table-bg: #bbb;
|
||||||
--bs-table-border-color: #1a1a1a;
|
--bs-table-border-color: #a8a8a8;
|
||||||
--bs-table-striped-bg: #0d0d0d;
|
--bs-table-striped-bg: #b2b2b2;
|
||||||
--bs-table-striped-color: #fff;
|
--bs-table-striped-color: #000;
|
||||||
--bs-table-active-bg: #1a1a1a;
|
--bs-table-active-bg: #a8a8a8;
|
||||||
--bs-table-active-color: #fff;
|
--bs-table-active-color: #000;
|
||||||
--bs-table-hover-bg: #131313;
|
--bs-table-hover-bg: #adadad;
|
||||||
--bs-table-hover-color: #fff;
|
--bs-table-hover-color: #000;
|
||||||
color: var(--bs-table-color);
|
color: var(--bs-table-color);
|
||||||
border-color: var(--bs-table-border-color);
|
border-color: var(--bs-table-border-color);
|
||||||
}
|
}
|
||||||
|
@ -2933,19 +2933,19 @@ textarea.form-control-lg {
|
||||||
|
|
||||||
.btn-secondary {
|
.btn-secondary {
|
||||||
--bs-btn-color: #fff;
|
--bs-btn-color: #fff;
|
||||||
--bs-btn-bg: #222;
|
--bs-btn-bg: #303030;
|
||||||
--bs-btn-border-color: #222;
|
--bs-btn-border-color: #303030;
|
||||||
--bs-btn-hover-color: #fff;
|
--bs-btn-hover-color: #fff;
|
||||||
--bs-btn-hover-bg: #1d1d1d;
|
--bs-btn-hover-bg: #292929;
|
||||||
--bs-btn-hover-border-color: #1b1b1b;
|
--bs-btn-hover-border-color: #262626;
|
||||||
--bs-btn-focus-shadow-rgb: 67, 67, 67;
|
--bs-btn-focus-shadow-rgb: 79, 79, 79;
|
||||||
--bs-btn-active-color: #fff;
|
--bs-btn-active-color: #fff;
|
||||||
--bs-btn-active-bg: #1b1b1b;
|
--bs-btn-active-bg: #262626;
|
||||||
--bs-btn-active-border-color: #1a1a1a;
|
--bs-btn-active-border-color: #242424;
|
||||||
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
|
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
|
||||||
--bs-btn-disabled-color: #fff;
|
--bs-btn-disabled-color: #fff;
|
||||||
--bs-btn-disabled-bg: #222;
|
--bs-btn-disabled-bg: #303030;
|
||||||
--bs-btn-disabled-border-color: #222;
|
--bs-btn-disabled-border-color: #303030;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-success {
|
.btn-success {
|
||||||
|
@ -3018,36 +3018,36 @@ textarea.form-control-lg {
|
||||||
|
|
||||||
.btn-light {
|
.btn-light {
|
||||||
--bs-btn-color: #fff;
|
--bs-btn-color: #fff;
|
||||||
--bs-btn-bg: #303030;
|
--bs-btn-bg: #444;
|
||||||
--bs-btn-border-color: #303030;
|
--bs-btn-border-color: #444;
|
||||||
--bs-btn-hover-color: #fff;
|
--bs-btn-hover-color: #fff;
|
||||||
--bs-btn-hover-bg: #292929;
|
--bs-btn-hover-bg: #3a3a3a;
|
||||||
--bs-btn-hover-border-color: #262626;
|
--bs-btn-hover-border-color: #363636;
|
||||||
--bs-btn-focus-shadow-rgb: 79, 79, 79;
|
--bs-btn-focus-shadow-rgb: 96, 96, 96;
|
||||||
--bs-btn-active-color: #fff;
|
--bs-btn-active-color: #fff;
|
||||||
--bs-btn-active-bg: #262626;
|
--bs-btn-active-bg: #363636;
|
||||||
--bs-btn-active-border-color: #242424;
|
--bs-btn-active-border-color: #333333;
|
||||||
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
|
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
|
||||||
--bs-btn-disabled-color: #fff;
|
--bs-btn-disabled-color: #fff;
|
||||||
--bs-btn-disabled-bg: #303030;
|
--bs-btn-disabled-bg: #444;
|
||||||
--bs-btn-disabled-border-color: #303030;
|
--bs-btn-disabled-border-color: #444;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-dark {
|
.btn-dark {
|
||||||
--bs-btn-color: #fff;
|
--bs-btn-color: #000;
|
||||||
--bs-btn-bg: black;
|
--bs-btn-bg: #bbb;
|
||||||
--bs-btn-border-color: black;
|
--bs-btn-border-color: #bbb;
|
||||||
--bs-btn-hover-color: #fff;
|
--bs-btn-hover-color: #000;
|
||||||
--bs-btn-hover-bg: #262626;
|
--bs-btn-hover-bg: #c5c5c5;
|
||||||
--bs-btn-hover-border-color: #1a1a1a;
|
--bs-btn-hover-border-color: #c2c2c2;
|
||||||
--bs-btn-focus-shadow-rgb: 38, 38, 38;
|
--bs-btn-focus-shadow-rgb: 159, 159, 159;
|
||||||
--bs-btn-active-color: #fff;
|
--bs-btn-active-color: #000;
|
||||||
--bs-btn-active-bg: #333333;
|
--bs-btn-active-bg: #c9c9c9;
|
||||||
--bs-btn-active-border-color: #1a1a1a;
|
--bs-btn-active-border-color: #c2c2c2;
|
||||||
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
|
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
|
||||||
--bs-btn-disabled-color: #fff;
|
--bs-btn-disabled-color: #000;
|
||||||
--bs-btn-disabled-bg: black;
|
--bs-btn-disabled-bg: #bbb;
|
||||||
--bs-btn-disabled-border-color: black;
|
--bs-btn-disabled-border-color: #bbb;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-outline-primary {
|
.btn-outline-primary {
|
||||||
|
@ -3068,19 +3068,19 @@ textarea.form-control-lg {
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-outline-secondary {
|
.btn-outline-secondary {
|
||||||
--bs-btn-color: #222;
|
--bs-btn-color: #303030;
|
||||||
--bs-btn-border-color: #222;
|
--bs-btn-border-color: #303030;
|
||||||
--bs-btn-hover-color: #fff;
|
--bs-btn-hover-color: #fff;
|
||||||
--bs-btn-hover-bg: #222;
|
--bs-btn-hover-bg: #303030;
|
||||||
--bs-btn-hover-border-color: #222;
|
--bs-btn-hover-border-color: #303030;
|
||||||
--bs-btn-focus-shadow-rgb: 34, 34, 34;
|
--bs-btn-focus-shadow-rgb: 48, 48, 48;
|
||||||
--bs-btn-active-color: #fff;
|
--bs-btn-active-color: #fff;
|
||||||
--bs-btn-active-bg: #222;
|
--bs-btn-active-bg: #303030;
|
||||||
--bs-btn-active-border-color: #222;
|
--bs-btn-active-border-color: #303030;
|
||||||
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
|
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
|
||||||
--bs-btn-disabled-color: #222;
|
--bs-btn-disabled-color: #303030;
|
||||||
--bs-btn-disabled-bg: transparent;
|
--bs-btn-disabled-bg: transparent;
|
||||||
--bs-btn-disabled-border-color: #222;
|
--bs-btn-disabled-border-color: #303030;
|
||||||
--bs-gradient: none;
|
--bs-gradient: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3153,36 +3153,36 @@ textarea.form-control-lg {
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-outline-light {
|
.btn-outline-light {
|
||||||
--bs-btn-color: #303030;
|
--bs-btn-color: #444;
|
||||||
--bs-btn-border-color: #303030;
|
--bs-btn-border-color: #444;
|
||||||
--bs-btn-hover-color: #fff;
|
--bs-btn-hover-color: #fff;
|
||||||
--bs-btn-hover-bg: #303030;
|
--bs-btn-hover-bg: #444;
|
||||||
--bs-btn-hover-border-color: #303030;
|
--bs-btn-hover-border-color: #444;
|
||||||
--bs-btn-focus-shadow-rgb: 48, 48, 48;
|
--bs-btn-focus-shadow-rgb: 68, 68, 68;
|
||||||
--bs-btn-active-color: #fff;
|
--bs-btn-active-color: #fff;
|
||||||
--bs-btn-active-bg: #303030;
|
--bs-btn-active-bg: #444;
|
||||||
--bs-btn-active-border-color: #303030;
|
--bs-btn-active-border-color: #444;
|
||||||
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
|
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
|
||||||
--bs-btn-disabled-color: #303030;
|
--bs-btn-disabled-color: #444;
|
||||||
--bs-btn-disabled-bg: transparent;
|
--bs-btn-disabled-bg: transparent;
|
||||||
--bs-btn-disabled-border-color: #303030;
|
--bs-btn-disabled-border-color: #444;
|
||||||
--bs-gradient: none;
|
--bs-gradient: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-outline-dark {
|
.btn-outline-dark {
|
||||||
--bs-btn-color: black;
|
--bs-btn-color: #bbb;
|
||||||
--bs-btn-border-color: black;
|
--bs-btn-border-color: #bbb;
|
||||||
--bs-btn-hover-color: #fff;
|
--bs-btn-hover-color: #000;
|
||||||
--bs-btn-hover-bg: black;
|
--bs-btn-hover-bg: #bbb;
|
||||||
--bs-btn-hover-border-color: black;
|
--bs-btn-hover-border-color: #bbb;
|
||||||
--bs-btn-focus-shadow-rgb: 0, 0, 0;
|
--bs-btn-focus-shadow-rgb: 187, 187, 187;
|
||||||
--bs-btn-active-color: #fff;
|
--bs-btn-active-color: #000;
|
||||||
--bs-btn-active-bg: black;
|
--bs-btn-active-bg: #bbb;
|
||||||
--bs-btn-active-border-color: black;
|
--bs-btn-active-border-color: #bbb;
|
||||||
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
|
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
|
||||||
--bs-btn-disabled-color: black;
|
--bs-btn-disabled-color: #bbb;
|
||||||
--bs-btn-disabled-bg: transparent;
|
--bs-btn-disabled-bg: transparent;
|
||||||
--bs-btn-disabled-border-color: black;
|
--bs-btn-disabled-border-color: #bbb;
|
||||||
--bs-gradient: none;
|
--bs-gradient: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6490,7 +6490,7 @@ textarea.form-control-lg {
|
||||||
|
|
||||||
.text-bg-secondary {
|
.text-bg-secondary {
|
||||||
color: #fff !important;
|
color: #fff !important;
|
||||||
background-color: RGBA(34, 34, 34, var(--bs-bg-opacity, 1)) !important;
|
background-color: RGBA(48, 48, 48, var(--bs-bg-opacity, 1)) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-bg-success {
|
.text-bg-success {
|
||||||
|
@ -6515,12 +6515,12 @@ textarea.form-control-lg {
|
||||||
|
|
||||||
.text-bg-light {
|
.text-bg-light {
|
||||||
color: #fff !important;
|
color: #fff !important;
|
||||||
background-color: RGBA(48, 48, 48, var(--bs-bg-opacity, 1)) !important;
|
background-color: RGBA(68, 68, 68, var(--bs-bg-opacity, 1)) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-bg-dark {
|
.text-bg-dark {
|
||||||
color: #fff !important;
|
color: #000 !important;
|
||||||
background-color: RGBA(0, 0, 0, var(--bs-bg-opacity, 1)) !important;
|
background-color: RGBA(187, 187, 187, var(--bs-bg-opacity, 1)) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.link-primary {
|
.link-primary {
|
||||||
|
@ -6537,8 +6537,8 @@ textarea.form-control-lg {
|
||||||
text-decoration-color: RGBA(var(--bs-secondary-rgb), var(--bs-link-underline-opacity, 1)) !important;
|
text-decoration-color: RGBA(var(--bs-secondary-rgb), var(--bs-link-underline-opacity, 1)) !important;
|
||||||
}
|
}
|
||||||
.link-secondary:hover, .link-secondary:focus {
|
.link-secondary:hover, .link-secondary:focus {
|
||||||
color: RGBA(27, 27, 27, var(--bs-link-opacity, 1)) !important;
|
color: RGBA(38, 38, 38, var(--bs-link-opacity, 1)) !important;
|
||||||
text-decoration-color: RGBA(27, 27, 27, var(--bs-link-underline-opacity, 1)) !important;
|
text-decoration-color: RGBA(38, 38, 38, var(--bs-link-underline-opacity, 1)) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.link-success {
|
.link-success {
|
||||||
|
@ -6582,8 +6582,8 @@ textarea.form-control-lg {
|
||||||
text-decoration-color: RGBA(var(--bs-light-rgb), var(--bs-link-underline-opacity, 1)) !important;
|
text-decoration-color: RGBA(var(--bs-light-rgb), var(--bs-link-underline-opacity, 1)) !important;
|
||||||
}
|
}
|
||||||
.link-light:hover, .link-light:focus {
|
.link-light:hover, .link-light:focus {
|
||||||
color: RGBA(38, 38, 38, var(--bs-link-opacity, 1)) !important;
|
color: RGBA(54, 54, 54, var(--bs-link-opacity, 1)) !important;
|
||||||
text-decoration-color: RGBA(38, 38, 38, var(--bs-link-underline-opacity, 1)) !important;
|
text-decoration-color: RGBA(54, 54, 54, var(--bs-link-underline-opacity, 1)) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.link-dark {
|
.link-dark {
|
||||||
|
@ -6591,8 +6591,8 @@ textarea.form-control-lg {
|
||||||
text-decoration-color: RGBA(var(--bs-dark-rgb), var(--bs-link-underline-opacity, 1)) !important;
|
text-decoration-color: RGBA(var(--bs-dark-rgb), var(--bs-link-underline-opacity, 1)) !important;
|
||||||
}
|
}
|
||||||
.link-dark:hover, .link-dark:focus {
|
.link-dark:hover, .link-dark:focus {
|
||||||
color: RGBA(0, 0, 0, var(--bs-link-opacity, 1)) !important;
|
color: RGBA(201, 201, 201, var(--bs-link-opacity, 1)) !important;
|
||||||
text-decoration-color: RGBA(0, 0, 0, var(--bs-link-underline-opacity, 1)) !important;
|
text-decoration-color: RGBA(201, 201, 201, var(--bs-link-underline-opacity, 1)) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.link-body-emphasis {
|
.link-body-emphasis {
|
||||||
|
@ -11588,7 +11588,7 @@ textarea.form-control-lg {
|
||||||
.dropdown-item.active,
|
.dropdown-item.active,
|
||||||
.dropdown-item:hover,
|
.dropdown-item:hover,
|
||||||
option:disabled {
|
option:disabled {
|
||||||
color: #222;
|
color: #303030;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-group-text {
|
.input-group-text {
|
||||||
|
|
|
@ -36,7 +36,7 @@
|
||||||
--bs-warning: #fffb96;
|
--bs-warning: #fffb96;
|
||||||
--bs-danger: rgb(255, 95, 110);
|
--bs-danger: rgb(255, 95, 110);
|
||||||
--bs-light: #444;
|
--bs-light: #444;
|
||||||
--bs-dark: #222;
|
--bs-dark: #ebebeb;
|
||||||
--bs-primary-rgb: 255, 64, 186;
|
--bs-primary-rgb: 255, 64, 186;
|
||||||
--bs-secondary-rgb: 1, 205, 254;
|
--bs-secondary-rgb: 1, 205, 254;
|
||||||
--bs-success-rgb: 5, 255, 161;
|
--bs-success-rgb: 5, 255, 161;
|
||||||
|
@ -44,7 +44,7 @@
|
||||||
--bs-warning-rgb: 255, 251, 150;
|
--bs-warning-rgb: 255, 251, 150;
|
||||||
--bs-danger-rgb: 255, 95, 110;
|
--bs-danger-rgb: 255, 95, 110;
|
||||||
--bs-light-rgb: 68, 68, 68;
|
--bs-light-rgb: 68, 68, 68;
|
||||||
--bs-dark-rgb: 34, 34, 34;
|
--bs-dark-rgb: 235, 235, 235;
|
||||||
--bs-primary-text-emphasis: #661a4a;
|
--bs-primary-text-emphasis: #661a4a;
|
||||||
--bs-secondary-text-emphasis: #005266;
|
--bs-secondary-text-emphasis: #005266;
|
||||||
--bs-success-text-emphasis: #026640;
|
--bs-success-text-emphasis: #026640;
|
||||||
|
@ -74,8 +74,9 @@
|
||||||
--bs-font-sans-serif: "Lucida Console", Monaco, monospace;
|
--bs-font-sans-serif: "Lucida Console", Monaco, monospace;
|
||||||
--bs-font-monospace: Arial, "Noto Sans", sans-serif;
|
--bs-font-monospace: Arial, "Noto Sans", sans-serif;
|
||||||
--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
|
--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
|
||||||
|
--bs-root-font-size: 93.75%;
|
||||||
--bs-body-font-family: var(--bs-font-sans-serif);
|
--bs-body-font-family: var(--bs-font-sans-serif);
|
||||||
--bs-body-font-size: 0.875rem;
|
--bs-body-font-size: 1rem;
|
||||||
--bs-body-font-weight: 400;
|
--bs-body-font-weight: 400;
|
||||||
--bs-body-line-height: 1.5;
|
--bs-body-line-height: 1.5;
|
||||||
--bs-body-color: #ebebeb;
|
--bs-body-color: #ebebeb;
|
||||||
|
@ -184,6 +185,9 @@
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
font-size: var(--bs-root-font-size);
|
||||||
|
}
|
||||||
@media (prefers-reduced-motion: no-preference) {
|
@media (prefers-reduced-motion: no-preference) {
|
||||||
:root {
|
:root {
|
||||||
scroll-behavior: smooth;
|
scroll-behavior: smooth;
|
||||||
|
@ -220,47 +224,47 @@ h6, .h6, h5, .h5, h4, .h4, h3, .h3, h2, .h2, h1, .h1 {
|
||||||
}
|
}
|
||||||
|
|
||||||
h1, .h1 {
|
h1, .h1 {
|
||||||
font-size: calc(1.34375rem + 1.125vw);
|
font-size: calc(1.375rem + 1.5vw);
|
||||||
}
|
}
|
||||||
@media (min-width: 1200px) {
|
@media (min-width: 1200px) {
|
||||||
h1, .h1 {
|
h1, .h1 {
|
||||||
font-size: 2.1875rem;
|
font-size: 2.5rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
h2, .h2 {
|
h2, .h2 {
|
||||||
font-size: calc(1.3rem + 0.6vw);
|
font-size: calc(1.325rem + 0.9vw);
|
||||||
}
|
}
|
||||||
@media (min-width: 1200px) {
|
@media (min-width: 1200px) {
|
||||||
h2, .h2 {
|
h2, .h2 {
|
||||||
font-size: 1.75rem;
|
font-size: 2rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
h3, .h3 {
|
h3, .h3 {
|
||||||
font-size: calc(1.278125rem + 0.3375vw);
|
font-size: calc(1.3rem + 0.6vw);
|
||||||
}
|
}
|
||||||
@media (min-width: 1200px) {
|
@media (min-width: 1200px) {
|
||||||
h3, .h3 {
|
h3, .h3 {
|
||||||
font-size: 1.53125rem;
|
font-size: 1.75rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
h4, .h4 {
|
h4, .h4 {
|
||||||
font-size: calc(1.25625rem + 0.075vw);
|
font-size: calc(1.275rem + 0.3vw);
|
||||||
}
|
}
|
||||||
@media (min-width: 1200px) {
|
@media (min-width: 1200px) {
|
||||||
h4, .h4 {
|
h4, .h4 {
|
||||||
font-size: 1.3125rem;
|
font-size: 1.5rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
h5, .h5 {
|
h5, .h5 {
|
||||||
font-size: 1.09375rem;
|
font-size: 1.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
h6, .h6 {
|
h6, .h6 {
|
||||||
font-size: 0.875rem;
|
font-size: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
|
@ -586,7 +590,7 @@ progress {
|
||||||
}
|
}
|
||||||
|
|
||||||
.lead {
|
.lead {
|
||||||
font-size: 1.09375rem;
|
font-size: 1.25rem;
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -680,7 +684,7 @@ progress {
|
||||||
|
|
||||||
.blockquote {
|
.blockquote {
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
font-size: 1.09375rem;
|
font-size: 1.25rem;
|
||||||
}
|
}
|
||||||
.blockquote > :last-child {
|
.blockquote > :last-child {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
|
@ -2025,15 +2029,15 @@ progress {
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-dark {
|
.table-dark {
|
||||||
--bs-table-color: #fff;
|
--bs-table-color: #000;
|
||||||
--bs-table-bg: #222;
|
--bs-table-bg: #ebebeb;
|
||||||
--bs-table-border-color: #383838;
|
--bs-table-border-color: #d4d4d4;
|
||||||
--bs-table-striped-bg: #2d2d2d;
|
--bs-table-striped-bg: #dfdfdf;
|
||||||
--bs-table-striped-color: #fff;
|
--bs-table-striped-color: #000;
|
||||||
--bs-table-active-bg: #383838;
|
--bs-table-active-bg: #d4d4d4;
|
||||||
--bs-table-active-color: #fff;
|
--bs-table-active-color: #000;
|
||||||
--bs-table-hover-bg: #333333;
|
--bs-table-hover-bg: #d9d9d9;
|
||||||
--bs-table-hover-color: #fff;
|
--bs-table-hover-color: #000;
|
||||||
color: var(--bs-table-color);
|
color: var(--bs-table-color);
|
||||||
border-color: var(--bs-table-border-color);
|
border-color: var(--bs-table-border-color);
|
||||||
}
|
}
|
||||||
|
@ -2088,13 +2092,13 @@ progress {
|
||||||
.col-form-label-lg {
|
.col-form-label-lg {
|
||||||
padding-top: calc(0.5rem + var(--bs-border-width));
|
padding-top: calc(0.5rem + var(--bs-border-width));
|
||||||
padding-bottom: calc(0.5rem + var(--bs-border-width));
|
padding-bottom: calc(0.5rem + var(--bs-border-width));
|
||||||
font-size: 1.09375rem;
|
font-size: 1.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.col-form-label-sm {
|
.col-form-label-sm {
|
||||||
padding-top: calc(0.25rem + var(--bs-border-width));
|
padding-top: calc(0.25rem + var(--bs-border-width));
|
||||||
padding-bottom: calc(0.25rem + var(--bs-border-width));
|
padding-bottom: calc(0.25rem + var(--bs-border-width));
|
||||||
font-size: 0.765625rem;
|
font-size: 0.875rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-text {
|
.form-text {
|
||||||
|
@ -2107,7 +2111,7 @@ progress {
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 0.375rem 0.75rem;
|
padding: 0.375rem 0.75rem;
|
||||||
font-size: 0.875rem;
|
font-size: 1rem;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
|
@ -2200,7 +2204,7 @@ progress {
|
||||||
.form-control-sm {
|
.form-control-sm {
|
||||||
min-height: calc(1.5em + 0.5rem + calc(var(--bs-border-width) * 2));
|
min-height: calc(1.5em + 0.5rem + calc(var(--bs-border-width) * 2));
|
||||||
padding: 0.25rem 0.5rem;
|
padding: 0.25rem 0.5rem;
|
||||||
font-size: 0.765625rem;
|
font-size: 0.875rem;
|
||||||
border-radius: var(--bs-border-radius-sm);
|
border-radius: var(--bs-border-radius-sm);
|
||||||
}
|
}
|
||||||
.form-control-sm::file-selector-button {
|
.form-control-sm::file-selector-button {
|
||||||
|
@ -2212,7 +2216,7 @@ progress {
|
||||||
.form-control-lg {
|
.form-control-lg {
|
||||||
min-height: calc(1.5em + 1rem + calc(var(--bs-border-width) * 2));
|
min-height: calc(1.5em + 1rem + calc(var(--bs-border-width) * 2));
|
||||||
padding: 0.5rem 1rem;
|
padding: 0.5rem 1rem;
|
||||||
font-size: 1.09375rem;
|
font-size: 1.25rem;
|
||||||
border-radius: var(--bs-border-radius-lg);
|
border-radius: var(--bs-border-radius-lg);
|
||||||
}
|
}
|
||||||
.form-control-lg::file-selector-button {
|
.form-control-lg::file-selector-button {
|
||||||
|
@ -2259,7 +2263,7 @@ textarea.form-control-lg {
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 0.375rem 2.25rem 0.375rem 0.75rem;
|
padding: 0.375rem 2.25rem 0.375rem 0.75rem;
|
||||||
font-size: 0.875rem;
|
font-size: 1rem;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
|
@ -2300,7 +2304,7 @@ textarea.form-control-lg {
|
||||||
padding-top: 0.25rem;
|
padding-top: 0.25rem;
|
||||||
padding-bottom: 0.25rem;
|
padding-bottom: 0.25rem;
|
||||||
padding-left: 0.5rem;
|
padding-left: 0.5rem;
|
||||||
font-size: 0.765625rem;
|
font-size: 0.875rem;
|
||||||
border-radius: var(--bs-border-radius-sm);
|
border-radius: var(--bs-border-radius-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2308,7 +2312,7 @@ textarea.form-control-lg {
|
||||||
padding-top: 0.5rem;
|
padding-top: 0.5rem;
|
||||||
padding-bottom: 0.5rem;
|
padding-bottom: 0.5rem;
|
||||||
padding-left: 1rem;
|
padding-left: 1rem;
|
||||||
font-size: 1.09375rem;
|
font-size: 1.25rem;
|
||||||
border-radius: var(--bs-border-radius-lg);
|
border-radius: var(--bs-border-radius-lg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2318,7 +2322,7 @@ textarea.form-control-lg {
|
||||||
|
|
||||||
.form-check {
|
.form-check {
|
||||||
display: block;
|
display: block;
|
||||||
min-height: 1.3125rem;
|
min-height: 1.5rem;
|
||||||
padding-left: 1.5em;
|
padding-left: 1.5em;
|
||||||
margin-bottom: 0.125rem;
|
margin-bottom: 0.125rem;
|
||||||
}
|
}
|
||||||
|
@ -2654,7 +2658,7 @@ textarea.form-control-lg {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 0.375rem 0.75rem;
|
padding: 0.375rem 0.75rem;
|
||||||
font-size: 0.875rem;
|
font-size: 1rem;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
|
@ -2670,7 +2674,7 @@ textarea.form-control-lg {
|
||||||
.input-group-lg > .input-group-text,
|
.input-group-lg > .input-group-text,
|
||||||
.input-group-lg > .btn {
|
.input-group-lg > .btn {
|
||||||
padding: 0.5rem 1rem;
|
padding: 0.5rem 1rem;
|
||||||
font-size: 1.09375rem;
|
font-size: 1.25rem;
|
||||||
border-radius: var(--bs-border-radius-lg);
|
border-radius: var(--bs-border-radius-lg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2679,7 +2683,7 @@ textarea.form-control-lg {
|
||||||
.input-group-sm > .input-group-text,
|
.input-group-sm > .input-group-text,
|
||||||
.input-group-sm > .btn {
|
.input-group-sm > .btn {
|
||||||
padding: 0.25rem 0.5rem;
|
padding: 0.25rem 0.5rem;
|
||||||
font-size: 0.765625rem;
|
font-size: 0.875rem;
|
||||||
border-radius: var(--bs-border-radius-sm);
|
border-radius: var(--bs-border-radius-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2729,7 +2733,7 @@ textarea.form-control-lg {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
padding: 0.25rem 0.5rem;
|
padding: 0.25rem 0.5rem;
|
||||||
margin-top: 0.1rem;
|
margin-top: 0.1rem;
|
||||||
font-size: 0.765625rem;
|
font-size: 0.875rem;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
background-color: var(--bs-success);
|
background-color: var(--bs-success);
|
||||||
border-radius: var(--bs-border-radius);
|
border-radius: var(--bs-border-radius);
|
||||||
|
@ -2819,7 +2823,7 @@ textarea.form-control-lg {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
padding: 0.25rem 0.5rem;
|
padding: 0.25rem 0.5rem;
|
||||||
margin-top: 0.1rem;
|
margin-top: 0.1rem;
|
||||||
font-size: 0.765625rem;
|
font-size: 0.875rem;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
background-color: var(--bs-danger);
|
background-color: var(--bs-danger);
|
||||||
border-radius: var(--bs-border-radius);
|
border-radius: var(--bs-border-radius);
|
||||||
|
@ -2897,7 +2901,7 @@ textarea.form-control-lg {
|
||||||
--bs-btn-padding-x: 0.75rem;
|
--bs-btn-padding-x: 0.75rem;
|
||||||
--bs-btn-padding-y: 0.375rem;
|
--bs-btn-padding-y: 0.375rem;
|
||||||
--bs-btn-font-family: ;
|
--bs-btn-font-family: ;
|
||||||
--bs-btn-font-size: 0.875rem;
|
--bs-btn-font-size: 1rem;
|
||||||
--bs-btn-font-weight: 400;
|
--bs-btn-font-weight: 400;
|
||||||
--bs-btn-line-height: 1.5;
|
--bs-btn-line-height: 1.5;
|
||||||
--bs-btn-color: var(--bs-body-color);
|
--bs-btn-color: var(--bs-body-color);
|
||||||
|
@ -3095,20 +3099,20 @@ textarea.form-control-lg {
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-dark {
|
.btn-dark {
|
||||||
--bs-btn-color: #fff;
|
--bs-btn-color: #000;
|
||||||
--bs-btn-bg: #222;
|
--bs-btn-bg: #ebebeb;
|
||||||
--bs-btn-border-color: #222;
|
--bs-btn-border-color: #ebebeb;
|
||||||
--bs-btn-hover-color: #fff;
|
--bs-btn-hover-color: #000;
|
||||||
--bs-btn-hover-bg: #434343;
|
--bs-btn-hover-bg: #eeeeee;
|
||||||
--bs-btn-hover-border-color: #383838;
|
--bs-btn-hover-border-color: #ededed;
|
||||||
--bs-btn-focus-shadow-rgb: 67, 67, 67;
|
--bs-btn-focus-shadow-rgb: 200, 200, 200;
|
||||||
--bs-btn-active-color: #fff;
|
--bs-btn-active-color: #000;
|
||||||
--bs-btn-active-bg: #4e4e4e;
|
--bs-btn-active-bg: #efefef;
|
||||||
--bs-btn-active-border-color: #383838;
|
--bs-btn-active-border-color: #ededed;
|
||||||
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
|
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
|
||||||
--bs-btn-disabled-color: #fff;
|
--bs-btn-disabled-color: #000;
|
||||||
--bs-btn-disabled-bg: #222;
|
--bs-btn-disabled-bg: #ebebeb;
|
||||||
--bs-btn-disabled-border-color: #222;
|
--bs-btn-disabled-border-color: #ebebeb;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-outline-primary {
|
.btn-outline-primary {
|
||||||
|
@ -3231,19 +3235,19 @@ textarea.form-control-lg {
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-outline-dark {
|
.btn-outline-dark {
|
||||||
--bs-btn-color: #222;
|
--bs-btn-color: #ebebeb;
|
||||||
--bs-btn-border-color: #222;
|
--bs-btn-border-color: #ebebeb;
|
||||||
--bs-btn-hover-color: #fff;
|
--bs-btn-hover-color: #000;
|
||||||
--bs-btn-hover-bg: #222;
|
--bs-btn-hover-bg: #ebebeb;
|
||||||
--bs-btn-hover-border-color: #222;
|
--bs-btn-hover-border-color: #ebebeb;
|
||||||
--bs-btn-focus-shadow-rgb: 34, 34, 34;
|
--bs-btn-focus-shadow-rgb: 235, 235, 235;
|
||||||
--bs-btn-active-color: #fff;
|
--bs-btn-active-color: #000;
|
||||||
--bs-btn-active-bg: #222;
|
--bs-btn-active-bg: #ebebeb;
|
||||||
--bs-btn-active-border-color: #222;
|
--bs-btn-active-border-color: #ebebeb;
|
||||||
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
|
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
|
||||||
--bs-btn-disabled-color: #222;
|
--bs-btn-disabled-color: #ebebeb;
|
||||||
--bs-btn-disabled-bg: transparent;
|
--bs-btn-disabled-bg: transparent;
|
||||||
--bs-btn-disabled-border-color: #222;
|
--bs-btn-disabled-border-color: #ebebeb;
|
||||||
--bs-gradient: none;
|
--bs-gradient: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3273,14 +3277,14 @@ textarea.form-control-lg {
|
||||||
.btn-lg, .btn-group-lg > .btn {
|
.btn-lg, .btn-group-lg > .btn {
|
||||||
--bs-btn-padding-y: 0.5rem;
|
--bs-btn-padding-y: 0.5rem;
|
||||||
--bs-btn-padding-x: 1rem;
|
--bs-btn-padding-x: 1rem;
|
||||||
--bs-btn-font-size: 1.09375rem;
|
--bs-btn-font-size: 1.25rem;
|
||||||
--bs-btn-border-radius: var(--bs-border-radius-lg);
|
--bs-btn-border-radius: var(--bs-border-radius-lg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-sm, .btn-group-sm > .btn {
|
.btn-sm, .btn-group-sm > .btn {
|
||||||
--bs-btn-padding-y: 0.25rem;
|
--bs-btn-padding-y: 0.25rem;
|
||||||
--bs-btn-padding-x: 0.5rem;
|
--bs-btn-padding-x: 0.5rem;
|
||||||
--bs-btn-font-size: 0.765625rem;
|
--bs-btn-font-size: 0.875rem;
|
||||||
--bs-btn-border-radius: var(--bs-border-radius-sm);
|
--bs-btn-border-radius: var(--bs-border-radius-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3353,7 +3357,7 @@ textarea.form-control-lg {
|
||||||
--bs-dropdown-padding-x: 0;
|
--bs-dropdown-padding-x: 0;
|
||||||
--bs-dropdown-padding-y: 0.5rem;
|
--bs-dropdown-padding-y: 0.5rem;
|
||||||
--bs-dropdown-spacer: 0.125rem;
|
--bs-dropdown-spacer: 0.125rem;
|
||||||
--bs-dropdown-font-size: 0.875rem;
|
--bs-dropdown-font-size: 1rem;
|
||||||
--bs-dropdown-color: var(--bs-body-color);
|
--bs-dropdown-color: var(--bs-body-color);
|
||||||
--bs-dropdown-bg: var(--bs-body-bg);
|
--bs-dropdown-bg: var(--bs-body-bg);
|
||||||
--bs-dropdown-border-color: var(--bs-border-color-translucent);
|
--bs-dropdown-border-color: var(--bs-border-color-translucent);
|
||||||
|
@ -3615,7 +3619,7 @@ textarea.form-control-lg {
|
||||||
display: block;
|
display: block;
|
||||||
padding: var(--bs-dropdown-header-padding-y) var(--bs-dropdown-header-padding-x);
|
padding: var(--bs-dropdown-header-padding-y) var(--bs-dropdown-header-padding-x);
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
font-size: 0.765625rem;
|
font-size: 0.875rem;
|
||||||
color: var(--bs-dropdown-header-color);
|
color: var(--bs-dropdown-header-color);
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
@ -3900,15 +3904,15 @@ textarea.form-control-lg {
|
||||||
--bs-navbar-hover-color: rgba(255, 64, 186, 0.7);
|
--bs-navbar-hover-color: rgba(255, 64, 186, 0.7);
|
||||||
--bs-navbar-disabled-color: rgba(235, 235, 235, 0.3);
|
--bs-navbar-disabled-color: rgba(235, 235, 235, 0.3);
|
||||||
--bs-navbar-active-color: rgba(235, 235, 235, 0.9);
|
--bs-navbar-active-color: rgba(235, 235, 235, 0.9);
|
||||||
--bs-navbar-brand-padding-y: 0.3359375rem;
|
--bs-navbar-brand-padding-y: 0.3125rem;
|
||||||
--bs-navbar-brand-margin-end: 1rem;
|
--bs-navbar-brand-margin-end: 1rem;
|
||||||
--bs-navbar-brand-font-size: 1.09375rem;
|
--bs-navbar-brand-font-size: 1.25rem;
|
||||||
--bs-navbar-brand-color: rgba(235, 235, 235, 0.9);
|
--bs-navbar-brand-color: rgba(235, 235, 235, 0.9);
|
||||||
--bs-navbar-brand-hover-color: rgba(235, 235, 235, 0.9);
|
--bs-navbar-brand-hover-color: rgba(235, 235, 235, 0.9);
|
||||||
--bs-navbar-nav-link-padding-x: 0.5rem;
|
--bs-navbar-nav-link-padding-x: 0.5rem;
|
||||||
--bs-navbar-toggler-padding-y: 0.25rem;
|
--bs-navbar-toggler-padding-y: 0.25rem;
|
||||||
--bs-navbar-toggler-padding-x: 0.75rem;
|
--bs-navbar-toggler-padding-x: 0.75rem;
|
||||||
--bs-navbar-toggler-font-size: 1.09375rem;
|
--bs-navbar-toggler-font-size: 1.25rem;
|
||||||
--bs-navbar-toggler-icon-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28235, 235, 235, 0.75%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e");
|
--bs-navbar-toggler-icon-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28235, 235, 235, 0.75%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e");
|
||||||
--bs-navbar-toggler-border-color: rgba(var(--bs-emphasis-color-rgb), 0.15);
|
--bs-navbar-toggler-border-color: rgba(var(--bs-emphasis-color-rgb), 0.15);
|
||||||
--bs-navbar-toggler-border-radius: var(--bs-border-radius);
|
--bs-navbar-toggler-border-radius: var(--bs-border-radius);
|
||||||
|
@ -4545,7 +4549,7 @@ textarea.form-control-lg {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: var(--bs-accordion-btn-padding-y) var(--bs-accordion-btn-padding-x);
|
padding: var(--bs-accordion-btn-padding-y) var(--bs-accordion-btn-padding-x);
|
||||||
font-size: 0.875rem;
|
font-size: 1rem;
|
||||||
color: var(--bs-accordion-btn-color);
|
color: var(--bs-accordion-btn-color);
|
||||||
text-align: left;
|
text-align: left;
|
||||||
background-color: var(--bs-accordion-btn-bg);
|
background-color: var(--bs-accordion-btn-bg);
|
||||||
|
@ -4689,7 +4693,7 @@ textarea.form-control-lg {
|
||||||
.pagination {
|
.pagination {
|
||||||
--bs-pagination-padding-x: 0.75rem;
|
--bs-pagination-padding-x: 0.75rem;
|
||||||
--bs-pagination-padding-y: 0.375rem;
|
--bs-pagination-padding-y: 0.375rem;
|
||||||
--bs-pagination-font-size: 0.875rem;
|
--bs-pagination-font-size: 1rem;
|
||||||
--bs-pagination-color: var(--bs-link-color);
|
--bs-pagination-color: var(--bs-link-color);
|
||||||
--bs-pagination-bg: var(--bs-body-bg);
|
--bs-pagination-bg: var(--bs-body-bg);
|
||||||
--bs-pagination-border-width: var(--bs-border-width);
|
--bs-pagination-border-width: var(--bs-border-width);
|
||||||
|
@ -4769,14 +4773,14 @@ textarea.form-control-lg {
|
||||||
.pagination-lg {
|
.pagination-lg {
|
||||||
--bs-pagination-padding-x: 1.5rem;
|
--bs-pagination-padding-x: 1.5rem;
|
||||||
--bs-pagination-padding-y: 0.75rem;
|
--bs-pagination-padding-y: 0.75rem;
|
||||||
--bs-pagination-font-size: 1.09375rem;
|
--bs-pagination-font-size: 1.25rem;
|
||||||
--bs-pagination-border-radius: var(--bs-border-radius-lg);
|
--bs-pagination-border-radius: var(--bs-border-radius-lg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.pagination-sm {
|
.pagination-sm {
|
||||||
--bs-pagination-padding-x: 0.5rem;
|
--bs-pagination-padding-x: 0.5rem;
|
||||||
--bs-pagination-padding-y: 0.25rem;
|
--bs-pagination-padding-y: 0.25rem;
|
||||||
--bs-pagination-font-size: 0.765625rem;
|
--bs-pagination-font-size: 0.875rem;
|
||||||
--bs-pagination-border-radius: var(--bs-border-radius-sm);
|
--bs-pagination-border-radius: var(--bs-border-radius-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4911,7 +4915,7 @@ textarea.form-control-lg {
|
||||||
.progress,
|
.progress,
|
||||||
.progress-stacked {
|
.progress-stacked {
|
||||||
--bs-progress-height: 1rem;
|
--bs-progress-height: 1rem;
|
||||||
--bs-progress-font-size: 0.65625rem;
|
--bs-progress-font-size: 0.75rem;
|
||||||
--bs-progress-bg: var(--bs-secondary-bg);
|
--bs-progress-bg: var(--bs-secondary-bg);
|
||||||
--bs-progress-border-radius: var(--bs-border-radius);
|
--bs-progress-border-radius: var(--bs-border-radius);
|
||||||
--bs-progress-box-shadow: var(--bs-box-shadow-inset);
|
--bs-progress-box-shadow: var(--bs-box-shadow-inset);
|
||||||
|
@ -5717,7 +5721,7 @@ textarea.form-control-lg {
|
||||||
--bs-tooltip-padding-x: 0.5rem;
|
--bs-tooltip-padding-x: 0.5rem;
|
||||||
--bs-tooltip-padding-y: 0.25rem;
|
--bs-tooltip-padding-y: 0.25rem;
|
||||||
--bs-tooltip-margin: ;
|
--bs-tooltip-margin: ;
|
||||||
--bs-tooltip-font-size: 0.765625rem;
|
--bs-tooltip-font-size: 0.875rem;
|
||||||
--bs-tooltip-color: var(--bs-body-bg);
|
--bs-tooltip-color: var(--bs-body-bg);
|
||||||
--bs-tooltip-bg: var(--bs-emphasis-color);
|
--bs-tooltip-bg: var(--bs-emphasis-color);
|
||||||
--bs-tooltip-border-radius: var(--bs-border-radius);
|
--bs-tooltip-border-radius: var(--bs-border-radius);
|
||||||
|
@ -5816,7 +5820,7 @@ textarea.form-control-lg {
|
||||||
.popover {
|
.popover {
|
||||||
--bs-popover-zindex: 1070;
|
--bs-popover-zindex: 1070;
|
||||||
--bs-popover-max-width: 276px;
|
--bs-popover-max-width: 276px;
|
||||||
--bs-popover-font-size: 0.765625rem;
|
--bs-popover-font-size: 0.875rem;
|
||||||
--bs-popover-bg: var(--bs-body-bg);
|
--bs-popover-bg: var(--bs-body-bg);
|
||||||
--bs-popover-border-width: var(--bs-border-width);
|
--bs-popover-border-width: var(--bs-border-width);
|
||||||
--bs-popover-border-color: var(--bs-border-color-translucent);
|
--bs-popover-border-color: var(--bs-border-color-translucent);
|
||||||
|
@ -5825,7 +5829,7 @@ textarea.form-control-lg {
|
||||||
--bs-popover-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
|
--bs-popover-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
|
||||||
--bs-popover-header-padding-x: 1rem;
|
--bs-popover-header-padding-x: 1rem;
|
||||||
--bs-popover-header-padding-y: 0.5rem;
|
--bs-popover-header-padding-y: 0.5rem;
|
||||||
--bs-popover-header-font-size: 0.875rem;
|
--bs-popover-header-font-size: 1rem;
|
||||||
--bs-popover-header-color: inherit;
|
--bs-popover-header-color: inherit;
|
||||||
--bs-popover-header-bg: var(--bs-secondary-bg);
|
--bs-popover-header-bg: var(--bs-secondary-bg);
|
||||||
--bs-popover-body-padding-x: 1rem;
|
--bs-popover-body-padding-x: 1rem;
|
||||||
|
@ -6844,8 +6848,8 @@ textarea.form-control-lg {
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-bg-dark {
|
.text-bg-dark {
|
||||||
color: #fff !important;
|
color: #000 !important;
|
||||||
background-color: RGBA(34, 34, 34, var(--bs-bg-opacity, 1)) !important;
|
background-color: RGBA(235, 235, 235, var(--bs-bg-opacity, 1)) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.link-primary {
|
.link-primary {
|
||||||
|
@ -6916,8 +6920,8 @@ textarea.form-control-lg {
|
||||||
text-decoration-color: RGBA(var(--bs-dark-rgb), var(--bs-link-underline-opacity, 1)) !important;
|
text-decoration-color: RGBA(var(--bs-dark-rgb), var(--bs-link-underline-opacity, 1)) !important;
|
||||||
}
|
}
|
||||||
.link-dark:hover, .link-dark:focus {
|
.link-dark:hover, .link-dark:focus {
|
||||||
color: RGBA(27, 27, 27, var(--bs-link-opacity, 1)) !important;
|
color: RGBA(239, 239, 239, var(--bs-link-opacity, 1)) !important;
|
||||||
text-decoration-color: RGBA(27, 27, 27, var(--bs-link-underline-opacity, 1)) !important;
|
text-decoration-color: RGBA(239, 239, 239, var(--bs-link-underline-opacity, 1)) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.link-body-emphasis {
|
.link-body-emphasis {
|
||||||
|
@ -8296,27 +8300,27 @@ textarea.form-control-lg {
|
||||||
}
|
}
|
||||||
|
|
||||||
.fs-1 {
|
.fs-1 {
|
||||||
font-size: calc(1.34375rem + 1.125vw) !important;
|
font-size: calc(1.375rem + 1.5vw) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fs-2 {
|
.fs-2 {
|
||||||
font-size: calc(1.3rem + 0.6vw) !important;
|
font-size: calc(1.325rem + 0.9vw) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fs-3 {
|
.fs-3 {
|
||||||
font-size: calc(1.278125rem + 0.3375vw) !important;
|
font-size: calc(1.3rem + 0.6vw) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fs-4 {
|
.fs-4 {
|
||||||
font-size: calc(1.25625rem + 0.075vw) !important;
|
font-size: calc(1.275rem + 0.3vw) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fs-5 {
|
.fs-5 {
|
||||||
font-size: 1.09375rem !important;
|
font-size: 1.25rem !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fs-6 {
|
.fs-6 {
|
||||||
font-size: 0.875rem !important;
|
font-size: 1rem !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fst-italic {
|
.fst-italic {
|
||||||
|
@ -11859,16 +11863,16 @@ textarea.form-control-lg {
|
||||||
}
|
}
|
||||||
@media (min-width: 1200px) {
|
@media (min-width: 1200px) {
|
||||||
.fs-1 {
|
.fs-1 {
|
||||||
font-size: 2.1875rem !important;
|
font-size: 2.5rem !important;
|
||||||
}
|
}
|
||||||
.fs-2 {
|
.fs-2 {
|
||||||
font-size: 1.75rem !important;
|
font-size: 2rem !important;
|
||||||
}
|
}
|
||||||
.fs-3 {
|
.fs-3 {
|
||||||
font-size: 1.53125rem !important;
|
font-size: 1.75rem !important;
|
||||||
}
|
}
|
||||||
.fs-4 {
|
.fs-4 {
|
||||||
font-size: 1.3125rem !important;
|
font-size: 1.5rem !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@media print {
|
@media print {
|
||||||
|
|
|
@ -23,8 +23,11 @@ option:disabled {
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-control::placeholder {
|
.form-control::placeholder {
|
||||||
text-shadow: 0.5px 0.5px 0 $secondary, 0.5px -0.5px 0 $secondary,
|
text-shadow:
|
||||||
-0.5px 0.5px 0 $secondary, -0.5px -0.5px 0 $secondary;
|
0.5px 0.5px 0 $secondary,
|
||||||
|
0.5px -0.5px 0 $secondary,
|
||||||
|
-0.5px 0.5px 0 $secondary,
|
||||||
|
-0.5px -0.5px 0 $secondary;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-group-text {
|
.input-group-text {
|
||||||
|
|
|
@ -74,8 +74,9 @@
|
||||||
--bs-font-sans-serif: "Lucida Console", Monaco, monospace;
|
--bs-font-sans-serif: "Lucida Console", Monaco, monospace;
|
||||||
--bs-font-monospace: Arial, "Noto Sans", sans-serif;
|
--bs-font-monospace: Arial, "Noto Sans", sans-serif;
|
||||||
--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
|
--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
|
||||||
|
--bs-root-font-size: 93.75%;
|
||||||
--bs-body-font-family: var(--bs-font-sans-serif);
|
--bs-body-font-family: var(--bs-font-sans-serif);
|
||||||
--bs-body-font-size: 0.875rem;
|
--bs-body-font-size: 1rem;
|
||||||
--bs-body-font-weight: 400;
|
--bs-body-font-weight: 400;
|
||||||
--bs-body-line-height: 1.5;
|
--bs-body-line-height: 1.5;
|
||||||
--bs-body-color: #495057;
|
--bs-body-color: #495057;
|
||||||
|
@ -184,6 +185,9 @@
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
font-size: var(--bs-root-font-size);
|
||||||
|
}
|
||||||
@media (prefers-reduced-motion: no-preference) {
|
@media (prefers-reduced-motion: no-preference) {
|
||||||
:root {
|
:root {
|
||||||
scroll-behavior: smooth;
|
scroll-behavior: smooth;
|
||||||
|
@ -220,47 +224,47 @@ h6, .h6, h5, .h5, h4, .h4, h3, .h3, h2, .h2, h1, .h1 {
|
||||||
}
|
}
|
||||||
|
|
||||||
h1, .h1 {
|
h1, .h1 {
|
||||||
font-size: calc(1.34375rem + 1.125vw);
|
font-size: calc(1.375rem + 1.5vw);
|
||||||
}
|
}
|
||||||
@media (min-width: 1200px) {
|
@media (min-width: 1200px) {
|
||||||
h1, .h1 {
|
h1, .h1 {
|
||||||
font-size: 2.1875rem;
|
font-size: 2.5rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
h2, .h2 {
|
h2, .h2 {
|
||||||
font-size: calc(1.3rem + 0.6vw);
|
font-size: calc(1.325rem + 0.9vw);
|
||||||
}
|
}
|
||||||
@media (min-width: 1200px) {
|
@media (min-width: 1200px) {
|
||||||
h2, .h2 {
|
h2, .h2 {
|
||||||
font-size: 1.75rem;
|
font-size: 2rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
h3, .h3 {
|
h3, .h3 {
|
||||||
font-size: calc(1.278125rem + 0.3375vw);
|
font-size: calc(1.3rem + 0.6vw);
|
||||||
}
|
}
|
||||||
@media (min-width: 1200px) {
|
@media (min-width: 1200px) {
|
||||||
h3, .h3 {
|
h3, .h3 {
|
||||||
font-size: 1.53125rem;
|
font-size: 1.75rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
h4, .h4 {
|
h4, .h4 {
|
||||||
font-size: calc(1.25625rem + 0.075vw);
|
font-size: calc(1.275rem + 0.3vw);
|
||||||
}
|
}
|
||||||
@media (min-width: 1200px) {
|
@media (min-width: 1200px) {
|
||||||
h4, .h4 {
|
h4, .h4 {
|
||||||
font-size: 1.3125rem;
|
font-size: 1.5rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
h5, .h5 {
|
h5, .h5 {
|
||||||
font-size: 1.09375rem;
|
font-size: 1.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
h6, .h6 {
|
h6, .h6 {
|
||||||
font-size: 0.875rem;
|
font-size: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
|
@ -585,7 +589,7 @@ progress {
|
||||||
}
|
}
|
||||||
|
|
||||||
.lead {
|
.lead {
|
||||||
font-size: 1.09375rem;
|
font-size: 1.25rem;
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -679,7 +683,7 @@ progress {
|
||||||
|
|
||||||
.blockquote {
|
.blockquote {
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
font-size: 1.09375rem;
|
font-size: 1.25rem;
|
||||||
}
|
}
|
||||||
.blockquote > :last-child {
|
.blockquote > :last-child {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
|
@ -2087,13 +2091,13 @@ progress {
|
||||||
.col-form-label-lg {
|
.col-form-label-lg {
|
||||||
padding-top: calc(0.5rem + var(--bs-border-width));
|
padding-top: calc(0.5rem + var(--bs-border-width));
|
||||||
padding-bottom: calc(0.5rem + var(--bs-border-width));
|
padding-bottom: calc(0.5rem + var(--bs-border-width));
|
||||||
font-size: 1.09375rem;
|
font-size: 1.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.col-form-label-sm {
|
.col-form-label-sm {
|
||||||
padding-top: calc(0.25rem + var(--bs-border-width));
|
padding-top: calc(0.25rem + var(--bs-border-width));
|
||||||
padding-bottom: calc(0.25rem + var(--bs-border-width));
|
padding-bottom: calc(0.25rem + var(--bs-border-width));
|
||||||
font-size: 0.765625rem;
|
font-size: 0.875rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-text {
|
.form-text {
|
||||||
|
@ -2106,7 +2110,7 @@ progress {
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 0.375rem 0.75rem;
|
padding: 0.375rem 0.75rem;
|
||||||
font-size: 0.875rem;
|
font-size: 1rem;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
color: var(--bs-body-color);
|
color: var(--bs-body-color);
|
||||||
|
@ -2199,7 +2203,7 @@ progress {
|
||||||
.form-control-sm {
|
.form-control-sm {
|
||||||
min-height: calc(1.5em + 0.5rem + calc(var(--bs-border-width) * 2));
|
min-height: calc(1.5em + 0.5rem + calc(var(--bs-border-width) * 2));
|
||||||
padding: 0.25rem 0.5rem;
|
padding: 0.25rem 0.5rem;
|
||||||
font-size: 0.765625rem;
|
font-size: 0.875rem;
|
||||||
border-radius: var(--bs-border-radius-sm);
|
border-radius: var(--bs-border-radius-sm);
|
||||||
}
|
}
|
||||||
.form-control-sm::file-selector-button {
|
.form-control-sm::file-selector-button {
|
||||||
|
@ -2211,7 +2215,7 @@ progress {
|
||||||
.form-control-lg {
|
.form-control-lg {
|
||||||
min-height: calc(1.5em + 1rem + calc(var(--bs-border-width) * 2));
|
min-height: calc(1.5em + 1rem + calc(var(--bs-border-width) * 2));
|
||||||
padding: 0.5rem 1rem;
|
padding: 0.5rem 1rem;
|
||||||
font-size: 1.09375rem;
|
font-size: 1.25rem;
|
||||||
border-radius: var(--bs-border-radius-lg);
|
border-radius: var(--bs-border-radius-lg);
|
||||||
}
|
}
|
||||||
.form-control-lg::file-selector-button {
|
.form-control-lg::file-selector-button {
|
||||||
|
@ -2258,7 +2262,7 @@ textarea.form-control-lg {
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 0.375rem 2.25rem 0.375rem 0.75rem;
|
padding: 0.375rem 2.25rem 0.375rem 0.75rem;
|
||||||
font-size: 0.875rem;
|
font-size: 1rem;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
color: var(--bs-body-color);
|
color: var(--bs-body-color);
|
||||||
|
@ -2299,7 +2303,7 @@ textarea.form-control-lg {
|
||||||
padding-top: 0.25rem;
|
padding-top: 0.25rem;
|
||||||
padding-bottom: 0.25rem;
|
padding-bottom: 0.25rem;
|
||||||
padding-left: 0.5rem;
|
padding-left: 0.5rem;
|
||||||
font-size: 0.765625rem;
|
font-size: 0.875rem;
|
||||||
border-radius: var(--bs-border-radius-sm);
|
border-radius: var(--bs-border-radius-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2307,7 +2311,7 @@ textarea.form-control-lg {
|
||||||
padding-top: 0.5rem;
|
padding-top: 0.5rem;
|
||||||
padding-bottom: 0.5rem;
|
padding-bottom: 0.5rem;
|
||||||
padding-left: 1rem;
|
padding-left: 1rem;
|
||||||
font-size: 1.09375rem;
|
font-size: 1.25rem;
|
||||||
border-radius: var(--bs-border-radius-lg);
|
border-radius: var(--bs-border-radius-lg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2317,7 +2321,7 @@ textarea.form-control-lg {
|
||||||
|
|
||||||
.form-check {
|
.form-check {
|
||||||
display: block;
|
display: block;
|
||||||
min-height: 1.3125rem;
|
min-height: 1.5rem;
|
||||||
padding-left: 1.5em;
|
padding-left: 1.5em;
|
||||||
margin-bottom: 0.125rem;
|
margin-bottom: 0.125rem;
|
||||||
}
|
}
|
||||||
|
@ -2653,7 +2657,7 @@ textarea.form-control-lg {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 0.375rem 0.75rem;
|
padding: 0.375rem 0.75rem;
|
||||||
font-size: 0.875rem;
|
font-size: 1rem;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
color: var(--bs-body-color);
|
color: var(--bs-body-color);
|
||||||
|
@ -2669,7 +2673,7 @@ textarea.form-control-lg {
|
||||||
.input-group-lg > .input-group-text,
|
.input-group-lg > .input-group-text,
|
||||||
.input-group-lg > .btn {
|
.input-group-lg > .btn {
|
||||||
padding: 0.5rem 1rem;
|
padding: 0.5rem 1rem;
|
||||||
font-size: 1.09375rem;
|
font-size: 1.25rem;
|
||||||
border-radius: var(--bs-border-radius-lg);
|
border-radius: var(--bs-border-radius-lg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2678,7 +2682,7 @@ textarea.form-control-lg {
|
||||||
.input-group-sm > .input-group-text,
|
.input-group-sm > .input-group-text,
|
||||||
.input-group-sm > .btn {
|
.input-group-sm > .btn {
|
||||||
padding: 0.25rem 0.5rem;
|
padding: 0.25rem 0.5rem;
|
||||||
font-size: 0.765625rem;
|
font-size: 0.875rem;
|
||||||
border-radius: var(--bs-border-radius-sm);
|
border-radius: var(--bs-border-radius-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2728,7 +2732,7 @@ textarea.form-control-lg {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
padding: 0.25rem 0.5rem;
|
padding: 0.25rem 0.5rem;
|
||||||
margin-top: 0.1rem;
|
margin-top: 0.1rem;
|
||||||
font-size: 0.765625rem;
|
font-size: 0.875rem;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
background-color: var(--bs-success);
|
background-color: var(--bs-success);
|
||||||
border-radius: var(--bs-border-radius);
|
border-radius: var(--bs-border-radius);
|
||||||
|
@ -2818,7 +2822,7 @@ textarea.form-control-lg {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
padding: 0.25rem 0.5rem;
|
padding: 0.25rem 0.5rem;
|
||||||
margin-top: 0.1rem;
|
margin-top: 0.1rem;
|
||||||
font-size: 0.765625rem;
|
font-size: 0.875rem;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
background-color: var(--bs-danger);
|
background-color: var(--bs-danger);
|
||||||
border-radius: var(--bs-border-radius);
|
border-radius: var(--bs-border-radius);
|
||||||
|
@ -2896,7 +2900,7 @@ textarea.form-control-lg {
|
||||||
--bs-btn-padding-x: 0.75rem;
|
--bs-btn-padding-x: 0.75rem;
|
||||||
--bs-btn-padding-y: 0.375rem;
|
--bs-btn-padding-y: 0.375rem;
|
||||||
--bs-btn-font-family: ;
|
--bs-btn-font-family: ;
|
||||||
--bs-btn-font-size: 0.875rem;
|
--bs-btn-font-size: 1rem;
|
||||||
--bs-btn-font-weight: 400;
|
--bs-btn-font-weight: 400;
|
||||||
--bs-btn-line-height: 1.5;
|
--bs-btn-line-height: 1.5;
|
||||||
--bs-btn-color: var(--bs-body-color);
|
--bs-btn-color: var(--bs-body-color);
|
||||||
|
@ -3272,14 +3276,14 @@ textarea.form-control-lg {
|
||||||
.btn-lg, .btn-group-lg > .btn {
|
.btn-lg, .btn-group-lg > .btn {
|
||||||
--bs-btn-padding-y: 0.5rem;
|
--bs-btn-padding-y: 0.5rem;
|
||||||
--bs-btn-padding-x: 1rem;
|
--bs-btn-padding-x: 1rem;
|
||||||
--bs-btn-font-size: 1.09375rem;
|
--bs-btn-font-size: 1.25rem;
|
||||||
--bs-btn-border-radius: var(--bs-border-radius-lg);
|
--bs-btn-border-radius: var(--bs-border-radius-lg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-sm, .btn-group-sm > .btn {
|
.btn-sm, .btn-group-sm > .btn {
|
||||||
--bs-btn-padding-y: 0.25rem;
|
--bs-btn-padding-y: 0.25rem;
|
||||||
--bs-btn-padding-x: 0.5rem;
|
--bs-btn-padding-x: 0.5rem;
|
||||||
--bs-btn-font-size: 0.765625rem;
|
--bs-btn-font-size: 0.875rem;
|
||||||
--bs-btn-border-radius: var(--bs-border-radius-sm);
|
--bs-btn-border-radius: var(--bs-border-radius-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3352,7 +3356,7 @@ textarea.form-control-lg {
|
||||||
--bs-dropdown-padding-x: 0;
|
--bs-dropdown-padding-x: 0;
|
||||||
--bs-dropdown-padding-y: 0.5rem;
|
--bs-dropdown-padding-y: 0.5rem;
|
||||||
--bs-dropdown-spacer: 0.125rem;
|
--bs-dropdown-spacer: 0.125rem;
|
||||||
--bs-dropdown-font-size: 0.875rem;
|
--bs-dropdown-font-size: 1rem;
|
||||||
--bs-dropdown-color: var(--bs-body-color);
|
--bs-dropdown-color: var(--bs-body-color);
|
||||||
--bs-dropdown-bg: var(--bs-body-bg);
|
--bs-dropdown-bg: var(--bs-body-bg);
|
||||||
--bs-dropdown-border-color: var(--bs-border-color-translucent);
|
--bs-dropdown-border-color: var(--bs-border-color-translucent);
|
||||||
|
@ -3614,7 +3618,7 @@ textarea.form-control-lg {
|
||||||
display: block;
|
display: block;
|
||||||
padding: var(--bs-dropdown-header-padding-y) var(--bs-dropdown-header-padding-x);
|
padding: var(--bs-dropdown-header-padding-y) var(--bs-dropdown-header-padding-x);
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
font-size: 0.765625rem;
|
font-size: 0.875rem;
|
||||||
color: var(--bs-dropdown-header-color);
|
color: var(--bs-dropdown-header-color);
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
@ -3899,15 +3903,15 @@ textarea.form-control-lg {
|
||||||
--bs-navbar-hover-color: rgba(255, 64, 186, 0.7);
|
--bs-navbar-hover-color: rgba(255, 64, 186, 0.7);
|
||||||
--bs-navbar-disabled-color: rgba(var(--bs-emphasis-color-rgb), 0.3);
|
--bs-navbar-disabled-color: rgba(var(--bs-emphasis-color-rgb), 0.3);
|
||||||
--bs-navbar-active-color: rgba(var(--bs-emphasis-color-rgb), 1);
|
--bs-navbar-active-color: rgba(var(--bs-emphasis-color-rgb), 1);
|
||||||
--bs-navbar-brand-padding-y: 0.3359375rem;
|
--bs-navbar-brand-padding-y: 0.3125rem;
|
||||||
--bs-navbar-brand-margin-end: 1rem;
|
--bs-navbar-brand-margin-end: 1rem;
|
||||||
--bs-navbar-brand-font-size: 1.09375rem;
|
--bs-navbar-brand-font-size: 1.25rem;
|
||||||
--bs-navbar-brand-color: rgba(var(--bs-emphasis-color-rgb), 1);
|
--bs-navbar-brand-color: rgba(var(--bs-emphasis-color-rgb), 1);
|
||||||
--bs-navbar-brand-hover-color: rgba(var(--bs-emphasis-color-rgb), 1);
|
--bs-navbar-brand-hover-color: rgba(var(--bs-emphasis-color-rgb), 1);
|
||||||
--bs-navbar-nav-link-padding-x: 0.5rem;
|
--bs-navbar-nav-link-padding-x: 0.5rem;
|
||||||
--bs-navbar-toggler-padding-y: 0.25rem;
|
--bs-navbar-toggler-padding-y: 0.25rem;
|
||||||
--bs-navbar-toggler-padding-x: 0.75rem;
|
--bs-navbar-toggler-padding-x: 0.75rem;
|
||||||
--bs-navbar-toggler-font-size: 1.09375rem;
|
--bs-navbar-toggler-font-size: 1.25rem;
|
||||||
--bs-navbar-toggler-icon-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%2873, 80, 87, 0.75%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e");
|
--bs-navbar-toggler-icon-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%2873, 80, 87, 0.75%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e");
|
||||||
--bs-navbar-toggler-border-color: rgba(var(--bs-emphasis-color-rgb), 0.15);
|
--bs-navbar-toggler-border-color: rgba(var(--bs-emphasis-color-rgb), 0.15);
|
||||||
--bs-navbar-toggler-border-radius: var(--bs-border-radius);
|
--bs-navbar-toggler-border-radius: var(--bs-border-radius);
|
||||||
|
@ -4544,7 +4548,7 @@ textarea.form-control-lg {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: var(--bs-accordion-btn-padding-y) var(--bs-accordion-btn-padding-x);
|
padding: var(--bs-accordion-btn-padding-y) var(--bs-accordion-btn-padding-x);
|
||||||
font-size: 0.875rem;
|
font-size: 1rem;
|
||||||
color: var(--bs-accordion-btn-color);
|
color: var(--bs-accordion-btn-color);
|
||||||
text-align: left;
|
text-align: left;
|
||||||
background-color: var(--bs-accordion-btn-bg);
|
background-color: var(--bs-accordion-btn-bg);
|
||||||
|
@ -4688,7 +4692,7 @@ textarea.form-control-lg {
|
||||||
.pagination {
|
.pagination {
|
||||||
--bs-pagination-padding-x: 0.75rem;
|
--bs-pagination-padding-x: 0.75rem;
|
||||||
--bs-pagination-padding-y: 0.375rem;
|
--bs-pagination-padding-y: 0.375rem;
|
||||||
--bs-pagination-font-size: 0.875rem;
|
--bs-pagination-font-size: 1rem;
|
||||||
--bs-pagination-color: var(--bs-link-color);
|
--bs-pagination-color: var(--bs-link-color);
|
||||||
--bs-pagination-bg: var(--bs-body-bg);
|
--bs-pagination-bg: var(--bs-body-bg);
|
||||||
--bs-pagination-border-width: var(--bs-border-width);
|
--bs-pagination-border-width: var(--bs-border-width);
|
||||||
|
@ -4768,14 +4772,14 @@ textarea.form-control-lg {
|
||||||
.pagination-lg {
|
.pagination-lg {
|
||||||
--bs-pagination-padding-x: 1.5rem;
|
--bs-pagination-padding-x: 1.5rem;
|
||||||
--bs-pagination-padding-y: 0.75rem;
|
--bs-pagination-padding-y: 0.75rem;
|
||||||
--bs-pagination-font-size: 1.09375rem;
|
--bs-pagination-font-size: 1.25rem;
|
||||||
--bs-pagination-border-radius: var(--bs-border-radius-lg);
|
--bs-pagination-border-radius: var(--bs-border-radius-lg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.pagination-sm {
|
.pagination-sm {
|
||||||
--bs-pagination-padding-x: 0.5rem;
|
--bs-pagination-padding-x: 0.5rem;
|
||||||
--bs-pagination-padding-y: 0.25rem;
|
--bs-pagination-padding-y: 0.25rem;
|
||||||
--bs-pagination-font-size: 0.765625rem;
|
--bs-pagination-font-size: 0.875rem;
|
||||||
--bs-pagination-border-radius: var(--bs-border-radius-sm);
|
--bs-pagination-border-radius: var(--bs-border-radius-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4910,7 +4914,7 @@ textarea.form-control-lg {
|
||||||
.progress,
|
.progress,
|
||||||
.progress-stacked {
|
.progress-stacked {
|
||||||
--bs-progress-height: 1rem;
|
--bs-progress-height: 1rem;
|
||||||
--bs-progress-font-size: 0.65625rem;
|
--bs-progress-font-size: 0.75rem;
|
||||||
--bs-progress-bg: var(--bs-secondary-bg);
|
--bs-progress-bg: var(--bs-secondary-bg);
|
||||||
--bs-progress-border-radius: var(--bs-border-radius);
|
--bs-progress-border-radius: var(--bs-border-radius);
|
||||||
--bs-progress-box-shadow: var(--bs-box-shadow-inset);
|
--bs-progress-box-shadow: var(--bs-box-shadow-inset);
|
||||||
|
@ -5716,7 +5720,7 @@ textarea.form-control-lg {
|
||||||
--bs-tooltip-padding-x: 0.5rem;
|
--bs-tooltip-padding-x: 0.5rem;
|
||||||
--bs-tooltip-padding-y: 0.25rem;
|
--bs-tooltip-padding-y: 0.25rem;
|
||||||
--bs-tooltip-margin: ;
|
--bs-tooltip-margin: ;
|
||||||
--bs-tooltip-font-size: 0.765625rem;
|
--bs-tooltip-font-size: 0.875rem;
|
||||||
--bs-tooltip-color: var(--bs-body-bg);
|
--bs-tooltip-color: var(--bs-body-bg);
|
||||||
--bs-tooltip-bg: var(--bs-emphasis-color);
|
--bs-tooltip-bg: var(--bs-emphasis-color);
|
||||||
--bs-tooltip-border-radius: var(--bs-border-radius);
|
--bs-tooltip-border-radius: var(--bs-border-radius);
|
||||||
|
@ -5815,7 +5819,7 @@ textarea.form-control-lg {
|
||||||
.popover {
|
.popover {
|
||||||
--bs-popover-zindex: 1070;
|
--bs-popover-zindex: 1070;
|
||||||
--bs-popover-max-width: 276px;
|
--bs-popover-max-width: 276px;
|
||||||
--bs-popover-font-size: 0.765625rem;
|
--bs-popover-font-size: 0.875rem;
|
||||||
--bs-popover-bg: var(--bs-body-bg);
|
--bs-popover-bg: var(--bs-body-bg);
|
||||||
--bs-popover-border-width: var(--bs-border-width);
|
--bs-popover-border-width: var(--bs-border-width);
|
||||||
--bs-popover-border-color: var(--bs-border-color-translucent);
|
--bs-popover-border-color: var(--bs-border-color-translucent);
|
||||||
|
@ -5824,7 +5828,7 @@ textarea.form-control-lg {
|
||||||
--bs-popover-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
|
--bs-popover-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
|
||||||
--bs-popover-header-padding-x: 1rem;
|
--bs-popover-header-padding-x: 1rem;
|
||||||
--bs-popover-header-padding-y: 0.5rem;
|
--bs-popover-header-padding-y: 0.5rem;
|
||||||
--bs-popover-header-font-size: 0.875rem;
|
--bs-popover-header-font-size: 1rem;
|
||||||
--bs-popover-header-color: inherit;
|
--bs-popover-header-color: inherit;
|
||||||
--bs-popover-header-bg: var(--bs-secondary-bg);
|
--bs-popover-header-bg: var(--bs-secondary-bg);
|
||||||
--bs-popover-body-padding-x: 1rem;
|
--bs-popover-body-padding-x: 1rem;
|
||||||
|
@ -8295,27 +8299,27 @@ textarea.form-control-lg {
|
||||||
}
|
}
|
||||||
|
|
||||||
.fs-1 {
|
.fs-1 {
|
||||||
font-size: calc(1.34375rem + 1.125vw) !important;
|
font-size: calc(1.375rem + 1.5vw) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fs-2 {
|
.fs-2 {
|
||||||
font-size: calc(1.3rem + 0.6vw) !important;
|
font-size: calc(1.325rem + 0.9vw) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fs-3 {
|
.fs-3 {
|
||||||
font-size: calc(1.278125rem + 0.3375vw) !important;
|
font-size: calc(1.3rem + 0.6vw) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fs-4 {
|
.fs-4 {
|
||||||
font-size: calc(1.25625rem + 0.075vw) !important;
|
font-size: calc(1.275rem + 0.3vw) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fs-5 {
|
.fs-5 {
|
||||||
font-size: 1.09375rem !important;
|
font-size: 1.25rem !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fs-6 {
|
.fs-6 {
|
||||||
font-size: 0.875rem !important;
|
font-size: 1rem !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fst-italic {
|
.fst-italic {
|
||||||
|
@ -11858,16 +11862,16 @@ textarea.form-control-lg {
|
||||||
}
|
}
|
||||||
@media (min-width: 1200px) {
|
@media (min-width: 1200px) {
|
||||||
.fs-1 {
|
.fs-1 {
|
||||||
font-size: 2.1875rem !important;
|
font-size: 2.5rem !important;
|
||||||
}
|
}
|
||||||
.fs-2 {
|
.fs-2 {
|
||||||
font-size: 1.75rem !important;
|
font-size: 2rem !important;
|
||||||
}
|
}
|
||||||
.fs-3 {
|
.fs-3 {
|
||||||
font-size: 1.53125rem !important;
|
font-size: 1.75rem !important;
|
||||||
}
|
}
|
||||||
.fs-4 {
|
.fs-4 {
|
||||||
font-size: 1.3125rem !important;
|
font-size: 1.5rem !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@media print {
|
@media print {
|
||||||
|
|
|
@ -1,118 +1,27 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
|
||||||
|
|
||||||
<svg
|
<svg
|
||||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
|
||||||
xmlns:cc="http://creativecommons.org/ns#"
|
|
||||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
|
||||||
xmlns:svg="http://www.w3.org/2000/svg"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
|
||||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
|
||||||
width="1024"
|
width="1024"
|
||||||
height="1024"
|
height="1024"
|
||||||
viewBox="0 0 1024 1024"
|
viewBox="0 0 1024 1024"
|
||||||
version="1.1"
|
version="1.1">
|
||||||
id="svg8"
|
<g transform="translate(0,-26.066658)">
|
||||||
inkscape:version="0.92.4 (unknown)"
|
|
||||||
sodipodi:docname="lemmy-logo-border.svg"
|
|
||||||
inkscape:export-filename="/home/andres/Pictures/References/Logos/Lemmy/lemmy-logo-border.png"
|
|
||||||
inkscape:export-xdpi="300"
|
|
||||||
inkscape:export-ydpi="300"
|
|
||||||
enable-background="new">
|
|
||||||
<defs
|
|
||||||
id="defs2" />
|
|
||||||
<sodipodi:namedview
|
|
||||||
id="base"
|
|
||||||
pagecolor="#ffffff"
|
|
||||||
bordercolor="#666666"
|
|
||||||
borderopacity="1.0"
|
|
||||||
inkscape:pageopacity="0.0"
|
|
||||||
inkscape:pageshadow="2"
|
|
||||||
inkscape:zoom="0.49497475"
|
|
||||||
inkscape:cx="452.38625"
|
|
||||||
inkscape:cy="470.53357"
|
|
||||||
inkscape:document-units="px"
|
|
||||||
inkscape:current-layer="layer1"
|
|
||||||
showgrid="false"
|
|
||||||
units="px"
|
|
||||||
inkscape:showpageshadow="false"
|
|
||||||
inkscape:window-width="1366"
|
|
||||||
inkscape:window-height="740"
|
|
||||||
inkscape:window-x="0"
|
|
||||||
inkscape:window-y="0"
|
|
||||||
inkscape:window-maximized="1"
|
|
||||||
showguides="true"
|
|
||||||
inkscape:guide-bbox="true"
|
|
||||||
inkscape:snap-global="true"
|
|
||||||
inkscape:snap-midpoints="false"
|
|
||||||
inkscape:snap-smooth-nodes="false"
|
|
||||||
inkscape:object-paths="false"
|
|
||||||
inkscape:pagecheckerboard="true" />
|
|
||||||
<metadata
|
|
||||||
id="metadata5">
|
|
||||||
<rdf:RDF>
|
|
||||||
<cc:Work
|
|
||||||
rdf:about="">
|
|
||||||
<dc:format>image/svg+xml</dc:format>
|
|
||||||
<dc:type
|
|
||||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
|
||||||
<dc:title />
|
|
||||||
</cc:Work>
|
|
||||||
</rdf:RDF>
|
|
||||||
</metadata>
|
|
||||||
<g
|
|
||||||
inkscape:label="Layer 1"
|
|
||||||
inkscape:groupmode="layer"
|
|
||||||
id="layer1"
|
|
||||||
transform="translate(0,-26.066658)"
|
|
||||||
style="display:inline">
|
|
||||||
<path
|
<path
|
||||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:28;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:28;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||||
d="m 167.03908,270.78735 c -0.94784,-0.002 -1.8939,0.004 -2.83789,0.0215 -4.31538,0.0778 -8.58934,0.3593 -12.8125,0.8457 -33.78522,3.89116 -64.215716,21.86394 -82.871086,53.27344 -18.27982,30.77718 -22.77749,64.66635 -13.46094,96.06837 9.31655,31.40203 31.88488,59.93174 65.296886,82.5332 0.20163,0.13618 0.40678,0.26709 0.61523,0.39258 28.65434,17.27768 57.18167,28.93179 87.74218,34.95508 -0.74566,12.61339 -0.72532,25.5717 0.082,38.84375 2.43989,40.10943 16.60718,77.03742 38.0957,109.67187 l -77.00781,31.4375 c -8.30605,3.25932 -12.34178,12.68234 -8.96967,20.94324 3.37211,8.2609 12.84919,12.16798 21.06342,8.68371 l 84.69727,-34.57617 c 15.70675,18.72702 33.75346,35.68305 53.12109,50.57032 0.74013,0.56891 1.4904,1.12236 2.23437,1.68554 l -49.61132,65.69141 c -5.45446,7.0474 -4.10058,17.19288 3.01098,22.5634 7.11156,5.37052 17.24028,3.89649 22.52612,-3.27824 l 50.38672,-66.71876 c 27.68572,17.53469 57.07524,31.20388 86.07227,40.25196 14.88153,27.28008 43.96965,44.64648 77.58789,44.64648 33.93762,0 63.04252,-18.68693 77.80082,-45.4375 28.7072,-9.21295 57.7527,-22.93196 85.1484,-40.40234 l 51.0977,67.66016 c 5.2858,7.17473 15.4145,8.64876 22.5261,3.27824 7.1115,-5.37052 8.4654,-15.516 3.011,-22.5634 l -50.3614,-66.68555 c 0.334,-0.25394 0.6727,-0.50077 1.0059,-0.75586 19.1376,-14.64919 37.0259,-31.28581 52.7031,-49.63476 l 82.5625,33.70507 c 8.2143,3.48427 17.6913,-0.42281 21.0634,-8.68371 3.3722,-8.2609 -0.6636,-17.68392 -8.9696,-20.94324 l -74.5391,-30.42773 c 22.1722,-32.82971 37.0383,-70.03397 40.1426,-110.46094 1.0253,-13.35251 1.2292,-26.42535 0.6387,-39.17578 30.3557,-6.05408 58.7164,-17.66833 87.2011,-34.84375 0.2085,-0.12549 0.4136,-0.2564 0.6153,-0.39258 33.412,-22.60147 55.9803,-51.13117 65.2968,-82.5332 9.3166,-31.40202 4.8189,-65.29118 -13.4609,-96.06837 -18.6553,-31.40951 -49.0859,-49.38228 -82.8711,-53.27344 -4.2231,-0.4864 -8.4971,-0.76791 -12.8125,-0.8457 -30.2077,-0.54448 -62.4407,8.82427 -93.4316,26.71484 -22.7976,13.16063 -43.3521,33.31423 -59.4375,55.30469 -44.9968,-25.75094 -103.5444,-40.25065 -175.4785,-41.43945 -6.4522,-0.10663 -13.0125,-0.10696 -19.67974,0.002 -80.18875,1.30929 -144.38284,16.5086 -192.87109,43.9922 -0.11914,-0.19111 -0.24287,-0.37932 -0.37109,-0.56446 -16.29,-22.764 -37.41085,-43.73706 -60.89649,-57.29493 -30.02247,-17.33149 -61.21051,-26.66489 -90.59375,-26.73633 z"
|
d="m 167.03908,270.78735 c -0.94784,-0.002 -1.8939,0.004 -2.83789,0.0215 -4.31538,0.0778 -8.58934,0.3593 -12.8125,0.8457 -33.78522,3.89116 -64.215716,21.86394 -82.871086,53.27344 -18.27982,30.77718 -22.77749,64.66635 -13.46094,96.06837 9.31655,31.40203 31.88488,59.93174 65.296886,82.5332 0.20163,0.13618 0.40678,0.26709 0.61523,0.39258 28.65434,17.27768 57.18167,28.93179 87.74218,34.95508 -0.74566,12.61339 -0.72532,25.5717 0.082,38.84375 2.43989,40.10943 16.60718,77.03742 38.0957,109.67187 l -77.00781,31.4375 c -8.30605,3.25932 -12.34178,12.68234 -8.96967,20.94324 3.37211,8.2609 12.84919,12.16798 21.06342,8.68371 l 84.69727,-34.57617 c 15.70675,18.72702 33.75346,35.68305 53.12109,50.57032 0.74013,0.56891 1.4904,1.12236 2.23437,1.68554 l -49.61132,65.69141 c -5.45446,7.0474 -4.10058,17.19288 3.01098,22.5634 7.11156,5.37052 17.24028,3.89649 22.52612,-3.27824 l 50.38672,-66.71876 c 27.68572,17.53469 57.07524,31.20388 86.07227,40.25196 14.88153,27.28008 43.96965,44.64648 77.58789,44.64648 33.93762,0 63.04252,-18.68693 77.80082,-45.4375 28.7072,-9.21295 57.7527,-22.93196 85.1484,-40.40234 l 51.0977,67.66016 c 5.2858,7.17473 15.4145,8.64876 22.5261,3.27824 7.1115,-5.37052 8.4654,-15.516 3.011,-22.5634 l -50.3614,-66.68555 c 0.334,-0.25394 0.6727,-0.50077 1.0059,-0.75586 19.1376,-14.64919 37.0259,-31.28581 52.7031,-49.63476 l 82.5625,33.70507 c 8.2143,3.48427 17.6913,-0.42281 21.0634,-8.68371 3.3722,-8.2609 -0.6636,-17.68392 -8.9696,-20.94324 l -74.5391,-30.42773 c 22.1722,-32.82971 37.0383,-70.03397 40.1426,-110.46094 1.0253,-13.35251 1.2292,-26.42535 0.6387,-39.17578 30.3557,-6.05408 58.7164,-17.66833 87.2011,-34.84375 0.2085,-0.12549 0.4136,-0.2564 0.6153,-0.39258 33.412,-22.60147 55.9803,-51.13117 65.2968,-82.5332 9.3166,-31.40202 4.8189,-65.29118 -13.4609,-96.06837 -18.6553,-31.40951 -49.0859,-49.38228 -82.8711,-53.27344 -4.2231,-0.4864 -8.4971,-0.76791 -12.8125,-0.8457 -30.2077,-0.54448 -62.4407,8.82427 -93.4316,26.71484 -22.7976,13.16063 -43.3521,33.31423 -59.4375,55.30469 -44.9968,-25.75094 -103.5444,-40.25065 -175.4785,-41.43945 -6.4522,-0.10663 -13.0125,-0.10696 -19.67974,0.002 -80.18875,1.30929 -144.38284,16.5086 -192.87109,43.9922 -0.11914,-0.19111 -0.24287,-0.37932 -0.37109,-0.56446 -16.29,-22.764 -37.41085,-43.73706 -60.89649,-57.29493 -30.02247,-17.33149 -61.21051,-26.66489 -90.59375,-26.73633 z" />
|
||||||
id="path817-3"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
sodipodi:nodetypes="cccssccccccscccccscccscccscccccsccscccssccscscccscc"
|
|
||||||
inkscape:label="white-border"
|
|
||||||
sodipodi:insensitive="true" />
|
|
||||||
<path
|
|
||||||
id="path1087"
|
|
||||||
style="display:inline;opacity:1;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:28;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
|
||||||
d="m 716.85595,362.96478 c 15.29075,-21.36763 35.36198,-41.10921 56.50979,-53.31749 66.66377,-38.48393 137.02617,-33.22172 170.08018,22.43043 33.09493,55.72093 14.98656,117.48866 -47.64399,159.85496 -31.95554,19.26819 -62.93318,30.92309 -97.22892,35.54473 M 307.14407,362.96478 C 291.85332,341.59715 271.78209,321.85557 250.63429,309.64729 183.97051,271.16336 113.60811,276.42557 80.554051,332.07772 47.459131,387.79865 65.56752,449.56638 128.19809,491.93268 c 31.95554,19.26819 62.93319,30.92309 97.22893,35.54473"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
inkscape:label="ears"
|
|
||||||
sodipodi:insensitive="true" />
|
|
||||||
<path
|
<path
|
||||||
style="display:inline;opacity:1;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:28;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
style="display:inline;opacity:1;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:28;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
d="M 801.23205,576.8699 C 812.73478,427.06971 720.58431,321.98291 511.99999,325.38859 303.41568,328.79426 213.71393,428.0311 222.76794,576.8699 c 8.64289,142.08048 176.80223,246.40388 288.12038,246.40388 111.31815,0 279.45076,-104.5447 290.34373,-246.40388 z"
|
d="m 716.85595,362.96478 c 15.29075,-21.36763 35.36198,-41.10921 56.50979,-53.31749 66.66377,-38.48393 137.02617,-33.22172 170.08018,22.43043 33.09493,55.72093 14.98656,117.48866 -47.64399,159.85496 -31.95554,19.26819 -62.93318,30.92309 -97.22892,35.54473 M 307.14407,362.96478 C 291.85332,341.59715 271.78209,321.85557 250.63429,309.64729 183.97051,271.16336 113.60811,276.42557 80.554051,332.07772 47.459131,387.79865 65.56752,449.56638 128.19809,491.93268 c 31.95554,19.26819 62.93319,30.92309 97.22893,35.54473" />
|
||||||
id="path969"
|
<path
|
||||||
inkscape:connector-curvature="0"
|
style="display:inline;opacity:1;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:28;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
sodipodi:nodetypes="szszs"
|
d="M 801.23205,576.8699 C 812.73478,427.06971 720.58431,321.98291 511.99999,325.38859 303.41568,328.79426 213.71393,428.0311 222.76794,576.8699 c 8.64289,142.08048 176.80223,246.40388 288.12038,246.40388 111.31815,0 279.45076,-104.5447 290.34373,-246.40388 z" />
|
||||||
inkscape:label="head"
|
|
||||||
sodipodi:insensitive="true" />
|
|
||||||
<path
|
<path
|
||||||
id="path1084"
|
|
||||||
style="display:inline;opacity:1;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
style="display:inline;opacity:1;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
d="m 610.4991,644.28932 c 0,23.11198 18.70595,41.84795 41.78091,41.84795 23.07495,0 41.7809,-18.73597 41.7809,-41.84795 0,-23.112 -18.70594,-41.84796 -41.7809,-41.84796 -23.07496,0 -41.78091,18.73596 -41.78091,41.84796 z m -280.56002,0 c 0,23.32492 18.87829,42.23352 42.16586,42.23352 23.28755,0 42.16585,-18.9086 42.16585,-42.23352 0,-23.32494 -18.87829,-42.23353 -42.16585,-42.23353 -23.28757,0 -42.16586,18.90859 -42.16586,42.23353 z"
|
d="m 610.4991,644.28932 c 0,23.11198 18.70595,41.84795 41.78091,41.84795 23.07495,0 41.7809,-18.73597 41.7809,-41.84795 0,-23.112 -18.70594,-41.84796 -41.7809,-41.84796 -23.07496,0 -41.78091,18.73596 -41.78091,41.84796 z m -280.56002,0 c 0,23.32492 18.87829,42.23352 42.16586,42.23352 23.28755,0 42.16585,-18.9086 42.16585,-42.23352 0,-23.32494 -18.87829,-42.23353 -42.16585,-42.23353 -23.28757,0 -42.16586,18.90859 -42.16586,42.23353 z" />
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
inkscape:label="eyes"
|
|
||||||
sodipodi:nodetypes="ssssssssss"
|
|
||||||
sodipodi:insensitive="true" />
|
|
||||||
<path
|
<path
|
||||||
id="path1008"
|
|
||||||
style="display:inline;opacity:1;fill:none;stroke:#000000;stroke-width:32;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
style="display:inline;opacity:1;fill:none;stroke:#000000;stroke-width:32;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
d="m 339.72919,769.2467 -54.54422,72.22481 m 399.08582,-72.22481 54.54423,72.22481 M 263.68341,697.82002 175.92752,733.64353 m 579.85765,-35.82351 87.7559,35.82351"
|
d="m 339.72919,769.2467 -54.54422,72.22481 m 399.08582,-72.22481 54.54423,72.22481 M 263.68341,697.82002 175.92752,733.64353 m 579.85765,-35.82351 87.7559,35.82351" />
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
inkscape:label="whiskers"
|
|
||||||
sodipodi:nodetypes="cccccccc"
|
|
||||||
sodipodi:insensitive="true" />
|
|
||||||
<path
|
<path
|
||||||
style="display:inline;opacity:1;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:28;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
style="display:inline;opacity:1;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:28;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
d="m 512.00082,713.08977 c -45.86417,0 -75.13006,31.84485 -74.14159,71.10084 1.07048,42.51275 32.46865,71.10323 74.14159,71.10323 41.67296,0 74.05118,-32.99608 74.14161,-71.10323 0.0932,-39.26839 -28.27742,-71.10084 -74.14161,-71.10084 z"
|
d="m 512.00082,713.08977 c -45.86417,0 -75.13006,31.84485 -74.14159,71.10084 1.07048,42.51275 32.46865,71.10323 74.14159,71.10323 41.67296,0 74.05118,-32.99608 74.14161,-71.10323 0.0932,-39.26839 -28.27742,-71.10084 -74.14161,-71.10084 z" />
|
||||||
id="path1115"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
inkscape:label="nose"
|
|
||||||
sodipodi:nodetypes="zszsz"
|
|
||||||
sodipodi:insensitive="true" />
|
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
Before Width: | Height: | Size: 9.2 KiB After Width: | Height: | Size: 6.3 KiB |
|
@ -142,8 +142,8 @@
|
||||||
<symbol id="icon-book-open" viewBox="0 0 24 24">
|
<symbol id="icon-book-open" viewBox="0 0 24 24">
|
||||||
<path d="M21 4v13h-6c-0.728 0-1.412 0.195-2 0.535v-10.535c0-0.829 0.335-1.577 0.879-2.121s1.292-0.879 2.121-0.879zM11 17.535c-0.588-0.34-1.272-0.535-2-0.535h-6v-13h5c0.829 0 1.577 0.335 2.121 0.879s0.879 1.292 0.879 2.121zM22 2h-6c-1.38 0-2.632 0.561-3.536 1.464-0.167 0.167-0.322 0.346-0.464 0.536-0.142-0.19-0.297-0.369-0.464-0.536-0.904-0.903-2.156-1.464-3.536-1.464h-6c-0.552 0-1 0.448-1 1v15c0 0.552 0.448 1 1 1h7c0.553 0 1.051 0.223 1.414 0.586s0.586 0.861 0.586 1.414c0 0.552 0.448 1 1 1s1-0.448 1-1c0-0.553 0.223-1.051 0.586-1.414s0.861-0.586 1.414-0.586h7c0.552 0 1-0.448 1-1v-15c0-0.552-0.448-1-1-1z"></path>
|
<path d="M21 4v13h-6c-0.728 0-1.412 0.195-2 0.535v-10.535c0-0.829 0.335-1.577 0.879-2.121s1.292-0.879 2.121-0.879zM11 17.535c-0.588-0.34-1.272-0.535-2-0.535h-6v-13h5c0.829 0 1.577 0.335 2.121 0.879s0.879 1.292 0.879 2.121zM22 2h-6c-1.38 0-2.632 0.561-3.536 1.464-0.167 0.167-0.322 0.346-0.464 0.536-0.142-0.19-0.297-0.369-0.464-0.536-0.904-0.903-2.156-1.464-3.536-1.464h-6c-0.552 0-1 0.448-1 1v15c0 0.552 0.448 1 1 1h7c0.553 0 1.051 0.223 1.414 0.586s0.586 0.861 0.586 1.414c0 0.552 0.448 1 1 1s1-0.448 1-1c0-0.553 0.223-1.051 0.586-1.414s0.861-0.586 1.414-0.586h7c0.552 0 1-0.448 1-1v-15c0-0.552-0.448-1-1-1z"></path>
|
||||||
</symbol>
|
</symbol>
|
||||||
<symbol id="icon-alert-triangle" viewBox="0 0 24 24">
|
<symbol id="icon-alert-triangle" viewBox="0 -960 960 960">
|
||||||
<path d="M11.148 4.374c0.073-0.123 0.185-0.242 0.334-0.332 0.236-0.143 0.506-0.178 0.756-0.116s0.474 0.216 0.614 0.448l8.466 14.133c0.070 0.12 0.119 0.268 0.128 0.434-0.015 0.368-0.119 0.591-0.283 0.759-0.18 0.184-0.427 0.298-0.693 0.301l-16.937-0.001c-0.152-0.001-0.321-0.041-0.481-0.134-0.239-0.138-0.399-0.359-0.466-0.607s-0.038-0.519 0.092-0.745zM9.432 3.346l-8.47 14.14c-0.422 0.731-0.506 1.55-0.308 2.29s0.68 1.408 1.398 1.822c0.464 0.268 0.976 0.4 1.475 0.402h16.943c0.839-0.009 1.587-0.354 2.123-0.902s0.864-1.303 0.855-2.131c-0.006-0.536-0.153-1.044-0.406-1.474l-8.474-14.147c-0.432-0.713-1.11-1.181-1.854-1.363s-1.561-0.081-2.269 0.349c-0.429 0.26-0.775 0.615-1.012 1.014zM11 9v4c0 0.552 0.448 1 1 1s1-0.448 1-1v-4c0-0.552-0.448-1-1-1s-1 0.448-1 1zM12 18c0.552 0 1-0.448 1-1s-0.448-1-1-1-1 0.448-1 1 0.448 1 1 1z"></path>
|
<path d="M480-79q-16 0-30.5-6T423-102L102-423q-11-12-17-26.5T79-480q0-16 6-31t17-26l321-321q12-12 26.5-17.5T480-881q16 0 31 5.5t26 17.5l321 321q12 11 17.5 26t5.5 31q0 16-5.5 30.5T858-423L537-102q-11 11-26 17t-31 6Zm0-80 321-321-321-321-321 321 321 321Zm-40-281h80v-240h-80v240Zm40 120q17 0 28.5-11.5T520-360q0-17-11.5-28.5T480-400q-17 0-28.5 11.5T440-360q0 17 11.5 28.5T480-320Zm0-160Z"></path>
|
||||||
</symbol>
|
</symbol>
|
||||||
<symbol id="icon-zap" viewBox="0 0 24 24">
|
<symbol id="icon-zap" viewBox="0 0 24 24">
|
||||||
<path d="M11.585 5.26l-0.577 4.616c0.033 0.716 0.465 1.124 0.992 1.124h6.865l-6.45 7.74 0.577-4.616c-0.033-0.716-0.465-1.124-0.992-1.124h-6.865zM12.232 1.36l-10 12c-0.354 0.424-0.296 1.055 0.128 1.408 0.187 0.157 0.415 0.233 0.64 0.232h7.867l-0.859 6.876c-0.069 0.548 0.32 1.048 0.868 1.116 0.349 0.044 0.678-0.098 0.892-0.352l10-12c0.354-0.424 0.296-1.055-0.128-1.408-0.187-0.157-0.415-0.233-0.64-0.232h-7.867l0.859-6.876c0.069-0.548-0.32-1.048-0.868-1.116-0.349-0.044-0.678 0.098-0.892 0.352z"></path>
|
<path d="M11.585 5.26l-0.577 4.616c0.033 0.716 0.465 1.124 0.992 1.124h6.865l-6.45 7.74 0.577-4.616c-0.033-0.716-0.465-1.124-0.992-1.124h-6.865zM12.232 1.36l-10 12c-0.354 0.424-0.296 1.055 0.128 1.408 0.187 0.157 0.415 0.233 0.64 0.232h7.867l-0.859 6.876c-0.069 0.548 0.32 1.048 0.868 1.116 0.349 0.044 0.678-0.098 0.892-0.352l10-12c0.354-0.424 0.296-1.055-0.128-1.408-0.187-0.157-0.415-0.233-0.64-0.232h-7.867l0.859-6.876c0.069-0.548-0.32-1.048-0.868-1.116-0.349-0.044-0.678 0.098-0.892 0.352z"></path>
|
||||||
|
@ -258,5 +258,12 @@
|
||||||
<path d="M8.72046 10.6397L14.9999 7.5" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
<path d="M8.72046 10.6397L14.9999 7.5" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
<path d="M8.70605 13.353L15 16.5" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
<path d="M8.70605 13.353L15 16.5" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
</symbol>
|
</symbol>
|
||||||
|
<symbol id="icon-eye" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M2.036 12.322a1.012 1.012 0 010-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178z" />
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||||
|
</symbol>
|
||||||
|
<symbol id="icon-eye-slash" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M3.98 8.223A10.477 10.477 0 001.934 12C3.226 16.338 7.244 19.5 12 19.5c.993 0 1.953-.138 2.863-.395M6.228 6.228A10.45 10.45 0 0112 4.5c4.756 0 8.773 3.162 10.065 7.498a10.523 10.523 0 01-4.293 5.774M6.228 6.228L3 3m3.228 3.228l3.65 3.65m7.894 7.894L21 21m-3.228-3.228l-3.65-3.65m0 0a3 3 0 10-4.243-4.243m4.242 4.242L9.88 9.88" />
|
||||||
|
</symbol>
|
||||||
</defs>
|
</defs>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 54 KiB |
|
@ -1,11 +1,12 @@
|
||||||
import { initializeSite, setupDateFns } from "@utils/app";
|
import { initializeSite, setupDateFns } from "@utils/app";
|
||||||
import { hydrate } from "inferno-hydrate";
|
import { hydrate } from "inferno-hydrate";
|
||||||
import { Router } from "inferno-router";
|
import { BrowserRouter } from "inferno-router";
|
||||||
import { App } from "../shared/components/app/app";
|
import { App } from "../shared/components/app/app";
|
||||||
import { HistoryService, UserService } from "../shared/services";
|
import { UserService } from "../shared/services";
|
||||||
|
|
||||||
import "bootstrap/js/dist/collapse";
|
import "bootstrap/js/dist/collapse";
|
||||||
import "bootstrap/js/dist/dropdown";
|
import "bootstrap/js/dist/dropdown";
|
||||||
|
import "bootstrap/js/dist/modal";
|
||||||
|
|
||||||
async function startClient() {
|
async function startClient() {
|
||||||
initializeSite(window.isoData.site_res);
|
initializeSite(window.isoData.site_res);
|
||||||
|
@ -13,9 +14,9 @@ async function startClient() {
|
||||||
await setupDateFns();
|
await setupDateFns();
|
||||||
|
|
||||||
const wrapper = (
|
const wrapper = (
|
||||||
<Router history={HistoryService.history}>
|
<BrowserRouter>
|
||||||
<App user={UserService.Instance.myUserInfo} />
|
<App user={UserService.Instance.myUserInfo} />
|
||||||
</Router>
|
</BrowserRouter>
|
||||||
);
|
);
|
||||||
|
|
||||||
const root = document.getElementById("root");
|
const root = document.getElementById("root");
|
||||||
|
|
|
@ -6,7 +6,7 @@ import fetch from "cross-fetch";
|
||||||
import type { Request, Response } from "express";
|
import type { Request, Response } from "express";
|
||||||
import { StaticRouter, matchPath } from "inferno-router";
|
import { StaticRouter, matchPath } from "inferno-router";
|
||||||
import { renderToString } from "inferno-server";
|
import { renderToString } from "inferno-server";
|
||||||
import { GetSite, GetSiteResponse, LemmyHttp } from "lemmy-js-client";
|
import { GetSiteResponse, LemmyHttp } from "lemmy-js-client";
|
||||||
import { App } from "../../shared/components/app/app";
|
import { App } from "../../shared/components/app/app";
|
||||||
import {
|
import {
|
||||||
InitialFetchRequest,
|
InitialFetchRequest,
|
||||||
|
@ -26,18 +26,19 @@ export default async (req: Request, res: Response) => {
|
||||||
try {
|
try {
|
||||||
const activeRoute = routes.find(route => matchPath(req.path, route));
|
const activeRoute = routes.find(route => matchPath(req.path, route));
|
||||||
|
|
||||||
let auth = req.headers.cookie
|
|
||||||
? cookie.parse(req.headers.cookie).jwt
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
const getSiteForm: GetSite = { auth };
|
|
||||||
|
|
||||||
const headers = setForwardedHeaders(req.headers);
|
const headers = setForwardedHeaders(req.headers);
|
||||||
|
|
||||||
const client = wrapClient(
|
const client = wrapClient(
|
||||||
new LemmyHttp(getHttpBaseInternal(), { fetchFunction: fetch, headers })
|
new LemmyHttp(getHttpBaseInternal(), { fetchFunction: fetch, headers }),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const auth = req.headers.cookie
|
||||||
|
? cookie.parse(req.headers.cookie).jwt
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
if (auth) {
|
||||||
|
client.setHeaders({ Authorization: `Bearer ${auth}` });
|
||||||
|
}
|
||||||
const { path, url, query } = req;
|
const { path, url, query } = req;
|
||||||
|
|
||||||
// Get site data first
|
// Get site data first
|
||||||
|
@ -46,19 +47,18 @@ export default async (req: Request, res: Response) => {
|
||||||
let site: GetSiteResponse | undefined = undefined;
|
let site: GetSiteResponse | undefined = undefined;
|
||||||
let routeData: RouteData = {};
|
let routeData: RouteData = {};
|
||||||
let errorPageData: ErrorPageData | undefined = undefined;
|
let errorPageData: ErrorPageData | undefined = undefined;
|
||||||
let try_site = await client.getSite(getSiteForm);
|
let try_site = await client.getSite();
|
||||||
|
|
||||||
if (try_site.state === "failed" && try_site.msg == "not_logged_in") {
|
if (try_site.state === "failed" && try_site.msg === "not_logged_in") {
|
||||||
console.error(
|
console.error(
|
||||||
"Incorrect JWT token, skipping auth so frontend can remove jwt cookie"
|
"Incorrect JWT token, skipping auth so frontend can remove jwt cookie",
|
||||||
);
|
);
|
||||||
getSiteForm.auth = undefined;
|
client.setHeaders({});
|
||||||
auth = undefined;
|
try_site = await client.getSite();
|
||||||
try_site = await client.getSite(getSiteForm);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!auth && isAuthPath(path)) {
|
if (!auth && isAuthPath(path)) {
|
||||||
return res.redirect("/login");
|
return res.redirect(`/login?prev=${encodeURIComponent(url)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (try_site.state === "success") {
|
if (try_site.state === "success") {
|
||||||
|
@ -72,7 +72,6 @@ export default async (req: Request, res: Response) => {
|
||||||
if (site && activeRoute?.fetchInitialData) {
|
if (site && activeRoute?.fetchInitialData) {
|
||||||
const initialFetchReq: InitialFetchRequest = {
|
const initialFetchReq: InitialFetchRequest = {
|
||||||
client,
|
client,
|
||||||
auth,
|
|
||||||
path,
|
path,
|
||||||
query,
|
query,
|
||||||
site,
|
site,
|
||||||
|
@ -90,7 +89,7 @@ export default async (req: Request, res: Response) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const error = Object.values(routeData).find(
|
const error = Object.values(routeData).find(
|
||||||
res => res.state === "failed" && res.msg !== "couldnt_find_object" // TODO: find a better way of handling errors
|
res => res.state === "failed" && res.msg !== "couldnt_find_object", // TODO: find a better way of handling errors
|
||||||
) as FailedRequestState | undefined;
|
) as FailedRequestState | undefined;
|
||||||
|
|
||||||
// Redirect to the 404 if there's an API error
|
// Redirect to the 404 if there's an API error
|
||||||
|
@ -120,14 +119,14 @@ export default async (req: Request, res: Response) => {
|
||||||
|
|
||||||
const root = renderToString(wrapper);
|
const root = renderToString(wrapper);
|
||||||
|
|
||||||
res.send(await createSsrHtml(root, isoData));
|
res.send(await createSsrHtml(root, isoData, res.locals.cspNonce));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// If an error is caught here, the error page couldn't even be rendered
|
// If an error is caught here, the error page couldn't even be rendered
|
||||||
console.error(err);
|
console.error(err);
|
||||||
res.statusCode = 500;
|
res.statusCode = 500;
|
||||||
|
|
||||||
return res.send(
|
return res.send(
|
||||||
process.env.NODE_ENV === "development" ? err.message : "Server error"
|
process.env.NODE_ENV === "development" ? err.message : "Server error",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
35
src/server/handlers/code-theme-handler.ts
Normal file
35
src/server/handlers/code-theme-handler.ts
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
import type { Request, Response } from "express";
|
||||||
|
import { existsSync } from "fs";
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
|
const extraThemesFolder =
|
||||||
|
process.env["LEMMY_UI_EXTRA_THEMES_FOLDER"] || "./extra_themes";
|
||||||
|
|
||||||
|
export default async (req: Request, res: Response) => {
|
||||||
|
res.contentType("text/css");
|
||||||
|
|
||||||
|
const theme = req.params.name;
|
||||||
|
|
||||||
|
if (!theme.endsWith(".css")) {
|
||||||
|
return res.status(400).send("Theme must be a css file");
|
||||||
|
}
|
||||||
|
|
||||||
|
const customTheme = path.resolve(extraThemesFolder, theme);
|
||||||
|
|
||||||
|
if (existsSync(customTheme)) {
|
||||||
|
return res.sendFile(customTheme);
|
||||||
|
} else {
|
||||||
|
const internalTheme = path.resolve(
|
||||||
|
`./dist/assets/css/code-themes/${theme}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
// If the theme doesn't exist, just send atom-one-light
|
||||||
|
if (existsSync(internalTheme)) {
|
||||||
|
return res.sendFile(internalTheme);
|
||||||
|
} else {
|
||||||
|
return res.sendFile(
|
||||||
|
path.resolve("./dist/assets/css/code-themes/atom-one-light.css"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
|
@ -13,9 +13,9 @@ export default async (req: Request, res: Response) => {
|
||||||
if (!manifest || manifest.start_url !== getHttpBaseExternal()) {
|
if (!manifest || manifest.start_url !== getHttpBaseExternal()) {
|
||||||
const headers = setForwardedHeaders(req.headers);
|
const headers = setForwardedHeaders(req.headers);
|
||||||
const client = wrapClient(
|
const client = wrapClient(
|
||||||
new LemmyHttp(getHttpBaseInternal(), { fetchFunction: fetch, headers })
|
new LemmyHttp(getHttpBaseInternal(), { fetchFunction: fetch, headers }),
|
||||||
);
|
);
|
||||||
const site = await client.getSite({});
|
const site = await client.getSite();
|
||||||
|
|
||||||
if (site.state === "success") {
|
if (site.state === "success") {
|
||||||
manifest = await generateManifestJson(site.data);
|
manifest = await generateManifestJson(site.data);
|
||||||
|
|
|
@ -15,5 +15,6 @@ export default async ({ res }: { res: Response }) => {
|
||||||
Disallow: /admin
|
Disallow: /admin
|
||||||
Disallow: /password_change
|
Disallow: /password_change
|
||||||
Disallow: /search/
|
Disallow: /search/
|
||||||
|
Disallow: /modlog
|
||||||
`);
|
`);
|
||||||
};
|
};
|
||||||
|
|
|
@ -12,6 +12,6 @@ export default async ({ res }: { res: Response }) => {
|
||||||
process.env.LEMMY_UI_LEMMY_EXTERNAL_HOST +
|
process.env.LEMMY_UI_LEMMY_EXTERNAL_HOST +
|
||||||
`
|
`
|
||||||
Expires: 2024-01-01T04:59:00.000Z
|
Expires: 2024-01-01T04:59:00.000Z
|
||||||
`
|
`,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -8,7 +8,7 @@ export default async ({ res }: { res: Response }) => {
|
||||||
path.resolve(
|
path.resolve(
|
||||||
`./dist/service-worker${
|
`./dist/service-worker${
|
||||||
process.env.NODE_ENV === "development" ? "-development" : ""
|
process.env.NODE_ENV === "development" ? "-development" : ""
|
||||||
}.js`
|
}.js`,
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -11,6 +11,7 @@ import ServiceWorkerHandler from "./handlers/service-worker-handler";
|
||||||
import ThemeHandler from "./handlers/theme-handler";
|
import ThemeHandler from "./handlers/theme-handler";
|
||||||
import ThemesListHandler from "./handlers/themes-list-handler";
|
import ThemesListHandler from "./handlers/themes-list-handler";
|
||||||
import { setCacheControl, setDefaultCsp } from "./middleware";
|
import { setCacheControl, setDefaultCsp } from "./middleware";
|
||||||
|
import CodeThemeHandler from "./handlers/code-theme-handler";
|
||||||
|
|
||||||
const server = express();
|
const server = express();
|
||||||
|
|
||||||
|
@ -20,17 +21,26 @@ const [hostname, port] = process.env["LEMMY_UI_HOST"]
|
||||||
|
|
||||||
server.use(express.json());
|
server.use(express.json());
|
||||||
server.use(express.urlencoded({ extended: false }));
|
server.use(express.urlencoded({ extended: false }));
|
||||||
server.use(
|
|
||||||
getStaticDir(),
|
|
||||||
express.static(path.resolve("./dist"), {
|
|
||||||
maxAge: 24 * 60 * 60 * 1000, // 1 day
|
|
||||||
immutable: true,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
server.use(setCacheControl);
|
|
||||||
|
|
||||||
if (!process.env["LEMMY_UI_DISABLE_CSP"] && !process.env["LEMMY_UI_DEBUG"]) {
|
const serverPath = path.resolve("./dist");
|
||||||
|
|
||||||
|
if (
|
||||||
|
!process.env["LEMMY_UI_DISABLE_CSP"] &&
|
||||||
|
!process.env["LEMMY_UI_DEBUG"] &&
|
||||||
|
process.env["NODE_ENV"] !== "development"
|
||||||
|
) {
|
||||||
|
server.use(
|
||||||
|
getStaticDir(),
|
||||||
|
express.static(serverPath, {
|
||||||
|
maxAge: 24 * 60 * 60 * 1000, // 1 day
|
||||||
|
immutable: true,
|
||||||
|
}),
|
||||||
|
);
|
||||||
server.use(setDefaultCsp);
|
server.use(setDefaultCsp);
|
||||||
|
server.use(setCacheControl);
|
||||||
|
} else {
|
||||||
|
// In debug mode, don't use the maxAge and immutable, or it breaks live reload for dev
|
||||||
|
server.use(getStaticDir(), express.static(serverPath));
|
||||||
}
|
}
|
||||||
|
|
||||||
server.get("/.well-known/security.txt", SecurityHandler);
|
server.get("/.well-known/security.txt", SecurityHandler);
|
||||||
|
@ -38,6 +48,7 @@ server.get("/robots.txt", RobotsHandler);
|
||||||
server.get("/service-worker.js", ServiceWorkerHandler);
|
server.get("/service-worker.js", ServiceWorkerHandler);
|
||||||
server.get("/manifest.webmanifest", ManifestHandler);
|
server.get("/manifest.webmanifest", ManifestHandler);
|
||||||
server.get("/css/themes/:name", ThemeHandler);
|
server.get("/css/themes/:name", ThemeHandler);
|
||||||
|
server.get("/css/code-themes/:name", CodeThemeHandler);
|
||||||
server.get("/css/themelist", ThemesListHandler);
|
server.get("/css/themelist", ThemesListHandler);
|
||||||
server.get("/*", CatchAllHandler);
|
server.get("/*", CatchAllHandler);
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import * as crypto from "crypto";
|
||||||
import type { NextFunction, Request, Response } from "express";
|
import type { NextFunction, Request, Response } from "express";
|
||||||
import { hasJwtCookie } from "./utils/has-jwt-cookie";
|
import { hasJwtCookie } from "./utils/has-jwt-cookie";
|
||||||
|
|
||||||
|
@ -8,9 +9,20 @@ export function setDefaultCsp({
|
||||||
res: Response;
|
res: Response;
|
||||||
next: NextFunction;
|
next: NextFunction;
|
||||||
}) {
|
}) {
|
||||||
|
res.locals.cspNonce = crypto.randomBytes(16).toString("hex");
|
||||||
|
|
||||||
res.setHeader(
|
res.setHeader(
|
||||||
"Content-Security-Policy",
|
"Content-Security-Policy",
|
||||||
`default-src 'self'; manifest-src *; connect-src *; img-src * data:; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; form-action 'self'; base-uri 'self'; frame-src *; media-src * data:`
|
`default-src 'self';
|
||||||
|
manifest-src *;
|
||||||
|
connect-src *;
|
||||||
|
img-src * data:;
|
||||||
|
script-src 'self' 'nonce-${res.locals.cspNonce}';
|
||||||
|
style-src 'self' 'unsafe-inline';
|
||||||
|
form-action 'self';
|
||||||
|
base-uri 'self';
|
||||||
|
frame-src *;
|
||||||
|
media-src * data:`.replace(/\s+/g, " "),
|
||||||
);
|
);
|
||||||
|
|
||||||
next();
|
next();
|
||||||
|
@ -25,7 +37,7 @@ export function setDefaultCsp({
|
||||||
export function setCacheControl(
|
export function setCacheControl(
|
||||||
req: Request,
|
req: Request,
|
||||||
res: Response,
|
res: Response,
|
||||||
next: NextFunction
|
next: NextFunction,
|
||||||
) {
|
) {
|
||||||
if (process.env.NODE_ENV !== "production") {
|
if (process.env.NODE_ENV !== "production") {
|
||||||
return next();
|
return next();
|
||||||
|
@ -43,7 +55,7 @@ export function setCacheControl(
|
||||||
if (hasJwtCookie(req)) {
|
if (hasJwtCookie(req)) {
|
||||||
caching = "private";
|
caching = "private";
|
||||||
} else {
|
} else {
|
||||||
caching = "public, max-age=5";
|
caching = "public, max-age=60";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { renderToString } from "inferno-server";
|
||||||
import serialize from "serialize-javascript";
|
import serialize from "serialize-javascript";
|
||||||
import sharp from "sharp";
|
import sharp from "sharp";
|
||||||
import { favIconPngUrl, favIconUrl } from "../../shared/config";
|
import { favIconPngUrl, favIconUrl } from "../../shared/config";
|
||||||
import { ILemmyConfig, IsoDataOptionalSite } from "../../shared/interfaces";
|
import { IsoDataOptionalSite } from "../../shared/interfaces";
|
||||||
import { buildThemeList } from "./build-themes-list";
|
import { buildThemeList } from "./build-themes-list";
|
||||||
import { fetchIconPng } from "./fetch-icon-png";
|
import { fetchIconPng } from "./fetch-icon-png";
|
||||||
|
|
||||||
|
@ -14,7 +14,8 @@ let appleTouchIcon: string | undefined = undefined;
|
||||||
|
|
||||||
export async function createSsrHtml(
|
export async function createSsrHtml(
|
||||||
root: string,
|
root: string,
|
||||||
isoData: IsoDataOptionalSite
|
isoData: IsoDataOptionalSite,
|
||||||
|
cspNonce: string,
|
||||||
) {
|
) {
|
||||||
const site = isoData.site_res;
|
const site = isoData.site_res;
|
||||||
|
|
||||||
|
@ -22,10 +23,16 @@ export async function createSsrHtml(
|
||||||
(await buildThemeList())[0]
|
(await buildThemeList())[0]
|
||||||
}.css" />`;
|
}.css" />`;
|
||||||
|
|
||||||
|
const customHtmlHeaderScriptTag = new RegExp("<script", "g");
|
||||||
|
const customHtmlHeaderWithNonce = customHtmlHeader.replace(
|
||||||
|
customHtmlHeaderScriptTag,
|
||||||
|
`<script nonce="${cspNonce}"`,
|
||||||
|
);
|
||||||
|
|
||||||
if (!appleTouchIcon) {
|
if (!appleTouchIcon) {
|
||||||
appleTouchIcon = site?.site_view.site.icon
|
appleTouchIcon = site?.site_view.site.icon
|
||||||
? `data:image/png;base64,${await sharp(
|
? `data:image/png;base64,${await sharp(
|
||||||
await fetchIconPng(site.site_view.site.icon)
|
await fetchIconPng(site.site_view.site.icon),
|
||||||
)
|
)
|
||||||
.resize(180, 180)
|
.resize(180, 180)
|
||||||
.extend({
|
.extend({
|
||||||
|
@ -45,28 +52,28 @@ export async function createSsrHtml(
|
||||||
process.env["LEMMY_UI_DEBUG"] === "true"
|
process.env["LEMMY_UI_DEBUG"] === "true"
|
||||||
? renderToString(
|
? renderToString(
|
||||||
<>
|
<>
|
||||||
<script src="//cdn.jsdelivr.net/npm/eruda"></script>
|
<script
|
||||||
<script>eruda.init();</script>
|
nonce={cspNonce}
|
||||||
</>
|
src="//cdn.jsdelivr.net/npm/eruda"
|
||||||
|
></script>
|
||||||
|
<script nonce={cspNonce}>eruda.init();</script>
|
||||||
|
</>,
|
||||||
)
|
)
|
||||||
: "";
|
: "";
|
||||||
|
|
||||||
const helmet = Helmet.renderStatic();
|
const helmet = Helmet.renderStatic();
|
||||||
|
|
||||||
const config: ILemmyConfig = { wsHost: process.env.LEMMY_UI_LEMMY_WS_HOST };
|
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html ${helmet.htmlAttributes.toString()}>
|
<html ${helmet.htmlAttributes.toString()}>
|
||||||
<head>
|
<head>
|
||||||
<script>window.isoData = ${serialize(isoData)}</script>
|
<script nonce="${cspNonce}">window.isoData = ${serialize(isoData)}</script>
|
||||||
<script>window.lemmyConfig = ${serialize(config)}</script>
|
|
||||||
|
|
||||||
<!-- A remote debugging utility for mobile -->
|
<!-- A remote debugging utility for mobile -->
|
||||||
${erudaStr}
|
${erudaStr}
|
||||||
|
|
||||||
<!-- Custom injected script -->
|
<!-- Custom injected script -->
|
||||||
${customHtmlHeader}
|
${customHtmlHeaderWithNonce}
|
||||||
|
|
||||||
${helmet.title.toString()}
|
${helmet.title.toString()}
|
||||||
${helmet.meta.toString()}
|
${helmet.meta.toString()}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import { getHttpBaseExternal } from "@utils/env";
|
|
||||||
import { readFile } from "fs/promises";
|
import { readFile } from "fs/promises";
|
||||||
import { GetSiteResponse } from "lemmy-js-client";
|
import { GetSiteResponse } from "lemmy-js-client";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
|
@ -11,7 +10,7 @@ const defaultLogoPathDirectory = path.join(
|
||||||
process.cwd(),
|
process.cwd(),
|
||||||
"dist",
|
"dist",
|
||||||
"assets",
|
"assets",
|
||||||
"icons"
|
"icons",
|
||||||
);
|
);
|
||||||
|
|
||||||
export default async function ({
|
export default async function ({
|
||||||
|
@ -21,15 +20,13 @@ export default async function ({
|
||||||
local_site: { community_creation_admin_only },
|
local_site: { community_creation_admin_only },
|
||||||
},
|
},
|
||||||
}: GetSiteResponse) {
|
}: GetSiteResponse) {
|
||||||
const url = getHttpBaseExternal();
|
|
||||||
|
|
||||||
const icon = site.icon ? await fetchIconPng(site.icon) : null;
|
const icon = site.icon ? await fetchIconPng(site.icon) : null;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: site.name,
|
name: site.name,
|
||||||
description: site.description ?? "A link aggregator for the fediverse",
|
description: site.description ?? "A link aggregator for the fediverse",
|
||||||
start_url: url,
|
start_url: "/",
|
||||||
scope: url,
|
scope: "/",
|
||||||
display: "standalone",
|
display: "standalone",
|
||||||
id: "/",
|
id: "/",
|
||||||
background_color: "#222222",
|
background_color: "#222222",
|
||||||
|
@ -37,7 +34,7 @@ export default async function ({
|
||||||
icons: await Promise.all(
|
icons: await Promise.all(
|
||||||
iconSizes.map(async size => {
|
iconSizes.map(async size => {
|
||||||
let src = await readFile(
|
let src = await readFile(
|
||||||
path.join(defaultLogoPathDirectory, `icon-${size}x${size}.png`)
|
path.join(defaultLogoPathDirectory, `icon-${size}x${size}.png`),
|
||||||
).then(buf => buf.toString("base64"));
|
).then(buf => buf.toString("base64"));
|
||||||
|
|
||||||
if (icon) {
|
if (icon) {
|
||||||
|
@ -54,7 +51,7 @@ export default async function ({
|
||||||
src: `data:image/png;base64,${src}`,
|
src: `data:image/png;base64,${src}`,
|
||||||
purpose: "any maskable",
|
purpose: "any maskable",
|
||||||
};
|
};
|
||||||
})
|
}),
|
||||||
),
|
),
|
||||||
shortcuts: [
|
shortcuts: [
|
||||||
{
|
{
|
||||||
|
@ -76,7 +73,8 @@ export default async function ({
|
||||||
description: "Create a post.",
|
description: "Create a post.",
|
||||||
},
|
},
|
||||||
].concat(
|
].concat(
|
||||||
my_user?.local_user_view.person.admin || !community_creation_admin_only
|
my_user?.local_user_view.local_user.admin ||
|
||||||
|
!community_creation_admin_only
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
name: "Create Community",
|
name: "Create Community",
|
||||||
|
@ -85,7 +83,7 @@ export default async function ({
|
||||||
description: "Create a community",
|
description: "Create a community",
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
: []
|
: [],
|
||||||
),
|
),
|
||||||
related_applications: [
|
related_applications: [
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { isAuthPath, setIsoData } from "@utils/app";
|
import { isAnonymousPath, isAuthPath, setIsoData } from "@utils/app";
|
||||||
import { dataBsTheme } from "@utils/browser";
|
import { dataBsTheme } from "@utils/browser";
|
||||||
import { Component, RefObject, createRef, linkEvent } from "inferno";
|
import { Component, RefObject, createRef, linkEvent } from "inferno";
|
||||||
import { Provider } from "inferno-i18next-dess";
|
import { Provider } from "inferno-i18next-dess";
|
||||||
|
@ -14,6 +14,8 @@ import { Footer } from "./footer";
|
||||||
import { Navbar } from "./navbar";
|
import { Navbar } from "./navbar";
|
||||||
import "./styles.scss";
|
import "./styles.scss";
|
||||||
import { Theme } from "./theme";
|
import { Theme } from "./theme";
|
||||||
|
import AnonymousGuard from "../common/anonymous-guard";
|
||||||
|
import { CodeTheme } from "./code-theme";
|
||||||
|
|
||||||
interface AppProps {
|
interface AppProps {
|
||||||
user?: MyUserInfo;
|
user?: MyUserInfo;
|
||||||
|
@ -54,7 +56,10 @@ export class App extends Component<AppProps, any> {
|
||||||
{I18NextService.i18n.t("jump_to_content", "Jump to content")}
|
{I18NextService.i18n.t("jump_to_content", "Jump to content")}
|
||||||
</button>
|
</button>
|
||||||
{siteView && (
|
{siteView && (
|
||||||
<Theme defaultTheme={siteView.local_site.default_theme} />
|
<>
|
||||||
|
<Theme defaultTheme={siteView.local_site.default_theme} />
|
||||||
|
<CodeTheme />
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
<Navbar siteRes={siteRes} />
|
<Navbar siteRes={siteRes} />
|
||||||
<div className="mt-4 p-0 fl-1">
|
<div className="mt-4 p-0 fl-1">
|
||||||
|
@ -75,9 +80,13 @@ export class App extends Component<AppProps, any> {
|
||||||
<div tabIndex={-1}>
|
<div tabIndex={-1}>
|
||||||
{RouteComponent &&
|
{RouteComponent &&
|
||||||
(isAuthPath(path ?? "") ? (
|
(isAuthPath(path ?? "") ? (
|
||||||
<AuthGuard>
|
<AuthGuard {...routeProps}>
|
||||||
<RouteComponent {...routeProps} />
|
<RouteComponent {...routeProps} />
|
||||||
</AuthGuard>
|
</AuthGuard>
|
||||||
|
) : isAnonymousPath(path ?? "") ? (
|
||||||
|
<AnonymousGuard>
|
||||||
|
<RouteComponent {...routeProps} />
|
||||||
|
</AnonymousGuard>
|
||||||
) : (
|
) : (
|
||||||
<RouteComponent {...routeProps} />
|
<RouteComponent {...routeProps} />
|
||||||
))}
|
))}
|
||||||
|
@ -86,7 +95,7 @@ export class App extends Component<AppProps, any> {
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)
|
),
|
||||||
)}
|
)}
|
||||||
<Route component={ErrorPage} />
|
<Route component={ErrorPage} />
|
||||||
</Switch>
|
</Switch>
|
||||||
|
|
22
src/shared/components/app/code-theme.tsx
Normal file
22
src/shared/components/app/code-theme.tsx
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import { Component } from "inferno";
|
||||||
|
|
||||||
|
export class CodeTheme extends Component {
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
type="text/css"
|
||||||
|
href={`/css/code-themes/atom-one-light.css`}
|
||||||
|
media="(prefers-color-scheme: light)"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
type="text/css"
|
||||||
|
href={`/css/code-themes/atom-one-dark.css`}
|
||||||
|
media="(prefers-color-scheme: no-preference), (prefers-color-scheme: dark)"
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,4 @@
|
||||||
import { setIsoData } from "@utils/app";
|
import { setIsoData } from "@utils/app";
|
||||||
import { removeAuthParam } from "@utils/helpers";
|
|
||||||
import { Component } from "inferno";
|
import { Component } from "inferno";
|
||||||
import { T } from "inferno-i18next-dess";
|
import { T } from "inferno-i18next-dess";
|
||||||
import { Link } from "inferno-router";
|
import { Link } from "inferno-router";
|
||||||
|
@ -56,11 +55,7 @@ export class ErrorPage extends Component<any, any> {
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{errorPageData?.error && (
|
{errorPageData?.error && (
|
||||||
<T
|
<T i18nKey="error_code_message" parent="p">
|
||||||
i18nKey="error_code_message"
|
|
||||||
parent="p"
|
|
||||||
interpolation={{ error: removeAuthParam(errorPageData.error) }}
|
|
||||||
>
|
|
||||||
#<strong className="text-danger">#</strong>#
|
#<strong className="text-danger">#</strong>#
|
||||||
</T>
|
</T>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -1,31 +1,30 @@
|
||||||
import { myAuth, showAvatars } from "@utils/app";
|
import { showAvatars } from "@utils/app";
|
||||||
import { isBrowser } from "@utils/browser";
|
import { isBrowser } from "@utils/browser";
|
||||||
import { numToSI, poll } from "@utils/helpers";
|
import { numToSI } from "@utils/helpers";
|
||||||
import { amAdmin, canCreateCommunity } from "@utils/roles";
|
import { amAdmin, canCreateCommunity } from "@utils/roles";
|
||||||
import { Component, createRef, linkEvent } from "inferno";
|
import { Component, createRef, linkEvent } from "inferno";
|
||||||
import { NavLink } from "inferno-router";
|
import { NavLink } from "inferno-router";
|
||||||
|
import { GetSiteResponse } from "lemmy-js-client";
|
||||||
|
import { donateLemmyUrl } from "../../config";
|
||||||
import {
|
import {
|
||||||
GetReportCountResponse,
|
I18NextService,
|
||||||
GetSiteResponse,
|
UserService,
|
||||||
GetUnreadCountResponse,
|
UnreadCounterService,
|
||||||
GetUnreadRegistrationApplicationCountResponse,
|
} from "../../services";
|
||||||
} from "lemmy-js-client";
|
|
||||||
import { donateLemmyUrl, updateUnreadCountsInterval } from "../../config";
|
|
||||||
import { I18NextService, UserService } from "../../services";
|
|
||||||
import { HttpService, RequestState } from "../../services/HttpService";
|
|
||||||
import { toast } from "../../toast";
|
import { toast } from "../../toast";
|
||||||
import { Icon } from "../common/icon";
|
import { Icon } from "../common/icon";
|
||||||
import { PictrsImage } from "../common/pictrs-image";
|
import { PictrsImage } from "../common/pictrs-image";
|
||||||
|
import { Subscription } from "rxjs";
|
||||||
|
|
||||||
interface NavbarProps {
|
interface NavbarProps {
|
||||||
siteRes?: GetSiteResponse;
|
siteRes?: GetSiteResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface NavbarState {
|
interface NavbarState {
|
||||||
unreadInboxCountRes: RequestState<GetUnreadCountResponse>;
|
|
||||||
unreadReportCountRes: RequestState<GetReportCountResponse>;
|
|
||||||
unreadApplicationCountRes: RequestState<GetUnreadRegistrationApplicationCountResponse>;
|
|
||||||
onSiteBanner?(url: string): any;
|
onSiteBanner?(url: string): any;
|
||||||
|
unreadInboxCount: number;
|
||||||
|
unreadReportCount: number;
|
||||||
|
unreadApplicationCount: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleCollapseClick(i: Navbar) {
|
function handleCollapseClick(i: Navbar) {
|
||||||
|
@ -44,13 +43,17 @@ function handleLogOut(i: Navbar) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Navbar extends Component<NavbarProps, NavbarState> {
|
export class Navbar extends Component<NavbarProps, NavbarState> {
|
||||||
state: NavbarState = {
|
|
||||||
unreadInboxCountRes: { state: "empty" },
|
|
||||||
unreadReportCountRes: { state: "empty" },
|
|
||||||
unreadApplicationCountRes: { state: "empty" },
|
|
||||||
};
|
|
||||||
collapseButtonRef = createRef<HTMLButtonElement>();
|
collapseButtonRef = createRef<HTMLButtonElement>();
|
||||||
mobileMenuRef = createRef<HTMLDivElement>();
|
mobileMenuRef = createRef<HTMLDivElement>();
|
||||||
|
unreadInboxCountSubscription: Subscription;
|
||||||
|
unreadReportCountSubscription: Subscription;
|
||||||
|
unreadApplicationCountSubscription: Subscription;
|
||||||
|
|
||||||
|
state: NavbarState = {
|
||||||
|
unreadInboxCount: 0,
|
||||||
|
unreadReportCount: 0,
|
||||||
|
unreadApplicationCount: 0,
|
||||||
|
};
|
||||||
|
|
||||||
constructor(props: any, context: any) {
|
constructor(props: any, context: any) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
|
@ -63,7 +66,18 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
|
||||||
if (isBrowser()) {
|
if (isBrowser()) {
|
||||||
// On the first load, check the unreads
|
// On the first load, check the unreads
|
||||||
this.requestNotificationPermission();
|
this.requestNotificationPermission();
|
||||||
this.fetchUnreads();
|
this.unreadInboxCountSubscription =
|
||||||
|
UnreadCounterService.Instance.unreadInboxCountSubject.subscribe(
|
||||||
|
unreadInboxCount => this.setState({ unreadInboxCount }),
|
||||||
|
);
|
||||||
|
this.unreadReportCountSubscription =
|
||||||
|
UnreadCounterService.Instance.unreadReportCountSubject.subscribe(
|
||||||
|
unreadReportCount => this.setState({ unreadReportCount }),
|
||||||
|
);
|
||||||
|
this.unreadApplicationCountSubscription =
|
||||||
|
UnreadCounterService.Instance.unreadApplicationCountSubject.subscribe(
|
||||||
|
unreadApplicationCount => this.setState({ unreadApplicationCount }),
|
||||||
|
);
|
||||||
this.requestNotificationPermission();
|
this.requestNotificationPermission();
|
||||||
|
|
||||||
document.addEventListener("mouseup", this.handleOutsideMenuClick);
|
document.addEventListener("mouseup", this.handleOutsideMenuClick);
|
||||||
|
@ -72,6 +86,9 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
document.removeEventListener("mouseup", this.handleOutsideMenuClick);
|
document.removeEventListener("mouseup", this.handleOutsideMenuClick);
|
||||||
|
this.unreadInboxCountSubscription.unsubscribe();
|
||||||
|
this.unreadReportCountSubscription.unsubscribe();
|
||||||
|
this.unreadApplicationCountSubscription.unsubscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO class active corresponding to current pages
|
// TODO class active corresponding to current pages
|
||||||
|
@ -103,34 +120,34 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
|
||||||
to="/inbox"
|
to="/inbox"
|
||||||
className="p-1 nav-link border-0 nav-messages"
|
className="p-1 nav-link border-0 nav-messages"
|
||||||
title={I18NextService.i18n.t("unread_messages", {
|
title={I18NextService.i18n.t("unread_messages", {
|
||||||
count: Number(this.state.unreadApplicationCountRes.state),
|
count: Number(this.state.unreadInboxCount),
|
||||||
formattedCount: numToSI(this.unreadInboxCount),
|
formattedCount: numToSI(this.state.unreadInboxCount),
|
||||||
})}
|
})}
|
||||||
onMouseUp={linkEvent(this, handleCollapseClick)}
|
onMouseUp={linkEvent(this, handleCollapseClick)}
|
||||||
>
|
>
|
||||||
<Icon icon="bell" />
|
<Icon icon="bell" />
|
||||||
{this.unreadInboxCount > 0 && (
|
{this.state.unreadInboxCount > 0 && (
|
||||||
<span className="mx-1 badge text-bg-light">
|
<span className="mx-1 badge text-bg-light">
|
||||||
{numToSI(this.unreadInboxCount)}
|
{numToSI(this.state.unreadInboxCount)}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</li>
|
</li>
|
||||||
{this.moderatesSomething && (
|
{UserService.Instance.moderatesSomething && (
|
||||||
<li className="nav-item nav-item-icon">
|
<li className="nav-item nav-item-icon">
|
||||||
<NavLink
|
<NavLink
|
||||||
to="/reports"
|
to="/reports"
|
||||||
className="p-1 nav-link border-0"
|
className="p-1 nav-link border-0"
|
||||||
title={I18NextService.i18n.t("unread_reports", {
|
title={I18NextService.i18n.t("unread_reports", {
|
||||||
count: Number(this.unreadReportCount),
|
count: Number(this.state.unreadReportCount),
|
||||||
formattedCount: numToSI(this.unreadReportCount),
|
formattedCount: numToSI(this.state.unreadReportCount),
|
||||||
})}
|
})}
|
||||||
onMouseUp={linkEvent(this, handleCollapseClick)}
|
onMouseUp={linkEvent(this, handleCollapseClick)}
|
||||||
>
|
>
|
||||||
<Icon icon="shield" />
|
<Icon icon="shield" />
|
||||||
{this.unreadReportCount > 0 && (
|
{this.state.unreadReportCount > 0 && (
|
||||||
<span className="mx-1 badge text-bg-light">
|
<span className="mx-1 badge text-bg-light">
|
||||||
{numToSI(this.unreadReportCount)}
|
{numToSI(this.state.unreadReportCount)}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</NavLink>
|
</NavLink>
|
||||||
|
@ -144,16 +161,18 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
|
||||||
title={I18NextService.i18n.t(
|
title={I18NextService.i18n.t(
|
||||||
"unread_registration_applications",
|
"unread_registration_applications",
|
||||||
{
|
{
|
||||||
count: Number(this.unreadApplicationCount),
|
count: Number(this.state.unreadApplicationCount),
|
||||||
formattedCount: numToSI(this.unreadApplicationCount),
|
formattedCount: numToSI(
|
||||||
}
|
this.state.unreadApplicationCount,
|
||||||
|
),
|
||||||
|
},
|
||||||
)}
|
)}
|
||||||
onMouseUp={linkEvent(this, handleCollapseClick)}
|
onMouseUp={linkEvent(this, handleCollapseClick)}
|
||||||
>
|
>
|
||||||
<Icon icon="clipboard" />
|
<Icon icon="clipboard" />
|
||||||
{this.unreadApplicationCount > 0 && (
|
{this.state.unreadApplicationCount > 0 && (
|
||||||
<span className="mx-1 badge text-bg-light">
|
<span className="mx-1 badge text-bg-light">
|
||||||
{numToSI(this.unreadApplicationCount)}
|
{numToSI(this.state.unreadApplicationCount)}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</NavLink>
|
</NavLink>
|
||||||
|
@ -268,46 +287,48 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
|
||||||
className="nav-link d-inline-flex align-items-center d-md-inline-block"
|
className="nav-link d-inline-flex align-items-center d-md-inline-block"
|
||||||
to="/inbox"
|
to="/inbox"
|
||||||
title={I18NextService.i18n.t("unread_messages", {
|
title={I18NextService.i18n.t("unread_messages", {
|
||||||
count: Number(this.unreadInboxCount),
|
count: Number(this.state.unreadInboxCount),
|
||||||
formattedCount: numToSI(this.unreadInboxCount),
|
formattedCount: numToSI(this.state.unreadInboxCount),
|
||||||
})}
|
})}
|
||||||
onMouseUp={linkEvent(this, handleCollapseClick)}
|
onMouseUp={linkEvent(this, handleCollapseClick)}
|
||||||
>
|
>
|
||||||
<Icon icon="bell" />
|
<Icon icon="bell" />
|
||||||
<span className="badge text-bg-light d-inline ms-1 d-md-none ms-md-0">
|
<span className="badge text-bg-light d-inline ms-1 d-md-none ms-md-0">
|
||||||
{I18NextService.i18n.t("unread_messages", {
|
{I18NextService.i18n.t("unread_messages", {
|
||||||
count: Number(this.unreadInboxCount),
|
count: Number(this.state.unreadInboxCount),
|
||||||
formattedCount: numToSI(this.unreadInboxCount),
|
formattedCount: numToSI(this.state.unreadInboxCount),
|
||||||
})}
|
})}
|
||||||
</span>
|
</span>
|
||||||
{this.unreadInboxCount > 0 && (
|
{this.state.unreadInboxCount > 0 && (
|
||||||
<span className="mx-1 badge text-bg-light">
|
<span className="mx-1 badge text-bg-light">
|
||||||
{numToSI(this.unreadInboxCount)}
|
{numToSI(this.state.unreadInboxCount)}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</li>
|
</li>
|
||||||
{this.moderatesSomething && (
|
{UserService.Instance.moderatesSomething && (
|
||||||
<li id="navModeration" className="nav-item">
|
<li id="navModeration" className="nav-item">
|
||||||
<NavLink
|
<NavLink
|
||||||
className="nav-link d-inline-flex align-items-center d-md-inline-block"
|
className="nav-link d-inline-flex align-items-center d-md-inline-block"
|
||||||
to="/reports"
|
to="/reports"
|
||||||
title={I18NextService.i18n.t("unread_reports", {
|
title={I18NextService.i18n.t("unread_reports", {
|
||||||
count: Number(this.unreadReportCount),
|
count: Number(this.state.unreadReportCount),
|
||||||
formattedCount: numToSI(this.unreadReportCount),
|
formattedCount: numToSI(this.state.unreadReportCount),
|
||||||
})}
|
})}
|
||||||
onMouseUp={linkEvent(this, handleCollapseClick)}
|
onMouseUp={linkEvent(this, handleCollapseClick)}
|
||||||
>
|
>
|
||||||
<Icon icon="shield" />
|
<Icon icon="shield" />
|
||||||
<span className="badge text-bg-light d-inline ms-1 d-md-none ms-md-0">
|
<span className="badge text-bg-light d-inline ms-1 d-md-none ms-md-0">
|
||||||
{I18NextService.i18n.t("unread_reports", {
|
{I18NextService.i18n.t("unread_reports", {
|
||||||
count: Number(this.unreadReportCount),
|
count: Number(this.state.unreadReportCount),
|
||||||
formattedCount: numToSI(this.unreadReportCount),
|
formattedCount: numToSI(
|
||||||
|
this.state.unreadReportCount,
|
||||||
|
),
|
||||||
})}
|
})}
|
||||||
</span>
|
</span>
|
||||||
{this.unreadReportCount > 0 && (
|
{this.state.unreadReportCount > 0 && (
|
||||||
<span className="mx-1 badge text-bg-light">
|
<span className="mx-1 badge text-bg-light">
|
||||||
{numToSI(this.unreadReportCount)}
|
{numToSI(this.state.unreadReportCount)}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</NavLink>
|
</NavLink>
|
||||||
|
@ -321,11 +342,11 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
|
||||||
title={I18NextService.i18n.t(
|
title={I18NextService.i18n.t(
|
||||||
"unread_registration_applications",
|
"unread_registration_applications",
|
||||||
{
|
{
|
||||||
count: Number(this.unreadApplicationCount),
|
count: Number(this.state.unreadApplicationCount),
|
||||||
formattedCount: numToSI(
|
formattedCount: numToSI(
|
||||||
this.unreadApplicationCount
|
this.state.unreadApplicationCount,
|
||||||
),
|
),
|
||||||
}
|
},
|
||||||
)}
|
)}
|
||||||
onMouseUp={linkEvent(this, handleCollapseClick)}
|
onMouseUp={linkEvent(this, handleCollapseClick)}
|
||||||
>
|
>
|
||||||
|
@ -334,16 +355,16 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
|
||||||
{I18NextService.i18n.t(
|
{I18NextService.i18n.t(
|
||||||
"unread_registration_applications",
|
"unread_registration_applications",
|
||||||
{
|
{
|
||||||
count: Number(this.unreadApplicationCount),
|
count: Number(this.state.unreadApplicationCount),
|
||||||
formattedCount: numToSI(
|
formattedCount: numToSI(
|
||||||
this.unreadApplicationCount
|
this.state.unreadApplicationCount,
|
||||||
),
|
),
|
||||||
}
|
},
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
{this.unreadApplicationCount > 0 && (
|
{this.state.unreadApplicationCount > 0 && (
|
||||||
<span className="mx-1 badge text-bg-light">
|
<span className="mx-1 badge text-bg-light">
|
||||||
{numToSI(this.unreadApplicationCount)}
|
{numToSI(this.state.unreadApplicationCount)}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</NavLink>
|
</NavLink>
|
||||||
|
@ -441,75 +462,6 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get moderatesSomething(): boolean {
|
|
||||||
const mods = UserService.Instance.myUserInfo?.moderates;
|
|
||||||
const moderatesS = (mods && mods.length > 0) || false;
|
|
||||||
return amAdmin() || moderatesS;
|
|
||||||
}
|
|
||||||
|
|
||||||
fetchUnreads() {
|
|
||||||
poll(async () => {
|
|
||||||
if (window.document.visibilityState !== "hidden") {
|
|
||||||
const auth = myAuth();
|
|
||||||
if (auth) {
|
|
||||||
this.setState({
|
|
||||||
unreadInboxCountRes: await HttpService.client.getUnreadCount({
|
|
||||||
auth,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (this.moderatesSomething) {
|
|
||||||
this.setState({
|
|
||||||
unreadReportCountRes: await HttpService.client.getReportCount({
|
|
||||||
auth,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (amAdmin()) {
|
|
||||||
this.setState({
|
|
||||||
unreadApplicationCountRes:
|
|
||||||
await HttpService.client.getUnreadRegistrationApplicationCount({
|
|
||||||
auth,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, updateUnreadCountsInterval);
|
|
||||||
}
|
|
||||||
|
|
||||||
get unreadInboxCount(): number {
|
|
||||||
if (this.state.unreadInboxCountRes.state == "success") {
|
|
||||||
const data = this.state.unreadInboxCountRes.data;
|
|
||||||
return data.replies + data.mentions + data.private_messages;
|
|
||||||
} else {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get unreadReportCount(): number {
|
|
||||||
if (this.state.unreadReportCountRes.state == "success") {
|
|
||||||
const data = this.state.unreadReportCountRes.data;
|
|
||||||
return (
|
|
||||||
data.post_reports +
|
|
||||||
data.comment_reports +
|
|
||||||
(data.private_message_reports ?? 0)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get unreadApplicationCount(): number {
|
|
||||||
if (this.state.unreadApplicationCountRes.state == "success") {
|
|
||||||
const data = this.state.unreadApplicationCountRes.data;
|
|
||||||
return data.registration_applications;
|
|
||||||
} else {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get currentLocation() {
|
get currentLocation() {
|
||||||
return this.context.router.history.location.pathname;
|
return this.context.router.history.location.pathname;
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,10 @@ export class Theme extends Component<Props> {
|
||||||
/>
|
/>
|
||||||
</Helmet>
|
</Helmet>
|
||||||
);
|
);
|
||||||
} else if (this.props.defaultTheme != "browser") {
|
} else if (
|
||||||
|
this.props.defaultTheme !== "browser" &&
|
||||||
|
this.props.defaultTheme !== "browser-compact"
|
||||||
|
) {
|
||||||
return (
|
return (
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<link
|
<link
|
||||||
|
@ -31,6 +34,25 @@ export class Theme extends Component<Props> {
|
||||||
/>
|
/>
|
||||||
</Helmet>
|
</Helmet>
|
||||||
);
|
);
|
||||||
|
} else if (this.props.defaultTheme === "browser-compact") {
|
||||||
|
return (
|
||||||
|
<Helmet>
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
type="text/css"
|
||||||
|
href="/css/themes/litely-compact.css"
|
||||||
|
id="default-light"
|
||||||
|
media="(prefers-color-scheme: light)"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
type="text/css"
|
||||||
|
href="/css/themes/darkly-compact.css"
|
||||||
|
id="default-dark"
|
||||||
|
media="(prefers-color-scheme: no-preference), (prefers-color-scheme: dark)"
|
||||||
|
/>
|
||||||
|
</Helmet>
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<Helmet>
|
<Helmet>
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import { myAuthRequired } from "@utils/app";
|
|
||||||
import { capitalizeFirstLetter } from "@utils/helpers";
|
import { capitalizeFirstLetter } from "@utils/helpers";
|
||||||
import { Component } from "inferno";
|
import { Component } from "inferno";
|
||||||
import { T } from "inferno-i18next-dess";
|
import { T } from "inferno-i18next-dess";
|
||||||
|
@ -43,7 +42,7 @@ export class CommentForm extends Component<CommentFormProps, any> {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={["comment-form", "mb-3", this.props.containerClass].join(
|
className={["comment-form", "mb-3", this.props.containerClass].join(
|
||||||
" "
|
" ",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{UserService.Instance.myUserInfo ? (
|
{UserService.Instance.myUserInfo ? (
|
||||||
|
@ -84,7 +83,7 @@ export class CommentForm extends Component<CommentFormProps, any> {
|
||||||
: capitalizeFirstLetter(I18NextService.i18n.t("reply"));
|
: capitalizeFirstLetter(I18NextService.i18n.t("reply"));
|
||||||
}
|
}
|
||||||
|
|
||||||
handleCommentSubmit(content: string, form_id: string, language_id?: number) {
|
handleCommentSubmit(content: string, language_id?: number) {
|
||||||
const { node, onUpsertComment, edit } = this.props;
|
const { node, onUpsertComment, edit } = this.props;
|
||||||
if (typeof node === "number") {
|
if (typeof node === "number") {
|
||||||
const post_id = node;
|
const post_id = node;
|
||||||
|
@ -92,8 +91,6 @@ export class CommentForm extends Component<CommentFormProps, any> {
|
||||||
content,
|
content,
|
||||||
post_id,
|
post_id,
|
||||||
language_id,
|
language_id,
|
||||||
form_id,
|
|
||||||
auth: myAuthRequired(),
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
if (edit) {
|
if (edit) {
|
||||||
|
@ -101,9 +98,7 @@ export class CommentForm extends Component<CommentFormProps, any> {
|
||||||
onUpsertComment({
|
onUpsertComment({
|
||||||
content,
|
content,
|
||||||
comment_id,
|
comment_id,
|
||||||
form_id,
|
|
||||||
language_id,
|
language_id,
|
||||||
auth: myAuthRequired(),
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const post_id = node.comment_view.post.id;
|
const post_id = node.comment_view.post.id;
|
||||||
|
@ -112,9 +107,7 @@ export class CommentForm extends Component<CommentFormProps, any> {
|
||||||
content,
|
content,
|
||||||
parent_id,
|
parent_id,
|
||||||
post_id,
|
post_id,
|
||||||
form_id,
|
|
||||||
language_id,
|
language_id,
|
||||||
auth: myAuthRequired(),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,4 @@
|
||||||
import {
|
import { colorList, getCommentParentId, showScores } from "@utils/app";
|
||||||
colorList,
|
|
||||||
getCommentParentId,
|
|
||||||
myAuth,
|
|
||||||
myAuthRequired,
|
|
||||||
showScores,
|
|
||||||
} from "@utils/app";
|
|
||||||
import { futureDaysToUnixTime, numToSI } from "@utils/helpers";
|
import { futureDaysToUnixTime, numToSI } from "@utils/helpers";
|
||||||
import {
|
import {
|
||||||
amCommunityCreator,
|
amCommunityCreator,
|
||||||
|
@ -68,6 +62,7 @@ import { CommunityLink } from "../community/community-link";
|
||||||
import { PersonListing } from "../person/person-listing";
|
import { PersonListing } from "../person/person-listing";
|
||||||
import { CommentForm } from "./comment-form";
|
import { CommentForm } from "./comment-form";
|
||||||
import { CommentNodes } from "./comment-nodes";
|
import { CommentNodes } from "./comment-nodes";
|
||||||
|
import ReportForm from "../common/report-form";
|
||||||
|
|
||||||
interface CommentNodeState {
|
interface CommentNodeState {
|
||||||
showReply: boolean;
|
showReply: boolean;
|
||||||
|
@ -90,7 +85,6 @@ interface CommentNodeState {
|
||||||
viewSource: boolean;
|
viewSource: boolean;
|
||||||
showAdvanced: boolean;
|
showAdvanced: boolean;
|
||||||
showReportDialog: boolean;
|
showReportDialog: boolean;
|
||||||
reportReason?: string;
|
|
||||||
createOrEditCommentLoading: boolean;
|
createOrEditCommentLoading: boolean;
|
||||||
upvoteLoading: boolean;
|
upvoteLoading: boolean;
|
||||||
downvoteLoading: boolean;
|
downvoteLoading: boolean;
|
||||||
|
@ -105,7 +99,6 @@ interface CommentNodeState {
|
||||||
addAdminLoading: boolean;
|
addAdminLoading: boolean;
|
||||||
transferCommunityLoading: boolean;
|
transferCommunityLoading: boolean;
|
||||||
fetchChildrenLoading: boolean;
|
fetchChildrenLoading: boolean;
|
||||||
reportLoading: boolean;
|
|
||||||
purgeLoading: boolean;
|
purgeLoading: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,7 +107,7 @@ interface CommentNodeProps {
|
||||||
moderators?: CommunityModeratorView[];
|
moderators?: CommunityModeratorView[];
|
||||||
admins?: PersonView[];
|
admins?: PersonView[];
|
||||||
noBorder?: boolean;
|
noBorder?: boolean;
|
||||||
noIndent?: boolean;
|
isTopLevel?: boolean;
|
||||||
viewOnly?: boolean;
|
viewOnly?: boolean;
|
||||||
locked?: boolean;
|
locked?: boolean;
|
||||||
markable?: boolean;
|
markable?: boolean;
|
||||||
|
@ -179,7 +172,6 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
addAdminLoading: false,
|
addAdminLoading: false,
|
||||||
transferCommunityLoading: false,
|
transferCommunityLoading: false,
|
||||||
fetchChildrenLoading: false,
|
fetchChildrenLoading: false,
|
||||||
reportLoading: false,
|
|
||||||
purgeLoading: false,
|
purgeLoading: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -187,6 +179,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
|
|
||||||
this.handleReplyCancel = this.handleReplyCancel.bind(this);
|
this.handleReplyCancel = this.handleReplyCancel.bind(this);
|
||||||
|
this.handleReportComment = this.handleReportComment.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
get commentView(): CommentView {
|
get commentView(): CommentView {
|
||||||
|
@ -198,11 +191,10 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(
|
componentWillReceiveProps(
|
||||||
nextProps: Readonly<{ children?: InfernoNode } & CommentNodeProps>
|
nextProps: Readonly<{ children?: InfernoNode } & CommentNodeProps>,
|
||||||
): void {
|
): void {
|
||||||
if (!deepEqual(this.props, nextProps)) {
|
if (!deepEqual(this.props, nextProps)) {
|
||||||
this.setState({
|
this.setState({
|
||||||
showReply: false,
|
|
||||||
showEdit: false,
|
showEdit: false,
|
||||||
showRemoveDialog: false,
|
showRemoveDialog: false,
|
||||||
showBanDialog: false,
|
showBanDialog: false,
|
||||||
|
@ -232,7 +224,6 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
addAdminLoading: false,
|
addAdminLoading: false,
|
||||||
transferCommunityLoading: false,
|
transferCommunityLoading: false,
|
||||||
fetchChildrenLoading: false,
|
fetchChildrenLoading: false,
|
||||||
reportLoading: false,
|
|
||||||
purgeLoading: false,
|
purgeLoading: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -243,34 +234,34 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
const cv = this.commentView;
|
const cv = this.commentView;
|
||||||
|
|
||||||
const purgeTypeText =
|
const purgeTypeText =
|
||||||
this.state.purgeType == PurgeType.Comment
|
this.state.purgeType === PurgeType.Comment
|
||||||
? I18NextService.i18n.t("purge_comment")
|
? I18NextService.i18n.t("purge_comment")
|
||||||
: `${I18NextService.i18n.t("purge")} ${cv.creator.name}`;
|
: `${I18NextService.i18n.t("purge")} ${cv.creator.name}`;
|
||||||
|
|
||||||
const canMod_ = canMod(
|
const canMod_ = canMod(
|
||||||
cv.creator.id,
|
cv.creator.id,
|
||||||
this.props.moderators,
|
this.props.moderators,
|
||||||
this.props.admins
|
this.props.admins,
|
||||||
);
|
);
|
||||||
const canModOnSelf = canMod(
|
const canModOnSelf = canMod(
|
||||||
cv.creator.id,
|
cv.creator.id,
|
||||||
this.props.moderators,
|
this.props.moderators,
|
||||||
this.props.admins,
|
this.props.admins,
|
||||||
UserService.Instance.myUserInfo,
|
UserService.Instance.myUserInfo,
|
||||||
true
|
true,
|
||||||
);
|
);
|
||||||
const canAdmin_ = canAdmin(cv.creator.id, this.props.admins);
|
const canAdmin_ = canAdmin(cv.creator.id, this.props.admins);
|
||||||
const canAdminOnSelf = canAdmin(
|
const canAdminOnSelf = canAdmin(
|
||||||
cv.creator.id,
|
cv.creator.id,
|
||||||
this.props.admins,
|
this.props.admins,
|
||||||
UserService.Instance.myUserInfo,
|
UserService.Instance.myUserInfo,
|
||||||
true
|
true,
|
||||||
);
|
);
|
||||||
const isMod_ = isMod(cv.creator.id, this.props.moderators);
|
const isMod_ = isMod(cv.creator.id, this.props.moderators);
|
||||||
const isAdmin_ = isAdmin(cv.creator.id, this.props.admins);
|
const isAdmin_ = isAdmin(cv.creator.id, this.props.admins);
|
||||||
const amCommunityCreator_ = amCommunityCreator(
|
const amCommunityCreator_ = amCommunityCreator(
|
||||||
cv.creator.id,
|
cv.creator.id,
|
||||||
this.props.moderators
|
this.props.moderators,
|
||||||
);
|
);
|
||||||
|
|
||||||
const moreRepliesBorderColor = this.props.node.depth
|
const moreRepliesBorderColor = this.props.node.depth
|
||||||
|
@ -278,13 +269,13 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
: colorList[0];
|
: colorList[0];
|
||||||
|
|
||||||
const showMoreChildren =
|
const showMoreChildren =
|
||||||
this.props.viewType == CommentViewType.Tree &&
|
this.props.viewType === CommentViewType.Tree &&
|
||||||
!this.state.collapsed &&
|
!this.state.collapsed &&
|
||||||
node.children.length == 0 &&
|
node.children.length === 0 &&
|
||||||
node.comment_view.counts.child_count > 0;
|
node.comment_view.counts.child_count > 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li className="comment">
|
<li className="comment list-unstyled">
|
||||||
<article
|
<article
|
||||||
id={`comment-${cv.comment.id}`}
|
id={`comment-${cv.comment.id}`}
|
||||||
className={classNames(`details comment-node py-2`, {
|
className={classNames(`details comment-node py-2`, {
|
||||||
|
@ -292,11 +283,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
mark: this.isCommentNew || this.commentView.comment.distinguished,
|
mark: this.isCommentNew || this.commentView.comment.distinguished,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<div
|
<div className="ms-2">
|
||||||
className={classNames({
|
|
||||||
"ms-2": !this.props.noIndent,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<div className="d-flex flex-wrap align-items-center text-muted small">
|
<div className="d-flex flex-wrap align-items-center text-muted small">
|
||||||
<button
|
<button
|
||||||
className="btn btn-sm btn-link text-muted me-2"
|
className="btn btn-sm btn-link text-muted me-2"
|
||||||
|
@ -341,7 +328,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
<span className="badge text-bg-light d-none d-sm-inline me-2">
|
<span className="badge text-bg-light d-none d-sm-inline me-2">
|
||||||
{
|
{
|
||||||
this.props.allLanguages.find(
|
this.props.allLanguages.find(
|
||||||
lang => lang.id === cv.comment.language_id
|
lang => lang.id === cv.comment.language_id,
|
||||||
)?.name
|
)?.name
|
||||||
}
|
}
|
||||||
</span>
|
</span>
|
||||||
|
@ -352,7 +339,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
{showScores() && (
|
{showScores() && (
|
||||||
<>
|
<>
|
||||||
<span
|
<span
|
||||||
className="me-1 fw-bold"
|
className={`me-1 fw-bold ${this.scoreColor}`}
|
||||||
aria-label={I18NextService.i18n.t("number_of_points", {
|
aria-label={I18NextService.i18n.t("number_of_points", {
|
||||||
count: Number(this.commentView.counts.score),
|
count: Number(this.commentView.counts.score),
|
||||||
formattedCount: numToSI(this.commentView.counts.score),
|
formattedCount: numToSI(this.commentView.counts.score),
|
||||||
|
@ -378,7 +365,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
onReplyCancel={this.handleReplyCancel}
|
onReplyCancel={this.handleReplyCancel}
|
||||||
disabled={this.props.locked}
|
disabled={this.props.locked}
|
||||||
finished={this.props.finished.get(
|
finished={this.props.finished.get(
|
||||||
this.props.node.comment_view.comment.id
|
this.props.node.comment_view.comment.id,
|
||||||
)}
|
)}
|
||||||
focus
|
focus
|
||||||
allLanguages={this.props.allLanguages}
|
allLanguages={this.props.allLanguages}
|
||||||
|
@ -388,20 +375,22 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{!this.state.showEdit && !this.state.collapsed && (
|
{!this.state.showEdit && !this.state.collapsed && (
|
||||||
<div>
|
<>
|
||||||
{this.state.viewSource ? (
|
<div className="comment-content">
|
||||||
<pre>{this.commentUnlessRemoved}</pre>
|
{this.state.viewSource ? (
|
||||||
) : (
|
<pre>{this.commentUnlessRemoved}</pre>
|
||||||
<div
|
) : (
|
||||||
className="md-div"
|
<div
|
||||||
dangerouslySetInnerHTML={
|
className="md-div"
|
||||||
this.props.hideImages
|
dangerouslySetInnerHTML={
|
||||||
? mdToHtmlNoImages(this.commentUnlessRemoved)
|
this.props.hideImages
|
||||||
: mdToHtml(this.commentUnlessRemoved)
|
? mdToHtmlNoImages(this.commentUnlessRemoved)
|
||||||
}
|
: mdToHtml(this.commentUnlessRemoved)
|
||||||
/>
|
}
|
||||||
)}
|
/>
|
||||||
<div className="d-flex justify-content-between justify-content-lg-start flex-wrap text-muted fw-bold">
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="comment-bottom-btns d-flex justify-content-between justify-content-lg-start flex-wrap text-muted fw-bold">
|
||||||
{this.props.showContext && this.getLinkButton()}
|
{this.props.showContext && this.getLinkButton()}
|
||||||
{this.props.markable && (
|
{this.props.markable && (
|
||||||
<button
|
<button
|
||||||
|
@ -474,13 +463,13 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
className="btn btn-link btn-animate text-muted"
|
className="btn btn-link btn-animate text-muted"
|
||||||
onClick={linkEvent(
|
onClick={linkEvent(
|
||||||
this,
|
this,
|
||||||
this.handleShowReportDialog
|
this.handleShowReportDialog,
|
||||||
)}
|
)}
|
||||||
data-tippy-content={I18NextService.i18n.t(
|
data-tippy-content={I18NextService.i18n.t(
|
||||||
"show_report_dialog"
|
"show_report_dialog",
|
||||||
)}
|
)}
|
||||||
aria-label={I18NextService.i18n.t(
|
aria-label={I18NextService.i18n.t(
|
||||||
"show_report_dialog"
|
"show_report_dialog",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Icon icon="flag" />
|
<Icon icon="flag" />
|
||||||
|
@ -489,10 +478,10 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
className="btn btn-link btn-animate text-muted"
|
className="btn btn-link btn-animate text-muted"
|
||||||
onClick={linkEvent(
|
onClick={linkEvent(
|
||||||
this,
|
this,
|
||||||
this.handleBlockPerson
|
this.handleBlockPerson,
|
||||||
)}
|
)}
|
||||||
data-tippy-content={I18NextService.i18n.t(
|
data-tippy-content={I18NextService.i18n.t(
|
||||||
"block_user"
|
"block_user",
|
||||||
)}
|
)}
|
||||||
aria-label={I18NextService.i18n.t("block_user")}
|
aria-label={I18NextService.i18n.t("block_user")}
|
||||||
>
|
>
|
||||||
|
@ -533,7 +522,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
className="btn btn-link btn-animate text-muted"
|
className="btn btn-link btn-animate text-muted"
|
||||||
onClick={linkEvent(this, this.handleViewSource)}
|
onClick={linkEvent(this, this.handleViewSource)}
|
||||||
data-tippy-content={I18NextService.i18n.t(
|
data-tippy-content={I18NextService.i18n.t(
|
||||||
"view_source"
|
"view_source",
|
||||||
)}
|
)}
|
||||||
aria-label={I18NextService.i18n.t("view_source")}
|
aria-label={I18NextService.i18n.t("view_source")}
|
||||||
>
|
>
|
||||||
|
@ -550,7 +539,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
className="btn btn-link btn-animate text-muted"
|
className="btn btn-link btn-animate text-muted"
|
||||||
onClick={linkEvent(this, this.handleEditClick)}
|
onClick={linkEvent(this, this.handleEditClick)}
|
||||||
data-tippy-content={I18NextService.i18n.t(
|
data-tippy-content={I18NextService.i18n.t(
|
||||||
"edit"
|
"edit",
|
||||||
)}
|
)}
|
||||||
aria-label={I18NextService.i18n.t("edit")}
|
aria-label={I18NextService.i18n.t("edit")}
|
||||||
>
|
>
|
||||||
|
@ -560,7 +549,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
className="btn btn-link btn-animate text-muted"
|
className="btn btn-link btn-animate text-muted"
|
||||||
onClick={linkEvent(
|
onClick={linkEvent(
|
||||||
this,
|
this,
|
||||||
this.handleDeleteComment
|
this.handleDeleteComment,
|
||||||
)}
|
)}
|
||||||
data-tippy-content={
|
data-tippy-content={
|
||||||
!cv.comment.deleted
|
!cv.comment.deleted
|
||||||
|
@ -590,7 +579,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
className="btn btn-link btn-animate text-muted"
|
className="btn btn-link btn-animate text-muted"
|
||||||
onClick={linkEvent(
|
onClick={linkEvent(
|
||||||
this,
|
this,
|
||||||
this.handleDistinguishComment
|
this.handleDistinguishComment,
|
||||||
)}
|
)}
|
||||||
data-tippy-content={
|
data-tippy-content={
|
||||||
!cv.comment.distinguished
|
!cv.comment.distinguished
|
||||||
|
@ -621,7 +610,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
className="btn btn-link btn-animate text-muted"
|
className="btn btn-link btn-animate text-muted"
|
||||||
onClick={linkEvent(
|
onClick={linkEvent(
|
||||||
this,
|
this,
|
||||||
this.handleModRemoveShow
|
this.handleModRemoveShow,
|
||||||
)}
|
)}
|
||||||
aria-label={I18NextService.i18n.t("remove")}
|
aria-label={I18NextService.i18n.t("remove")}
|
||||||
>
|
>
|
||||||
|
@ -632,7 +621,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
className="btn btn-link btn-animate text-muted"
|
className="btn btn-link btn-animate text-muted"
|
||||||
onClick={linkEvent(
|
onClick={linkEvent(
|
||||||
this,
|
this,
|
||||||
this.handleRemoveComment
|
this.handleRemoveComment,
|
||||||
)}
|
)}
|
||||||
aria-label={I18NextService.i18n.t("restore")}
|
aria-label={I18NextService.i18n.t("restore")}
|
||||||
>
|
>
|
||||||
|
@ -654,14 +643,14 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
className="btn btn-link btn-animate text-muted"
|
className="btn btn-link btn-animate text-muted"
|
||||||
onClick={linkEvent(
|
onClick={linkEvent(
|
||||||
this,
|
this,
|
||||||
this.handleModBanFromCommunityShow
|
this.handleModBanFromCommunityShow,
|
||||||
)}
|
)}
|
||||||
aria-label={I18NextService.i18n.t(
|
aria-label={I18NextService.i18n.t(
|
||||||
"ban_from_community"
|
"ban_from_community",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{I18NextService.i18n.t(
|
{I18NextService.i18n.t(
|
||||||
"ban_from_community"
|
"ban_from_community",
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
) : (
|
) : (
|
||||||
|
@ -669,7 +658,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
className="btn btn-link btn-animate text-muted"
|
className="btn btn-link btn-animate text-muted"
|
||||||
onClick={linkEvent(
|
onClick={linkEvent(
|
||||||
this,
|
this,
|
||||||
this.handleBanPersonFromCommunity
|
this.handleBanPersonFromCommunity,
|
||||||
)}
|
)}
|
||||||
aria-label={I18NextService.i18n.t("unban")}
|
aria-label={I18NextService.i18n.t("unban")}
|
||||||
>
|
>
|
||||||
|
@ -686,13 +675,13 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
className="btn btn-link btn-animate text-muted"
|
className="btn btn-link btn-animate text-muted"
|
||||||
onClick={linkEvent(
|
onClick={linkEvent(
|
||||||
this,
|
this,
|
||||||
this.handleShowConfirmAppointAsMod
|
this.handleShowConfirmAppointAsMod,
|
||||||
)}
|
)}
|
||||||
aria-label={
|
aria-label={
|
||||||
isMod_
|
isMod_
|
||||||
? I18NextService.i18n.t("remove_as_mod")
|
? I18NextService.i18n.t("remove_as_mod")
|
||||||
: I18NextService.i18n.t(
|
: I18NextService.i18n.t(
|
||||||
"appoint_as_mod"
|
"appoint_as_mod",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
@ -705,7 +694,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
<button
|
<button
|
||||||
className="btn btn-link btn-animate text-muted"
|
className="btn btn-link btn-animate text-muted"
|
||||||
aria-label={I18NextService.i18n.t(
|
aria-label={I18NextService.i18n.t(
|
||||||
"are_you_sure"
|
"are_you_sure",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{I18NextService.i18n.t("are_you_sure")}
|
{I18NextService.i18n.t("are_you_sure")}
|
||||||
|
@ -714,7 +703,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
className="btn btn-link btn-animate text-muted"
|
className="btn btn-link btn-animate text-muted"
|
||||||
onClick={linkEvent(
|
onClick={linkEvent(
|
||||||
this,
|
this,
|
||||||
this.handleAddModToCommunity
|
this.handleAddModToCommunity,
|
||||||
)}
|
)}
|
||||||
aria-label={I18NextService.i18n.t("yes")}
|
aria-label={I18NextService.i18n.t("yes")}
|
||||||
>
|
>
|
||||||
|
@ -728,7 +717,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
className="btn btn-link btn-animate text-muted"
|
className="btn btn-link btn-animate text-muted"
|
||||||
onClick={linkEvent(
|
onClick={linkEvent(
|
||||||
this,
|
this,
|
||||||
this.handleCancelConfirmAppointAsMod
|
this.handleCancelConfirmAppointAsMod,
|
||||||
)}
|
)}
|
||||||
aria-label={I18NextService.i18n.t("no")}
|
aria-label={I18NextService.i18n.t("no")}
|
||||||
>
|
>
|
||||||
|
@ -747,10 +736,10 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
className="btn btn-link btn-animate text-muted"
|
className="btn btn-link btn-animate text-muted"
|
||||||
onClick={linkEvent(
|
onClick={linkEvent(
|
||||||
this,
|
this,
|
||||||
this.handleShowConfirmTransferCommunity
|
this.handleShowConfirmTransferCommunity,
|
||||||
)}
|
)}
|
||||||
aria-label={I18NextService.i18n.t(
|
aria-label={I18NextService.i18n.t(
|
||||||
"transfer_community"
|
"transfer_community",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{I18NextService.i18n.t("transfer_community")}
|
{I18NextService.i18n.t("transfer_community")}
|
||||||
|
@ -760,7 +749,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
<button
|
<button
|
||||||
className="btn btn-link btn-animate text-muted"
|
className="btn btn-link btn-animate text-muted"
|
||||||
aria-label={I18NextService.i18n.t(
|
aria-label={I18NextService.i18n.t(
|
||||||
"are_you_sure"
|
"are_you_sure",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{I18NextService.i18n.t("are_you_sure")}
|
{I18NextService.i18n.t("are_you_sure")}
|
||||||
|
@ -769,7 +758,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
className="btn btn-link btn-animate text-muted"
|
className="btn btn-link btn-animate text-muted"
|
||||||
onClick={linkEvent(
|
onClick={linkEvent(
|
||||||
this,
|
this,
|
||||||
this.handleTransferCommunity
|
this.handleTransferCommunity,
|
||||||
)}
|
)}
|
||||||
aria-label={I18NextService.i18n.t("yes")}
|
aria-label={I18NextService.i18n.t("yes")}
|
||||||
>
|
>
|
||||||
|
@ -784,7 +773,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
onClick={linkEvent(
|
onClick={linkEvent(
|
||||||
this,
|
this,
|
||||||
this
|
this
|
||||||
.handleCancelShowConfirmTransferCommunity
|
.handleCancelShowConfirmTransferCommunity,
|
||||||
)}
|
)}
|
||||||
aria-label={I18NextService.i18n.t("no")}
|
aria-label={I18NextService.i18n.t("no")}
|
||||||
>
|
>
|
||||||
|
@ -801,10 +790,10 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
className="btn btn-link btn-animate text-muted"
|
className="btn btn-link btn-animate text-muted"
|
||||||
onClick={linkEvent(
|
onClick={linkEvent(
|
||||||
this,
|
this,
|
||||||
this.handlePurgePersonShow
|
this.handlePurgePersonShow,
|
||||||
)}
|
)}
|
||||||
aria-label={I18NextService.i18n.t(
|
aria-label={I18NextService.i18n.t(
|
||||||
"purge_user"
|
"purge_user",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{I18NextService.i18n.t("purge_user")}
|
{I18NextService.i18n.t("purge_user")}
|
||||||
|
@ -813,10 +802,10 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
className="btn btn-link btn-animate text-muted"
|
className="btn btn-link btn-animate text-muted"
|
||||||
onClick={linkEvent(
|
onClick={linkEvent(
|
||||||
this,
|
this,
|
||||||
this.handlePurgeCommentShow
|
this.handlePurgeCommentShow,
|
||||||
)}
|
)}
|
||||||
aria-label={I18NextService.i18n.t(
|
aria-label={I18NextService.i18n.t(
|
||||||
"purge_comment"
|
"purge_comment",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{I18NextService.i18n.t("purge_comment")}
|
{I18NextService.i18n.t("purge_comment")}
|
||||||
|
@ -827,10 +816,10 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
className="btn btn-link btn-animate text-muted"
|
className="btn btn-link btn-animate text-muted"
|
||||||
onClick={linkEvent(
|
onClick={linkEvent(
|
||||||
this,
|
this,
|
||||||
this.handleModBanShow
|
this.handleModBanShow,
|
||||||
)}
|
)}
|
||||||
aria-label={I18NextService.i18n.t(
|
aria-label={I18NextService.i18n.t(
|
||||||
"ban_from_site"
|
"ban_from_site",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{I18NextService.i18n.t("ban_from_site")}
|
{I18NextService.i18n.t("ban_from_site")}
|
||||||
|
@ -840,10 +829,10 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
className="btn btn-link btn-animate text-muted"
|
className="btn btn-link btn-animate text-muted"
|
||||||
onClick={linkEvent(
|
onClick={linkEvent(
|
||||||
this,
|
this,
|
||||||
this.handleBanPerson
|
this.handleBanPerson,
|
||||||
)}
|
)}
|
||||||
aria-label={I18NextService.i18n.t(
|
aria-label={I18NextService.i18n.t(
|
||||||
"unban_from_site"
|
"unban_from_site",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{this.state.banLoading ? (
|
{this.state.banLoading ? (
|
||||||
|
@ -862,22 +851,22 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
className="btn btn-link btn-animate text-muted"
|
className="btn btn-link btn-animate text-muted"
|
||||||
onClick={linkEvent(
|
onClick={linkEvent(
|
||||||
this,
|
this,
|
||||||
this.handleShowConfirmAppointAsAdmin
|
this.handleShowConfirmAppointAsAdmin,
|
||||||
)}
|
)}
|
||||||
aria-label={
|
aria-label={
|
||||||
isAdmin_
|
isAdmin_
|
||||||
? I18NextService.i18n.t(
|
? I18NextService.i18n.t(
|
||||||
"remove_as_admin"
|
"remove_as_admin",
|
||||||
)
|
)
|
||||||
: I18NextService.i18n.t(
|
: I18NextService.i18n.t(
|
||||||
"appoint_as_admin"
|
"appoint_as_admin",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{isAdmin_
|
{isAdmin_
|
||||||
? I18NextService.i18n.t("remove_as_admin")
|
? I18NextService.i18n.t("remove_as_admin")
|
||||||
: I18NextService.i18n.t(
|
: I18NextService.i18n.t(
|
||||||
"appoint_as_admin"
|
"appoint_as_admin",
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
) : (
|
) : (
|
||||||
|
@ -889,7 +878,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
className="btn btn-link btn-animate text-muted"
|
className="btn btn-link btn-animate text-muted"
|
||||||
onClick={linkEvent(
|
onClick={linkEvent(
|
||||||
this,
|
this,
|
||||||
this.handleAddAdmin
|
this.handleAddAdmin,
|
||||||
)}
|
)}
|
||||||
aria-label={I18NextService.i18n.t("yes")}
|
aria-label={I18NextService.i18n.t("yes")}
|
||||||
>
|
>
|
||||||
|
@ -903,7 +892,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
className="btn btn-link btn-animate text-muted"
|
className="btn btn-link btn-animate text-muted"
|
||||||
onClick={linkEvent(
|
onClick={linkEvent(
|
||||||
this,
|
this,
|
||||||
this.handleCancelConfirmAppointAsAdmin
|
this.handleCancelConfirmAppointAsAdmin,
|
||||||
)}
|
)}
|
||||||
aria-label={I18NextService.i18n.t("no")}
|
aria-label={I18NextService.i18n.t("no")}
|
||||||
>
|
>
|
||||||
|
@ -919,7 +908,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{/* end of button group */}
|
{/* end of button group */}
|
||||||
</div>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
|
@ -941,7 +930,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
{I18NextService.i18n.t("x_more_replies", {
|
{I18NextService.i18n.t("x_more_replies", {
|
||||||
count: node.comment_view.counts.child_count,
|
count: node.comment_view.counts.child_count,
|
||||||
formattedCount: numToSI(
|
formattedCount: numToSI(
|
||||||
node.comment_view.counts.child_count
|
node.comment_view.counts.child_count,
|
||||||
),
|
),
|
||||||
})}{" "}
|
})}{" "}
|
||||||
➔
|
➔
|
||||||
|
@ -980,33 +969,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
</form>
|
</form>
|
||||||
)}
|
)}
|
||||||
{this.state.showReportDialog && (
|
{this.state.showReportDialog && (
|
||||||
<form
|
<ReportForm onSubmit={this.handleReportComment} />
|
||||||
className="form-inline"
|
|
||||||
onSubmit={linkEvent(this, this.handleReportComment)}
|
|
||||||
>
|
|
||||||
<label
|
|
||||||
className="visually-hidden"
|
|
||||||
htmlFor={`report-reason-${cv.comment.id}`}
|
|
||||||
>
|
|
||||||
{I18NextService.i18n.t("reason")}
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
required
|
|
||||||
id={`report-reason-${cv.comment.id}`}
|
|
||||||
className="form-control me-2"
|
|
||||||
placeholder={I18NextService.i18n.t("reason")}
|
|
||||||
value={this.state.reportReason}
|
|
||||||
onInput={linkEvent(this, this.handleReportReasonChange)}
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
className="btn btn-secondary"
|
|
||||||
aria-label={I18NextService.i18n.t("create_report")}
|
|
||||||
>
|
|
||||||
{I18NextService.i18n.t("create_report")}
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
)}
|
)}
|
||||||
{this.state.showBanDialog && (
|
{this.state.showBanDialog && (
|
||||||
<form onSubmit={linkEvent(this, this.handleModBanBothSubmit)}>
|
<form onSubmit={linkEvent(this, this.handleModBanBothSubmit)}>
|
||||||
|
@ -1116,7 +1079,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
onReplyCancel={this.handleReplyCancel}
|
onReplyCancel={this.handleReplyCancel}
|
||||||
disabled={this.props.locked}
|
disabled={this.props.locked}
|
||||||
finished={this.props.finished.get(
|
finished={this.props.finished.get(
|
||||||
this.props.node.comment_view.comment.id
|
this.props.node.comment_view.comment.id,
|
||||||
)}
|
)}
|
||||||
focus
|
focus
|
||||||
allLanguages={this.props.allLanguages}
|
allLanguages={this.props.allLanguages}
|
||||||
|
@ -1136,7 +1099,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
allLanguages={this.props.allLanguages}
|
allLanguages={this.props.allLanguages}
|
||||||
siteLanguages={this.props.siteLanguages}
|
siteLanguages={this.props.siteLanguages}
|
||||||
hideImages={this.props.hideImages}
|
hideImages={this.props.hideImages}
|
||||||
isChild={!this.props.noIndent}
|
isChild={!this.props.isTopLevel}
|
||||||
depth={this.props.node.depth + 1}
|
depth={this.props.node.depth + 1}
|
||||||
finished={this.props.finished}
|
finished={this.props.finished}
|
||||||
onCommentReplyRead={this.props.onCommentReplyRead}
|
onCommentReplyRead={this.props.onCommentReplyRead}
|
||||||
|
@ -1212,19 +1175,19 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
|
|
||||||
get myComment(): boolean {
|
get myComment(): boolean {
|
||||||
return (
|
return (
|
||||||
UserService.Instance.myUserInfo?.local_user_view.person.id ==
|
UserService.Instance.myUserInfo?.local_user_view.person.id ===
|
||||||
this.commentView.creator.id
|
this.commentView.creator.id
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
get isPostCreator(): boolean {
|
get isPostCreator(): boolean {
|
||||||
return this.commentView.creator.id == this.commentView.post.creator_id;
|
return this.commentView.creator.id === this.commentView.post.creator_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
get scoreColor() {
|
get scoreColor() {
|
||||||
if (this.commentView.my_vote == 1) {
|
if (this.commentView.my_vote === 1) {
|
||||||
return "text-info";
|
return "text-info";
|
||||||
} else if (this.commentView.my_vote == -1) {
|
} else if (this.commentView.my_vote === -1) {
|
||||||
return "text-danger";
|
return "text-danger";
|
||||||
} else {
|
} else {
|
||||||
return "text-muted";
|
return "text-muted";
|
||||||
|
@ -1281,10 +1244,6 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
i.setState({ showReportDialog: !i.state.showReportDialog });
|
i.setState({ showReportDialog: !i.state.showReportDialog });
|
||||||
}
|
}
|
||||||
|
|
||||||
handleReportReasonChange(i: CommentNode, event: any) {
|
|
||||||
i.setState({ reportReason: event.target.value });
|
|
||||||
}
|
|
||||||
|
|
||||||
handleModRemoveShow(i: CommentNode) {
|
handleModRemoveShow(i: CommentNode) {
|
||||||
i.setState({
|
i.setState({
|
||||||
showRemoveDialog: !i.state.showRemoveDialog,
|
showRemoveDialog: !i.state.showRemoveDialog,
|
||||||
|
@ -1301,13 +1260,13 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
isPersonMentionType(
|
isPersonMentionType(
|
||||||
item: CommentView | PersonMentionView | CommentReplyView
|
item: CommentView | PersonMentionView | CommentReplyView,
|
||||||
): item is PersonMentionView {
|
): item is PersonMentionView {
|
||||||
return (item as PersonMentionView).person_mention?.id !== undefined;
|
return (item as PersonMentionView).person_mention?.id !== undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
isCommentReplyType(
|
isCommentReplyType(
|
||||||
item: CommentView | PersonMentionView | CommentReplyView
|
item: CommentView | PersonMentionView | CommentReplyView,
|
||||||
): item is CommentReplyView {
|
): item is CommentReplyView {
|
||||||
return (item as CommentReplyView).comment_reply?.id !== undefined;
|
return (item as CommentReplyView).comment_reply?.id !== undefined;
|
||||||
}
|
}
|
||||||
|
@ -1414,7 +1373,6 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
i.props.onSaveComment({
|
i.props.onSaveComment({
|
||||||
comment_id: i.commentView.comment.id,
|
comment_id: i.commentView.comment.id,
|
||||||
save: !i.commentView.saved,
|
save: !i.commentView.saved,
|
||||||
auth: myAuthRequired(),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1423,7 +1381,6 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
i.props.onBlockPerson({
|
i.props.onBlockPerson({
|
||||||
person_id: i.commentView.creator.id,
|
person_id: i.commentView.creator.id,
|
||||||
block: true,
|
block: true,
|
||||||
auth: myAuthRequired(),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1434,13 +1391,11 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
i.props.onPersonMentionRead({
|
i.props.onPersonMentionRead({
|
||||||
person_mention_id: cv.person_mention.id,
|
person_mention_id: cv.person_mention.id,
|
||||||
read: !cv.person_mention.read,
|
read: !cv.person_mention.read,
|
||||||
auth: myAuthRequired(),
|
|
||||||
});
|
});
|
||||||
} else if (i.isCommentReplyType(cv)) {
|
} else if (i.isCommentReplyType(cv)) {
|
||||||
i.props.onCommentReplyRead({
|
i.props.onCommentReplyRead({
|
||||||
comment_reply_id: cv.comment_reply.id,
|
comment_reply_id: cv.comment_reply.id,
|
||||||
read: !cv.comment_reply.read,
|
read: !cv.comment_reply.read,
|
||||||
auth: myAuthRequired(),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1450,7 +1405,6 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
i.props.onDeleteComment({
|
i.props.onDeleteComment({
|
||||||
comment_id: i.commentId,
|
comment_id: i.commentId,
|
||||||
deleted: !i.commentView.comment.deleted,
|
deleted: !i.commentView.comment.deleted,
|
||||||
auth: myAuthRequired(),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1460,7 +1414,6 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
i.props.onRemoveComment({
|
i.props.onRemoveComment({
|
||||||
comment_id: i.commentId,
|
comment_id: i.commentId,
|
||||||
removed: !i.commentView.comment.removed,
|
removed: !i.commentView.comment.removed,
|
||||||
auth: myAuthRequired(),
|
|
||||||
reason: i.state.removeReason,
|
reason: i.state.removeReason,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1470,7 +1423,6 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
i.props.onDistinguishComment({
|
i.props.onDistinguishComment({
|
||||||
comment_id: i.commentId,
|
comment_id: i.commentId,
|
||||||
distinguished: !i.commentView.comment.distinguished,
|
distinguished: !i.commentView.comment.distinguished,
|
||||||
auth: myAuthRequired(),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1483,7 +1435,6 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
reason: i.state.banReason,
|
reason: i.state.banReason,
|
||||||
remove_data: i.state.removeData,
|
remove_data: i.state.removeData,
|
||||||
expires: futureDaysToUnixTime(i.state.banExpireDays),
|
expires: futureDaysToUnixTime(i.state.banExpireDays),
|
||||||
auth: myAuthRequired(),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1495,13 +1446,12 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
reason: i.state.banReason,
|
reason: i.state.banReason,
|
||||||
remove_data: i.state.removeData,
|
remove_data: i.state.removeData,
|
||||||
expires: futureDaysToUnixTime(i.state.banExpireDays),
|
expires: futureDaysToUnixTime(i.state.banExpireDays),
|
||||||
auth: myAuthRequired(),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
handleModBanBothSubmit(i: CommentNode, event: any) {
|
handleModBanBothSubmit(i: CommentNode, event: any) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (i.state.banType == BanType.Community) {
|
if (i.state.banType === BanType.Community) {
|
||||||
i.handleBanPersonFromCommunity(i);
|
i.handleBanPersonFromCommunity(i);
|
||||||
} else {
|
} else {
|
||||||
i.handleBanPerson(i);
|
i.handleBanPerson(i);
|
||||||
|
@ -1516,7 +1466,6 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
community_id: i.commentView.community.id,
|
community_id: i.commentView.community.id,
|
||||||
person_id: i.commentView.creator.id,
|
person_id: i.commentView.creator.id,
|
||||||
added,
|
added,
|
||||||
auth: myAuthRequired(),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1527,7 +1476,6 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
i.props.onAddAdmin({
|
i.props.onAddAdmin({
|
||||||
person_id: i.commentView.creator.id,
|
person_id: i.commentView.creator.id,
|
||||||
added,
|
added,
|
||||||
auth: myAuthRequired(),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1536,17 +1484,17 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
i.props.onTransferCommunity({
|
i.props.onTransferCommunity({
|
||||||
community_id: i.commentView.community.id,
|
community_id: i.commentView.community.id,
|
||||||
person_id: i.commentView.creator.id,
|
person_id: i.commentView.creator.id,
|
||||||
auth: myAuthRequired(),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
handleReportComment(i: CommentNode, event: any) {
|
handleReportComment(reason: string) {
|
||||||
event.preventDefault();
|
this.props.onCommentReport({
|
||||||
i.setState({ reportLoading: true });
|
comment_id: this.commentId,
|
||||||
i.props.onCommentReport({
|
reason,
|
||||||
comment_id: i.commentId,
|
});
|
||||||
reason: i.state.reportReason ?? "",
|
|
||||||
auth: myAuthRequired(),
|
this.setState({
|
||||||
|
showReportDialog: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1554,17 +1502,15 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
i.setState({ purgeLoading: true });
|
i.setState({ purgeLoading: true });
|
||||||
|
|
||||||
if (i.state.purgeType == PurgeType.Person) {
|
if (i.state.purgeType === PurgeType.Person) {
|
||||||
i.props.onPurgePerson({
|
i.props.onPurgePerson({
|
||||||
person_id: i.commentView.creator.id,
|
person_id: i.commentView.creator.id,
|
||||||
reason: i.state.purgeReason,
|
reason: i.state.purgeReason,
|
||||||
auth: myAuthRequired(),
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
i.props.onPurgeComment({
|
i.props.onPurgeComment({
|
||||||
comment_id: i.commentId,
|
comment_id: i.commentId,
|
||||||
reason: i.state.purgeReason,
|
reason: i.state.purgeReason,
|
||||||
auth: myAuthRequired(),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1577,7 +1523,6 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
limit: 999, // TODO
|
limit: 999, // TODO
|
||||||
type_: "All",
|
type_: "All",
|
||||||
saved_only: false,
|
saved_only: false,
|
||||||
auth: myAuth(),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,7 +35,7 @@ interface CommentNodesProps {
|
||||||
admins?: PersonView[];
|
admins?: PersonView[];
|
||||||
maxCommentsShown?: number;
|
maxCommentsShown?: number;
|
||||||
noBorder?: boolean;
|
noBorder?: boolean;
|
||||||
noIndent?: boolean;
|
isTopLevel?: boolean;
|
||||||
viewOnly?: boolean;
|
viewOnly?: boolean;
|
||||||
locked?: boolean;
|
locked?: boolean;
|
||||||
markable?: boolean;
|
markable?: boolean;
|
||||||
|
@ -86,7 +86,7 @@ export class CommentNodes extends Component<CommentNodesProps, any> {
|
||||||
this.props.nodes.length > 0 && (
|
this.props.nodes.length > 0 && (
|
||||||
<ul
|
<ul
|
||||||
className={classNames("comments", {
|
className={classNames("comments", {
|
||||||
"ms-1": !!this.props.isChild,
|
"ms-1": this.props.depth && this.props.depth > 1,
|
||||||
"border-top border-light": !this.props.noBorder,
|
"border-top border-light": !this.props.noBorder,
|
||||||
})}
|
})}
|
||||||
style={
|
style={
|
||||||
|
@ -100,7 +100,7 @@ export class CommentNodes extends Component<CommentNodesProps, any> {
|
||||||
key={node.comment_view.comment.id}
|
key={node.comment_view.comment.id}
|
||||||
node={node}
|
node={node}
|
||||||
noBorder={this.props.noBorder}
|
noBorder={this.props.noBorder}
|
||||||
noIndent={this.props.noIndent}
|
isTopLevel={this.props.isTopLevel}
|
||||||
viewOnly={this.props.viewOnly}
|
viewOnly={this.props.viewOnly}
|
||||||
locked={this.props.locked}
|
locked={this.props.locked}
|
||||||
moderators={this.props.moderators}
|
moderators={this.props.moderators}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import { myAuthRequired } from "@utils/app";
|
|
||||||
import { Component, InfernoNode, linkEvent } from "inferno";
|
import { Component, InfernoNode, linkEvent } from "inferno";
|
||||||
import { T } from "inferno-i18next-dess";
|
import { T } from "inferno-i18next-dess";
|
||||||
import {
|
import {
|
||||||
|
@ -11,6 +10,7 @@ import { I18NextService } from "../../services";
|
||||||
import { Icon, Spinner } from "../common/icon";
|
import { Icon, Spinner } from "../common/icon";
|
||||||
import { PersonListing } from "../person/person-listing";
|
import { PersonListing } from "../person/person-listing";
|
||||||
import { CommentNode } from "./comment-node";
|
import { CommentNode } from "./comment-node";
|
||||||
|
import { EMPTY_REQUEST } from "../../services/HttpService";
|
||||||
|
|
||||||
interface CommentReportProps {
|
interface CommentReportProps {
|
||||||
report: CommentReportView;
|
report: CommentReportView;
|
||||||
|
@ -33,9 +33,9 @@ export class CommentReport extends Component<
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(
|
componentWillReceiveProps(
|
||||||
nextProps: Readonly<{ children?: InfernoNode } & CommentReportProps>
|
nextProps: Readonly<{ children?: InfernoNode } & CommentReportProps>,
|
||||||
): void {
|
): void {
|
||||||
if (this.props != nextProps) {
|
if (this.props !== nextProps) {
|
||||||
this.setState({ loading: false });
|
this.setState({ loading: false });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -44,7 +44,7 @@ export class CommentReport extends Component<
|
||||||
const r = this.props.report;
|
const r = this.props.report;
|
||||||
const comment = r.comment;
|
const comment = r.comment;
|
||||||
const tippyContent = I18NextService.i18n.t(
|
const tippyContent = I18NextService.i18n.t(
|
||||||
r.comment_report.resolved ? "unresolve_report" : "resolve_report"
|
r.comment_report.resolved ? "unresolve_report" : "resolve_report",
|
||||||
);
|
);
|
||||||
|
|
||||||
// Set the original post data ( a troll could change it )
|
// Set the original post data ( a troll could change it )
|
||||||
|
@ -98,8 +98,8 @@ export class CommentReport extends Component<
|
||||||
onPersonMentionRead={() => {}}
|
onPersonMentionRead={() => {}}
|
||||||
onBanPersonFromCommunity={() => {}}
|
onBanPersonFromCommunity={() => {}}
|
||||||
onBanPerson={() => {}}
|
onBanPerson={() => {}}
|
||||||
onCreateComment={() => Promise.resolve({ state: "empty" })}
|
onCreateComment={() => Promise.resolve(EMPTY_REQUEST)}
|
||||||
onEditComment={() => Promise.resolve({ state: "empty" })}
|
onEditComment={() => Promise.resolve(EMPTY_REQUEST)}
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
{I18NextService.i18n.t("reporter")}:{" "}
|
{I18NextService.i18n.t("reporter")}:{" "}
|
||||||
|
@ -149,7 +149,6 @@ export class CommentReport extends Component<
|
||||||
i.props.onResolveReport({
|
i.props.onResolveReport({
|
||||||
report_id: i.props.report.comment_report.id,
|
report_id: i.props.report.comment_report.id,
|
||||||
resolved: !i.props.report.comment_report.resolved,
|
resolved: !i.props.report.comment_report.resolved,
|
||||||
auth: myAuthRequired(),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
31
src/shared/components/common/anonymous-guard.tsx
Normal file
31
src/shared/components/common/anonymous-guard.tsx
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
import { Component } from "inferno";
|
||||||
|
import { UserService } from "../../services";
|
||||||
|
import { Spinner } from "./icon";
|
||||||
|
|
||||||
|
interface AnonymousGuardState {
|
||||||
|
hasRedirected: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
class AnonymousGuard extends Component<any, AnonymousGuardState> {
|
||||||
|
state = {
|
||||||
|
hasRedirected: false,
|
||||||
|
} as AnonymousGuardState;
|
||||||
|
|
||||||
|
constructor(props: any, context: any) {
|
||||||
|
super(props, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
if (UserService.Instance.myUserInfo) {
|
||||||
|
this.context.router.history.replace(`/`);
|
||||||
|
} else {
|
||||||
|
this.setState({ hasRedirected: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return this.state.hasRedirected ? this.props.children : <Spinner />;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AnonymousGuard;
|
|
@ -1,12 +1,40 @@
|
||||||
import { InfernoNode } from "inferno";
|
import { Component } from "inferno";
|
||||||
import { Redirect } from "inferno-router";
|
import { RouteComponentProps } from "inferno-router/dist/Route";
|
||||||
import { UserService } from "../../services";
|
import { UserService } from "../../services";
|
||||||
|
import { Spinner } from "./icon";
|
||||||
|
|
||||||
function AuthGuard(props: { children?: InfernoNode }) {
|
interface AuthGuardState {
|
||||||
if (!UserService.Instance.myUserInfo) {
|
hasRedirected: boolean;
|
||||||
return <Redirect to="/login" />;
|
}
|
||||||
} else {
|
|
||||||
return props.children;
|
class AuthGuard extends Component<
|
||||||
|
RouteComponentProps<Record<string, string>>,
|
||||||
|
AuthGuardState
|
||||||
|
> {
|
||||||
|
state = {
|
||||||
|
hasRedirected: false,
|
||||||
|
} as AuthGuardState;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
props: RouteComponentProps<Record<string, string>>,
|
||||||
|
context: any,
|
||||||
|
) {
|
||||||
|
super(props, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
if (!UserService.Instance.myUserInfo) {
|
||||||
|
const { pathname, search } = this.props.location;
|
||||||
|
this.context.router.history.replace(
|
||||||
|
`/login?prev=${encodeURIComponent(pathname + search)}`,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.setState({ hasRedirected: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return this.state.hasRedirected ? this.props.children : <Spinner />;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,13 +13,13 @@ interface BadgesProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
const isCommunityAggregates = (
|
const isCommunityAggregates = (
|
||||||
counts: CommunityAggregates | SiteAggregates
|
counts: CommunityAggregates | SiteAggregates,
|
||||||
): counts is CommunityAggregates => {
|
): counts is CommunityAggregates => {
|
||||||
return "subscribers" in counts;
|
return "subscribers" in counts;
|
||||||
};
|
};
|
||||||
|
|
||||||
const isSiteAggregates = (
|
const isSiteAggregates = (
|
||||||
counts: CommunityAggregates | SiteAggregates
|
counts: CommunityAggregates | SiteAggregates,
|
||||||
): counts is SiteAggregates => {
|
): counts is SiteAggregates => {
|
||||||
return "communities" in counts;
|
return "communities" in counts;
|
||||||
};
|
};
|
||||||
|
@ -34,7 +34,7 @@ export const Badges = ({ counts, communityId }: BadgesProps) => {
|
||||||
{
|
{
|
||||||
count: Number(counts.users_active_day),
|
count: Number(counts.users_active_day),
|
||||||
formattedCount: numToSI(counts.users_active_day),
|
formattedCount: numToSI(counts.users_active_day),
|
||||||
}
|
},
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{I18NextService.i18n.t("number_of_users", {
|
{I18NextService.i18n.t("number_of_users", {
|
||||||
|
@ -50,7 +50,7 @@ export const Badges = ({ counts, communityId }: BadgesProps) => {
|
||||||
{
|
{
|
||||||
count: Number(counts.users_active_week),
|
count: Number(counts.users_active_week),
|
||||||
formattedCount: numToSI(counts.users_active_week),
|
formattedCount: numToSI(counts.users_active_week),
|
||||||
}
|
},
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{I18NextService.i18n.t("number_of_users", {
|
{I18NextService.i18n.t("number_of_users", {
|
||||||
|
@ -66,7 +66,7 @@ export const Badges = ({ counts, communityId }: BadgesProps) => {
|
||||||
{
|
{
|
||||||
count: Number(counts.users_active_month),
|
count: Number(counts.users_active_month),
|
||||||
formattedCount: numToSI(counts.users_active_month),
|
formattedCount: numToSI(counts.users_active_month),
|
||||||
}
|
},
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{I18NextService.i18n.t("number_of_users", {
|
{I18NextService.i18n.t("number_of_users", {
|
||||||
|
@ -82,7 +82,7 @@ export const Badges = ({ counts, communityId }: BadgesProps) => {
|
||||||
{
|
{
|
||||||
count: Number(counts.users_active_half_year),
|
count: Number(counts.users_active_half_year),
|
||||||
formattedCount: numToSI(counts.users_active_half_year),
|
formattedCount: numToSI(counts.users_active_half_year),
|
||||||
}
|
},
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{I18NextService.i18n.t("number_of_users", {
|
{I18NextService.i18n.t("number_of_users", {
|
||||||
|
|
|
@ -48,6 +48,9 @@ export class CommentSortSelect extends Component<
|
||||||
{I18NextService.i18n.t("sort_type")}
|
{I18NextService.i18n.t("sort_type")}
|
||||||
</option>
|
</option>
|
||||||
<option value={"Hot"}>{I18NextService.i18n.t("hot")}</option>,
|
<option value={"Hot"}>{I18NextService.i18n.t("hot")}</option>,
|
||||||
|
<option value={"Controversial"}>
|
||||||
|
{I18NextService.i18n.t("controversial")}
|
||||||
|
</option>
|
||||||
<option value={"Top"}>{I18NextService.i18n.t("top")}</option>,
|
<option value={"Top"}>{I18NextService.i18n.t("top")}</option>,
|
||||||
<option value={"New"}>{I18NextService.i18n.t("new")}</option>
|
<option value={"New"}>{I18NextService.i18n.t("new")}</option>
|
||||||
<option value={"Old"}>{I18NextService.i18n.t("old")}</option>
|
<option value={"Old"}>{I18NextService.i18n.t("old")}</option>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Component } from "inferno";
|
import { Component, RefObject, createRef } from "inferno";
|
||||||
import { getEmojiMart } from "../../markdown";
|
import { getEmojiMart } from "../../markdown";
|
||||||
|
|
||||||
interface EmojiMartProps {
|
interface EmojiMartProps {
|
||||||
|
@ -7,21 +7,24 @@ interface EmojiMartProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class EmojiMart extends Component<EmojiMartProps> {
|
export class EmojiMart extends Component<EmojiMartProps> {
|
||||||
|
div: RefObject<HTMLDivElement>;
|
||||||
|
|
||||||
constructor(props: any, context: any) {
|
constructor(props: any, context: any) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
|
|
||||||
|
this.div = createRef();
|
||||||
|
|
||||||
this.handleEmojiClick = this.handleEmojiClick.bind(this);
|
this.handleEmojiClick = this.handleEmojiClick.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const div: any = document.getElementById("emoji-picker");
|
this.div.current?.appendChild(
|
||||||
if (div) {
|
getEmojiMart(this.handleEmojiClick, this.props.pickerOptions) as any,
|
||||||
div.appendChild(
|
);
|
||||||
getEmojiMart(this.handleEmojiClick, this.props.pickerOptions)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return <div id="emoji-picker"></div>;
|
return <div id="emoji-picker" ref={this.div} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
handleEmojiClick(e: any) {
|
handleEmojiClick(e: any) {
|
||||||
|
|
|
@ -77,5 +77,6 @@ export class EmojiPicker extends Component<EmojiPickerProps, EmojiPickerState> {
|
||||||
|
|
||||||
handleEmojiClick(e: any) {
|
handleEmojiClick(e: any) {
|
||||||
this.props.onEmojiClick?.(e);
|
this.props.onEmojiClick?.(e);
|
||||||
|
this.setState({ showPicker: false });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import { I18NextService } from "../../services";
|
||||||
interface HtmlTagsProps {
|
interface HtmlTagsProps {
|
||||||
title: string;
|
title: string;
|
||||||
path: string;
|
path: string;
|
||||||
|
canonicalPath?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
image?: string;
|
image?: string;
|
||||||
}
|
}
|
||||||
|
@ -16,6 +17,8 @@ interface HtmlTagsProps {
|
||||||
export class HtmlTags extends Component<HtmlTagsProps, any> {
|
export class HtmlTags extends Component<HtmlTagsProps, any> {
|
||||||
render() {
|
render() {
|
||||||
const url = httpExternalPath(this.props.path);
|
const url = httpExternalPath(this.props.path);
|
||||||
|
const canonicalUrl =
|
||||||
|
this.props.canonicalPath ?? httpExternalPath(this.props.path);
|
||||||
const desc = this.props.description;
|
const desc = this.props.description;
|
||||||
const image = this.props.image;
|
const image = this.props.image;
|
||||||
|
|
||||||
|
@ -30,6 +33,8 @@ export class HtmlTags extends Component<HtmlTagsProps, any> {
|
||||||
<meta key={u} property={u} content={url} />
|
<meta key={u} property={u} content={url} />
|
||||||
))}
|
))}
|
||||||
|
|
||||||
|
<link rel="canonical" href={canonicalUrl} />
|
||||||
|
|
||||||
{/* Open Graph / Facebook */}
|
{/* Open Graph / Facebook */}
|
||||||
<meta property="og:type" content="website" />
|
<meta property="og:type" content="website" />
|
||||||
|
|
||||||
|
@ -45,10 +50,10 @@ export class HtmlTags extends Component<HtmlTagsProps, any> {
|
||||||
name={n}
|
name={n}
|
||||||
content={htmlToText(md.renderInline(desc))}
|
content={htmlToText(md.renderInline(desc))}
|
||||||
/>
|
/>
|
||||||
)
|
),
|
||||||
)}
|
)}
|
||||||
{["og:image", "twitter:image"].map(
|
{["og:image", "twitter:image"].map(
|
||||||
p => image && <meta key={p} property={p} content={image} />
|
p => image && <meta key={p} property={p} content={image} />,
|
||||||
)}
|
)}
|
||||||
</Helmet>
|
</Helmet>
|
||||||
);
|
);
|
||||||
|
|
|
@ -53,12 +53,12 @@ export class LanguageSelect extends Component<LanguageSelectProps, any> {
|
||||||
<label
|
<label
|
||||||
className={classNames(
|
className={classNames(
|
||||||
"col-form-label",
|
"col-form-label",
|
||||||
`col-sm-${this.props.multiple ? 3 : 2}`
|
`col-sm-${this.props.multiple ? 3 : 2}`,
|
||||||
)}
|
)}
|
||||||
htmlFor={this.id}
|
htmlFor={this.id}
|
||||||
>
|
>
|
||||||
{I18NextService.i18n.t(
|
{I18NextService.i18n.t(
|
||||||
this.props.multiple ? "language_plural" : "language"
|
this.props.multiple ? "language_plural" : "language",
|
||||||
)}
|
)}
|
||||||
</label>
|
</label>
|
||||||
{this.props.multiple && this.props.showLanguageWarning && (
|
{this.props.multiple && this.props.showLanguageWarning && (
|
||||||
|
@ -97,7 +97,7 @@ export class LanguageSelect extends Component<LanguageSelectProps, any> {
|
||||||
this.props.siteLanguages,
|
this.props.siteLanguages,
|
||||||
this.props.showAll,
|
this.props.showAll,
|
||||||
this.props.showSite,
|
this.props.showSite,
|
||||||
UserService.Instance.myUserInfo
|
UserService.Instance.myUserInfo,
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -30,7 +30,7 @@ export class ListingTypeSelect extends Component<
|
||||||
}
|
}
|
||||||
|
|
||||||
static getDerivedStateFromProps(
|
static getDerivedStateFromProps(
|
||||||
props: ListingTypeSelectProps
|
props: ListingTypeSelectProps,
|
||||||
): ListingTypeSelectState {
|
): ListingTypeSelectState {
|
||||||
return {
|
return {
|
||||||
type_: props.type_,
|
type_: props.type_,
|
||||||
|
@ -107,6 +107,27 @@ export class ListingTypeSelect extends Component<
|
||||||
>
|
>
|
||||||
{I18NextService.i18n.t("all")}
|
{I18NextService.i18n.t("all")}
|
||||||
</label>
|
</label>
|
||||||
|
{(UserService.Instance.myUserInfo?.moderates.length ?? 0) > 0 && (
|
||||||
|
<>
|
||||||
|
<input
|
||||||
|
id={`${this.id}-moderator-view`}
|
||||||
|
type="radio"
|
||||||
|
className="btn-check"
|
||||||
|
value={"ModeratorView"}
|
||||||
|
checked={this.state.type_ === "ModeratorView"}
|
||||||
|
onChange={linkEvent(this, this.handleTypeChange)}
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
htmlFor={`${this.id}-moderator-view`}
|
||||||
|
title={I18NextService.i18n.t("moderator_view_description")}
|
||||||
|
className={classNames("pointer btn btn-outline-secondary", {
|
||||||
|
active: this.state.type_ === "ModeratorView",
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{I18NextService.i18n.t("moderator_view")}
|
||||||
|
</label>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
34
src/shared/components/common/loading-ellipses.tsx
Normal file
34
src/shared/components/common/loading-ellipses.tsx
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
import { Component } from "inferno";
|
||||||
|
|
||||||
|
interface LoadingEllipsesState {
|
||||||
|
ellipses: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class LoadingEllipses extends Component<any, LoadingEllipsesState> {
|
||||||
|
state: LoadingEllipsesState = {
|
||||||
|
ellipses: "...",
|
||||||
|
};
|
||||||
|
#interval?: NodeJS.Timer;
|
||||||
|
|
||||||
|
constructor(props: any, context: any) {
|
||||||
|
super(props, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return this.state.ellipses;
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.#interval = setInterval(this.#updateEllipses, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
clearInterval(this.#interval);
|
||||||
|
}
|
||||||
|
|
||||||
|
#updateEllipses = () => {
|
||||||
|
this.setState(({ ellipses }) => ({
|
||||||
|
ellipses: ellipses.length === 3 ? "" : ellipses + ".",
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,9 +1,10 @@
|
||||||
import { isBrowser } from "@utils/browser";
|
import { isBrowser, platform } from "@utils/browser";
|
||||||
import { numToSI, randomStr } from "@utils/helpers";
|
import { numToSI, randomStr } from "@utils/helpers";
|
||||||
import autosize from "autosize";
|
import autosize from "autosize";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { NoOptionI18nKeys } from "i18next";
|
import { NoOptionI18nKeys } from "i18next";
|
||||||
import { Component, linkEvent } from "inferno";
|
import { Component, linkEvent } from "inferno";
|
||||||
|
import { Prompt } from "inferno-router";
|
||||||
import { Language } from "lemmy-js-client";
|
import { Language } from "lemmy-js-client";
|
||||||
import {
|
import {
|
||||||
concurrentImageUpload,
|
concurrentImageUpload,
|
||||||
|
@ -19,9 +20,8 @@ import { pictrsDeleteToast, toast } from "../../toast";
|
||||||
import { EmojiPicker } from "./emoji-picker";
|
import { EmojiPicker } from "./emoji-picker";
|
||||||
import { Icon, Spinner } from "./icon";
|
import { Icon, Spinner } from "./icon";
|
||||||
import { LanguageSelect } from "./language-select";
|
import { LanguageSelect } from "./language-select";
|
||||||
import NavigationPrompt from "./navigation-prompt";
|
|
||||||
import ProgressBar from "./progress-bar";
|
import ProgressBar from "./progress-bar";
|
||||||
|
import validUrl from "@utils/helpers/valid-url";
|
||||||
interface MarkdownTextAreaProps {
|
interface MarkdownTextAreaProps {
|
||||||
/**
|
/**
|
||||||
* Initial content inside the textarea
|
* Initial content inside the textarea
|
||||||
|
@ -49,7 +49,7 @@ interface MarkdownTextAreaProps {
|
||||||
hideNavigationWarnings?: boolean;
|
hideNavigationWarnings?: boolean;
|
||||||
onContentChange?(val: string): void;
|
onContentChange?(val: string): void;
|
||||||
onReplyCancel?(): void;
|
onReplyCancel?(): void;
|
||||||
onSubmit?(content: string, formId: string, languageId?: number): void;
|
onSubmit?(content: string, languageId?: number): void;
|
||||||
allLanguages: Language[]; // TODO should probably be nullable
|
allLanguages: Language[]; // TODO should probably be nullable
|
||||||
siteLanguages: number[]; // TODO same
|
siteLanguages: number[]; // TODO same
|
||||||
}
|
}
|
||||||
|
@ -89,6 +89,7 @@ export class MarkdownTextArea extends Component<
|
||||||
super(props, context);
|
super(props, context);
|
||||||
|
|
||||||
this.handleLanguageChange = this.handleLanguageChange.bind(this);
|
this.handleLanguageChange = this.handleLanguageChange.bind(this);
|
||||||
|
this.handleEmoji = this.handleEmoji.bind(this);
|
||||||
|
|
||||||
if (isBrowser()) {
|
if (isBrowser()) {
|
||||||
this.tribute = setupTribute();
|
this.tribute = setupTribute();
|
||||||
|
@ -138,18 +139,14 @@ export class MarkdownTextArea extends Component<
|
||||||
render() {
|
render() {
|
||||||
const languageId = this.state.languageId;
|
const languageId = this.state.languageId;
|
||||||
|
|
||||||
// TODO add these prompts back in at some point
|
|
||||||
// <Prompt
|
|
||||||
// when={!this.props.hideNavigationWarnings && this.state.content}
|
|
||||||
// message={I18NextService.i18n.t("block_leaving")}
|
|
||||||
// />
|
|
||||||
return (
|
return (
|
||||||
<form
|
<form
|
||||||
className="markdown-textarea"
|
className="markdown-textarea"
|
||||||
id={this.formId}
|
id={this.formId}
|
||||||
onSubmit={linkEvent(this, this.handleSubmit)}
|
onSubmit={linkEvent(this, this.handleSubmit)}
|
||||||
>
|
>
|
||||||
<NavigationPrompt
|
<Prompt
|
||||||
|
message={I18NextService.i18n.t("block_leaving")}
|
||||||
when={
|
when={
|
||||||
!this.props.hideNavigationWarnings &&
|
!this.props.hideNavigationWarnings &&
|
||||||
!!this.state.content &&
|
!!this.state.content &&
|
||||||
|
@ -167,9 +164,7 @@ export class MarkdownTextArea extends Component<
|
||||||
{this.getFormatButton("bold", this.handleInsertBold)}
|
{this.getFormatButton("bold", this.handleInsertBold)}
|
||||||
{this.getFormatButton("italic", this.handleInsertItalic)}
|
{this.getFormatButton("italic", this.handleInsertItalic)}
|
||||||
{this.getFormatButton("link", this.handleInsertLink)}
|
{this.getFormatButton("link", this.handleInsertLink)}
|
||||||
<EmojiPicker
|
<EmojiPicker onEmojiClick={this.handleEmoji}></EmojiPicker>
|
||||||
onEmojiClick={e => this.handleEmoji(this, e)}
|
|
||||||
></EmojiPicker>
|
|
||||||
<label
|
<label
|
||||||
htmlFor={`file-upload-${this.id}`}
|
htmlFor={`file-upload-${this.id}`}
|
||||||
className={classNames("mb-0", {
|
className={classNames("mb-0", {
|
||||||
|
@ -206,7 +201,7 @@ export class MarkdownTextArea extends Component<
|
||||||
{this.getFormatButton("header", this.handleInsertHeader)}
|
{this.getFormatButton("header", this.handleInsertHeader)}
|
||||||
{this.getFormatButton(
|
{this.getFormatButton(
|
||||||
"strikethrough",
|
"strikethrough",
|
||||||
this.handleInsertStrikethrough
|
this.handleInsertStrikethrough,
|
||||||
)}
|
)}
|
||||||
{this.getFormatButton("quote", this.handleInsertQuote)}
|
{this.getFormatButton("quote", this.handleInsertQuote)}
|
||||||
{this.getFormatButton("list", this.handleInsertList)}
|
{this.getFormatButton("list", this.handleInsertList)}
|
||||||
|
@ -214,7 +209,7 @@ export class MarkdownTextArea extends Component<
|
||||||
{this.getFormatButton("subscript", this.handleInsertSubscript)}
|
{this.getFormatButton("subscript", this.handleInsertSubscript)}
|
||||||
{this.getFormatButton(
|
{this.getFormatButton(
|
||||||
"superscript",
|
"superscript",
|
||||||
this.handleInsertSuperscript
|
this.handleInsertSuperscript,
|
||||||
)}
|
)}
|
||||||
{this.getFormatButton("spoiler", this.handleInsertSpoiler)}
|
{this.getFormatButton("spoiler", this.handleInsertSpoiler)}
|
||||||
<a
|
<a
|
||||||
|
@ -234,11 +229,11 @@ export class MarkdownTextArea extends Component<
|
||||||
"form-control border-0 rounded-top-0 rounded-bottom",
|
"form-control border-0 rounded-top-0 rounded-bottom",
|
||||||
{
|
{
|
||||||
"d-none": this.state.previewMode,
|
"d-none": this.state.previewMode,
|
||||||
}
|
},
|
||||||
)}
|
)}
|
||||||
value={this.state.content}
|
value={this.state.content}
|
||||||
onInput={linkEvent(this, this.handleContentChange)}
|
onInput={linkEvent(this, this.handleContentChange)}
|
||||||
onPaste={linkEvent(this, this.handleImageUploadPaste)}
|
onPaste={linkEvent(this, this.handlePaste)}
|
||||||
onKeyDown={linkEvent(this, this.handleKeyBinds)}
|
onKeyDown={linkEvent(this, this.handleKeyBinds)}
|
||||||
required
|
required
|
||||||
disabled={this.isDisabled}
|
disabled={this.isDisabled}
|
||||||
|
@ -263,7 +258,7 @@ export class MarkdownTextArea extends Component<
|
||||||
value={this.state.imageUploadStatus.uploaded}
|
value={this.state.imageUploadStatus.uploaded}
|
||||||
max={this.state.imageUploadStatus.total}
|
max={this.state.imageUploadStatus.total}
|
||||||
text={
|
text={
|
||||||
I18NextService.i18n.t("pictures_uploded_progess", {
|
I18NextService.i18n.t("pictures_uploaded_progess", {
|
||||||
uploaded: this.state.imageUploadStatus.uploaded,
|
uploaded: this.state.imageUploadStatus.uploaded,
|
||||||
total: this.state.imageUploadStatus.total,
|
total: this.state.imageUploadStatus.total,
|
||||||
}) ?? undefined
|
}) ?? undefined
|
||||||
|
@ -333,7 +328,7 @@ export class MarkdownTextArea extends Component<
|
||||||
|
|
||||||
getFormatButton(
|
getFormatButton(
|
||||||
type: NoOptionI18nKeys,
|
type: NoOptionI18nKeys,
|
||||||
handleClick: (i: MarkdownTextArea, event: any) => void
|
handleClick: (i: MarkdownTextArea, event: any) => void,
|
||||||
) {
|
) {
|
||||||
let iconType: string;
|
let iconType: string;
|
||||||
|
|
||||||
|
@ -363,26 +358,65 @@ export class MarkdownTextArea extends Component<
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleEmoji(i: MarkdownTextArea, e: any) {
|
handleEmoji(e: any) {
|
||||||
let value = e.native;
|
let value = e.native;
|
||||||
if (value == null) {
|
if (!value) {
|
||||||
const emoji = customEmojisLookup.get(e.id)?.custom_emoji;
|
const emoji = customEmojisLookup.get(e.id)?.custom_emoji;
|
||||||
if (emoji) {
|
if (emoji) {
|
||||||
value = `![${emoji.alt_text}](${emoji.image_url} "${emoji.shortcode}")`;
|
value = `![${emoji.alt_text}](${emoji.image_url} "emoji ${emoji.shortcode}")`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
i.setState({
|
this.setState({
|
||||||
content: `${i.state.content ?? ""} ${value} `,
|
content: `${this.state.content ?? ""} ${value} `,
|
||||||
});
|
});
|
||||||
i.contentChange();
|
this.contentChange();
|
||||||
const textarea: any = document.getElementById(i.id);
|
const textarea: any = document.getElementById(this.id);
|
||||||
autosize.update(textarea);
|
autosize.update(textarea);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleImageUploadPaste(i: MarkdownTextArea, event: any) {
|
handlePaste(i: MarkdownTextArea, event: ClipboardEvent) {
|
||||||
|
if (!event.clipboardData) return;
|
||||||
|
|
||||||
|
// check clipboard files
|
||||||
const image = event.clipboardData.files[0];
|
const image = event.clipboardData.files[0];
|
||||||
if (image) {
|
if (image) {
|
||||||
i.handleImageUpload(i, image);
|
i.handleImageUpload(i, image);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check clipboard url
|
||||||
|
const url = event.clipboardData.getData("text");
|
||||||
|
if (validUrl(url)) {
|
||||||
|
i.handleUrlPaste(url, i, event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleUrlPaste(url: string, i: MarkdownTextArea, event: ClipboardEvent) {
|
||||||
|
// query textarea element
|
||||||
|
const textarea = document.getElementById(i.id);
|
||||||
|
|
||||||
|
if (textarea instanceof HTMLTextAreaElement) {
|
||||||
|
const { selectionStart, selectionEnd } = textarea;
|
||||||
|
|
||||||
|
// if no selection, just insert url
|
||||||
|
if (selectionStart === selectionEnd) return;
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
const selectedText = i.getSelectedText();
|
||||||
|
|
||||||
|
// update textarea content
|
||||||
|
i.setState(({ content }) => ({
|
||||||
|
content: `${
|
||||||
|
content?.substring(0, selectionStart) ?? ""
|
||||||
|
}[${selectedText}](${url})${content?.substring(selectionEnd) ?? ""}`,
|
||||||
|
}));
|
||||||
|
i.contentChange();
|
||||||
|
|
||||||
|
// shift selection 1 to the right
|
||||||
|
textarea.setSelectionRange(
|
||||||
|
selectionStart + 1,
|
||||||
|
selectionStart + 1 + selectedText.length,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -401,7 +435,7 @@ export class MarkdownTextArea extends Component<
|
||||||
count: Number(maxUploadImages),
|
count: Number(maxUploadImages),
|
||||||
formattedCount: numToSI(maxUploadImages),
|
formattedCount: numToSI(maxUploadImages),
|
||||||
}),
|
}),
|
||||||
"danger"
|
"danger",
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
i.setState({
|
i.setState({
|
||||||
|
@ -429,7 +463,7 @@ export class MarkdownTextArea extends Component<
|
||||||
uploaded: (imageUploadStatus?.uploaded ?? 0) + 1,
|
uploaded: (imageUploadStatus?.uploaded ?? 0) + 1,
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
errorOccurred = true;
|
errorOccurred = true;
|
||||||
|
@ -481,7 +515,7 @@ export class MarkdownTextArea extends Component<
|
||||||
// Keybind handler
|
// Keybind handler
|
||||||
// Keybinds inspired by github comment area
|
// Keybinds inspired by github comment area
|
||||||
handleKeyBinds(i: MarkdownTextArea, event: KeyboardEvent) {
|
handleKeyBinds(i: MarkdownTextArea, event: KeyboardEvent) {
|
||||||
if (event.ctrlKey || event.metaKey) {
|
if (platform.isMac() ? event.metaKey : event.ctrlKey) {
|
||||||
switch (event.key) {
|
switch (event.key) {
|
||||||
case "k": {
|
case "k": {
|
||||||
i.handleInsertLink(i, event);
|
i.handleInsertLink(i, event);
|
||||||
|
@ -539,7 +573,7 @@ export class MarkdownTextArea extends Component<
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (i.state.content) {
|
if (i.state.content) {
|
||||||
i.setState({ loading: true, submitted: true });
|
i.setState({ loading: true, submitted: true });
|
||||||
i.props.onSubmit?.(i.state.content, i.formId, i.state.languageId);
|
i.props.onSubmit?.(i.state.content, i.state.languageId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -565,7 +599,7 @@ export class MarkdownTextArea extends Component<
|
||||||
i.setState({
|
i.setState({
|
||||||
content: `${content?.substring(
|
content: `${content?.substring(
|
||||||
0,
|
0,
|
||||||
start
|
start,
|
||||||
)}[${selectedText}]()${content?.substring(end)}`,
|
)}[${selectedText}]()${content?.substring(end)}`,
|
||||||
});
|
});
|
||||||
textarea.focus();
|
textarea.focus();
|
||||||
|
@ -589,7 +623,7 @@ export class MarkdownTextArea extends Component<
|
||||||
simpleSurroundBeforeAfter(
|
simpleSurroundBeforeAfter(
|
||||||
beforeChars: string,
|
beforeChars: string,
|
||||||
afterChars: string,
|
afterChars: string,
|
||||||
emptyChars = "___"
|
emptyChars = "___",
|
||||||
) {
|
) {
|
||||||
const content = this.state.content ?? "";
|
const content = this.state.content ?? "";
|
||||||
if (!this.state.content) {
|
if (!this.state.content) {
|
||||||
|
@ -604,7 +638,7 @@ export class MarkdownTextArea extends Component<
|
||||||
this.setState({
|
this.setState({
|
||||||
content: `${content?.substring(
|
content: `${content?.substring(
|
||||||
0,
|
0,
|
||||||
start
|
start,
|
||||||
)}${beforeChars}${selectedText}${afterChars}${content?.substring(end)}`,
|
)}${beforeChars}${selectedText}${afterChars}${content?.substring(end)}`,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
@ -619,12 +653,12 @@ export class MarkdownTextArea extends Component<
|
||||||
if (start !== end) {
|
if (start !== end) {
|
||||||
textarea.setSelectionRange(
|
textarea.setSelectionRange(
|
||||||
start + beforeChars.length,
|
start + beforeChars.length,
|
||||||
end + afterChars.length
|
end + afterChars.length,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
textarea.setSelectionRange(
|
textarea.setSelectionRange(
|
||||||
start + beforeChars.length,
|
start + beforeChars.length,
|
||||||
end + emptyChars.length + afterChars.length
|
end + emptyChars.length + afterChars.length,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,11 +25,11 @@ export class MomentTime extends Component<MomentTimeProps, any> {
|
||||||
createdAndModifiedTimes() {
|
createdAndModifiedTimes() {
|
||||||
const updated = this.props.updated;
|
const updated = this.props.updated;
|
||||||
let line = `${capitalizeFirstLetter(
|
let line = `${capitalizeFirstLetter(
|
||||||
I18NextService.i18n.t("created")
|
I18NextService.i18n.t("created"),
|
||||||
)}: ${formatDate(this.props.published)}`;
|
)}: ${formatDate(this.props.published)}`;
|
||||||
if (updated) {
|
if (updated) {
|
||||||
line += `\n\n\n${capitalizeFirstLetter(
|
line += `\n\n\n${capitalizeFirstLetter(
|
||||||
I18NextService.i18n.t("modified")
|
I18NextService.i18n.t("modified"),
|
||||||
)} ${formatDate(updated)}`;
|
)} ${formatDate(updated)}`;
|
||||||
}
|
}
|
||||||
return line;
|
return line;
|
||||||
|
|
|
@ -1,53 +0,0 @@
|
||||||
import { Component } from "inferno";
|
|
||||||
import { I18NextService } from "../../services";
|
|
||||||
|
|
||||||
export interface IPromptProps {
|
|
||||||
when: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class NavigationPrompt extends Component<IPromptProps, any> {
|
|
||||||
public unblock;
|
|
||||||
|
|
||||||
public enable() {
|
|
||||||
if (this.unblock) {
|
|
||||||
this.unblock();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.unblock = this.context.router.history.block(tx => {
|
|
||||||
if (window.confirm(I18NextService.i18n.t("block_leaving") ?? undefined)) {
|
|
||||||
this.unblock();
|
|
||||||
tx.retry();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public disable() {
|
|
||||||
if (this.unblock) {
|
|
||||||
this.unblock();
|
|
||||||
this.unblock = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public componentWillMount() {
|
|
||||||
if (this.props.when) {
|
|
||||||
this.enable();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public componentWillReceiveProps(nextProps: IPromptProps) {
|
|
||||||
if (nextProps.when) {
|
|
||||||
if (!this.props.when) {
|
|
||||||
this.enable();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.disable();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public componentWillUnmount() {
|
|
||||||
this.disable();
|
|
||||||
}
|
|
||||||
|
|
||||||
public render() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
46
src/shared/components/common/paginator-cursor.tsx
Normal file
46
src/shared/components/common/paginator-cursor.tsx
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
import { Component, linkEvent } from "inferno";
|
||||||
|
import { I18NextService } from "../../services";
|
||||||
|
import { PaginationCursor } from "lemmy-js-client";
|
||||||
|
|
||||||
|
interface PaginatorCursorProps {
|
||||||
|
prevPage?: PaginationCursor;
|
||||||
|
nextPage?: PaginationCursor;
|
||||||
|
onNext(val: PaginationCursor): void;
|
||||||
|
onPrev(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handlePrev(i: PaginatorCursor) {
|
||||||
|
i.props.onPrev();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleNext(i: PaginatorCursor) {
|
||||||
|
if (i.props.nextPage) {
|
||||||
|
i.props.onNext(i.props.nextPage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PaginatorCursor extends Component<PaginatorCursorProps, any> {
|
||||||
|
constructor(props: any, context: any) {
|
||||||
|
super(props, context);
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div className="paginator my-2">
|
||||||
|
<button
|
||||||
|
className="btn btn-secondary me-2"
|
||||||
|
disabled={!this.props.prevPage}
|
||||||
|
onClick={linkEvent(this, handlePrev)}
|
||||||
|
>
|
||||||
|
{I18NextService.i18n.t("prev")}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="btn btn-secondary"
|
||||||
|
onClick={linkEvent(this, handleNext)}
|
||||||
|
disabled={!this.props.nextPage}
|
||||||
|
>
|
||||||
|
{I18NextService.i18n.t("next")}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,6 +4,7 @@ import { I18NextService } from "../../services";
|
||||||
interface PaginatorProps {
|
interface PaginatorProps {
|
||||||
page: number;
|
page: number;
|
||||||
onChange(val: number): any;
|
onChange(val: number): any;
|
||||||
|
nextDisabled: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Paginator extends Component<PaginatorProps, any> {
|
export class Paginator extends Component<PaginatorProps, any> {
|
||||||
|
@ -15,7 +16,7 @@ export class Paginator extends Component<PaginatorProps, any> {
|
||||||
<div className="paginator my-2">
|
<div className="paginator my-2">
|
||||||
<button
|
<button
|
||||||
className="btn btn-secondary me-2"
|
className="btn btn-secondary me-2"
|
||||||
disabled={this.props.page == 1}
|
disabled={this.props.page === 1}
|
||||||
onClick={linkEvent(this, this.handlePrev)}
|
onClick={linkEvent(this, this.handlePrev)}
|
||||||
>
|
>
|
||||||
{I18NextService.i18n.t("prev")}
|
{I18NextService.i18n.t("prev")}
|
||||||
|
@ -23,6 +24,7 @@ export class Paginator extends Component<PaginatorProps, any> {
|
||||||
<button
|
<button
|
||||||
className="btn btn-secondary"
|
className="btn btn-secondary"
|
||||||
onClick={linkEvent(this, this.handleNext)}
|
onClick={linkEvent(this, this.handleNext)}
|
||||||
|
disabled={this.props.nextDisabled || false}
|
||||||
>
|
>
|
||||||
{I18NextService.i18n.t("next")}
|
{I18NextService.i18n.t("next")}
|
||||||
</button>
|
</button>
|
||||||
|
|
159
src/shared/components/common/password-input.tsx
Normal file
159
src/shared/components/common/password-input.tsx
Normal file
|
@ -0,0 +1,159 @@
|
||||||
|
import { Options, passwordStrength } from "check-password-strength";
|
||||||
|
import classNames from "classnames";
|
||||||
|
import { NoOptionI18nKeys } from "i18next";
|
||||||
|
import { Component, FormEventHandler, linkEvent } from "inferno";
|
||||||
|
import { NavLink } from "inferno-router";
|
||||||
|
import { I18NextService } from "../../services";
|
||||||
|
import { Icon } from "./icon";
|
||||||
|
|
||||||
|
interface PasswordInputProps {
|
||||||
|
id: string;
|
||||||
|
value?: string;
|
||||||
|
onInput: FormEventHandler<HTMLInputElement>;
|
||||||
|
className?: string;
|
||||||
|
showStrength?: boolean;
|
||||||
|
label?: string | null;
|
||||||
|
showForgotLink?: boolean;
|
||||||
|
isNew?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PasswordInputState {
|
||||||
|
show: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const passwordStrengthOptions: Options<string> = [
|
||||||
|
{
|
||||||
|
id: 0,
|
||||||
|
value: "very_weak",
|
||||||
|
minDiversity: 0,
|
||||||
|
minLength: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
value: "weak",
|
||||||
|
minDiversity: 2,
|
||||||
|
minLength: 10,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
value: "medium",
|
||||||
|
minDiversity: 3,
|
||||||
|
minLength: 12,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
value: "strong",
|
||||||
|
minDiversity: 4,
|
||||||
|
minLength: 14,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
function handleToggleShow(i: PasswordInput) {
|
||||||
|
i.setState(prev => ({
|
||||||
|
...prev,
|
||||||
|
show: !prev.show,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
class PasswordInput extends Component<PasswordInputProps, PasswordInputState> {
|
||||||
|
state: PasswordInputState = {
|
||||||
|
show: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(props: PasswordInputProps, context: any) {
|
||||||
|
super(props, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
props: {
|
||||||
|
id,
|
||||||
|
value,
|
||||||
|
onInput,
|
||||||
|
className,
|
||||||
|
showStrength,
|
||||||
|
label,
|
||||||
|
showForgotLink,
|
||||||
|
isNew,
|
||||||
|
},
|
||||||
|
state: { show },
|
||||||
|
} = this;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className={classNames("row", className)}>
|
||||||
|
{label && (
|
||||||
|
<label className="col-sm-2 col-form-label" htmlFor={id}>
|
||||||
|
{label}
|
||||||
|
</label>
|
||||||
|
)}
|
||||||
|
<div className={`col-sm-${label ? 10 : 12}`}>
|
||||||
|
<div className="input-group">
|
||||||
|
<input
|
||||||
|
type={show ? "text" : "password"}
|
||||||
|
className="form-control"
|
||||||
|
aria-describedby={id}
|
||||||
|
autoComplete={isNew ? "new-password" : "current-password"}
|
||||||
|
onInput={onInput}
|
||||||
|
value={value}
|
||||||
|
required
|
||||||
|
maxLength={60}
|
||||||
|
minLength={10}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
className="btn btn-outline-dark"
|
||||||
|
type="button"
|
||||||
|
id={id}
|
||||||
|
onClick={linkEvent(this, handleToggleShow)}
|
||||||
|
aria-label={I18NextService.i18n.t(
|
||||||
|
`${show ? "show" : "hide"}_password`,
|
||||||
|
)}
|
||||||
|
data-tippy-content={I18NextService.i18n.t(
|
||||||
|
`${show ? "show" : "hide"}_password`,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Icon icon={`eye${show ? "-slash" : ""}`} inline />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{showStrength && value && (
|
||||||
|
<div className={this.passwordColorClass}>
|
||||||
|
{I18NextService.i18n.t(
|
||||||
|
this.passwordStrength as NoOptionI18nKeys,
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{showForgotLink && (
|
||||||
|
<NavLink
|
||||||
|
className="btn p-0 btn-link d-inline-block float-right text-muted small font-weight-bold pointer-events not-allowed"
|
||||||
|
to="/login_reset"
|
||||||
|
>
|
||||||
|
{I18NextService.i18n.t("forgot_password")}
|
||||||
|
</NavLink>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
get passwordStrength(): string | undefined {
|
||||||
|
const password = this.props.value;
|
||||||
|
return password
|
||||||
|
? passwordStrength(password, passwordStrengthOptions).value
|
||||||
|
: undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
get passwordColorClass(): string {
|
||||||
|
const strength = this.passwordStrength;
|
||||||
|
|
||||||
|
if (strength && ["weak", "medium"].includes(strength)) {
|
||||||
|
return "text-warning";
|
||||||
|
} else if (strength === "strong") {
|
||||||
|
return "text-success";
|
||||||
|
} else {
|
||||||
|
return "text-danger";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PasswordInput;
|
|
@ -1,6 +1,8 @@
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { Component } from "inferno";
|
import { Component } from "inferno";
|
||||||
|
|
||||||
|
import { UserService } from "../../services";
|
||||||
|
|
||||||
const iconThumbnailSize = 96;
|
const iconThumbnailSize = 96;
|
||||||
const thumbnailSize = 256;
|
const thumbnailSize = 256;
|
||||||
|
|
||||||
|
@ -13,6 +15,7 @@ interface PictrsImageProps {
|
||||||
nsfw?: boolean;
|
nsfw?: boolean;
|
||||||
iconOverlay?: boolean;
|
iconOverlay?: boolean;
|
||||||
pushup?: boolean;
|
pushup?: boolean;
|
||||||
|
cardTop?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PictrsImage extends Component<PictrsImageProps, any> {
|
export class PictrsImage extends Component<PictrsImageProps, any> {
|
||||||
|
@ -21,28 +24,40 @@ export class PictrsImage extends Component<PictrsImageProps, any> {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const { src, icon, iconOverlay, banner, thumbnail, nsfw, pushup, cardTop } =
|
||||||
|
this.props;
|
||||||
|
let user_blur_nsfw = true;
|
||||||
|
if (UserService.Instance.myUserInfo) {
|
||||||
|
user_blur_nsfw =
|
||||||
|
UserService.Instance.myUserInfo?.local_user_view.local_user.blur_nsfw;
|
||||||
|
}
|
||||||
|
|
||||||
|
const blur_image = nsfw && user_blur_nsfw;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<picture>
|
<picture>
|
||||||
<source srcSet={this.src("webp")} type="image/webp" />
|
<source srcSet={this.src("webp")} type="image/webp" />
|
||||||
<source srcSet={this.props.src} />
|
<source srcSet={src} />
|
||||||
<source srcSet={this.src("jpg")} type="image/jpeg" />
|
<source srcSet={this.src("jpg")} type="image/jpeg" />
|
||||||
<img
|
<img
|
||||||
src={this.props.src}
|
src={src}
|
||||||
alt={this.alt()}
|
alt={this.alt()}
|
||||||
title={this.alt()}
|
title={this.alt()}
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
className={classNames("overflow-hidden pictrs-image", {
|
className={classNames("overflow-hidden pictrs-image", {
|
||||||
"img-fluid": !this.props.icon && !this.props.iconOverlay,
|
"img-fluid": !(icon || iconOverlay),
|
||||||
banner: this.props.banner,
|
banner,
|
||||||
"thumbnail rounded object-fit-cover":
|
"thumbnail rounded object-fit-cover":
|
||||||
this.props.thumbnail && !this.props.icon && !this.props.banner,
|
thumbnail && !(icon || banner),
|
||||||
"img-expanded slight-radius":
|
"img-expanded slight-radius": !(thumbnail || icon),
|
||||||
!this.props.thumbnail && !this.props.icon,
|
"img-blur": thumbnail && nsfw,
|
||||||
"img-blur": this.props.thumbnail && this.props.nsfw,
|
"object-fit-cover img-icon me-1": icon,
|
||||||
"object-fit-cover img-icon me-1": this.props.icon,
|
"img-blur-icon": icon && blur_image,
|
||||||
|
"img-blur-thumb": thumbnail && blur_image,
|
||||||
"ms-2 mb-0 rounded-circle object-fit-cover avatar-overlay":
|
"ms-2 mb-0 rounded-circle object-fit-cover avatar-overlay":
|
||||||
this.props.iconOverlay,
|
iconOverlay,
|
||||||
"avatar-pushup": this.props.pushup,
|
"avatar-pushup": pushup,
|
||||||
|
"card-img-top": cardTop,
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
</picture>
|
</picture>
|
||||||
|
@ -56,7 +71,7 @@ export class PictrsImage extends Component<PictrsImageProps, any> {
|
||||||
const split = this.props.src.split("/pictrs/image/");
|
const split = this.props.src.split("/pictrs/image/");
|
||||||
|
|
||||||
// If theres not multiple, then its not a pictrs image
|
// If theres not multiple, then its not a pictrs image
|
||||||
if (split.length == 1) {
|
if (split.length === 1) {
|
||||||
return this.props.src;
|
return this.props.src;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import { myAuthRequired } from "@utils/app";
|
|
||||||
import { Component, InfernoNode, linkEvent } from "inferno";
|
import { Component, InfernoNode, linkEvent } from "inferno";
|
||||||
import { T } from "inferno-i18next-dess";
|
import { T } from "inferno-i18next-dess";
|
||||||
import {
|
import {
|
||||||
|
@ -42,9 +41,9 @@ export class RegistrationApplication extends Component<
|
||||||
componentWillReceiveProps(
|
componentWillReceiveProps(
|
||||||
nextProps: Readonly<
|
nextProps: Readonly<
|
||||||
{ children?: InfernoNode } & RegistrationApplicationProps
|
{ children?: InfernoNode } & RegistrationApplicationProps
|
||||||
>
|
>,
|
||||||
): void {
|
): void {
|
||||||
if (this.props != nextProps) {
|
if (this.props !== nextProps) {
|
||||||
this.setState({
|
this.setState({
|
||||||
denyExpanded: false,
|
denyExpanded: false,
|
||||||
approveLoading: false,
|
approveLoading: false,
|
||||||
|
@ -149,7 +148,6 @@ export class RegistrationApplication extends Component<
|
||||||
i.props.onApproveApplication({
|
i.props.onApproveApplication({
|
||||||
id: i.props.application.registration_application.id,
|
id: i.props.application.registration_application.id,
|
||||||
approve: true,
|
approve: true,
|
||||||
auth: myAuthRequired(),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -160,7 +158,6 @@ export class RegistrationApplication extends Component<
|
||||||
id: i.props.application.registration_application.id,
|
id: i.props.application.registration_application.id,
|
||||||
approve: false,
|
approve: false,
|
||||||
deny_reason: i.state.denyReason,
|
deny_reason: i.state.denyReason,
|
||||||
auth: myAuthRequired(),
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
i.setState({ denyExpanded: true });
|
i.setState({ denyExpanded: true });
|
||||||
|
|
69
src/shared/components/common/report-form.tsx
Normal file
69
src/shared/components/common/report-form.tsx
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
import { Component, linkEvent } from "inferno";
|
||||||
|
import { I18NextService } from "../../services/I18NextService";
|
||||||
|
import { Spinner } from "./icon";
|
||||||
|
import { randomStr } from "@utils/helpers";
|
||||||
|
|
||||||
|
interface ReportFormProps {
|
||||||
|
onSubmit: (reason: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ReportFormState {
|
||||||
|
loading: boolean;
|
||||||
|
reason: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleReportReasonChange(i: ReportForm, event: any) {
|
||||||
|
i.setState({ reason: event.target.value });
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleReportSubmit(i: ReportForm, event: any) {
|
||||||
|
event.preventDefault();
|
||||||
|
i.setState({ loading: true });
|
||||||
|
i.props.onSubmit(i.state.reason);
|
||||||
|
|
||||||
|
i.setState({
|
||||||
|
loading: false,
|
||||||
|
reason: "",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class ReportForm extends Component<
|
||||||
|
ReportFormProps,
|
||||||
|
ReportFormState
|
||||||
|
> {
|
||||||
|
state: ReportFormState = {
|
||||||
|
loading: false,
|
||||||
|
reason: "",
|
||||||
|
};
|
||||||
|
constructor(props, context) {
|
||||||
|
super(props, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { loading, reason } = this.state;
|
||||||
|
const id = `report-form-${randomStr()}`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form
|
||||||
|
className="form-inline"
|
||||||
|
onSubmit={linkEvent(this, handleReportSubmit)}
|
||||||
|
>
|
||||||
|
<label className="visually-hidden" htmlFor={id}>
|
||||||
|
{I18NextService.i18n.t("reason")}
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id={id}
|
||||||
|
className="form-control me-2"
|
||||||
|
placeholder={I18NextService.i18n.t("reason")}
|
||||||
|
required
|
||||||
|
value={reason}
|
||||||
|
onInput={linkEvent(this, handleReportReasonChange)}
|
||||||
|
/>
|
||||||
|
<button type="submit" className="btn btn-secondary">
|
||||||
|
{loading ? <Spinner /> : I18NextService.i18n.t("create_report")}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,6 +9,7 @@ import {
|
||||||
} from "inferno";
|
} from "inferno";
|
||||||
import { I18NextService } from "../../services";
|
import { I18NextService } from "../../services";
|
||||||
import { Icon, Spinner } from "./icon";
|
import { Icon, Spinner } from "./icon";
|
||||||
|
import { LoadingEllipses } from "./loading-ellipses";
|
||||||
|
|
||||||
interface SearchableSelectProps {
|
interface SearchableSelectProps {
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -22,7 +23,6 @@ interface SearchableSelectProps {
|
||||||
interface SearchableSelectState {
|
interface SearchableSelectState {
|
||||||
selectedIndex: number;
|
selectedIndex: number;
|
||||||
searchText: string;
|
searchText: string;
|
||||||
loadingEllipses: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleSearch(i: SearchableSelect, e: ChangeEvent<HTMLInputElement>) {
|
function handleSearch(i: SearchableSelect, e: ChangeEvent<HTMLInputElement>) {
|
||||||
|
@ -70,12 +70,10 @@ export class SearchableSelect extends Component<
|
||||||
> {
|
> {
|
||||||
searchInputRef: RefObject<HTMLInputElement> = createRef();
|
searchInputRef: RefObject<HTMLInputElement> = createRef();
|
||||||
toggleButtonRef: RefObject<HTMLButtonElement> = createRef();
|
toggleButtonRef: RefObject<HTMLButtonElement> = createRef();
|
||||||
private loadingEllipsesInterval?: NodeJS.Timer = undefined;
|
|
||||||
|
|
||||||
state: SearchableSelectState = {
|
state: SearchableSelectState = {
|
||||||
selectedIndex: 0,
|
selectedIndex: 0,
|
||||||
searchText: "",
|
searchText: "",
|
||||||
loadingEllipses: "...",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props: SearchableSelectProps, context: any) {
|
constructor(props: SearchableSelectProps, context: any) {
|
||||||
|
@ -83,7 +81,7 @@ export class SearchableSelect extends Component<
|
||||||
|
|
||||||
if (props.value) {
|
if (props.value) {
|
||||||
let selectedIndex = props.options.findIndex(
|
let selectedIndex = props.options.findIndex(
|
||||||
({ value }) => value === props.value?.toString()
|
({ value }) => value === props.value?.toString(),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (selectedIndex < 0) {
|
if (selectedIndex < 0) {
|
||||||
|
@ -99,7 +97,7 @@ export class SearchableSelect extends Component<
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { id, options, onSearch, loading } = this.props;
|
const { id, options, onSearch, loading } = this.props;
|
||||||
const { searchText, selectedIndex, loadingEllipses } = this.state;
|
const { searchText, selectedIndex } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="searchable-select dropdown col-12 col-sm-auto flex-grow-1">
|
<div className="searchable-select dropdown col-12 col-sm-auto flex-grow-1">
|
||||||
|
@ -116,9 +114,14 @@ export class SearchableSelect extends Component<
|
||||||
onClick={linkEvent(this, focusSearch)}
|
onClick={linkEvent(this, focusSearch)}
|
||||||
ref={this.toggleButtonRef}
|
ref={this.toggleButtonRef}
|
||||||
>
|
>
|
||||||
{loading
|
{loading ? (
|
||||||
? `${I18NextService.i18n.t("loading")}${loadingEllipses}`
|
<>
|
||||||
: options[selectedIndex].label}
|
{I18NextService.i18n.t("loading")}
|
||||||
|
<LoadingEllipses />
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
options[selectedIndex].label
|
||||||
|
)}
|
||||||
</button>
|
</button>
|
||||||
<div className="modlog-choices-font-size dropdown-menu w-100 p-2">
|
<div className="modlog-choices-font-size dropdown-menu w-100 p-2">
|
||||||
<div className="input-group">
|
<div className="input-group">
|
||||||
|
@ -140,7 +143,7 @@ export class SearchableSelect extends Component<
|
||||||
(onSearch || searchText.length === 0
|
(onSearch || searchText.length === 0
|
||||||
? options
|
? options
|
||||||
: options.filter(({ label }) =>
|
: options.filter(({ label }) =>
|
||||||
label.toLowerCase().includes(searchText.toLowerCase())
|
label.toLowerCase().includes(searchText.toLowerCase()),
|
||||||
)
|
)
|
||||||
).map((option, index) => (
|
).map((option, index) => (
|
||||||
<button
|
<button
|
||||||
|
@ -180,24 +183,4 @@ export class SearchableSelect extends Component<
|
||||||
selectedIndex,
|
selectedIndex,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate() {
|
|
||||||
const { loading } = this.props;
|
|
||||||
if (loading && !this.loadingEllipsesInterval) {
|
|
||||||
this.loadingEllipsesInterval = setInterval(() => {
|
|
||||||
this.setState(({ loadingEllipses }) => ({
|
|
||||||
loadingEllipses:
|
|
||||||
loadingEllipses.length === 3 ? "" : loadingEllipses + ".",
|
|
||||||
}));
|
|
||||||
}, 750);
|
|
||||||
} else if (!loading && this.loadingEllipsesInterval) {
|
|
||||||
clearInterval(this.loadingEllipsesInterval);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
if (this.loadingEllipsesInterval) {
|
|
||||||
clearInterval(this.loadingEllipsesInterval);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,7 +53,13 @@ export class SortSelect extends Component<SortSelectProps, SortSelectState> {
|
||||||
<option key={"Active"} value={"Active"}>
|
<option key={"Active"} value={"Active"}>
|
||||||
{I18NextService.i18n.t("active")}
|
{I18NextService.i18n.t("active")}
|
||||||
</option>,
|
</option>,
|
||||||
|
<option key={"Scaled"} value={"Scaled"}>
|
||||||
|
{I18NextService.i18n.t("scaled")}
|
||||||
|
</option>,
|
||||||
]}
|
]}
|
||||||
|
<option value={"Controversial"}>
|
||||||
|
{I18NextService.i18n.t("controversial")}
|
||||||
|
</option>
|
||||||
<option value={"New"}>{I18NextService.i18n.t("new")}</option>
|
<option value={"New"}>{I18NextService.i18n.t("new")}</option>
|
||||||
<option value={"Old"}>{I18NextService.i18n.t("old")}</option>
|
<option value={"Old"}>{I18NextService.i18n.t("old")}</option>
|
||||||
{!this.props.hideMostComments && [
|
{!this.props.hideMostComments && [
|
||||||
|
@ -79,6 +85,15 @@ export class SortSelect extends Component<SortSelectProps, SortSelectState> {
|
||||||
<option value={"TopMonth"}>
|
<option value={"TopMonth"}>
|
||||||
{I18NextService.i18n.t("top_month")}
|
{I18NextService.i18n.t("top_month")}
|
||||||
</option>
|
</option>
|
||||||
|
<option value={"TopThreeMonths"}>
|
||||||
|
{I18NextService.i18n.t("top_three_months")}
|
||||||
|
</option>
|
||||||
|
<option value={"TopSixMonths"}>
|
||||||
|
{I18NextService.i18n.t("top_six_months")}
|
||||||
|
</option>
|
||||||
|
<option value={"TopNineMonths"}>
|
||||||
|
{I18NextService.i18n.t("top_nine_months")}
|
||||||
|
</option>
|
||||||
<option value={"TopYear"}>{I18NextService.i18n.t("top_year")}</option>
|
<option value={"TopYear"}>{I18NextService.i18n.t("top_year")}</option>
|
||||||
<option value={"TopAll"}>{I18NextService.i18n.t("top_all")}</option>
|
<option value={"TopAll"}>{I18NextService.i18n.t("top_all")}</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
229
src/shared/components/common/subscribe-button.tsx
Normal file
229
src/shared/components/common/subscribe-button.tsx
Normal file
|
@ -0,0 +1,229 @@
|
||||||
|
import { validInstanceTLD } from "@utils/helpers";
|
||||||
|
import classNames from "classnames";
|
||||||
|
import { NoOptionI18nKeys } from "i18next";
|
||||||
|
import { Component, MouseEventHandler, linkEvent } from "inferno";
|
||||||
|
import { CommunityView } from "lemmy-js-client";
|
||||||
|
import { I18NextService, UserService } from "../../services";
|
||||||
|
import { VERSION } from "../../version";
|
||||||
|
import { Icon, Spinner } from "./icon";
|
||||||
|
import { toast } from "../../toast";
|
||||||
|
|
||||||
|
interface SubscribeButtonProps {
|
||||||
|
communityView: CommunityView;
|
||||||
|
onFollow: MouseEventHandler;
|
||||||
|
onUnFollow: MouseEventHandler;
|
||||||
|
loading?: boolean;
|
||||||
|
isLink?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SubscribeButton({
|
||||||
|
communityView: {
|
||||||
|
subscribed,
|
||||||
|
community: { actor_id },
|
||||||
|
},
|
||||||
|
onFollow,
|
||||||
|
onUnFollow,
|
||||||
|
loading = false,
|
||||||
|
isLink = false,
|
||||||
|
}: SubscribeButtonProps) {
|
||||||
|
let i18key: NoOptionI18nKeys;
|
||||||
|
|
||||||
|
switch (subscribed) {
|
||||||
|
case "NotSubscribed": {
|
||||||
|
i18key = "subscribe";
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "Subscribed": {
|
||||||
|
i18key = "joined";
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
i18key = "subscribe_pending";
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const buttonClass = classNames(
|
||||||
|
"btn",
|
||||||
|
isLink ? "btn-link d-inline-block" : "d-block mb-2 w-100",
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!UserService.Instance.myUserInfo) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={classNames(buttonClass, {
|
||||||
|
"btn-secondary": !isLink,
|
||||||
|
})}
|
||||||
|
data-bs-toggle="modal"
|
||||||
|
data-bs-target="#remoteFetchModal"
|
||||||
|
>
|
||||||
|
{I18NextService.i18n.t("subscribe")}
|
||||||
|
</button>
|
||||||
|
<RemoteFetchModal communityActorId={actor_id} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={classNames(buttonClass, {
|
||||||
|
[`btn-${subscribed === "Pending" ? "warning" : "secondary"}`]: !isLink,
|
||||||
|
})}
|
||||||
|
onClick={subscribed === "NotSubscribed" ? onFollow : onUnFollow}
|
||||||
|
>
|
||||||
|
{loading ? (
|
||||||
|
<Spinner />
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{subscribed === "Subscribed" && (
|
||||||
|
<Icon icon="check" classes="icon-inline me-1" />
|
||||||
|
)}
|
||||||
|
{I18NextService.i18n.t(i18key)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RemoteFetchModalProps {
|
||||||
|
communityActorId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RemoteFetchModalState {
|
||||||
|
instanceText: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleInput(i: RemoteFetchModal, event: any) {
|
||||||
|
i.setState({ instanceText: event.target.value });
|
||||||
|
}
|
||||||
|
|
||||||
|
function focusInput() {
|
||||||
|
document.getElementById("remoteFetchInstance")?.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
function submitRemoteFollow(
|
||||||
|
{ state: { instanceText }, props: { communityActorId } }: RemoteFetchModal,
|
||||||
|
event: Event,
|
||||||
|
) {
|
||||||
|
event.preventDefault();
|
||||||
|
instanceText = instanceText.trim();
|
||||||
|
|
||||||
|
if (!validInstanceTLD(instanceText)) {
|
||||||
|
toast(
|
||||||
|
I18NextService.i18n.t("remote_follow_invalid_instance", {
|
||||||
|
instance: instanceText,
|
||||||
|
}),
|
||||||
|
"danger",
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const protocolRegex = /^https?:\/\//;
|
||||||
|
if (instanceText.replace(protocolRegex, "") === window.location.host) {
|
||||||
|
toast(I18NextService.i18n.t("remote_follow_local_instance"), "danger");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!protocolRegex.test(instanceText)) {
|
||||||
|
instanceText = `http${VERSION !== "dev" ? "s" : ""}://${instanceText}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.location.href = `${instanceText}/activitypub/externalInteraction?uri=${encodeURIComponent(
|
||||||
|
communityActorId,
|
||||||
|
)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
class RemoteFetchModal extends Component<
|
||||||
|
RemoteFetchModalProps,
|
||||||
|
RemoteFetchModalState
|
||||||
|
> {
|
||||||
|
state: RemoteFetchModalState = {
|
||||||
|
instanceText: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(props: any, context: any) {
|
||||||
|
super(props, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
document
|
||||||
|
.getElementById("remoteFetchModal")
|
||||||
|
?.addEventListener("shown.bs.modal", focusInput);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount(): void {
|
||||||
|
document
|
||||||
|
.getElementById("remoteFetchModal")
|
||||||
|
?.removeEventListener("shown.bs.modal", focusInput);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="modal fade"
|
||||||
|
id="remoteFetchModal"
|
||||||
|
tabIndex={-1}
|
||||||
|
aria-hidden
|
||||||
|
aria-labelledby="#remoteFetchModalTitle"
|
||||||
|
>
|
||||||
|
<div className="modal-dialog modal-fullscreen-sm-down">
|
||||||
|
<div className="modal-content">
|
||||||
|
<header className="modal-header">
|
||||||
|
<h3 className="modal-title" id="remoteFetchModalTitle">
|
||||||
|
{I18NextService.i18n.t("remote_follow_modal_title")}
|
||||||
|
</h3>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn-close"
|
||||||
|
data-bs-dismiss="modal"
|
||||||
|
aria-label="Close"
|
||||||
|
/>
|
||||||
|
</header>
|
||||||
|
<form
|
||||||
|
id="remote-fetch-form"
|
||||||
|
className="modal-body d-flex flex-column justify-content-center"
|
||||||
|
onSubmit={linkEvent(this, submitRemoteFollow)}
|
||||||
|
>
|
||||||
|
<label className="form-label" htmlFor="remoteFetchInstance">
|
||||||
|
{I18NextService.i18n.t("remote_follow_prompt")}
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="remoteFetchInstance"
|
||||||
|
className="form-control"
|
||||||
|
name="instance"
|
||||||
|
value={this.state.instanceText}
|
||||||
|
onInput={linkEvent(this, handleInput)}
|
||||||
|
required
|
||||||
|
enterKeyHint="go"
|
||||||
|
inputMode="url"
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
<footer className="modal-footer">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn btn-danger"
|
||||||
|
data-bs-dismiss="modal"
|
||||||
|
>
|
||||||
|
{I18NextService.i18n.t("cancel")}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="btn btn-success"
|
||||||
|
form="remote-fetch-form"
|
||||||
|
>
|
||||||
|
{I18NextService.i18n.t("fetch_community")}
|
||||||
|
</button>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
244
src/shared/components/common/totp-modal.tsx
Normal file
244
src/shared/components/common/totp-modal.tsx
Normal file
|
@ -0,0 +1,244 @@
|
||||||
|
import {
|
||||||
|
Component,
|
||||||
|
MouseEventHandler,
|
||||||
|
RefObject,
|
||||||
|
createRef,
|
||||||
|
linkEvent,
|
||||||
|
} from "inferno";
|
||||||
|
import { I18NextService } from "../../services";
|
||||||
|
import { toast } from "../../toast";
|
||||||
|
import type { Modal } from "bootstrap";
|
||||||
|
|
||||||
|
interface TotpModalProps {
|
||||||
|
/**Takes totp as param, returns whether submit was successful*/
|
||||||
|
onSubmit: (totp: string) => Promise<boolean>;
|
||||||
|
onClose: MouseEventHandler;
|
||||||
|
type: "login" | "remove" | "generate";
|
||||||
|
secretUrl?: string;
|
||||||
|
show?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TotpModalState {
|
||||||
|
totp: string;
|
||||||
|
qrCode?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TOTP_LENGTH = 6;
|
||||||
|
|
||||||
|
async function handleSubmit(i: TotpModal, totp: string) {
|
||||||
|
const successful = await i.props.onSubmit(totp);
|
||||||
|
|
||||||
|
if (!successful) {
|
||||||
|
i.setState({ totp: "" });
|
||||||
|
i.inputRef.current?.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleInput(i: TotpModal, event: any) {
|
||||||
|
if (isNaN(event.target.value)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
i.setState({
|
||||||
|
totp: event.target.value,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { totp } = i.state;
|
||||||
|
if (totp.length >= TOTP_LENGTH) {
|
||||||
|
handleSubmit(i, totp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handlePaste(i: TotpModal, event: any) {
|
||||||
|
event.preventDefault();
|
||||||
|
const text: string = event.clipboardData.getData("text")?.trim();
|
||||||
|
|
||||||
|
if (text.length > TOTP_LENGTH || isNaN(Number(text))) {
|
||||||
|
toast(I18NextService.i18n.t("invalid_totp_code"), "danger");
|
||||||
|
i.clearTotp();
|
||||||
|
} else {
|
||||||
|
i.setState({ totp: text });
|
||||||
|
|
||||||
|
if (text.length === TOTP_LENGTH) {
|
||||||
|
handleSubmit(i, text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class TotpModal extends Component<
|
||||||
|
TotpModalProps,
|
||||||
|
TotpModalState
|
||||||
|
> {
|
||||||
|
readonly modalDivRef: RefObject<HTMLDivElement>;
|
||||||
|
readonly inputRef: RefObject<HTMLInputElement>;
|
||||||
|
modal: Modal;
|
||||||
|
state: TotpModalState = {
|
||||||
|
totp: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(props: TotpModalProps, context: any) {
|
||||||
|
super(props, context);
|
||||||
|
|
||||||
|
this.modalDivRef = createRef();
|
||||||
|
this.inputRef = createRef();
|
||||||
|
|
||||||
|
this.clearTotp = this.clearTotp.bind(this);
|
||||||
|
this.handleShow = this.handleShow.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
async componentDidMount() {
|
||||||
|
this.modalDivRef.current?.addEventListener(
|
||||||
|
"shown.bs.modal",
|
||||||
|
this.handleShow,
|
||||||
|
);
|
||||||
|
|
||||||
|
this.modalDivRef.current?.addEventListener(
|
||||||
|
"hidden.bs.modal",
|
||||||
|
this.clearTotp,
|
||||||
|
);
|
||||||
|
|
||||||
|
const Modal = (await import("bootstrap/js/dist/modal")).default;
|
||||||
|
this.modal = new Modal(this.modalDivRef.current!);
|
||||||
|
|
||||||
|
if (this.props.show) {
|
||||||
|
this.modal.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this.modalDivRef.current?.removeEventListener(
|
||||||
|
"shown.bs.modal",
|
||||||
|
this.handleShow,
|
||||||
|
);
|
||||||
|
|
||||||
|
this.modalDivRef.current?.removeEventListener(
|
||||||
|
"hidden.bs.modal",
|
||||||
|
this.clearTotp,
|
||||||
|
);
|
||||||
|
|
||||||
|
this.modal.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate({ show: prevShow }: TotpModalProps) {
|
||||||
|
if (!!prevShow !== !!this.props.show) {
|
||||||
|
if (this.props.show) {
|
||||||
|
this.modal.show();
|
||||||
|
} else {
|
||||||
|
this.modal.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { type, secretUrl, onClose } = this.props;
|
||||||
|
const { totp } = this.state;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="modal fade"
|
||||||
|
id="totpModal"
|
||||||
|
tabIndex={-1}
|
||||||
|
aria-hidden
|
||||||
|
aria-labelledby="#totpModalTitle"
|
||||||
|
data-bs-backdrop="static"
|
||||||
|
ref={this.modalDivRef}
|
||||||
|
>
|
||||||
|
<div className="modal-dialog modal-fullscreen-sm-down">
|
||||||
|
<div className="modal-content">
|
||||||
|
<header className="modal-header">
|
||||||
|
<h3 className="modal-title" id="totpModalTitle">
|
||||||
|
{I18NextService.i18n.t(
|
||||||
|
type === "generate"
|
||||||
|
? "enable_totp"
|
||||||
|
: type === "remove"
|
||||||
|
? "disable_totp"
|
||||||
|
: "enter_totp_code",
|
||||||
|
)}
|
||||||
|
</h3>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn-close"
|
||||||
|
aria-label="Close"
|
||||||
|
onClick={onClose}
|
||||||
|
/>
|
||||||
|
</header>
|
||||||
|
<div className="modal-body d-flex flex-column align-items-center justify-content-center">
|
||||||
|
{type === "generate" && (
|
||||||
|
<div>
|
||||||
|
<a
|
||||||
|
className="btn btn-secondary mx-auto d-block totp-link"
|
||||||
|
href={secretUrl}
|
||||||
|
>
|
||||||
|
{I18NextService.i18n.t("totp_link")}
|
||||||
|
</a>
|
||||||
|
<div className="mx-auto mt-3 w-50 h-50 text-center">
|
||||||
|
<strong className="fw-semibold">
|
||||||
|
{I18NextService.i18n.t("totp_qr_segue")}
|
||||||
|
</strong>
|
||||||
|
<img
|
||||||
|
src={this.state.qrCode}
|
||||||
|
className="d-block mt-1 mx-auto"
|
||||||
|
alt={I18NextService.i18n.t("totp_qr")}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<form id="totp-form">
|
||||||
|
<label
|
||||||
|
className="form-label ms-2 mt-4 fw-bold"
|
||||||
|
htmlFor="totp-input"
|
||||||
|
>
|
||||||
|
{I18NextService.i18n.t("enter_totp_code")}
|
||||||
|
</label>
|
||||||
|
<div className="d-flex justify-content-between align-items-center p-2">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
inputMode="numeric"
|
||||||
|
autoComplete="one-time-code"
|
||||||
|
maxLength={TOTP_LENGTH}
|
||||||
|
id="totp-input"
|
||||||
|
className="form-control form-control-lg mx-2 p-1 p-md-2 text-center"
|
||||||
|
onInput={linkEvent(this, handleInput)}
|
||||||
|
onPaste={linkEvent(this, handlePaste)}
|
||||||
|
ref={this.inputRef}
|
||||||
|
enterKeyHint="done"
|
||||||
|
value={totp}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<footer className="modal-footer">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn btn-danger"
|
||||||
|
onClick={onClose}
|
||||||
|
>
|
||||||
|
{I18NextService.i18n.t("cancel")}
|
||||||
|
</button>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
clearTotp() {
|
||||||
|
this.setState({ totp: "" });
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleShow() {
|
||||||
|
this.inputRef.current?.focus();
|
||||||
|
|
||||||
|
if (this.props.type === "generate") {
|
||||||
|
const { getSVG } = await import("@shortcm/qr-image/lib/svg");
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
qrCode: URL.createObjectURL(
|
||||||
|
new Blob([(await getSVG(this.props.secretUrl!)).buffer], {
|
||||||
|
type: "image/svg+xml",
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -45,7 +45,7 @@ export class UserBadges extends Component<UserBadgesProps> {
|
||||||
<span
|
<span
|
||||||
className={classNames(
|
className={classNames(
|
||||||
"row d-inline-flex gx-1",
|
"row d-inline-flex gx-1",
|
||||||
this.props.classNames
|
this.props.classNames,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{this.props.isBanned && (
|
{this.props.isBanned && (
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { myAuthRequired, newVote, showScores } from "@utils/app";
|
import { newVote, showScores } from "@utils/app";
|
||||||
import { numToSI } from "@utils/helpers";
|
import { numToSI } from "@utils/helpers";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { Component, linkEvent } from "inferno";
|
import { Component, linkEvent } from "inferno";
|
||||||
|
@ -53,7 +53,6 @@ const handleUpvote = (i: VoteButtons) => {
|
||||||
i.props.onVote({
|
i.props.onVote({
|
||||||
comment_id: i.props.id,
|
comment_id: i.props.id,
|
||||||
score: newVote(VoteType.Upvote, i.props.my_vote),
|
score: newVote(VoteType.Upvote, i.props.my_vote),
|
||||||
auth: myAuthRequired(),
|
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case VoteContentType.Post:
|
case VoteContentType.Post:
|
||||||
|
@ -61,7 +60,6 @@ const handleUpvote = (i: VoteButtons) => {
|
||||||
i.props.onVote({
|
i.props.onVote({
|
||||||
post_id: i.props.id,
|
post_id: i.props.id,
|
||||||
score: newVote(VoteType.Upvote, i.props.my_vote),
|
score: newVote(VoteType.Upvote, i.props.my_vote),
|
||||||
auth: myAuthRequired(),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -73,7 +71,6 @@ const handleDownvote = (i: VoteButtons) => {
|
||||||
i.props.onVote({
|
i.props.onVote({
|
||||||
comment_id: i.props.id,
|
comment_id: i.props.id,
|
||||||
score: newVote(VoteType.Downvote, i.props.my_vote),
|
score: newVote(VoteType.Downvote, i.props.my_vote),
|
||||||
auth: myAuthRequired(),
|
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case VoteContentType.Post:
|
case VoteContentType.Post:
|
||||||
|
@ -81,7 +78,6 @@ const handleDownvote = (i: VoteButtons) => {
|
||||||
i.props.onVote({
|
i.props.onVote({
|
||||||
post_id: i.props.id,
|
post_id: i.props.id,
|
||||||
score: newVote(VoteType.Downvote, i.props.my_vote),
|
score: newVote(VoteType.Downvote, i.props.my_vote),
|
||||||
auth: myAuthRequired(),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -193,7 +189,7 @@ export class VoteButtons extends Component<VoteButtonsProps, VoteButtonsState> {
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`btn-animate btn btn-link p-0 ${
|
className={`btn-animate btn btn-link p-0 ${
|
||||||
this.props.my_vote == 1 ? "text-info" : "text-muted"
|
this.props.my_vote === 1 ? "text-info" : "text-muted"
|
||||||
}`}
|
}`}
|
||||||
onClick={linkEvent(this, handleUpvote)}
|
onClick={linkEvent(this, handleUpvote)}
|
||||||
data-tippy-content={I18NextService.i18n.t("upvote")}
|
data-tippy-content={I18NextService.i18n.t("upvote")}
|
||||||
|
@ -220,7 +216,7 @@ export class VoteButtons extends Component<VoteButtonsProps, VoteButtonsState> {
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`btn-animate btn btn-link p-0 ${
|
className={`btn-animate btn btn-link p-0 ${
|
||||||
this.props.my_vote == -1 ? "text-danger" : "text-muted"
|
this.props.my_vote === -1 ? "text-danger" : "text-muted"
|
||||||
}`}
|
}`}
|
||||||
onClick={linkEvent(this, handleDownvote)}
|
onClick={linkEvent(this, handleDownvote)}
|
||||||
data-tippy-content={I18NextService.i18n.t("downvote")}
|
data-tippy-content={I18NextService.i18n.t("downvote")}
|
||||||
|
|
|
@ -1,10 +1,4 @@
|
||||||
import {
|
import { editCommunity, setIsoData, showLocal } from "@utils/app";
|
||||||
editCommunity,
|
|
||||||
myAuth,
|
|
||||||
myAuthRequired,
|
|
||||||
setIsoData,
|
|
||||||
showLocal,
|
|
||||||
} from "@utils/app";
|
|
||||||
import {
|
import {
|
||||||
getPageFromString,
|
getPageFromString,
|
||||||
getQueryParams,
|
getQueryParams,
|
||||||
|
@ -20,17 +14,25 @@ import {
|
||||||
ListCommunities,
|
ListCommunities,
|
||||||
ListCommunitiesResponse,
|
ListCommunitiesResponse,
|
||||||
ListingType,
|
ListingType,
|
||||||
|
SortType,
|
||||||
} from "lemmy-js-client";
|
} from "lemmy-js-client";
|
||||||
import { InitialFetchRequest } from "../../interfaces";
|
import { InitialFetchRequest } from "../../interfaces";
|
||||||
import { FirstLoadService, I18NextService } from "../../services";
|
import { FirstLoadService, I18NextService } from "../../services";
|
||||||
import { HttpService, RequestState } from "../../services/HttpService";
|
import {
|
||||||
|
EMPTY_REQUEST,
|
||||||
|
HttpService,
|
||||||
|
LOADING_REQUEST,
|
||||||
|
RequestState,
|
||||||
|
} from "../../services/HttpService";
|
||||||
import { HtmlTags } from "../common/html-tags";
|
import { HtmlTags } from "../common/html-tags";
|
||||||
import { Spinner } from "../common/icon";
|
import { Spinner } from "../common/icon";
|
||||||
import { ListingTypeSelect } from "../common/listing-type-select";
|
import { ListingTypeSelect } from "../common/listing-type-select";
|
||||||
import { Paginator } from "../common/paginator";
|
import { Paginator } from "../common/paginator";
|
||||||
|
import { SortSelect } from "../common/sort-select";
|
||||||
import { CommunityLink } from "./community-link";
|
import { CommunityLink } from "./community-link";
|
||||||
|
|
||||||
const communityLimit = 50;
|
import { communityLimit } from "../../config";
|
||||||
|
import { SubscribeButton } from "../common/subscribe-button";
|
||||||
|
|
||||||
type CommunitiesData = RouteDataResponse<{
|
type CommunitiesData = RouteDataResponse<{
|
||||||
listCommunitiesResponse: ListCommunitiesResponse;
|
listCommunitiesResponse: ListCommunitiesResponse;
|
||||||
|
@ -45,6 +47,7 @@ interface CommunitiesState {
|
||||||
|
|
||||||
interface CommunitiesProps {
|
interface CommunitiesProps {
|
||||||
listingType: ListingType;
|
listingType: ListingType;
|
||||||
|
sort: SortType;
|
||||||
page: number;
|
page: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,10 +55,21 @@ function getListingTypeFromQuery(listingType?: string): ListingType {
|
||||||
return listingType ? (listingType as ListingType) : "Local";
|
return listingType ? (listingType as ListingType) : "Local";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getSortTypeFromQuery(type?: string): SortType {
|
||||||
|
return type ? (type as SortType) : "TopMonth";
|
||||||
|
}
|
||||||
|
function getCommunitiesQueryParams() {
|
||||||
|
return getQueryParams<CommunitiesProps>({
|
||||||
|
listingType: getListingTypeFromQuery,
|
||||||
|
sort: getSortTypeFromQuery,
|
||||||
|
page: getPageFromString,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export class Communities extends Component<any, CommunitiesState> {
|
export class Communities extends Component<any, CommunitiesState> {
|
||||||
private isoData = setIsoData<CommunitiesData>(this.context);
|
private isoData = setIsoData<CommunitiesData>(this.context);
|
||||||
state: CommunitiesState = {
|
state: CommunitiesState = {
|
||||||
listCommunitiesResponse: { state: "empty" },
|
listCommunitiesResponse: EMPTY_REQUEST,
|
||||||
siteRes: this.isoData.site_res,
|
siteRes: this.isoData.site_res,
|
||||||
searchText: "",
|
searchText: "",
|
||||||
isIsomorphic: false,
|
isIsomorphic: false,
|
||||||
|
@ -64,6 +78,7 @@ export class Communities extends Component<any, CommunitiesState> {
|
||||||
constructor(props: any, context: any) {
|
constructor(props: any, context: any) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
this.handlePageChange = this.handlePageChange.bind(this);
|
this.handlePageChange = this.handlePageChange.bind(this);
|
||||||
|
this.handleSortChange = this.handleSortChange.bind(this);
|
||||||
this.handleListingTypeChange = this.handleListingTypeChange.bind(this);
|
this.handleListingTypeChange = this.handleListingTypeChange.bind(this);
|
||||||
|
|
||||||
// Only fetch the data if coming from another route
|
// Only fetch the data if coming from another route
|
||||||
|
@ -99,13 +114,13 @@ export class Communities extends Component<any, CommunitiesState> {
|
||||||
</h5>
|
</h5>
|
||||||
);
|
);
|
||||||
case "success": {
|
case "success": {
|
||||||
const { listingType, page } = this.getCommunitiesQueryParams();
|
const { listingType, sort, page } = getCommunitiesQueryParams();
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h1 className="h4 mb-4">
|
<h1 className="h4 mb-4">
|
||||||
{I18NextService.i18n.t("list_of_communities")}
|
{I18NextService.i18n.t("list_of_communities")}
|
||||||
</h1>
|
</h1>
|
||||||
<div className="row g-2 justify-content-between">
|
<div className="row g-3 align-items-center mb-2">
|
||||||
<div className="col-auto">
|
<div className="col-auto">
|
||||||
<ListingTypeSelect
|
<ListingTypeSelect
|
||||||
type_={listingType}
|
type_={listingType}
|
||||||
|
@ -114,6 +129,9 @@ export class Communities extends Component<any, CommunitiesState> {
|
||||||
onChange={this.handleListingTypeChange}
|
onChange={this.handleListingTypeChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="col-auto me-auto">
|
||||||
|
<SortSelect sort={sort} onChange={this.handleSortChange} />
|
||||||
|
</div>
|
||||||
<div className="col-auto">{this.searchForm()}</div>
|
<div className="col-auto">{this.searchForm()}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -161,49 +179,41 @@ export class Communities extends Component<any, CommunitiesState> {
|
||||||
{numToSI(cv.counts.comments)}
|
{numToSI(cv.counts.comments)}
|
||||||
</td>
|
</td>
|
||||||
<td className="text-right">
|
<td className="text-right">
|
||||||
{cv.subscribed == "Subscribed" && (
|
<SubscribeButton
|
||||||
<button
|
communityView={cv}
|
||||||
className="btn btn-link d-inline-block"
|
onFollow={linkEvent(
|
||||||
onClick={linkEvent(
|
{
|
||||||
{
|
i: this,
|
||||||
i: this,
|
communityId: cv.community.id,
|
||||||
communityId: cv.community.id,
|
follow: false,
|
||||||
follow: false,
|
},
|
||||||
},
|
this.handleFollow,
|
||||||
this.handleFollow
|
)}
|
||||||
)}
|
onUnFollow={linkEvent(
|
||||||
>
|
{
|
||||||
{I18NextService.i18n.t("unsubscribe")}
|
i: this,
|
||||||
</button>
|
communityId: cv.community.id,
|
||||||
)}
|
follow: true,
|
||||||
{cv.subscribed === "NotSubscribed" && (
|
},
|
||||||
<button
|
this.handleFollow,
|
||||||
className="btn btn-link d-inline-block"
|
)}
|
||||||
onClick={linkEvent(
|
isLink
|
||||||
{
|
/>
|
||||||
i: this,
|
|
||||||
communityId: cv.community.id,
|
|
||||||
follow: true,
|
|
||||||
},
|
|
||||||
this.handleFollow
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{I18NextService.i18n.t("subscribe")}
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
{cv.subscribed === "Pending" && (
|
|
||||||
<div className="text-warning d-inline-block">
|
|
||||||
{I18NextService.i18n.t("subscribe_pending")}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
)
|
),
|
||||||
)}
|
)}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<Paginator page={page} onChange={this.handlePageChange} />
|
<Paginator
|
||||||
|
page={page}
|
||||||
|
onChange={this.handlePageChange}
|
||||||
|
nextDisabled={
|
||||||
|
communityLimit >
|
||||||
|
this.state.listCommunitiesResponse.data.communities.length
|
||||||
|
}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -224,10 +234,7 @@ export class Communities extends Component<any, CommunitiesState> {
|
||||||
|
|
||||||
searchForm() {
|
searchForm() {
|
||||||
return (
|
return (
|
||||||
<form
|
<form className="row" onSubmit={linkEvent(this, this.handleSearchSubmit)}>
|
||||||
className="row mb-2"
|
|
||||||
onSubmit={linkEvent(this, this.handleSearchSubmit)}
|
|
||||||
>
|
|
||||||
<div className="col-auto">
|
<div className="col-auto">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
|
@ -252,12 +259,16 @@ export class Communities extends Component<any, CommunitiesState> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateUrl({ listingType, page }: Partial<CommunitiesProps>) {
|
async updateUrl({ listingType, sort, page }: Partial<CommunitiesProps>) {
|
||||||
const { listingType: urlListingType, page: urlPage } =
|
const {
|
||||||
this.getCommunitiesQueryParams();
|
listingType: urlListingType,
|
||||||
|
sort: urlSort,
|
||||||
|
page: urlPage,
|
||||||
|
} = getCommunitiesQueryParams();
|
||||||
|
|
||||||
const queryParams: QueryParams<CommunitiesProps> = {
|
const queryParams: QueryParams<CommunitiesProps> = {
|
||||||
listingType: listingType ?? urlListingType,
|
listingType: listingType ?? urlListingType,
|
||||||
|
sort: sort ?? urlSort,
|
||||||
page: (page ?? urlPage)?.toString(),
|
page: (page ?? urlPage)?.toString(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -270,6 +281,10 @@ export class Communities extends Component<any, CommunitiesState> {
|
||||||
this.updateUrl({ page });
|
this.updateUrl({ page });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleSortChange(val: SortType) {
|
||||||
|
this.updateUrl({ sort: val, page: 1 });
|
||||||
|
}
|
||||||
|
|
||||||
handleListingTypeChange(val: ListingType) {
|
handleListingTypeChange(val: ListingType) {
|
||||||
this.updateUrl({
|
this.updateUrl({
|
||||||
listingType: val,
|
listingType: val,
|
||||||
|
@ -284,40 +299,32 @@ export class Communities extends Component<any, CommunitiesState> {
|
||||||
handleSearchSubmit(i: Communities, event: any) {
|
handleSearchSubmit(i: Communities, event: any) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const searchParamEncoded = encodeURIComponent(i.state.searchText);
|
const searchParamEncoded = encodeURIComponent(i.state.searchText);
|
||||||
|
const { listingType } = getCommunitiesQueryParams();
|
||||||
i.context.router.history.push(
|
i.context.router.history.push(
|
||||||
`/search?q=${searchParamEncoded}&type=Communities`
|
`/search?q=${searchParamEncoded}&type=Communities&listingType=${listingType}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async fetchInitialData({
|
static async fetchInitialData({
|
||||||
query: { listingType, page },
|
query: { listingType, sort, page },
|
||||||
client,
|
client,
|
||||||
auth,
|
|
||||||
}: InitialFetchRequest<
|
}: InitialFetchRequest<
|
||||||
QueryParams<CommunitiesProps>
|
QueryParams<CommunitiesProps>
|
||||||
>): Promise<CommunitiesData> {
|
>): Promise<CommunitiesData> {
|
||||||
const listCommunitiesForm: ListCommunities = {
|
const listCommunitiesForm: ListCommunities = {
|
||||||
type_: getListingTypeFromQuery(listingType),
|
type_: getListingTypeFromQuery(listingType),
|
||||||
sort: "TopMonth",
|
sort: getSortTypeFromQuery(sort),
|
||||||
limit: communityLimit,
|
limit: communityLimit,
|
||||||
page: getPageFromString(page),
|
page: getPageFromString(page),
|
||||||
auth: auth,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
listCommunitiesResponse: await client.listCommunities(
|
listCommunitiesResponse: await client.listCommunities(
|
||||||
listCommunitiesForm
|
listCommunitiesForm,
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
getCommunitiesQueryParams() {
|
|
||||||
return getQueryParams<CommunitiesProps>({
|
|
||||||
listingType: getListingTypeFromQuery,
|
|
||||||
page: getPageFromString,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async handleFollow(data: {
|
async handleFollow(data: {
|
||||||
i: Communities;
|
i: Communities;
|
||||||
communityId: number;
|
communityId: number;
|
||||||
|
@ -326,23 +333,21 @@ export class Communities extends Component<any, CommunitiesState> {
|
||||||
const res = await HttpService.client.followCommunity({
|
const res = await HttpService.client.followCommunity({
|
||||||
community_id: data.communityId,
|
community_id: data.communityId,
|
||||||
follow: data.follow,
|
follow: data.follow,
|
||||||
auth: myAuthRequired(),
|
|
||||||
});
|
});
|
||||||
data.i.findAndUpdateCommunity(res);
|
data.i.findAndUpdateCommunity(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
async refetch() {
|
async refetch() {
|
||||||
this.setState({ listCommunitiesResponse: { state: "loading" } });
|
this.setState({ listCommunitiesResponse: LOADING_REQUEST });
|
||||||
|
|
||||||
const { listingType, page } = this.getCommunitiesQueryParams();
|
const { listingType, sort, page } = getCommunitiesQueryParams();
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
listCommunitiesResponse: await HttpService.client.listCommunities({
|
listCommunitiesResponse: await HttpService.client.listCommunities({
|
||||||
type_: listingType,
|
type_: listingType,
|
||||||
sort: "TopMonth",
|
sort: sort,
|
||||||
limit: communityLimit,
|
limit: communityLimit,
|
||||||
page,
|
page,
|
||||||
auth: myAuth(),
|
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -352,12 +357,12 @@ export class Communities extends Component<any, CommunitiesState> {
|
||||||
findAndUpdateCommunity(res: RequestState<CommunityResponse>) {
|
findAndUpdateCommunity(res: RequestState<CommunityResponse>) {
|
||||||
this.setState(s => {
|
this.setState(s => {
|
||||||
if (
|
if (
|
||||||
s.listCommunitiesResponse.state == "success" &&
|
s.listCommunitiesResponse.state === "success" &&
|
||||||
res.state == "success"
|
res.state === "success"
|
||||||
) {
|
) {
|
||||||
s.listCommunitiesResponse.data.communities = editCommunity(
|
s.listCommunitiesResponse.data.communities = editCommunity(
|
||||||
res.data.community_view,
|
res.data.community_view,
|
||||||
s.listCommunitiesResponse.data.communities
|
s.listCommunitiesResponse.data.communities,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return s;
|
return s;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { myAuthRequired } from "@utils/app";
|
|
||||||
import { capitalizeFirstLetter, randomStr } from "@utils/helpers";
|
import { capitalizeFirstLetter, randomStr } from "@utils/helpers";
|
||||||
import { Component, linkEvent } from "inferno";
|
import { Component, linkEvent } from "inferno";
|
||||||
|
import { Prompt } from "inferno-router";
|
||||||
import {
|
import {
|
||||||
CommunityView,
|
CommunityView,
|
||||||
CreateCommunity,
|
CreateCommunity,
|
||||||
|
@ -12,7 +12,6 @@ import { Icon, Spinner } from "../common/icon";
|
||||||
import { ImageUploadForm } from "../common/image-upload-form";
|
import { ImageUploadForm } from "../common/image-upload-form";
|
||||||
import { LanguageSelect } from "../common/language-select";
|
import { LanguageSelect } from "../common/language-select";
|
||||||
import { MarkdownTextArea } from "../common/markdown-textarea";
|
import { MarkdownTextArea } from "../common/markdown-textarea";
|
||||||
import NavigationPrompt from "../common/navigation-prompt";
|
|
||||||
|
|
||||||
interface CommunityFormProps {
|
interface CommunityFormProps {
|
||||||
community_view?: CommunityView; // If a community is given, that means this is an edit
|
community_view?: CommunityView; // If a community is given, that means this is an edit
|
||||||
|
@ -90,7 +89,8 @@ export class CommunityForm extends Component<
|
||||||
className="community-form"
|
className="community-form"
|
||||||
onSubmit={linkEvent(this, this.handleCreateCommunitySubmit)}
|
onSubmit={linkEvent(this, this.handleCreateCommunitySubmit)}
|
||||||
>
|
>
|
||||||
<NavigationPrompt
|
<Prompt
|
||||||
|
message={I18NextService.i18n.t("block_leaving")}
|
||||||
when={
|
when={
|
||||||
!this.props.loading &&
|
!this.props.loading &&
|
||||||
!!(
|
!!(
|
||||||
|
@ -230,7 +230,7 @@ export class CommunityForm extends Component<
|
||||||
checked={this.state.form.posting_restricted_to_mods}
|
checked={this.state.form.posting_restricted_to_mods}
|
||||||
onChange={linkEvent(
|
onChange={linkEvent(
|
||||||
this,
|
this,
|
||||||
this.handleCommunityPostingRestrictedToMods
|
this.handleCommunityPostingRestrictedToMods,
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -278,7 +278,6 @@ export class CommunityForm extends Component<
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
i.setState({ submitted: true });
|
i.setState({ submitted: true });
|
||||||
const cForm = i.state.form;
|
const cForm = i.state.form;
|
||||||
const auth = myAuthRequired();
|
|
||||||
|
|
||||||
const cv = i.props.community_view;
|
const cv = i.props.community_view;
|
||||||
|
|
||||||
|
@ -292,7 +291,6 @@ export class CommunityForm extends Component<
|
||||||
nsfw: cForm.nsfw,
|
nsfw: cForm.nsfw,
|
||||||
posting_restricted_to_mods: cForm.posting_restricted_to_mods,
|
posting_restricted_to_mods: cForm.posting_restricted_to_mods,
|
||||||
discussion_languages: cForm.discussion_languages,
|
discussion_languages: cForm.discussion_languages,
|
||||||
auth,
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
if (cForm.title && cForm.name) {
|
if (cForm.title && cForm.name) {
|
||||||
|
@ -305,7 +303,6 @@ export class CommunityForm extends Component<
|
||||||
nsfw: cForm.nsfw,
|
nsfw: cForm.nsfw,
|
||||||
posting_restricted_to_mods: cForm.posting_restricted_to_mods,
|
posting_restricted_to_mods: cForm.posting_restricted_to_mods,
|
||||||
discussion_languages: cForm.discussion_languages,
|
discussion_languages: cForm.discussion_languages,
|
||||||
auth,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -329,7 +326,7 @@ export class CommunityForm extends Component<
|
||||||
|
|
||||||
handleCommunityPostingRestrictedToMods(i: CommunityForm, event: any) {
|
handleCommunityPostingRestrictedToMods(i: CommunityForm, event: any) {
|
||||||
i.setState(
|
i.setState(
|
||||||
s => ((s.form.posting_restricted_to_mods = event.target.checked), s)
|
s => ((s.form.posting_restricted_to_mods = event.target.checked), s),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,20 +21,19 @@ export class CommunityLink extends Component<CommunityLinkProps, any> {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const community = this.props.community;
|
const community = this.props.community;
|
||||||
let name_: string, title: string, link: string;
|
let title: string, link: string;
|
||||||
const local = community.local == null ? true : community.local;
|
const local = community.local === null ? true : community.local;
|
||||||
|
const domain = hostname(community.actor_id);
|
||||||
if (local) {
|
if (local) {
|
||||||
name_ = community.name;
|
|
||||||
title = community.title;
|
title = community.title;
|
||||||
link = `/c/${community.name}`;
|
link = `/c/${community.name}`;
|
||||||
} else {
|
} else {
|
||||||
const domain = hostname(community.actor_id);
|
const name_ = `${community.name}@${domain}`;
|
||||||
name_ = `${community.name}@${domain}`;
|
|
||||||
title = `${community.title}@${domain}`;
|
title = `${community.title}@${domain}`;
|
||||||
link = !this.props.realLink ? `/c/${name_}` : community.actor_id;
|
link = !this.props.realLink ? `/c/${name_}` : community.actor_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
const apubName = `!${name_}`;
|
const apubName = `!${community.name}@${domain}`;
|
||||||
const displayName = this.props.useApubName ? apubName : title;
|
const displayName = this.props.useApubName ? apubName : title;
|
||||||
return !this.props.realLink ? (
|
return !this.props.realLink ? (
|
||||||
<Link
|
<Link
|
||||||
|
@ -58,12 +57,14 @@ export class CommunityLink extends Component<CommunityLinkProps, any> {
|
||||||
|
|
||||||
avatarAndName(displayName: string) {
|
avatarAndName(displayName: string) {
|
||||||
const icon = this.props.community.icon;
|
const icon = this.props.community.icon;
|
||||||
|
const nsfw = this.props.community.nsfw;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{!this.props.hideAvatar &&
|
{!this.props.hideAvatar &&
|
||||||
!this.props.community.removed &&
|
!this.props.community.removed &&
|
||||||
showAvatars() &&
|
showAvatars() &&
|
||||||
icon && <PictrsImage src={icon} icon />}
|
icon && <PictrsImage src={icon} icon nsfw={nsfw} />}
|
||||||
<span className="overflow-wrap-anywhere">{displayName}</span>
|
<span className="overflow-wrap-anywhere">{displayName}</span>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -8,18 +8,13 @@ import {
|
||||||
enableNsfw,
|
enableNsfw,
|
||||||
getCommentParentId,
|
getCommentParentId,
|
||||||
getDataTypeString,
|
getDataTypeString,
|
||||||
myAuth,
|
|
||||||
postToCommentSortType,
|
postToCommentSortType,
|
||||||
setIsoData,
|
setIsoData,
|
||||||
showLocal,
|
showLocal,
|
||||||
updateCommunityBlock,
|
updateCommunityBlock,
|
||||||
updatePersonBlock,
|
updatePersonBlock,
|
||||||
} from "@utils/app";
|
} from "@utils/app";
|
||||||
import {
|
import { getQueryParams, getQueryString } from "@utils/helpers";
|
||||||
getPageFromString,
|
|
||||||
getQueryParams,
|
|
||||||
getQueryString,
|
|
||||||
} from "@utils/helpers";
|
|
||||||
import type { QueryParams } from "@utils/types";
|
import type { QueryParams } from "@utils/types";
|
||||||
import { RouteDataResponse } from "@utils/types";
|
import { RouteDataResponse } from "@utils/types";
|
||||||
import { Component, RefObject, createRef, linkEvent } from "inferno";
|
import { Component, RefObject, createRef, linkEvent } from "inferno";
|
||||||
|
@ -62,6 +57,8 @@ import {
|
||||||
LockPost,
|
LockPost,
|
||||||
MarkCommentReplyAsRead,
|
MarkCommentReplyAsRead,
|
||||||
MarkPersonMentionAsRead,
|
MarkPersonMentionAsRead,
|
||||||
|
MarkPostAsRead,
|
||||||
|
PaginationCursor,
|
||||||
PostResponse,
|
PostResponse,
|
||||||
PurgeComment,
|
PurgeComment,
|
||||||
PurgeCommunity,
|
PurgeCommunity,
|
||||||
|
@ -83,7 +80,12 @@ import {
|
||||||
InitialFetchRequest,
|
InitialFetchRequest,
|
||||||
} from "../../interfaces";
|
} from "../../interfaces";
|
||||||
import { FirstLoadService, I18NextService, UserService } from "../../services";
|
import { FirstLoadService, I18NextService, UserService } from "../../services";
|
||||||
import { HttpService, RequestState } from "../../services/HttpService";
|
import {
|
||||||
|
EMPTY_REQUEST,
|
||||||
|
HttpService,
|
||||||
|
LOADING_REQUEST,
|
||||||
|
RequestState,
|
||||||
|
} from "../../services/HttpService";
|
||||||
import { setupTippy } from "../../tippy";
|
import { setupTippy } from "../../tippy";
|
||||||
import { toast } from "../../toast";
|
import { toast } from "../../toast";
|
||||||
import { CommentNodes } from "../comment/comment-nodes";
|
import { CommentNodes } from "../comment/comment-nodes";
|
||||||
|
@ -91,12 +93,12 @@ import { BannerIconHeader } from "../common/banner-icon-header";
|
||||||
import { DataTypeSelect } from "../common/data-type-select";
|
import { DataTypeSelect } from "../common/data-type-select";
|
||||||
import { HtmlTags } from "../common/html-tags";
|
import { HtmlTags } from "../common/html-tags";
|
||||||
import { Icon, Spinner } from "../common/icon";
|
import { Icon, Spinner } from "../common/icon";
|
||||||
import { Paginator } from "../common/paginator";
|
|
||||||
import { SortSelect } from "../common/sort-select";
|
import { SortSelect } from "../common/sort-select";
|
||||||
import { Sidebar } from "../community/sidebar";
|
import { Sidebar } from "../community/sidebar";
|
||||||
import { SiteSidebar } from "../home/site-sidebar";
|
import { SiteSidebar } from "../home/site-sidebar";
|
||||||
import { PostListings } from "../post/post-listings";
|
import { PostListings } from "../post/post-listings";
|
||||||
import { CommunityLink } from "./community-link";
|
import { CommunityLink } from "./community-link";
|
||||||
|
import { PaginatorCursor } from "../common/paginator-cursor";
|
||||||
|
|
||||||
type CommunityData = RouteDataResponse<{
|
type CommunityData = RouteDataResponse<{
|
||||||
communityRes: GetCommunityResponse;
|
communityRes: GetCommunityResponse;
|
||||||
|
@ -117,13 +119,13 @@ interface State {
|
||||||
interface CommunityProps {
|
interface CommunityProps {
|
||||||
dataType: DataType;
|
dataType: DataType;
|
||||||
sort: SortType;
|
sort: SortType;
|
||||||
page: number;
|
pageCursor?: PaginationCursor;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCommunityQueryParams() {
|
function getCommunityQueryParams() {
|
||||||
return getQueryParams<CommunityProps>({
|
return getQueryParams<CommunityProps>({
|
||||||
dataType: getDataTypeFromQuery,
|
dataType: getDataTypeFromQuery,
|
||||||
page: getPageFromString,
|
pageCursor: cursor => cursor,
|
||||||
sort: getSortTypeFromQuery,
|
sort: getSortTypeFromQuery,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -146,9 +148,9 @@ export class Community extends Component<
|
||||||
> {
|
> {
|
||||||
private isoData = setIsoData<CommunityData>(this.context);
|
private isoData = setIsoData<CommunityData>(this.context);
|
||||||
state: State = {
|
state: State = {
|
||||||
communityRes: { state: "empty" },
|
communityRes: EMPTY_REQUEST,
|
||||||
postsRes: { state: "empty" },
|
postsRes: EMPTY_REQUEST,
|
||||||
commentsRes: { state: "empty" },
|
commentsRes: EMPTY_REQUEST,
|
||||||
siteRes: this.isoData.site_res,
|
siteRes: this.isoData.site_res,
|
||||||
showSidebarMobile: false,
|
showSidebarMobile: false,
|
||||||
finished: new Map(),
|
finished: new Map(),
|
||||||
|
@ -160,7 +162,8 @@ export class Community extends Component<
|
||||||
|
|
||||||
this.handleSortChange = this.handleSortChange.bind(this);
|
this.handleSortChange = this.handleSortChange.bind(this);
|
||||||
this.handleDataTypeChange = this.handleDataTypeChange.bind(this);
|
this.handleDataTypeChange = this.handleDataTypeChange.bind(this);
|
||||||
this.handlePageChange = this.handlePageChange.bind(this);
|
this.handlePageNext = this.handlePageNext.bind(this);
|
||||||
|
this.handlePagePrev = this.handlePagePrev.bind(this);
|
||||||
|
|
||||||
// All of the action binds
|
// All of the action binds
|
||||||
this.handleDeleteCommunity = this.handleDeleteCommunity.bind(this);
|
this.handleDeleteCommunity = this.handleDeleteCommunity.bind(this);
|
||||||
|
@ -195,6 +198,7 @@ export class Community extends Component<
|
||||||
this.handleSavePost = this.handleSavePost.bind(this);
|
this.handleSavePost = this.handleSavePost.bind(this);
|
||||||
this.handlePurgePost = this.handlePurgePost.bind(this);
|
this.handlePurgePost = this.handlePurgePost.bind(this);
|
||||||
this.handleFeaturePost = this.handleFeaturePost.bind(this);
|
this.handleFeaturePost = this.handleFeaturePost.bind(this);
|
||||||
|
this.handleMarkPostAsRead = this.handleMarkPostAsRead.bind(this);
|
||||||
this.mainContentRef = createRef();
|
this.mainContentRef = createRef();
|
||||||
// Only fetch the data if coming from another route
|
// Only fetch the data if coming from another route
|
||||||
if (FirstLoadService.isFirstLoad) {
|
if (FirstLoadService.isFirstLoad) {
|
||||||
|
@ -211,11 +215,10 @@ export class Community extends Component<
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchCommunity() {
|
async fetchCommunity() {
|
||||||
this.setState({ communityRes: { state: "loading" } });
|
this.setState({ communityRes: LOADING_REQUEST });
|
||||||
this.setState({
|
this.setState({
|
||||||
communityRes: await HttpService.client.getCommunity({
|
communityRes: await HttpService.client.getCommunity({
|
||||||
name: this.props.match.params.name,
|
name: this.props.match.params.name,
|
||||||
auth: myAuth(),
|
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -231,8 +234,7 @@ export class Community extends Component<
|
||||||
static async fetchInitialData({
|
static async fetchInitialData({
|
||||||
client,
|
client,
|
||||||
path,
|
path,
|
||||||
query: { dataType: urlDataType, page: urlPage, sort: urlSort },
|
query: { dataType: urlDataType, pageCursor, sort: urlSort },
|
||||||
auth,
|
|
||||||
}: InitialFetchRequest<QueryParams<CommunityProps>>): Promise<
|
}: InitialFetchRequest<QueryParams<CommunityProps>>): Promise<
|
||||||
Promise<CommunityData>
|
Promise<CommunityData>
|
||||||
> {
|
> {
|
||||||
|
@ -241,41 +243,33 @@ export class Community extends Component<
|
||||||
const communityName = pathSplit[2];
|
const communityName = pathSplit[2];
|
||||||
const communityForm: GetCommunity = {
|
const communityForm: GetCommunity = {
|
||||||
name: communityName,
|
name: communityName,
|
||||||
auth,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const dataType = getDataTypeFromQuery(urlDataType);
|
const dataType = getDataTypeFromQuery(urlDataType);
|
||||||
|
|
||||||
const sort = getSortTypeFromQuery(urlSort);
|
const sort = getSortTypeFromQuery(urlSort);
|
||||||
|
|
||||||
const page = getPageFromString(urlPage);
|
let postsResponse: RequestState<GetPostsResponse> = EMPTY_REQUEST;
|
||||||
|
let commentsResponse: RequestState<GetCommentsResponse> = EMPTY_REQUEST;
|
||||||
let postsResponse: RequestState<GetPostsResponse> = { state: "empty" };
|
|
||||||
let commentsResponse: RequestState<GetCommentsResponse> = {
|
|
||||||
state: "empty",
|
|
||||||
};
|
|
||||||
|
|
||||||
if (dataType === DataType.Post) {
|
if (dataType === DataType.Post) {
|
||||||
const getPostsForm: GetPosts = {
|
const getPostsForm: GetPosts = {
|
||||||
community_name: communityName,
|
community_name: communityName,
|
||||||
page,
|
page_cursor: pageCursor,
|
||||||
limit: fetchLimit,
|
limit: fetchLimit,
|
||||||
sort,
|
sort,
|
||||||
type_: "All",
|
type_: "All",
|
||||||
saved_only: false,
|
saved_only: false,
|
||||||
auth,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
postsResponse = await client.getPosts(getPostsForm);
|
postsResponse = await client.getPosts(getPostsForm);
|
||||||
} else {
|
} else {
|
||||||
const getCommentsForm: GetComments = {
|
const getCommentsForm: GetComments = {
|
||||||
community_name: communityName,
|
community_name: communityName,
|
||||||
page,
|
|
||||||
limit: fetchLimit,
|
limit: fetchLimit,
|
||||||
sort: postToCommentSortType(sort),
|
sort: postToCommentSortType(sort),
|
||||||
type_: "All",
|
type_: "All",
|
||||||
saved_only: false,
|
saved_only: false,
|
||||||
auth,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
commentsResponse = await client.getComments(getCommentsForm);
|
commentsResponse = await client.getComments(getCommentsForm);
|
||||||
|
@ -288,14 +282,21 @@ export class Community extends Component<
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get getNextPage(): PaginationCursor | undefined {
|
||||||
|
return this.state.postsRes.state === "success"
|
||||||
|
? this.state.postsRes.data.next_page
|
||||||
|
: undefined;
|
||||||
|
}
|
||||||
|
|
||||||
get documentTitle(): string {
|
get documentTitle(): string {
|
||||||
const cRes = this.state.communityRes;
|
const cRes = this.state.communityRes;
|
||||||
return cRes.state == "success"
|
return cRes.state === "success"
|
||||||
? `${cRes.data.community_view.community.title} - ${this.isoData.site_res.site_view.site.name}`
|
? `${cRes.data.community_view.community.title} - ${this.isoData.site_res.site_view.site.name}`
|
||||||
: "";
|
: "";
|
||||||
}
|
}
|
||||||
|
|
||||||
renderCommunity() {
|
renderCommunity() {
|
||||||
|
const { pageCursor } = getCommunityQueryParams();
|
||||||
switch (this.state.communityRes.state) {
|
switch (this.state.communityRes.state) {
|
||||||
case "loading":
|
case "loading":
|
||||||
return (
|
return (
|
||||||
|
@ -305,13 +306,13 @@ export class Community extends Component<
|
||||||
);
|
);
|
||||||
case "success": {
|
case "success": {
|
||||||
const res = this.state.communityRes.data;
|
const res = this.state.communityRes.data;
|
||||||
const { page } = getCommunityQueryParams();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<HtmlTags
|
<HtmlTags
|
||||||
title={this.documentTitle}
|
title={this.documentTitle}
|
||||||
path={this.context.router.route.match.url}
|
path={this.context.router.route.match.url}
|
||||||
|
canonicalPath={res.community_view.community.actor_id}
|
||||||
description={res.community_view.community.description}
|
description={res.community_view.community.description}
|
||||||
image={res.community_view.community.icon}
|
image={res.community_view.community.icon}
|
||||||
/>
|
/>
|
||||||
|
@ -341,7 +342,12 @@ export class Community extends Component<
|
||||||
</div>
|
</div>
|
||||||
{this.selects(res)}
|
{this.selects(res)}
|
||||||
{this.listings(res)}
|
{this.listings(res)}
|
||||||
<Paginator page={page} onChange={this.handlePageChange} />
|
<PaginatorCursor
|
||||||
|
prevPage={pageCursor}
|
||||||
|
nextPage={this.getNextPage}
|
||||||
|
onNext={this.handlePageNext}
|
||||||
|
onPrev={this.handlePagePrev}
|
||||||
|
/>
|
||||||
</main>
|
</main>
|
||||||
<aside className="d-none d-md-block col-md-4 col-lg-3">
|
<aside className="d-none d-md-block col-md-4 col-lg-3">
|
||||||
{this.sidebar(res)}
|
{this.sidebar(res)}
|
||||||
|
@ -430,6 +436,7 @@ export class Community extends Component<
|
||||||
onAddAdmin={this.handleAddAdmin}
|
onAddAdmin={this.handleAddAdmin}
|
||||||
onTransferCommunity={this.handleTransferCommunity}
|
onTransferCommunity={this.handleTransferCommunity}
|
||||||
onFeaturePost={this.handleFeaturePost}
|
onFeaturePost={this.handleFeaturePost}
|
||||||
|
onMarkPostAsRead={this.handleMarkPostAsRead}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -447,7 +454,7 @@ export class Community extends Component<
|
||||||
nodes={commentsToFlatNodes(this.state.commentsRes.data.comments)}
|
nodes={commentsToFlatNodes(this.state.commentsRes.data.comments)}
|
||||||
viewType={CommentViewType.Flat}
|
viewType={CommentViewType.Flat}
|
||||||
finished={this.state.finished}
|
finished={this.state.finished}
|
||||||
noIndent
|
isTopLevel
|
||||||
showContext
|
showContext
|
||||||
enableDownvotes={enableDownvotes(site_res)}
|
enableDownvotes={enableDownvotes(site_res)}
|
||||||
moderators={communityRes.moderators}
|
moderators={communityRes.moderators}
|
||||||
|
@ -534,18 +541,22 @@ export class Community extends Component<
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
handlePageChange(page: number) {
|
handlePagePrev() {
|
||||||
this.updateUrl({ page });
|
this.props.history.back();
|
||||||
|
}
|
||||||
|
|
||||||
|
handlePageNext(nextPage: PaginationCursor) {
|
||||||
|
this.updateUrl({ pageCursor: nextPage });
|
||||||
window.scrollTo(0, 0);
|
window.scrollTo(0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSortChange(sort: SortType) {
|
handleSortChange(sort: SortType) {
|
||||||
this.updateUrl({ sort, page: 1 });
|
this.updateUrl({ sort, pageCursor: undefined });
|
||||||
window.scrollTo(0, 0);
|
window.scrollTo(0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDataTypeChange(dataType: DataType) {
|
handleDataTypeChange(dataType: DataType) {
|
||||||
this.updateUrl({ dataType, page: 1 });
|
this.updateUrl({ dataType, pageCursor: undefined });
|
||||||
window.scrollTo(0, 0);
|
window.scrollTo(0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -555,54 +566,47 @@ export class Community extends Component<
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateUrl({ dataType, page, sort }: Partial<CommunityProps>) {
|
async updateUrl({ dataType, pageCursor, sort }: Partial<CommunityProps>) {
|
||||||
const {
|
const { dataType: urlDataType, sort: urlSort } = getCommunityQueryParams();
|
||||||
dataType: urlDataType,
|
|
||||||
page: urlPage,
|
|
||||||
sort: urlSort,
|
|
||||||
} = getCommunityQueryParams();
|
|
||||||
|
|
||||||
const queryParams: QueryParams<CommunityProps> = {
|
const queryParams: QueryParams<CommunityProps> = {
|
||||||
dataType: getDataTypeString(dataType ?? urlDataType),
|
dataType: getDataTypeString(dataType ?? urlDataType),
|
||||||
page: (page ?? urlPage).toString(),
|
pageCursor: pageCursor,
|
||||||
sort: sort ?? urlSort,
|
sort: sort ?? urlSort,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.props.history.push(
|
this.props.history.push(
|
||||||
`/c/${this.props.match.params.name}${getQueryString(queryParams)}`
|
`/c/${this.props.match.params.name}${getQueryString(queryParams)}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
await this.fetchData();
|
await this.fetchData();
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchData() {
|
async fetchData() {
|
||||||
const { dataType, page, sort } = getCommunityQueryParams();
|
const { dataType, pageCursor, sort } = getCommunityQueryParams();
|
||||||
const { name } = this.props.match.params;
|
const { name } = this.props.match.params;
|
||||||
|
|
||||||
if (dataType === DataType.Post) {
|
if (dataType === DataType.Post) {
|
||||||
this.setState({ postsRes: { state: "loading" } });
|
this.setState({ postsRes: LOADING_REQUEST });
|
||||||
this.setState({
|
this.setState({
|
||||||
postsRes: await HttpService.client.getPosts({
|
postsRes: await HttpService.client.getPosts({
|
||||||
page,
|
page_cursor: pageCursor,
|
||||||
limit: fetchLimit,
|
limit: fetchLimit,
|
||||||
sort,
|
sort,
|
||||||
type_: "All",
|
type_: "All",
|
||||||
community_name: name,
|
community_name: name,
|
||||||
saved_only: false,
|
saved_only: false,
|
||||||
auth: myAuth(),
|
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.setState({ commentsRes: { state: "loading" } });
|
this.setState({ commentsRes: LOADING_REQUEST });
|
||||||
this.setState({
|
this.setState({
|
||||||
commentsRes: await HttpService.client.getComments({
|
commentsRes: await HttpService.client.getComments({
|
||||||
page,
|
|
||||||
limit: fetchLimit,
|
limit: fetchLimit,
|
||||||
sort: postToCommentSortType(sort),
|
sort: postToCommentSortType(sort),
|
||||||
type_: "All",
|
type_: "All",
|
||||||
community_name: name,
|
community_name: name,
|
||||||
saved_only: false,
|
saved_only: false,
|
||||||
auth: myAuth(),
|
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -625,11 +629,11 @@ export class Community extends Component<
|
||||||
this.updateCommunity(followCommunityRes);
|
this.updateCommunity(followCommunityRes);
|
||||||
|
|
||||||
// Update myUserInfo
|
// Update myUserInfo
|
||||||
if (followCommunityRes.state == "success") {
|
if (followCommunityRes.state === "success") {
|
||||||
const communityId = followCommunityRes.data.community_view.community.id;
|
const communityId = followCommunityRes.data.community_view.community.id;
|
||||||
const mui = UserService.Instance.myUserInfo;
|
const mui = UserService.Instance.myUserInfo;
|
||||||
if (mui) {
|
if (mui) {
|
||||||
mui.follows = mui.follows.filter(i => i.community.id != communityId);
|
mui.follows = mui.follows.filter(i => i.community.id !== communityId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -656,10 +660,10 @@ export class Community extends Component<
|
||||||
|
|
||||||
async handleBlockCommunity(form: BlockCommunity) {
|
async handleBlockCommunity(form: BlockCommunity) {
|
||||||
const blockCommunityRes = await HttpService.client.blockCommunity(form);
|
const blockCommunityRes = await HttpService.client.blockCommunity(form);
|
||||||
if (blockCommunityRes.state == "success") {
|
if (blockCommunityRes.state === "success") {
|
||||||
updateCommunityBlock(blockCommunityRes.data);
|
updateCommunityBlock(blockCommunityRes.data);
|
||||||
this.setState(s => {
|
this.setState(s => {
|
||||||
if (s.communityRes.state == "success") {
|
if (s.communityRes.state === "success") {
|
||||||
s.communityRes.data.community_view.blocked =
|
s.communityRes.data.community_view.blocked =
|
||||||
blockCommunityRes.data.blocked;
|
blockCommunityRes.data.blocked;
|
||||||
}
|
}
|
||||||
|
@ -669,7 +673,7 @@ export class Community extends Component<
|
||||||
|
|
||||||
async handleBlockPerson(form: BlockPerson) {
|
async handleBlockPerson(form: BlockPerson) {
|
||||||
const blockPersonRes = await HttpService.client.blockPerson(form);
|
const blockPersonRes = await HttpService.client.blockPerson(form);
|
||||||
if (blockPersonRes.state == "success") {
|
if (blockPersonRes.state === "success") {
|
||||||
updatePersonBlock(blockPersonRes.data);
|
updatePersonBlock(blockPersonRes.data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -695,7 +699,7 @@ export class Community extends Component<
|
||||||
|
|
||||||
async handleEditComment(form: EditComment) {
|
async handleEditComment(form: EditComment) {
|
||||||
const editCommentRes = await HttpService.client.editComment(form);
|
const editCommentRes = await HttpService.client.editComment(form);
|
||||||
this.findAndUpdateComment(editCommentRes);
|
this.findAndUpdateCommentEdit(editCommentRes);
|
||||||
|
|
||||||
return editCommentRes;
|
return editCommentRes;
|
||||||
}
|
}
|
||||||
|
@ -752,14 +756,14 @@ export class Community extends Component<
|
||||||
|
|
||||||
async handleCommentReport(form: CreateCommentReport) {
|
async handleCommentReport(form: CreateCommentReport) {
|
||||||
const reportRes = await HttpService.client.createCommentReport(form);
|
const reportRes = await HttpService.client.createCommentReport(form);
|
||||||
if (reportRes.state == "success") {
|
if (reportRes.state === "success") {
|
||||||
toast(I18NextService.i18n.t("report_created"));
|
toast(I18NextService.i18n.t("report_created"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handlePostReport(form: CreatePostReport) {
|
async handlePostReport(form: CreatePostReport) {
|
||||||
const reportRes = await HttpService.client.createPostReport(form);
|
const reportRes = await HttpService.client.createPostReport(form);
|
||||||
if (reportRes.state == "success") {
|
if (reportRes.state === "success") {
|
||||||
toast(I18NextService.i18n.t("report_created"));
|
toast(I18NextService.i18n.t("report_created"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -777,14 +781,14 @@ export class Community extends Component<
|
||||||
async handleAddAdmin(form: AddAdmin) {
|
async handleAddAdmin(form: AddAdmin) {
|
||||||
const addAdminRes = await HttpService.client.addAdmin(form);
|
const addAdminRes = await HttpService.client.addAdmin(form);
|
||||||
|
|
||||||
if (addAdminRes.state == "success") {
|
if (addAdminRes.state === "success") {
|
||||||
this.setState(s => ((s.siteRes.admins = addAdminRes.data.admins), s));
|
this.setState(s => ((s.siteRes.admins = addAdminRes.data.admins), s));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleTransferCommunity(form: TransferCommunity) {
|
async handleTransferCommunity(form: TransferCommunity) {
|
||||||
const transferCommunityRes = await HttpService.client.transferCommunity(
|
const transferCommunityRes = await HttpService.client.transferCommunity(
|
||||||
form
|
form,
|
||||||
);
|
);
|
||||||
toast(I18NextService.i18n.t("transfer_community"));
|
toast(I18NextService.i18n.t("transfer_community"));
|
||||||
this.updateCommunityFull(transferCommunityRes);
|
this.updateCommunityFull(transferCommunityRes);
|
||||||
|
@ -800,6 +804,11 @@ export class Community extends Component<
|
||||||
await HttpService.client.markPersonMentionAsRead(form);
|
await HttpService.client.markPersonMentionAsRead(form);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async handleMarkPostAsRead(form: MarkPostAsRead) {
|
||||||
|
const res = await HttpService.client.markPostAsRead(form);
|
||||||
|
this.findAndUpdatePost(res);
|
||||||
|
}
|
||||||
|
|
||||||
async handleBanFromCommunity(form: BanFromCommunity) {
|
async handleBanFromCommunity(form: BanFromCommunity) {
|
||||||
const banRes = await HttpService.client.banFromCommunity(form);
|
const banRes = await HttpService.client.banFromCommunity(form);
|
||||||
this.updateBanFromCommunity(banRes);
|
this.updateBanFromCommunity(banRes);
|
||||||
|
@ -812,20 +821,20 @@ export class Community extends Component<
|
||||||
|
|
||||||
updateBanFromCommunity(banRes: RequestState<BanFromCommunityResponse>) {
|
updateBanFromCommunity(banRes: RequestState<BanFromCommunityResponse>) {
|
||||||
// Maybe not necessary
|
// Maybe not necessary
|
||||||
if (banRes.state == "success") {
|
if (banRes.state === "success") {
|
||||||
this.setState(s => {
|
this.setState(s => {
|
||||||
if (s.postsRes.state == "success") {
|
if (s.postsRes.state === "success") {
|
||||||
s.postsRes.data.posts
|
s.postsRes.data.posts
|
||||||
.filter(c => c.creator.id == banRes.data.person_view.person.id)
|
.filter(c => c.creator.id === banRes.data.person_view.person.id)
|
||||||
.forEach(
|
.forEach(
|
||||||
c => (c.creator_banned_from_community = banRes.data.banned)
|
c => (c.creator_banned_from_community = banRes.data.banned),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (s.commentsRes.state == "success") {
|
if (s.commentsRes.state === "success") {
|
||||||
s.commentsRes.data.comments
|
s.commentsRes.data.comments
|
||||||
.filter(c => c.creator.id == banRes.data.person_view.person.id)
|
.filter(c => c.creator.id === banRes.data.person_view.person.id)
|
||||||
.forEach(
|
.forEach(
|
||||||
c => (c.creator_banned_from_community = banRes.data.banned)
|
c => (c.creator_banned_from_community = banRes.data.banned),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return s;
|
return s;
|
||||||
|
@ -835,16 +844,16 @@ export class Community extends Component<
|
||||||
|
|
||||||
updateBan(banRes: RequestState<BanPersonResponse>) {
|
updateBan(banRes: RequestState<BanPersonResponse>) {
|
||||||
// Maybe not necessary
|
// Maybe not necessary
|
||||||
if (banRes.state == "success") {
|
if (banRes.state === "success") {
|
||||||
this.setState(s => {
|
this.setState(s => {
|
||||||
if (s.postsRes.state == "success") {
|
if (s.postsRes.state === "success") {
|
||||||
s.postsRes.data.posts
|
s.postsRes.data.posts
|
||||||
.filter(c => c.creator.id == banRes.data.person_view.person.id)
|
.filter(c => c.creator.id === banRes.data.person_view.person.id)
|
||||||
.forEach(c => (c.creator.banned = banRes.data.banned));
|
.forEach(c => (c.creator.banned = banRes.data.banned));
|
||||||
}
|
}
|
||||||
if (s.commentsRes.state == "success") {
|
if (s.commentsRes.state === "success") {
|
||||||
s.commentsRes.data.comments
|
s.commentsRes.data.comments
|
||||||
.filter(c => c.creator.id == banRes.data.person_view.person.id)
|
.filter(c => c.creator.id === banRes.data.person_view.person.id)
|
||||||
.forEach(c => (c.creator.banned = banRes.data.banned));
|
.forEach(c => (c.creator.banned = banRes.data.banned));
|
||||||
}
|
}
|
||||||
return s;
|
return s;
|
||||||
|
@ -854,7 +863,7 @@ export class Community extends Component<
|
||||||
|
|
||||||
updateCommunity(res: RequestState<CommunityResponse>) {
|
updateCommunity(res: RequestState<CommunityResponse>) {
|
||||||
this.setState(s => {
|
this.setState(s => {
|
||||||
if (s.communityRes.state == "success" && res.state == "success") {
|
if (s.communityRes.state === "success" && res.state === "success") {
|
||||||
s.communityRes.data.community_view = res.data.community_view;
|
s.communityRes.data.community_view = res.data.community_view;
|
||||||
s.communityRes.data.discussion_languages =
|
s.communityRes.data.discussion_languages =
|
||||||
res.data.discussion_languages;
|
res.data.discussion_languages;
|
||||||
|
@ -865,7 +874,7 @@ export class Community extends Component<
|
||||||
|
|
||||||
updateCommunityFull(res: RequestState<GetCommunityResponse>) {
|
updateCommunityFull(res: RequestState<GetCommunityResponse>) {
|
||||||
this.setState(s => {
|
this.setState(s => {
|
||||||
if (s.communityRes.state == "success" && res.state == "success") {
|
if (s.communityRes.state === "success" && res.state === "success") {
|
||||||
s.communityRes.data.community_view = res.data.community_view;
|
s.communityRes.data.community_view = res.data.community_view;
|
||||||
s.communityRes.data.moderators = res.data.moderators;
|
s.communityRes.data.moderators = res.data.moderators;
|
||||||
}
|
}
|
||||||
|
@ -874,18 +883,18 @@ export class Community extends Component<
|
||||||
}
|
}
|
||||||
|
|
||||||
purgeItem(purgeRes: RequestState<PurgeItemResponse>) {
|
purgeItem(purgeRes: RequestState<PurgeItemResponse>) {
|
||||||
if (purgeRes.state == "success") {
|
if (purgeRes.state === "success") {
|
||||||
toast(I18NextService.i18n.t("purge_success"));
|
toast(I18NextService.i18n.t("purge_success"));
|
||||||
this.context.router.history.push(`/`);
|
this.context.router.history.push(`/`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
findAndUpdateComment(res: RequestState<CommentResponse>) {
|
findAndUpdateCommentEdit(res: RequestState<CommentResponse>) {
|
||||||
this.setState(s => {
|
this.setState(s => {
|
||||||
if (s.commentsRes.state == "success" && res.state == "success") {
|
if (s.commentsRes.state === "success" && res.state === "success") {
|
||||||
s.commentsRes.data.comments = editComment(
|
s.commentsRes.data.comments = editComment(
|
||||||
res.data.comment_view,
|
res.data.comment_view,
|
||||||
s.commentsRes.data.comments
|
s.commentsRes.data.comments,
|
||||||
);
|
);
|
||||||
s.finished.set(res.data.comment_view.comment.id, true);
|
s.finished.set(res.data.comment_view.comment.id, true);
|
||||||
}
|
}
|
||||||
|
@ -893,15 +902,27 @@ export class Community extends Component<
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
findAndUpdateComment(res: RequestState<CommentResponse>) {
|
||||||
|
this.setState(s => {
|
||||||
|
if (s.commentsRes.state === "success" && res.state === "success") {
|
||||||
|
s.commentsRes.data.comments = editComment(
|
||||||
|
res.data.comment_view,
|
||||||
|
s.commentsRes.data.comments,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
createAndUpdateComments(res: RequestState<CommentResponse>) {
|
createAndUpdateComments(res: RequestState<CommentResponse>) {
|
||||||
this.setState(s => {
|
this.setState(s => {
|
||||||
if (s.commentsRes.state == "success" && res.state == "success") {
|
if (s.commentsRes.state === "success" && res.state === "success") {
|
||||||
s.commentsRes.data.comments.unshift(res.data.comment_view);
|
s.commentsRes.data.comments.unshift(res.data.comment_view);
|
||||||
|
|
||||||
// Set finished for the parent
|
// Set finished for the parent
|
||||||
s.finished.set(
|
s.finished.set(
|
||||||
getCommentParentId(res.data.comment_view.comment) ?? 0,
|
getCommentParentId(res.data.comment_view.comment) ?? 0,
|
||||||
true
|
true,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return s;
|
return s;
|
||||||
|
@ -910,10 +931,10 @@ export class Community extends Component<
|
||||||
|
|
||||||
findAndUpdateCommentReply(res: RequestState<CommentReplyResponse>) {
|
findAndUpdateCommentReply(res: RequestState<CommentReplyResponse>) {
|
||||||
this.setState(s => {
|
this.setState(s => {
|
||||||
if (s.commentsRes.state == "success" && res.state == "success") {
|
if (s.commentsRes.state === "success" && res.state === "success") {
|
||||||
s.commentsRes.data.comments = editWith(
|
s.commentsRes.data.comments = editWith(
|
||||||
res.data.comment_reply_view,
|
res.data.comment_reply_view,
|
||||||
s.commentsRes.data.comments
|
s.commentsRes.data.comments,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return s;
|
return s;
|
||||||
|
@ -922,10 +943,10 @@ export class Community extends Component<
|
||||||
|
|
||||||
findAndUpdatePost(res: RequestState<PostResponse>) {
|
findAndUpdatePost(res: RequestState<PostResponse>) {
|
||||||
this.setState(s => {
|
this.setState(s => {
|
||||||
if (s.postsRes.state == "success" && res.state == "success") {
|
if (s.postsRes.state === "success" && res.state === "success") {
|
||||||
s.postsRes.data.posts = editPost(
|
s.postsRes.data.posts = editPost(
|
||||||
res.data.post_view,
|
res.data.post_view,
|
||||||
s.postsRes.data.posts
|
s.postsRes.data.posts,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return s;
|
return s;
|
||||||
|
@ -935,7 +956,7 @@ export class Community extends Component<
|
||||||
updateModerators(res: RequestState<AddModToCommunityResponse>) {
|
updateModerators(res: RequestState<AddModToCommunityResponse>) {
|
||||||
// Update the moderators
|
// Update the moderators
|
||||||
this.setState(s => {
|
this.setState(s => {
|
||||||
if (s.communityRes.state == "success" && res.state == "success") {
|
if (s.communityRes.state === "success" && res.state === "success") {
|
||||||
s.communityRes.data.moderators = res.data.moderators;
|
s.communityRes.data.moderators = res.data.moderators;
|
||||||
}
|
}
|
||||||
return s;
|
return s;
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import { myAuthRequired } from "@utils/app";
|
import { hostname } from "@utils/helpers";
|
||||||
import { getUnixTime, hostname } from "@utils/helpers";
|
|
||||||
import { amAdmin, amMod, amTopMod } from "@utils/roles";
|
import { amAdmin, amMod, amTopMod } from "@utils/roles";
|
||||||
import { Component, InfernoNode, linkEvent } from "inferno";
|
import { Component, InfernoNode, linkEvent } from "inferno";
|
||||||
import { T } from "inferno-i18next-dess";
|
import { T } from "inferno-i18next-dess";
|
||||||
|
@ -22,6 +21,7 @@ import { I18NextService, UserService } from "../../services";
|
||||||
import { Badges } from "../common/badges";
|
import { Badges } from "../common/badges";
|
||||||
import { BannerIconHeader } from "../common/banner-icon-header";
|
import { BannerIconHeader } from "../common/banner-icon-header";
|
||||||
import { Icon, PurgeWarning, Spinner } from "../common/icon";
|
import { Icon, PurgeWarning, Spinner } from "../common/icon";
|
||||||
|
import { SubscribeButton } from "../common/subscribe-button";
|
||||||
import { CommunityForm } from "../community/community-form";
|
import { CommunityForm } from "../community/community-form";
|
||||||
import { CommunityLink } from "../community/community-link";
|
import { CommunityLink } from "../community/community-link";
|
||||||
import { PersonListing } from "../person/person-listing";
|
import { PersonListing } from "../person/person-listing";
|
||||||
|
@ -79,15 +79,15 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(
|
componentWillReceiveProps(
|
||||||
nextProps: Readonly<{ children?: InfernoNode } & SidebarProps>
|
nextProps: Readonly<{ children?: InfernoNode } & SidebarProps>,
|
||||||
): void {
|
): void {
|
||||||
if (this.props.moderators != nextProps.moderators) {
|
if (this.props.moderators !== nextProps.moderators) {
|
||||||
this.setState({
|
this.setState({
|
||||||
showConfirmLeaveModTeam: false,
|
showConfirmLeaveModTeam: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.props.community_view != nextProps.community_view) {
|
if (this.props.community_view !== nextProps.community_view) {
|
||||||
this.setState({
|
this.setState({
|
||||||
showEdit: false,
|
showEdit: false,
|
||||||
showPurgeDialog: false,
|
showPurgeDialog: false,
|
||||||
|
@ -123,7 +123,9 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
|
||||||
|
|
||||||
sidebar() {
|
sidebar() {
|
||||||
const myUSerInfo = UserService.Instance.myUserInfo;
|
const myUSerInfo = UserService.Instance.myUserInfo;
|
||||||
const { name, actor_id } = this.props.community_view.community;
|
const {
|
||||||
|
community: { name, actor_id },
|
||||||
|
} = this.props.community_view;
|
||||||
return (
|
return (
|
||||||
<aside className="mb-3">
|
<aside className="mb-3">
|
||||||
<div id="sidebarContainer">
|
<div id="sidebarContainer">
|
||||||
|
@ -131,7 +133,12 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
|
||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
{this.communityTitle()}
|
{this.communityTitle()}
|
||||||
{this.props.editable && this.adminButtons()}
|
{this.props.editable && this.adminButtons()}
|
||||||
{myUSerInfo && this.subscribe()}
|
<SubscribeButton
|
||||||
|
communityView={this.props.community_view}
|
||||||
|
onFollow={linkEvent(this, this.handleFollowCommunity)}
|
||||||
|
onUnFollow={linkEvent(this, this.handleUnfollowCommunity)}
|
||||||
|
loading={this.state.followCommunityLoading}
|
||||||
|
/>
|
||||||
{this.canPost && this.createPost()}
|
{this.canPost && this.createPost()}
|
||||||
{myUSerInfo && this.blockCommunity()}
|
{myUSerInfo && this.blockCommunity()}
|
||||||
{!myUSerInfo && (
|
{!myUSerInfo && (
|
||||||
|
@ -230,58 +237,6 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
subscribe() {
|
|
||||||
const community_view = this.props.community_view;
|
|
||||||
|
|
||||||
if (community_view.subscribed === "NotSubscribed") {
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
className="btn btn-secondary d-block mb-2 w-100"
|
|
||||||
onClick={linkEvent(this, this.handleFollowCommunity)}
|
|
||||||
>
|
|
||||||
{this.state.followCommunityLoading ? (
|
|
||||||
<Spinner />
|
|
||||||
) : (
|
|
||||||
I18NextService.i18n.t("subscribe")
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (community_view.subscribed === "Subscribed") {
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
className="btn btn-secondary d-block mb-2 w-100"
|
|
||||||
onClick={linkEvent(this, this.handleUnfollowCommunity)}
|
|
||||||
>
|
|
||||||
{this.state.followCommunityLoading ? (
|
|
||||||
<Spinner />
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<Icon icon="check" classes="icon-inline me-1" />
|
|
||||||
{I18NextService.i18n.t("joined")}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (community_view.subscribed === "Pending") {
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
className="btn btn-warning d-block mb-2 w-100"
|
|
||||||
onClick={linkEvent(this, this.handleUnfollowCommunity)}
|
|
||||||
>
|
|
||||||
{this.state.followCommunityLoading ? (
|
|
||||||
<Spinner />
|
|
||||||
) : (
|
|
||||||
I18NextService.i18n.t("subscribe_pending")
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
blockCommunity() {
|
blockCommunity() {
|
||||||
const { subscribed, blocked } = this.props.community_view;
|
const { subscribed, blocked } = this.props.community_view;
|
||||||
|
|
||||||
|
@ -292,7 +247,7 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
|
||||||
onClick={linkEvent(this, this.handleBlockCommunity)}
|
onClick={linkEvent(this, this.handleBlockCommunity)}
|
||||||
>
|
>
|
||||||
{I18NextService.i18n.t(
|
{I18NextService.i18n.t(
|
||||||
blocked ? "unblock_community" : "block_community"
|
blocked ? "unblock_community" : "block_community",
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
|
@ -332,7 +287,7 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
|
||||||
className="btn btn-link text-muted d-inline-block"
|
className="btn btn-link text-muted d-inline-block"
|
||||||
onClick={linkEvent(
|
onClick={linkEvent(
|
||||||
this,
|
this,
|
||||||
this.handleShowConfirmLeaveModTeamClick
|
this.handleShowConfirmLeaveModTeamClick,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{I18NextService.i18n.t("leave_mod_team")}
|
{I18NextService.i18n.t("leave_mod_team")}
|
||||||
|
@ -356,7 +311,7 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
|
||||||
className="btn btn-link text-muted d-inline-block"
|
className="btn btn-link text-muted d-inline-block"
|
||||||
onClick={linkEvent(
|
onClick={linkEvent(
|
||||||
this,
|
this,
|
||||||
this.handleCancelLeaveModTeamClick
|
this.handleCancelLeaveModTeamClick,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{I18NextService.i18n.t("no")}
|
{I18NextService.i18n.t("no")}
|
||||||
|
@ -544,7 +499,6 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
|
||||||
i.props.onFollowCommunity({
|
i.props.onFollowCommunity({
|
||||||
community_id: i.props.community_view.community.id,
|
community_id: i.props.community_view.community.id,
|
||||||
follow: false,
|
follow: false,
|
||||||
auth: myAuthRequired(),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -553,7 +507,6 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
|
||||||
i.props.onFollowCommunity({
|
i.props.onFollowCommunity({
|
||||||
community_id: i.props.community_view.community.id,
|
community_id: i.props.community_view.community.id,
|
||||||
follow: true,
|
follow: true,
|
||||||
auth: myAuthRequired(),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -563,7 +516,6 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
|
||||||
i.props.onBlockCommunity({
|
i.props.onBlockCommunity({
|
||||||
community_id: community.id,
|
community_id: community.id,
|
||||||
block: !blocked,
|
block: !blocked,
|
||||||
auth: myAuthRequired(),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -573,9 +525,8 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
|
||||||
i.setState({ leaveModTeamLoading: true });
|
i.setState({ leaveModTeamLoading: true });
|
||||||
i.props.onLeaveModTeam({
|
i.props.onLeaveModTeam({
|
||||||
community_id: i.props.community_view.community.id,
|
community_id: i.props.community_view.community.id,
|
||||||
person_id: 92,
|
person_id: myId,
|
||||||
added: false,
|
added: false,
|
||||||
auth: myAuthRequired(),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -585,7 +536,6 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
|
||||||
i.props.onDeleteCommunity({
|
i.props.onDeleteCommunity({
|
||||||
community_id: i.props.community_view.community.id,
|
community_id: i.props.community_view.community.id,
|
||||||
deleted: !i.props.community_view.community.deleted,
|
deleted: !i.props.community_view.community.deleted,
|
||||||
auth: myAuthRequired(),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -596,8 +546,6 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
|
||||||
community_id: i.props.community_view.community.id,
|
community_id: i.props.community_view.community.id,
|
||||||
removed: !i.props.community_view.community.removed,
|
removed: !i.props.community_view.community.removed,
|
||||||
reason: i.state.removeReason,
|
reason: i.state.removeReason,
|
||||||
expires: getUnixTime(i.state.removeExpires), // TODO fix this
|
|
||||||
auth: myAuthRequired(),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -607,7 +555,6 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
|
||||||
i.props.onPurgeCommunity({
|
i.props.onPurgeCommunity({
|
||||||
community_id: i.props.community_view.community.id,
|
community_id: i.props.community_view.community.id,
|
||||||
reason: i.state.purgeReason,
|
reason: i.state.purgeReason,
|
||||||
auth: myAuthRequired(),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,4 @@
|
||||||
import {
|
import { fetchThemeList, setIsoData, showLocal } from "@utils/app";
|
||||||
fetchThemeList,
|
|
||||||
myAuthRequired,
|
|
||||||
setIsoData,
|
|
||||||
showLocal,
|
|
||||||
} from "@utils/app";
|
|
||||||
import { capitalizeFirstLetter } from "@utils/helpers";
|
import { capitalizeFirstLetter } from "@utils/helpers";
|
||||||
import { RouteDataResponse } from "@utils/types";
|
import { RouteDataResponse } from "@utils/types";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
|
@ -21,7 +16,12 @@ import {
|
||||||
import { InitialFetchRequest } from "../../interfaces";
|
import { InitialFetchRequest } from "../../interfaces";
|
||||||
import { removeFromEmojiDataModel, updateEmojiDataModel } from "../../markdown";
|
import { removeFromEmojiDataModel, updateEmojiDataModel } from "../../markdown";
|
||||||
import { FirstLoadService, I18NextService } from "../../services";
|
import { FirstLoadService, I18NextService } from "../../services";
|
||||||
import { HttpService, RequestState } from "../../services/HttpService";
|
import {
|
||||||
|
EMPTY_REQUEST,
|
||||||
|
HttpService,
|
||||||
|
LOADING_REQUEST,
|
||||||
|
RequestState,
|
||||||
|
} from "../../services/HttpService";
|
||||||
import { toast } from "../../toast";
|
import { toast } from "../../toast";
|
||||||
import { HtmlTags } from "../common/html-tags";
|
import { HtmlTags } from "../common/html-tags";
|
||||||
import { Spinner } from "../common/icon";
|
import { Spinner } from "../common/icon";
|
||||||
|
@ -55,9 +55,9 @@ export class AdminSettings extends Component<any, AdminSettingsState> {
|
||||||
siteRes: this.isoData.site_res,
|
siteRes: this.isoData.site_res,
|
||||||
banned: [],
|
banned: [],
|
||||||
currentTab: "site",
|
currentTab: "site",
|
||||||
bannedRes: { state: "empty" },
|
bannedRes: EMPTY_REQUEST,
|
||||||
instancesRes: { state: "empty" },
|
instancesRes: EMPTY_REQUEST,
|
||||||
leaveAdminTeamRes: { state: "empty" },
|
leaveAdminTeamRes: EMPTY_REQUEST,
|
||||||
loading: false,
|
loading: false,
|
||||||
themeList: [],
|
themeList: [],
|
||||||
isIsomorphic: false,
|
isIsomorphic: false,
|
||||||
|
@ -85,16 +85,11 @@ export class AdminSettings extends Component<any, AdminSettingsState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
static async fetchInitialData({
|
static async fetchInitialData({
|
||||||
auth,
|
|
||||||
client,
|
client,
|
||||||
}: InitialFetchRequest): Promise<AdminSettingsData> {
|
}: InitialFetchRequest): Promise<AdminSettingsData> {
|
||||||
return {
|
return {
|
||||||
bannedRes: await client.getBannedPersons({
|
bannedRes: await client.getBannedPersons(),
|
||||||
auth: auth as string,
|
instancesRes: await client.getFederatedInstances(),
|
||||||
}),
|
|
||||||
instancesRes: await client.getFederatedInstances({
|
|
||||||
auth: auth as string,
|
|
||||||
}),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,15 +145,26 @@ export class AdminSettings extends Component<any, AdminSettingsState> {
|
||||||
loading={this.state.loading}
|
loading={this.state.loading}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-12 col-md-6">
|
<div className="col-12 col-md-6">{this.admins()}</div>
|
||||||
{this.admins()}
|
|
||||||
<hr />
|
|
||||||
{this.bannedUsers()}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: "banned_users",
|
||||||
|
label: I18NextService.i18n.t("banned_users"),
|
||||||
|
getNode: isSelected => (
|
||||||
|
<div
|
||||||
|
className={classNames("tab-pane", {
|
||||||
|
active: isSelected,
|
||||||
|
})}
|
||||||
|
role="tabpanel"
|
||||||
|
id="banned_users-tab-pane"
|
||||||
|
>
|
||||||
|
{this.bannedUsers()}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: "rate_limiting",
|
key: "rate_limiting",
|
||||||
label: "Rate Limiting",
|
label: "Rate Limiting",
|
||||||
|
@ -230,16 +236,14 @@ export class AdminSettings extends Component<any, AdminSettingsState> {
|
||||||
|
|
||||||
async fetchData() {
|
async fetchData() {
|
||||||
this.setState({
|
this.setState({
|
||||||
bannedRes: { state: "loading" },
|
bannedRes: LOADING_REQUEST,
|
||||||
instancesRes: { state: "loading" },
|
instancesRes: LOADING_REQUEST,
|
||||||
themeList: [],
|
themeList: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
const auth = myAuthRequired();
|
|
||||||
|
|
||||||
const [bannedRes, instancesRes, themeList] = await Promise.all([
|
const [bannedRes, instancesRes, themeList] = await Promise.all([
|
||||||
HttpService.client.getBannedPersons({ auth }),
|
HttpService.client.getBannedPersons(),
|
||||||
HttpService.client.getFederatedInstances({ auth }),
|
HttpService.client.getFederatedInstances(),
|
||||||
fetchThemeList(),
|
fetchThemeList(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
@ -274,7 +278,7 @@ export class AdminSettings extends Component<any, AdminSettingsState> {
|
||||||
onClick={linkEvent(this, this.handleLeaveAdminTeam)}
|
onClick={linkEvent(this, this.handleLeaveAdminTeam)}
|
||||||
className="btn btn-danger mb-2"
|
className="btn btn-danger mb-2"
|
||||||
>
|
>
|
||||||
{this.state.leaveAdminTeamRes.state == "loading" ? (
|
{this.state.leaveAdminTeamRes.state === "loading" ? (
|
||||||
<Spinner />
|
<Spinner />
|
||||||
) : (
|
) : (
|
||||||
I18NextService.i18n.t("leave_admin_team")
|
I18NextService.i18n.t("leave_admin_team")
|
||||||
|
@ -295,7 +299,7 @@ export class AdminSettings extends Component<any, AdminSettingsState> {
|
||||||
const bans = this.state.bannedRes.data.banned;
|
const bans = this.state.bannedRes.data.banned;
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<h2 className="h5">{I18NextService.i18n.t("banned_users")}</h2>
|
<h1 className="h4 mb-4">{I18NextService.i18n.t("banned_users")}</h1>
|
||||||
<ul className="list-unstyled">
|
<ul className="list-unstyled">
|
||||||
{bans.map(banned => (
|
{bans.map(banned => (
|
||||||
<li key={banned.person.id} className="list-inline-item">
|
<li key={banned.person.id} className="list-inline-item">
|
||||||
|
@ -334,11 +338,9 @@ export class AdminSettings extends Component<any, AdminSettingsState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleLeaveAdminTeam(i: AdminSettings) {
|
async handleLeaveAdminTeam(i: AdminSettings) {
|
||||||
i.setState({ leaveAdminTeamRes: { state: "loading" } });
|
i.setState({ leaveAdminTeamRes: LOADING_REQUEST });
|
||||||
this.setState({
|
this.setState({
|
||||||
leaveAdminTeamRes: await HttpService.client.leaveAdmin({
|
leaveAdminTeamRes: await HttpService.client.leaveAdmin(),
|
||||||
auth: myAuthRequired(),
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this.state.leaveAdminTeamRes.state === "success") {
|
if (this.state.leaveAdminTeamRes.state === "success") {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { myAuthRequired, setIsoData } from "@utils/app";
|
import { setIsoData } from "@utils/app";
|
||||||
import { capitalizeFirstLetter } from "@utils/helpers";
|
import { capitalizeFirstLetter } from "@utils/helpers";
|
||||||
import { Component, linkEvent } from "inferno";
|
import { Component, linkEvent } from "inferno";
|
||||||
import {
|
import {
|
||||||
|
@ -11,7 +11,6 @@ import { customEmojisLookup } from "../../markdown";
|
||||||
import { HttpService, I18NextService } from "../../services";
|
import { HttpService, I18NextService } from "../../services";
|
||||||
import { pictrsDeleteToast, toast } from "../../toast";
|
import { pictrsDeleteToast, toast } from "../../toast";
|
||||||
import { EmojiMart } from "../common/emoji-mart";
|
import { EmojiMart } from "../common/emoji-mart";
|
||||||
import { HtmlTags } from "../common/html-tags";
|
|
||||||
import { Icon, Spinner } from "../common/icon";
|
import { Icon, Spinner } from "../common/icon";
|
||||||
import { Paginator } from "../common/paginator";
|
import { Paginator } from "../common/paginator";
|
||||||
|
|
||||||
|
@ -66,17 +65,9 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
|
||||||
this.handlePageChange = this.handlePageChange.bind(this);
|
this.handlePageChange = this.handlePageChange.bind(this);
|
||||||
this.handleEmojiClick = this.handleEmojiClick.bind(this);
|
this.handleEmojiClick = this.handleEmojiClick.bind(this);
|
||||||
}
|
}
|
||||||
get documentTitle(): string {
|
|
||||||
return I18NextService.i18n.t("custom_emojis");
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className="home-emojis-form col-12">
|
<div className="home-emojis-form col-12">
|
||||||
<HtmlTags
|
|
||||||
title={this.documentTitle}
|
|
||||||
path={this.context.router.route.match.url}
|
|
||||||
/>
|
|
||||||
<h1 className="h4 mb-4">{I18NextService.i18n.t("custom_emojis")}</h1>
|
<h1 className="h4 mb-4">{I18NextService.i18n.t("custom_emojis")}</h1>
|
||||||
{customEmojisLookup.size > 0 && (
|
{customEmojisLookup.size > 0 && (
|
||||||
<div>
|
<div>
|
||||||
|
@ -118,8 +109,8 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
|
||||||
Number((this.state.page - 1) * this.itemsPerPage),
|
Number((this.state.page - 1) * this.itemsPerPage),
|
||||||
Number(
|
Number(
|
||||||
(this.state.page - 1) * this.itemsPerPage +
|
(this.state.page - 1) * this.itemsPerPage +
|
||||||
this.itemsPerPage
|
this.itemsPerPage,
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
.map((cv, index) => (
|
.map((cv, index) => (
|
||||||
<tr key={index} ref={e => (this.scrollRef[cv.shortcode] = e)}>
|
<tr key={index} ref={e => (this.scrollRef[cv.shortcode] = e)}>
|
||||||
|
@ -139,11 +130,11 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
|
||||||
className="btn btn-sm btn-secondary pointer"
|
className="btn btn-sm btn-secondary pointer"
|
||||||
htmlFor={`file-uploader-${index}`}
|
htmlFor={`file-uploader-${index}`}
|
||||||
data-tippy-content={I18NextService.i18n.t(
|
data-tippy-content={I18NextService.i18n.t(
|
||||||
"upload_image"
|
"upload_image",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{capitalizeFirstLetter(
|
{capitalizeFirstLetter(
|
||||||
I18NextService.i18n.t("upload")
|
I18NextService.i18n.t("upload"),
|
||||||
)}
|
)}
|
||||||
<input
|
<input
|
||||||
name={`file-uploader-${index}`}
|
name={`file-uploader-${index}`}
|
||||||
|
@ -153,7 +144,7 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
|
||||||
className="d-none"
|
className="d-none"
|
||||||
onChange={linkEvent(
|
onChange={linkEvent(
|
||||||
{ form: this, index: index },
|
{ form: this, index: index },
|
||||||
this.handleImageUpload
|
this.handleImageUpload,
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
|
@ -168,7 +159,7 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
|
||||||
value={cv.shortcode}
|
value={cv.shortcode}
|
||||||
onInput={linkEvent(
|
onInput={linkEvent(
|
||||||
{ form: this, index: index },
|
{ form: this, index: index },
|
||||||
this.handleEmojiShortCodeChange
|
this.handleEmojiShortCodeChange,
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
|
@ -180,7 +171,7 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
|
||||||
value={cv.category}
|
value={cv.category}
|
||||||
onInput={linkEvent(
|
onInput={linkEvent(
|
||||||
{ form: this, index: index },
|
{ form: this, index: index },
|
||||||
this.handleEmojiCategoryChange
|
this.handleEmojiCategoryChange,
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
|
@ -192,7 +183,7 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
|
||||||
value={cv.image_url}
|
value={cv.image_url}
|
||||||
onInput={linkEvent(
|
onInput={linkEvent(
|
||||||
{ form: this, index: index, overrideValue: null },
|
{ form: this, index: index, overrideValue: null },
|
||||||
this.handleEmojiImageUrlChange
|
this.handleEmojiImageUrlChange,
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
|
@ -204,7 +195,7 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
|
||||||
value={cv.alt_text}
|
value={cv.alt_text}
|
||||||
onInput={linkEvent(
|
onInput={linkEvent(
|
||||||
{ form: this, index: index },
|
{ form: this, index: index },
|
||||||
this.handleEmojiAltTextChange
|
this.handleEmojiAltTextChange,
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
|
@ -216,7 +207,7 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
|
||||||
value={cv.keywords}
|
value={cv.keywords}
|
||||||
onInput={linkEvent(
|
onInput={linkEvent(
|
||||||
{ form: this, index: index },
|
{ form: this, index: index },
|
||||||
this.handleEmojiKeywordChange
|
this.handleEmojiKeywordChange,
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
|
@ -231,7 +222,7 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
|
||||||
}
|
}
|
||||||
onClick={linkEvent(
|
onClick={linkEvent(
|
||||||
{ i: this, cv: cv },
|
{ i: this, cv: cv },
|
||||||
this.handleEditEmojiClick
|
this.handleEditEmojiClick,
|
||||||
)}
|
)}
|
||||||
data-tippy-content={I18NextService.i18n.t("save")}
|
data-tippy-content={I18NextService.i18n.t("save")}
|
||||||
aria-label={I18NextService.i18n.t("save")}
|
aria-label={I18NextService.i18n.t("save")}
|
||||||
|
@ -241,7 +232,7 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
|
||||||
<Spinner />
|
<Spinner />
|
||||||
) : (
|
) : (
|
||||||
capitalizeFirstLetter(
|
capitalizeFirstLetter(
|
||||||
I18NextService.i18n.t("save")
|
I18NextService.i18n.t("save"),
|
||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
|
@ -250,7 +241,7 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
|
||||||
className="btn btn-link btn-animate text-muted"
|
className="btn btn-link btn-animate text-muted"
|
||||||
onClick={linkEvent(
|
onClick={linkEvent(
|
||||||
{ i: this, index: index, cv: cv },
|
{ i: this, index: index, cv: cv },
|
||||||
this.handleDeleteEmojiClick
|
this.handleDeleteEmojiClick,
|
||||||
)}
|
)}
|
||||||
data-tippy-content={I18NextService.i18n.t("delete")}
|
data-tippy-content={I18NextService.i18n.t("delete")}
|
||||||
aria-label={I18NextService.i18n.t("delete")}
|
aria-label={I18NextService.i18n.t("delete")}
|
||||||
|
@ -276,7 +267,11 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
|
||||||
{I18NextService.i18n.t("add_custom_emoji")}
|
{I18NextService.i18n.t("add_custom_emoji")}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<Paginator page={this.state.page} onChange={this.handlePageChange} />
|
<Paginator
|
||||||
|
page={this.state.page}
|
||||||
|
onChange={this.handlePageChange}
|
||||||
|
nextDisabled={false}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -290,8 +285,8 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
|
||||||
cv.shortcode.length > 0;
|
cv.shortcode.length > 0;
|
||||||
const noDuplicateShortCodes =
|
const noDuplicateShortCodes =
|
||||||
this.state.customEmojis.filter(
|
this.state.customEmojis.filter(
|
||||||
x => x.shortcode == cv.shortcode && x.id != cv.id
|
x => x.shortcode === cv.shortcode && x.id !== cv.id,
|
||||||
).length == 0;
|
).length === 0;
|
||||||
return noEmptyFields && noDuplicateShortCodes && !cv.loading && cv.changed;
|
return noEmptyFields && noDuplicateShortCodes && !cv.loading && cv.changed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -308,7 +303,7 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
|
||||||
const view = customEmojisLookup.get(e.id);
|
const view = customEmojisLookup.get(e.id);
|
||||||
if (view) {
|
if (view) {
|
||||||
const page = this.state.customEmojis.find(
|
const page = this.state.customEmojis.find(
|
||||||
x => x.id == view.custom_emoji.id
|
x => x.id === view.custom_emoji.id,
|
||||||
)?.page;
|
)?.page;
|
||||||
if (page) {
|
if (page) {
|
||||||
this.setState({ page: page });
|
this.setState({ page: page });
|
||||||
|
@ -319,7 +314,7 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
|
||||||
|
|
||||||
handleEmojiCategoryChange(
|
handleEmojiCategoryChange(
|
||||||
props: { form: EmojiForm; index: number },
|
props: { form: EmojiForm; index: number },
|
||||||
event: any
|
event: any,
|
||||||
) {
|
) {
|
||||||
const custom_emojis = [...props.form.state.customEmojis];
|
const custom_emojis = [...props.form.state.customEmojis];
|
||||||
const pagedIndex =
|
const pagedIndex =
|
||||||
|
@ -335,7 +330,7 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
|
||||||
|
|
||||||
handleEmojiShortCodeChange(
|
handleEmojiShortCodeChange(
|
||||||
props: { form: EmojiForm; index: number },
|
props: { form: EmojiForm; index: number },
|
||||||
event: any
|
event: any,
|
||||||
) {
|
) {
|
||||||
const custom_emojis = [...props.form.state.customEmojis];
|
const custom_emojis = [...props.form.state.customEmojis];
|
||||||
const pagedIndex =
|
const pagedIndex =
|
||||||
|
@ -355,7 +350,7 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
|
||||||
index,
|
index,
|
||||||
overrideValue,
|
overrideValue,
|
||||||
}: { form: EmojiForm; index: number; overrideValue: string | null },
|
}: { form: EmojiForm; index: number; overrideValue: string | null },
|
||||||
event: any
|
event: any,
|
||||||
) {
|
) {
|
||||||
form.setState(prevState => {
|
form.setState(prevState => {
|
||||||
const custom_emojis = [...form.state.customEmojis];
|
const custom_emojis = [...form.state.customEmojis];
|
||||||
|
@ -376,7 +371,7 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
|
||||||
changed: true,
|
changed: true,
|
||||||
loading: false,
|
loading: false,
|
||||||
}
|
}
|
||||||
: ce
|
: ce,
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@ -384,7 +379,7 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
|
||||||
|
|
||||||
handleEmojiAltTextChange(
|
handleEmojiAltTextChange(
|
||||||
props: { form: EmojiForm; index: number },
|
props: { form: EmojiForm; index: number },
|
||||||
event: any
|
event: any,
|
||||||
) {
|
) {
|
||||||
const custom_emojis = [...props.form.state.customEmojis];
|
const custom_emojis = [...props.form.state.customEmojis];
|
||||||
const pagedIndex =
|
const pagedIndex =
|
||||||
|
@ -400,7 +395,7 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
|
||||||
|
|
||||||
handleEmojiKeywordChange(
|
handleEmojiKeywordChange(
|
||||||
props: { form: EmojiForm; index: number },
|
props: { form: EmojiForm; index: number },
|
||||||
event: any
|
event: any,
|
||||||
) {
|
) {
|
||||||
const custom_emojis = [...props.form.state.customEmojis];
|
const custom_emojis = [...props.form.state.customEmojis];
|
||||||
const pagedIndex =
|
const pagedIndex =
|
||||||
|
@ -420,10 +415,9 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
|
||||||
cv: CustomEmojiViewForm;
|
cv: CustomEmojiViewForm;
|
||||||
}) {
|
}) {
|
||||||
const pagedIndex = (d.i.state.page - 1) * d.i.itemsPerPage + d.index;
|
const pagedIndex = (d.i.state.page - 1) * d.i.itemsPerPage + d.index;
|
||||||
if (d.cv.id != 0) {
|
if (d.cv.id !== 0) {
|
||||||
d.i.props.onDelete({
|
d.i.props.onDelete({
|
||||||
id: d.cv.id,
|
id: d.cv.id,
|
||||||
auth: myAuthRequired(),
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const custom_emojis = [...d.i.state.customEmojis];
|
const custom_emojis = [...d.i.state.customEmojis];
|
||||||
|
@ -444,7 +438,6 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
|
||||||
image_url: d.cv.image_url,
|
image_url: d.cv.image_url,
|
||||||
alt_text: d.cv.alt_text,
|
alt_text: d.cv.alt_text,
|
||||||
keywords: uniqueKeywords,
|
keywords: uniqueKeywords,
|
||||||
auth: myAuthRequired(),
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
d.i.props.onCreate({
|
d.i.props.onCreate({
|
||||||
|
@ -453,7 +446,6 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
|
||||||
image_url: d.cv.image_url,
|
image_url: d.cv.image_url,
|
||||||
alt_text: d.cv.alt_text,
|
alt_text: d.cv.alt_text,
|
||||||
keywords: uniqueKeywords,
|
keywords: uniqueKeywords,
|
||||||
auth: myAuthRequired(),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -485,7 +477,7 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
|
||||||
|
|
||||||
handleImageUpload(
|
handleImageUpload(
|
||||||
{ form, index }: { form: EmojiForm; index: number },
|
{ form, index }: { form: EmojiForm; index: number },
|
||||||
event: any
|
event: any,
|
||||||
) {
|
) {
|
||||||
let file: any;
|
let file: any;
|
||||||
if (event.target) {
|
if (event.target) {
|
||||||
|
@ -498,7 +490,7 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
|
||||||
form.setState(prevState => ({
|
form.setState(prevState => ({
|
||||||
...prevState,
|
...prevState,
|
||||||
customEmojis: prevState.customEmojis.map((cv, i) =>
|
customEmojis: prevState.customEmojis.map((cv, i) =>
|
||||||
i === index ? { ...cv, loading: true } : cv
|
i === index ? { ...cv, loading: true } : cv,
|
||||||
),
|
),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -510,7 +502,7 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
|
||||||
pictrsDeleteToast(file.name, res.data.delete_url as string);
|
pictrsDeleteToast(file.name, res.data.delete_url as string);
|
||||||
form.handleEmojiImageUrlChange(
|
form.handleEmojiImageUrlChange(
|
||||||
{ form: form, index: index, overrideValue: res.data.url as string },
|
{ form: form, index: index, overrideValue: res.data.url as string },
|
||||||
event
|
event,
|
||||||
);
|
);
|
||||||
} else if (res.data.msg === "too_large") {
|
} else if (res.data.msg === "too_large") {
|
||||||
toast(I18NextService.i18n.t("upload_too_large"), "danger");
|
toast(I18NextService.i18n.t("upload_too_large"), "danger");
|
||||||
|
|
|
@ -14,7 +14,6 @@ import {
|
||||||
updatePersonBlock,
|
updatePersonBlock,
|
||||||
} from "@utils/app";
|
} from "@utils/app";
|
||||||
import {
|
import {
|
||||||
getPageFromString,
|
|
||||||
getQueryParams,
|
getQueryParams,
|
||||||
getQueryString,
|
getQueryString,
|
||||||
getRandomFromList,
|
getRandomFromList,
|
||||||
|
@ -59,6 +58,8 @@ import {
|
||||||
LockPost,
|
LockPost,
|
||||||
MarkCommentReplyAsRead,
|
MarkCommentReplyAsRead,
|
||||||
MarkPersonMentionAsRead,
|
MarkPersonMentionAsRead,
|
||||||
|
MarkPostAsRead,
|
||||||
|
PaginationCursor,
|
||||||
PostResponse,
|
PostResponse,
|
||||||
PurgeComment,
|
PurgeComment,
|
||||||
PurgeItemResponse,
|
PurgeItemResponse,
|
||||||
|
@ -84,7 +85,12 @@ import {
|
||||||
I18NextService,
|
I18NextService,
|
||||||
UserService,
|
UserService,
|
||||||
} from "../../services";
|
} from "../../services";
|
||||||
import { HttpService, RequestState } from "../../services/HttpService";
|
import {
|
||||||
|
EMPTY_REQUEST,
|
||||||
|
HttpService,
|
||||||
|
LOADING_REQUEST,
|
||||||
|
RequestState,
|
||||||
|
} from "../../services/HttpService";
|
||||||
import { setupTippy } from "../../tippy";
|
import { setupTippy } from "../../tippy";
|
||||||
import { toast } from "../../toast";
|
import { toast } from "../../toast";
|
||||||
import { CommentNodes } from "../comment/comment-nodes";
|
import { CommentNodes } from "../comment/comment-nodes";
|
||||||
|
@ -92,11 +98,11 @@ import { DataTypeSelect } from "../common/data-type-select";
|
||||||
import { HtmlTags } from "../common/html-tags";
|
import { HtmlTags } from "../common/html-tags";
|
||||||
import { Icon, Spinner } from "../common/icon";
|
import { Icon, Spinner } from "../common/icon";
|
||||||
import { ListingTypeSelect } from "../common/listing-type-select";
|
import { ListingTypeSelect } from "../common/listing-type-select";
|
||||||
import { Paginator } from "../common/paginator";
|
|
||||||
import { SortSelect } from "../common/sort-select";
|
import { SortSelect } from "../common/sort-select";
|
||||||
import { CommunityLink } from "../community/community-link";
|
import { CommunityLink } from "../community/community-link";
|
||||||
import { PostListings } from "../post/post-listings";
|
import { PostListings } from "../post/post-listings";
|
||||||
import { SiteSidebar } from "./site-sidebar";
|
import { SiteSidebar } from "./site-sidebar";
|
||||||
|
import { PaginatorCursor } from "../common/paginator-cursor";
|
||||||
|
|
||||||
interface HomeState {
|
interface HomeState {
|
||||||
postsRes: RequestState<GetPostsResponse>;
|
postsRes: RequestState<GetPostsResponse>;
|
||||||
|
@ -117,7 +123,7 @@ interface HomeProps {
|
||||||
listingType?: ListingType;
|
listingType?: ListingType;
|
||||||
dataType: DataType;
|
dataType: DataType;
|
||||||
sort: SortType;
|
sort: SortType;
|
||||||
page: number;
|
pageCursor?: PaginationCursor;
|
||||||
}
|
}
|
||||||
|
|
||||||
type HomeData = RouteDataResponse<{
|
type HomeData = RouteDataResponse<{
|
||||||
|
@ -128,7 +134,6 @@ type HomeData = RouteDataResponse<{
|
||||||
|
|
||||||
function getRss(listingType: ListingType) {
|
function getRss(listingType: ListingType) {
|
||||||
const { sort } = getHomeQueryParams();
|
const { sort } = getHomeQueryParams();
|
||||||
const auth = myAuth();
|
|
||||||
|
|
||||||
let rss: string | undefined = undefined;
|
let rss: string | undefined = undefined;
|
||||||
|
|
||||||
|
@ -142,6 +147,7 @@ function getRss(listingType: ListingType) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "Subscribed": {
|
case "Subscribed": {
|
||||||
|
const auth = myAuth();
|
||||||
rss = auth ? `/feeds/front/${auth}.xml?sort=${sort}` : undefined;
|
rss = auth ? `/feeds/front/${auth}.xml?sort=${sort}` : undefined;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -179,13 +185,14 @@ function getSortTypeFromQuery(type?: string): SortType {
|
||||||
return (type ? (type as SortType) : mySortType) ?? "Active";
|
return (type ? (type as SortType) : mySortType) ?? "Active";
|
||||||
}
|
}
|
||||||
|
|
||||||
const getHomeQueryParams = () =>
|
function getHomeQueryParams() {
|
||||||
getQueryParams<HomeProps>({
|
return getQueryParams<HomeProps>({
|
||||||
sort: getSortTypeFromQuery,
|
sort: getSortTypeFromQuery,
|
||||||
listingType: getListingTypeFromQuery,
|
listingType: getListingTypeFromQuery,
|
||||||
page: getPageFromString,
|
pageCursor: cursor => cursor,
|
||||||
dataType: getDataTypeFromQuery,
|
dataType: getDataTypeFromQuery,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const MobileButton = ({
|
const MobileButton = ({
|
||||||
textKey,
|
textKey,
|
||||||
|
@ -220,9 +227,9 @@ const LinkButton = ({
|
||||||
export class Home extends Component<any, HomeState> {
|
export class Home extends Component<any, HomeState> {
|
||||||
private isoData = setIsoData<HomeData>(this.context);
|
private isoData = setIsoData<HomeData>(this.context);
|
||||||
state: HomeState = {
|
state: HomeState = {
|
||||||
postsRes: { state: "empty" },
|
postsRes: EMPTY_REQUEST,
|
||||||
commentsRes: { state: "empty" },
|
commentsRes: EMPTY_REQUEST,
|
||||||
trendingCommunitiesRes: { state: "empty" },
|
trendingCommunitiesRes: EMPTY_REQUEST,
|
||||||
scrolled: true,
|
scrolled: true,
|
||||||
siteRes: this.isoData.site_res,
|
siteRes: this.isoData.site_res,
|
||||||
showSubscribedMobile: false,
|
showSubscribedMobile: false,
|
||||||
|
@ -239,7 +246,8 @@ export class Home extends Component<any, HomeState> {
|
||||||
this.handleSortChange = this.handleSortChange.bind(this);
|
this.handleSortChange = this.handleSortChange.bind(this);
|
||||||
this.handleListingTypeChange = this.handleListingTypeChange.bind(this);
|
this.handleListingTypeChange = this.handleListingTypeChange.bind(this);
|
||||||
this.handleDataTypeChange = this.handleDataTypeChange.bind(this);
|
this.handleDataTypeChange = this.handleDataTypeChange.bind(this);
|
||||||
this.handlePageChange = this.handlePageChange.bind(this);
|
this.handlePageNext = this.handlePageNext.bind(this);
|
||||||
|
this.handlePagePrev = this.handlePagePrev.bind(this);
|
||||||
|
|
||||||
this.handleCreateComment = this.handleCreateComment.bind(this);
|
this.handleCreateComment = this.handleCreateComment.bind(this);
|
||||||
this.handleEditComment = this.handleEditComment.bind(this);
|
this.handleEditComment = this.handleEditComment.bind(this);
|
||||||
|
@ -268,6 +276,7 @@ export class Home extends Component<any, HomeState> {
|
||||||
this.handleSavePost = this.handleSavePost.bind(this);
|
this.handleSavePost = this.handleSavePost.bind(this);
|
||||||
this.handlePurgePost = this.handlePurgePost.bind(this);
|
this.handlePurgePost = this.handlePurgePost.bind(this);
|
||||||
this.handleFeaturePost = this.handleFeaturePost.bind(this);
|
this.handleFeaturePost = this.handleFeaturePost.bind(this);
|
||||||
|
this.handleMarkPostAsRead = this.handleMarkPostAsRead.bind(this);
|
||||||
|
|
||||||
// Only fetch the data if coming from another route
|
// Only fetch the data if coming from another route
|
||||||
if (FirstLoadService.isFirstLoad) {
|
if (FirstLoadService.isFirstLoad) {
|
||||||
|
@ -285,9 +294,8 @@ export class Home extends Component<any, HomeState> {
|
||||||
HomeCacheService.postsRes = postsRes;
|
HomeCacheService.postsRes = postsRes;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.state.tagline = getRandomFromList(
|
this.state.tagline = getRandomFromList(this.state?.siteRes?.taglines ?? [])
|
||||||
this.state?.siteRes?.taglines ?? []
|
?.content;
|
||||||
)?.content;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
|
@ -298,7 +306,7 @@ export class Home extends Component<any, HomeState> {
|
||||||
if (
|
if (
|
||||||
!this.state.isIsomorphic ||
|
!this.state.isIsomorphic ||
|
||||||
!Object.values(this.isoData.routeData).some(
|
!Object.values(this.isoData.routeData).some(
|
||||||
res => res.state === "success" || res.state === "failed"
|
res => res.state === "success" || res.state === "failed",
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
await Promise.all([this.fetchTrendingCommunities(), this.fetchData()]);
|
await Promise.all([this.fetchTrendingCommunities(), this.fetchData()]);
|
||||||
|
@ -309,8 +317,7 @@ export class Home extends Component<any, HomeState> {
|
||||||
|
|
||||||
static async fetchInitialData({
|
static async fetchInitialData({
|
||||||
client,
|
client,
|
||||||
auth,
|
query: { dataType: urlDataType, listingType, pageCursor, sort: urlSort },
|
||||||
query: { dataType: urlDataType, listingType, page: urlPage, sort: urlSort },
|
|
||||||
site,
|
site,
|
||||||
}: InitialFetchRequest<QueryParams<HomeProps>>): Promise<HomeData> {
|
}: InitialFetchRequest<QueryParams<HomeProps>>): Promise<HomeData> {
|
||||||
const dataType = getDataTypeFromQuery(urlDataType);
|
const dataType = getDataTypeFromQuery(urlDataType);
|
||||||
|
@ -319,32 +326,25 @@ export class Home extends Component<any, HomeState> {
|
||||||
site.site_view.local_site.default_post_listing_type;
|
site.site_view.local_site.default_post_listing_type;
|
||||||
const sort = getSortTypeFromQuery(urlSort);
|
const sort = getSortTypeFromQuery(urlSort);
|
||||||
|
|
||||||
const page = urlPage ? Number(urlPage) : 1;
|
let postsRes: RequestState<GetPostsResponse> = EMPTY_REQUEST;
|
||||||
|
let commentsRes: RequestState<GetCommentsResponse> = EMPTY_REQUEST;
|
||||||
let postsRes: RequestState<GetPostsResponse> = { state: "empty" };
|
|
||||||
let commentsRes: RequestState<GetCommentsResponse> = {
|
|
||||||
state: "empty",
|
|
||||||
};
|
|
||||||
|
|
||||||
if (dataType === DataType.Post) {
|
if (dataType === DataType.Post) {
|
||||||
const getPostsForm: GetPosts = {
|
const getPostsForm: GetPosts = {
|
||||||
type_,
|
type_,
|
||||||
page,
|
page_cursor: pageCursor,
|
||||||
limit: fetchLimit,
|
limit: fetchLimit,
|
||||||
sort,
|
sort,
|
||||||
saved_only: false,
|
saved_only: false,
|
||||||
auth,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
postsRes = await client.getPosts(getPostsForm);
|
postsRes = await client.getPosts(getPostsForm);
|
||||||
} else {
|
} else {
|
||||||
const getCommentsForm: GetComments = {
|
const getCommentsForm: GetComments = {
|
||||||
page,
|
|
||||||
limit: fetchLimit,
|
limit: fetchLimit,
|
||||||
sort: postToCommentSortType(sort),
|
sort: postToCommentSortType(sort),
|
||||||
type_,
|
type_,
|
||||||
saved_only: false,
|
saved_only: false,
|
||||||
auth,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
commentsRes = await client.getComments(getCommentsForm);
|
commentsRes = await client.getComments(getCommentsForm);
|
||||||
|
@ -354,12 +354,11 @@ export class Home extends Component<any, HomeState> {
|
||||||
type_: "Local",
|
type_: "Local",
|
||||||
sort: "Hot",
|
sort: "Hot",
|
||||||
limit: trendingFetchLimit,
|
limit: trendingFetchLimit,
|
||||||
auth,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
trendingCommunitiesRes: await client.listCommunities(
|
trendingCommunitiesRes: await client.listCommunities(
|
||||||
trendingCommunitiesForm
|
trendingCommunitiesForm,
|
||||||
),
|
),
|
||||||
commentsRes,
|
commentsRes,
|
||||||
postsRes,
|
postsRes,
|
||||||
|
@ -616,18 +615,22 @@ export class Home extends Component<any, HomeState> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateUrl({ dataType, listingType, page, sort }: Partial<HomeProps>) {
|
async updateUrl({
|
||||||
|
dataType,
|
||||||
|
listingType,
|
||||||
|
pageCursor,
|
||||||
|
sort,
|
||||||
|
}: Partial<HomeProps>) {
|
||||||
const {
|
const {
|
||||||
dataType: urlDataType,
|
dataType: urlDataType,
|
||||||
listingType: urlListingType,
|
listingType: urlListingType,
|
||||||
page: urlPage,
|
|
||||||
sort: urlSort,
|
sort: urlSort,
|
||||||
} = getHomeQueryParams();
|
} = getHomeQueryParams();
|
||||||
|
|
||||||
const queryParams: QueryParams<HomeProps> = {
|
const queryParams: QueryParams<HomeProps> = {
|
||||||
dataType: getDataTypeString(dataType ?? urlDataType),
|
dataType: getDataTypeString(dataType ?? urlDataType),
|
||||||
listingType: listingType ?? urlListingType,
|
listingType: listingType ?? urlListingType,
|
||||||
page: (page ?? urlPage).toString(),
|
pageCursor: pageCursor,
|
||||||
sort: sort ?? urlSort,
|
sort: sort ?? urlSort,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -645,19 +648,30 @@ export class Home extends Component<any, HomeState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
get posts() {
|
get posts() {
|
||||||
const { page } = getHomeQueryParams();
|
const { pageCursor } = getHomeQueryParams();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="main-content-wrapper">
|
<div className="main-content-wrapper">
|
||||||
<div>
|
<div>
|
||||||
{this.selects}
|
{this.selects}
|
||||||
{this.listings}
|
{this.listings}
|
||||||
<Paginator page={page} onChange={this.handlePageChange} />
|
<PaginatorCursor
|
||||||
|
prevPage={pageCursor}
|
||||||
|
nextPage={this.getNextPage}
|
||||||
|
onNext={this.handlePageNext}
|
||||||
|
onPrev={this.handlePagePrev}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get getNextPage(): PaginationCursor | undefined {
|
||||||
|
return this.state.postsRes.state === "success"
|
||||||
|
? this.state.postsRes.data.next_page
|
||||||
|
: undefined;
|
||||||
|
}
|
||||||
|
|
||||||
get listings() {
|
get listings() {
|
||||||
const { dataType } = getHomeQueryParams();
|
const { dataType } = getHomeQueryParams();
|
||||||
const siteRes = this.state.siteRes;
|
const siteRes = this.state.siteRes;
|
||||||
|
@ -699,6 +713,7 @@ export class Home extends Component<any, HomeState> {
|
||||||
onAddAdmin={this.handleAddAdmin}
|
onAddAdmin={this.handleAddAdmin}
|
||||||
onTransferCommunity={this.handleTransferCommunity}
|
onTransferCommunity={this.handleTransferCommunity}
|
||||||
onFeaturePost={this.handleFeaturePost}
|
onFeaturePost={this.handleFeaturePost}
|
||||||
|
onMarkPostAsRead={this.handleMarkPostAsRead}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -718,7 +733,7 @@ export class Home extends Component<any, HomeState> {
|
||||||
nodes={commentsToFlatNodes(comments)}
|
nodes={commentsToFlatNodes(comments)}
|
||||||
viewType={CommentViewType.Flat}
|
viewType={CommentViewType.Flat}
|
||||||
finished={this.state.finished}
|
finished={this.state.finished}
|
||||||
noIndent
|
isTopLevel
|
||||||
showCommunity
|
showCommunity
|
||||||
showContext
|
showContext
|
||||||
enableDownvotes={enableDownvotes(siteRes)}
|
enableDownvotes={enableDownvotes(siteRes)}
|
||||||
|
@ -777,7 +792,7 @@ export class Home extends Component<any, HomeState> {
|
||||||
<div className="col-auto ps-0">
|
<div className="col-auto ps-0">
|
||||||
{getRss(
|
{getRss(
|
||||||
listingType ??
|
listingType ??
|
||||||
this.state.siteRes.site_view.local_site.default_post_listing_type
|
this.state.siteRes.site_view.local_site.default_post_listing_type,
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -785,20 +800,18 @@ export class Home extends Component<any, HomeState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchTrendingCommunities() {
|
async fetchTrendingCommunities() {
|
||||||
this.setState({ trendingCommunitiesRes: { state: "loading" } });
|
this.setState({ trendingCommunitiesRes: LOADING_REQUEST });
|
||||||
this.setState({
|
this.setState({
|
||||||
trendingCommunitiesRes: await HttpService.client.listCommunities({
|
trendingCommunitiesRes: await HttpService.client.listCommunities({
|
||||||
type_: "Local",
|
type_: "Local",
|
||||||
sort: "Hot",
|
sort: "Hot",
|
||||||
limit: trendingFetchLimit,
|
limit: trendingFetchLimit,
|
||||||
auth: myAuth(),
|
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchData() {
|
async fetchData() {
|
||||||
const auth = myAuth();
|
const { dataType, pageCursor, listingType, sort } = getHomeQueryParams();
|
||||||
const { dataType, page, listingType, sort } = getHomeQueryParams();
|
|
||||||
|
|
||||||
if (dataType === DataType.Post) {
|
if (dataType === DataType.Post) {
|
||||||
if (HomeCacheService.active) {
|
if (HomeCacheService.active) {
|
||||||
|
@ -808,33 +821,29 @@ export class Home extends Component<any, HomeState> {
|
||||||
window.scrollTo({
|
window.scrollTo({
|
||||||
left: 0,
|
left: 0,
|
||||||
top: scrollY,
|
top: scrollY,
|
||||||
behavior: "instant",
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.setState({ postsRes: { state: "loading" } });
|
this.setState({ postsRes: LOADING_REQUEST });
|
||||||
this.setState({
|
this.setState({
|
||||||
postsRes: await HttpService.client.getPosts({
|
postsRes: await HttpService.client.getPosts({
|
||||||
page,
|
page_cursor: pageCursor,
|
||||||
limit: fetchLimit,
|
limit: fetchLimit,
|
||||||
sort,
|
sort,
|
||||||
saved_only: false,
|
saved_only: false,
|
||||||
type_: listingType,
|
type_: listingType,
|
||||||
auth,
|
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
HomeCacheService.postsRes = this.state.postsRes;
|
HomeCacheService.postsRes = this.state.postsRes;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.setState({ commentsRes: { state: "loading" } });
|
this.setState({ commentsRes: LOADING_REQUEST });
|
||||||
this.setState({
|
this.setState({
|
||||||
commentsRes: await HttpService.client.getComments({
|
commentsRes: await HttpService.client.getComments({
|
||||||
page,
|
|
||||||
limit: fetchLimit,
|
limit: fetchLimit,
|
||||||
sort: postToCommentSortType(sort),
|
sort: postToCommentSortType(sort),
|
||||||
saved_only: false,
|
saved_only: false,
|
||||||
type_: listingType,
|
type_: listingType,
|
||||||
auth,
|
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -858,24 +867,32 @@ export class Home extends Component<any, HomeState> {
|
||||||
i.setState({ subscribedCollapsed: !i.state.subscribedCollapsed });
|
i.setState({ subscribedCollapsed: !i.state.subscribedCollapsed });
|
||||||
}
|
}
|
||||||
|
|
||||||
handlePageChange(page: number) {
|
handlePagePrev() {
|
||||||
|
this.props.history.back();
|
||||||
|
// A hack to scroll to top
|
||||||
|
setTimeout(() => {
|
||||||
|
window.scrollTo(0, 0);
|
||||||
|
}, 50);
|
||||||
|
}
|
||||||
|
|
||||||
|
handlePageNext(nextPage: PaginationCursor) {
|
||||||
this.setState({ scrolled: false });
|
this.setState({ scrolled: false });
|
||||||
this.updateUrl({ page });
|
this.updateUrl({ pageCursor: nextPage });
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSortChange(val: SortType) {
|
handleSortChange(val: SortType) {
|
||||||
this.setState({ scrolled: false });
|
this.setState({ scrolled: false });
|
||||||
this.updateUrl({ sort: val, page: 1 });
|
this.updateUrl({ sort: val, pageCursor: undefined });
|
||||||
}
|
}
|
||||||
|
|
||||||
handleListingTypeChange(val: ListingType) {
|
handleListingTypeChange(val: ListingType) {
|
||||||
this.setState({ scrolled: false });
|
this.setState({ scrolled: false });
|
||||||
this.updateUrl({ listingType: val, page: 1 });
|
this.updateUrl({ listingType: val, pageCursor: undefined });
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDataTypeChange(val: DataType) {
|
handleDataTypeChange(val: DataType) {
|
||||||
this.setState({ scrolled: false });
|
this.setState({ scrolled: false });
|
||||||
this.updateUrl({ dataType: val, page: 1 });
|
this.updateUrl({ dataType: val, pageCursor: undefined });
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleAddModToCommunity(form: AddModToCommunity) {
|
async handleAddModToCommunity(form: AddModToCommunity) {
|
||||||
|
@ -900,7 +917,7 @@ export class Home extends Component<any, HomeState> {
|
||||||
|
|
||||||
async handleBlockPerson(form: BlockPerson) {
|
async handleBlockPerson(form: BlockPerson) {
|
||||||
const blockPersonRes = await HttpService.client.blockPerson(form);
|
const blockPersonRes = await HttpService.client.blockPerson(form);
|
||||||
if (blockPersonRes.state == "success") {
|
if (blockPersonRes.state === "success") {
|
||||||
updatePersonBlock(blockPersonRes.data);
|
updatePersonBlock(blockPersonRes.data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -914,7 +931,7 @@ export class Home extends Component<any, HomeState> {
|
||||||
|
|
||||||
async handleEditComment(form: EditComment) {
|
async handleEditComment(form: EditComment) {
|
||||||
const editCommentRes = await HttpService.client.editComment(form);
|
const editCommentRes = await HttpService.client.editComment(form);
|
||||||
this.findAndUpdateComment(editCommentRes);
|
this.findAndUpdateCommentEdit(editCommentRes);
|
||||||
|
|
||||||
return editCommentRes;
|
return editCommentRes;
|
||||||
}
|
}
|
||||||
|
@ -971,14 +988,14 @@ export class Home extends Component<any, HomeState> {
|
||||||
|
|
||||||
async handleCommentReport(form: CreateCommentReport) {
|
async handleCommentReport(form: CreateCommentReport) {
|
||||||
const reportRes = await HttpService.client.createCommentReport(form);
|
const reportRes = await HttpService.client.createCommentReport(form);
|
||||||
if (reportRes.state == "success") {
|
if (reportRes.state === "success") {
|
||||||
toast(I18NextService.i18n.t("report_created"));
|
toast(I18NextService.i18n.t("report_created"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handlePostReport(form: CreatePostReport) {
|
async handlePostReport(form: CreatePostReport) {
|
||||||
const reportRes = await HttpService.client.createPostReport(form);
|
const reportRes = await HttpService.client.createPostReport(form);
|
||||||
if (reportRes.state == "success") {
|
if (reportRes.state === "success") {
|
||||||
toast(I18NextService.i18n.t("report_created"));
|
toast(I18NextService.i18n.t("report_created"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -996,7 +1013,7 @@ export class Home extends Component<any, HomeState> {
|
||||||
async handleAddAdmin(form: AddAdmin) {
|
async handleAddAdmin(form: AddAdmin) {
|
||||||
const addAdminRes = await HttpService.client.addAdmin(form);
|
const addAdminRes = await HttpService.client.addAdmin(form);
|
||||||
|
|
||||||
if (addAdminRes.state == "success") {
|
if (addAdminRes.state === "success") {
|
||||||
this.setState(s => ((s.siteRes.admins = addAdminRes.data.admins), s));
|
this.setState(s => ((s.siteRes.admins = addAdminRes.data.admins), s));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1026,22 +1043,27 @@ export class Home extends Component<any, HomeState> {
|
||||||
this.updateBan(banRes);
|
this.updateBan(banRes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async handleMarkPostAsRead(form: MarkPostAsRead) {
|
||||||
|
const res = await HttpService.client.markPostAsRead(form);
|
||||||
|
this.findAndUpdatePost(res);
|
||||||
|
}
|
||||||
|
|
||||||
updateBanFromCommunity(banRes: RequestState<BanFromCommunityResponse>) {
|
updateBanFromCommunity(banRes: RequestState<BanFromCommunityResponse>) {
|
||||||
// Maybe not necessary
|
// Maybe not necessary
|
||||||
if (banRes.state == "success") {
|
if (banRes.state === "success") {
|
||||||
this.setState(s => {
|
this.setState(s => {
|
||||||
if (s.postsRes.state == "success") {
|
if (s.postsRes.state === "success") {
|
||||||
s.postsRes.data.posts
|
s.postsRes.data.posts
|
||||||
.filter(c => c.creator.id == banRes.data.person_view.person.id)
|
.filter(c => c.creator.id === banRes.data.person_view.person.id)
|
||||||
.forEach(
|
.forEach(
|
||||||
c => (c.creator_banned_from_community = banRes.data.banned)
|
c => (c.creator_banned_from_community = banRes.data.banned),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (s.commentsRes.state == "success") {
|
if (s.commentsRes.state === "success") {
|
||||||
s.commentsRes.data.comments
|
s.commentsRes.data.comments
|
||||||
.filter(c => c.creator.id == banRes.data.person_view.person.id)
|
.filter(c => c.creator.id === banRes.data.person_view.person.id)
|
||||||
.forEach(
|
.forEach(
|
||||||
c => (c.creator_banned_from_community = banRes.data.banned)
|
c => (c.creator_banned_from_community = banRes.data.banned),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return s;
|
return s;
|
||||||
|
@ -1051,16 +1073,16 @@ export class Home extends Component<any, HomeState> {
|
||||||
|
|
||||||
updateBan(banRes: RequestState<BanPersonResponse>) {
|
updateBan(banRes: RequestState<BanPersonResponse>) {
|
||||||
// Maybe not necessary
|
// Maybe not necessary
|
||||||
if (banRes.state == "success") {
|
if (banRes.state === "success") {
|
||||||
this.setState(s => {
|
this.setState(s => {
|
||||||
if (s.postsRes.state == "success") {
|
if (s.postsRes.state === "success") {
|
||||||
s.postsRes.data.posts
|
s.postsRes.data.posts
|
||||||
.filter(c => c.creator.id == banRes.data.person_view.person.id)
|
.filter(c => c.creator.id === banRes.data.person_view.person.id)
|
||||||
.forEach(c => (c.creator.banned = banRes.data.banned));
|
.forEach(c => (c.creator.banned = banRes.data.banned));
|
||||||
}
|
}
|
||||||
if (s.commentsRes.state == "success") {
|
if (s.commentsRes.state === "success") {
|
||||||
s.commentsRes.data.comments
|
s.commentsRes.data.comments
|
||||||
.filter(c => c.creator.id == banRes.data.person_view.person.id)
|
.filter(c => c.creator.id === banRes.data.person_view.person.id)
|
||||||
.forEach(c => (c.creator.banned = banRes.data.banned));
|
.forEach(c => (c.creator.banned = banRes.data.banned));
|
||||||
}
|
}
|
||||||
return s;
|
return s;
|
||||||
|
@ -1069,18 +1091,18 @@ export class Home extends Component<any, HomeState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
purgeItem(purgeRes: RequestState<PurgeItemResponse>) {
|
purgeItem(purgeRes: RequestState<PurgeItemResponse>) {
|
||||||
if (purgeRes.state == "success") {
|
if (purgeRes.state === "success") {
|
||||||
toast(I18NextService.i18n.t("purge_success"));
|
toast(I18NextService.i18n.t("purge_success"));
|
||||||
this.context.router.history.push(`/`);
|
this.context.router.history.push(`/`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
findAndUpdateComment(res: RequestState<CommentResponse>) {
|
findAndUpdateCommentEdit(res: RequestState<CommentResponse>) {
|
||||||
this.setState(s => {
|
this.setState(s => {
|
||||||
if (s.commentsRes.state == "success" && res.state == "success") {
|
if (s.commentsRes.state === "success" && res.state === "success") {
|
||||||
s.commentsRes.data.comments = editComment(
|
s.commentsRes.data.comments = editComment(
|
||||||
res.data.comment_view,
|
res.data.comment_view,
|
||||||
s.commentsRes.data.comments
|
s.commentsRes.data.comments,
|
||||||
);
|
);
|
||||||
s.finished.set(res.data.comment_view.comment.id, true);
|
s.finished.set(res.data.comment_view.comment.id, true);
|
||||||
}
|
}
|
||||||
|
@ -1088,15 +1110,27 @@ export class Home extends Component<any, HomeState> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
findAndUpdateComment(res: RequestState<CommentResponse>) {
|
||||||
|
this.setState(s => {
|
||||||
|
if (s.commentsRes.state === "success" && res.state === "success") {
|
||||||
|
s.commentsRes.data.comments = editComment(
|
||||||
|
res.data.comment_view,
|
||||||
|
s.commentsRes.data.comments,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
createAndUpdateComments(res: RequestState<CommentResponse>) {
|
createAndUpdateComments(res: RequestState<CommentResponse>) {
|
||||||
this.setState(s => {
|
this.setState(s => {
|
||||||
if (s.commentsRes.state == "success" && res.state == "success") {
|
if (s.commentsRes.state === "success" && res.state === "success") {
|
||||||
s.commentsRes.data.comments.unshift(res.data.comment_view);
|
s.commentsRes.data.comments.unshift(res.data.comment_view);
|
||||||
|
|
||||||
// Set finished for the parent
|
// Set finished for the parent
|
||||||
s.finished.set(
|
s.finished.set(
|
||||||
getCommentParentId(res.data.comment_view.comment) ?? 0,
|
getCommentParentId(res.data.comment_view.comment) ?? 0,
|
||||||
true
|
true,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return s;
|
return s;
|
||||||
|
@ -1105,10 +1139,10 @@ export class Home extends Component<any, HomeState> {
|
||||||
|
|
||||||
findAndUpdateCommentReply(res: RequestState<CommentReplyResponse>) {
|
findAndUpdateCommentReply(res: RequestState<CommentReplyResponse>) {
|
||||||
this.setState(s => {
|
this.setState(s => {
|
||||||
if (s.commentsRes.state == "success" && res.state == "success") {
|
if (s.commentsRes.state === "success" && res.state === "success") {
|
||||||
s.commentsRes.data.comments = editWith(
|
s.commentsRes.data.comments = editWith(
|
||||||
res.data.comment_reply_view,
|
res.data.comment_reply_view,
|
||||||
s.commentsRes.data.comments
|
s.commentsRes.data.comments,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return s;
|
return s;
|
||||||
|
@ -1117,10 +1151,10 @@ export class Home extends Component<any, HomeState> {
|
||||||
|
|
||||||
findAndUpdatePost(res: RequestState<PostResponse>) {
|
findAndUpdatePost(res: RequestState<PostResponse>) {
|
||||||
this.setState(s => {
|
this.setState(s => {
|
||||||
if (s.postsRes.state == "success" && res.state == "success") {
|
if (s.postsRes.state === "success" && res.state === "success") {
|
||||||
s.postsRes.data.posts = editPost(
|
s.postsRes.data.posts = editPost(
|
||||||
res.data.post_view,
|
res.data.post_view,
|
||||||
s.postsRes.data.posts
|
s.postsRes.data.posts,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return s;
|
return s;
|
||||||
|
|
|
@ -6,12 +6,19 @@ import {
|
||||||
GetSiteResponse,
|
GetSiteResponse,
|
||||||
Instance,
|
Instance,
|
||||||
} from "lemmy-js-client";
|
} from "lemmy-js-client";
|
||||||
|
import classNames from "classnames";
|
||||||
import { relTags } from "../../config";
|
import { relTags } from "../../config";
|
||||||
import { InitialFetchRequest } from "../../interfaces";
|
import { InitialFetchRequest } from "../../interfaces";
|
||||||
import { FirstLoadService, I18NextService } from "../../services";
|
import { FirstLoadService, I18NextService } from "../../services";
|
||||||
import { HttpService, RequestState } from "../../services/HttpService";
|
import {
|
||||||
|
EMPTY_REQUEST,
|
||||||
|
HttpService,
|
||||||
|
LOADING_REQUEST,
|
||||||
|
RequestState,
|
||||||
|
} from "../../services/HttpService";
|
||||||
import { HtmlTags } from "../common/html-tags";
|
import { HtmlTags } from "../common/html-tags";
|
||||||
import { Spinner } from "../common/icon";
|
import { Spinner } from "../common/icon";
|
||||||
|
import Tabs from "../common/tabs";
|
||||||
|
|
||||||
type InstancesData = RouteDataResponse<{
|
type InstancesData = RouteDataResponse<{
|
||||||
federatedInstancesResponse: GetFederatedInstancesResponse;
|
federatedInstancesResponse: GetFederatedInstancesResponse;
|
||||||
|
@ -26,7 +33,7 @@ interface InstancesState {
|
||||||
export class Instances extends Component<any, InstancesState> {
|
export class Instances extends Component<any, InstancesState> {
|
||||||
private isoData = setIsoData<InstancesData>(this.context);
|
private isoData = setIsoData<InstancesData>(this.context);
|
||||||
state: InstancesState = {
|
state: InstancesState = {
|
||||||
instancesRes: { state: "empty" },
|
instancesRes: EMPTY_REQUEST,
|
||||||
siteRes: this.isoData.site_res,
|
siteRes: this.isoData.site_res,
|
||||||
isIsomorphic: false,
|
isIsomorphic: false,
|
||||||
};
|
};
|
||||||
|
@ -52,11 +59,11 @@ export class Instances extends Component<any, InstancesState> {
|
||||||
|
|
||||||
async fetchInstances() {
|
async fetchInstances() {
|
||||||
this.setState({
|
this.setState({
|
||||||
instancesRes: { state: "loading" },
|
instancesRes: LOADING_REQUEST,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
instancesRes: await HttpService.client.getFederatedInstances({}),
|
instancesRes: await HttpService.client.getFederatedInstances(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,7 +71,7 @@ export class Instances extends Component<any, InstancesState> {
|
||||||
client,
|
client,
|
||||||
}: InitialFetchRequest): Promise<InstancesData> {
|
}: InitialFetchRequest): Promise<InstancesData> {
|
||||||
return {
|
return {
|
||||||
federatedInstancesResponse: await client.getFederatedInstances({}),
|
federatedInstancesResponse: await client.getFederatedInstances(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,37 +92,32 @@ export class Instances extends Component<any, InstancesState> {
|
||||||
case "success": {
|
case "success": {
|
||||||
const instances = this.state.instancesRes.data.federated_instances;
|
const instances = this.state.instancesRes.data.federated_instances;
|
||||||
return instances ? (
|
return instances ? (
|
||||||
<>
|
<div className="row">
|
||||||
<h1 className="h4 mb-4">{I18NextService.i18n.t("instances")}</h1>
|
<div className="col-lg-8">
|
||||||
<div className="row">
|
<Tabs
|
||||||
<div className="col-md-6">
|
tabs={["linked", "allowed", "blocked"]
|
||||||
<h2 className="h5 mb-3">
|
.filter(status => instances[status].length)
|
||||||
{I18NextService.i18n.t("linked_instances")}
|
.map(status => ({
|
||||||
</h2>
|
key: status,
|
||||||
{this.itemList(instances.linked)}
|
label: I18NextService.i18n.t(`${status}_instances`),
|
||||||
</div>
|
getNode: isSelected => (
|
||||||
|
<div
|
||||||
|
role="tabpanel"
|
||||||
|
className={classNames("tab-pane show", {
|
||||||
|
active: isSelected,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{status === "blocked"
|
||||||
|
? this.itemList(instances[status], false)
|
||||||
|
: this.itemList(instances[status])}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
}))}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="row">
|
</div>
|
||||||
{instances.allowed && instances.allowed.length > 0 && (
|
|
||||||
<div className="col-md-6">
|
|
||||||
<h2 className="h5 mb-3">
|
|
||||||
{I18NextService.i18n.t("allowed_instances")}
|
|
||||||
</h2>
|
|
||||||
{this.itemList(instances.allowed)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{instances.blocked && instances.blocked.length > 0 && (
|
|
||||||
<div className="col-md-6">
|
|
||||||
<h2 className="h5 mb-3">
|
|
||||||
{I18NextService.i18n.t("blocked_instances")}
|
|
||||||
</h2>
|
|
||||||
{this.itemList(instances.blocked)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
) : (
|
) : (
|
||||||
<></>
|
<h5>No linked instance</h5>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -133,7 +135,7 @@ export class Instances extends Component<any, InstancesState> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
itemList(items: Instance[]) {
|
itemList(items: Instance[], link = true) {
|
||||||
return items.length > 0 ? (
|
return items.length > 0 ? (
|
||||||
<div className="table-responsive">
|
<div className="table-responsive">
|
||||||
<table id="instances_table" className="table table-sm table-hover">
|
<table id="instances_table" className="table table-sm table-hover">
|
||||||
|
@ -148,9 +150,13 @@ export class Instances extends Component<any, InstancesState> {
|
||||||
{items.map(i => (
|
{items.map(i => (
|
||||||
<tr key={i.domain}>
|
<tr key={i.domain}>
|
||||||
<td>
|
<td>
|
||||||
<a href={`https://${i.domain}`} rel={relTags}>
|
{link ? (
|
||||||
{i.domain}
|
<a href={`https://${i.domain}`} rel={relTags}>
|
||||||
</a>
|
{i.domain}{" "}
|
||||||
|
</a>
|
||||||
|
) : (
|
||||||
|
<span>{i.domain}</span>
|
||||||
|
)}
|
||||||
</td>
|
</td>
|
||||||
<td>{i.software}</td>
|
<td>{i.software}</td>
|
||||||
<td>{i.version}</td>
|
<td>{i.version}</td>
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { setIsoData } from "@utils/app";
|
||||||
import { capitalizeFirstLetter, validEmail } from "@utils/helpers";
|
import { capitalizeFirstLetter, validEmail } from "@utils/helpers";
|
||||||
import { Component, linkEvent } from "inferno";
|
import { Component, linkEvent } from "inferno";
|
||||||
import { GetSiteResponse } from "lemmy-js-client";
|
import { GetSiteResponse } from "lemmy-js-client";
|
||||||
import { HttpService, I18NextService, UserService } from "../../services";
|
import { HttpService, I18NextService } from "../../services";
|
||||||
import { toast } from "../../toast";
|
import { toast } from "../../toast";
|
||||||
import { HtmlTags } from "../common/html-tags";
|
import { HtmlTags } from "../common/html-tags";
|
||||||
import { Spinner } from "../common/icon";
|
import { Spinner } from "../common/icon";
|
||||||
|
@ -30,15 +30,9 @@ export class LoginReset extends Component<any, State> {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
if (UserService.Instance.myUserInfo) {
|
|
||||||
this.context.router.history.push("/");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get documentTitle(): string {
|
get documentTitle(): string {
|
||||||
return `${capitalizeFirstLetter(
|
return `${capitalizeFirstLetter(
|
||||||
I18NextService.i18n.t("forgot_password")
|
I18NextService.i18n.t("forgot_password"),
|
||||||
)} - ${this.state.siteRes.site_view.site.name}`;
|
)} - ${this.state.siteRes.site_view.site.name}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,7 +121,7 @@ export class LoginReset extends Component<any, State> {
|
||||||
|
|
||||||
const res = await HttpService.client.passwordReset({ email });
|
const res = await HttpService.client.passwordReset({ email });
|
||||||
|
|
||||||
if (res.state == "success") {
|
if (res.state === "success") {
|
||||||
toast(I18NextService.i18n.t("reset_password_mail_sent"));
|
toast(I18NextService.i18n.t("reset_password_mail_sent"));
|
||||||
i.context.router.history.push("/login");
|
i.context.router.history.push("/login");
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,44 +1,130 @@
|
||||||
import { myAuth, setIsoData } from "@utils/app";
|
import { setIsoData } from "@utils/app";
|
||||||
import { isBrowser } from "@utils/browser";
|
import { isBrowser } from "@utils/browser";
|
||||||
|
import { getQueryParams } from "@utils/helpers";
|
||||||
import { Component, linkEvent } from "inferno";
|
import { Component, linkEvent } from "inferno";
|
||||||
import { NavLink } from "inferno-router";
|
import { RouteComponentProps } from "inferno-router/dist/Route";
|
||||||
import { GetSiteResponse, LoginResponse } from "lemmy-js-client";
|
import { GetSiteResponse, LoginResponse } from "lemmy-js-client";
|
||||||
import { I18NextService, UserService } from "../../services";
|
import { I18NextService, UserService } from "../../services";
|
||||||
import { HttpService, RequestState } from "../../services/HttpService";
|
import {
|
||||||
|
EMPTY_REQUEST,
|
||||||
|
HttpService,
|
||||||
|
LOADING_REQUEST,
|
||||||
|
RequestState,
|
||||||
|
} from "../../services/HttpService";
|
||||||
import { toast } from "../../toast";
|
import { toast } from "../../toast";
|
||||||
import { HtmlTags } from "../common/html-tags";
|
import { HtmlTags } from "../common/html-tags";
|
||||||
import { Spinner } from "../common/icon";
|
import { Spinner } from "../common/icon";
|
||||||
|
import PasswordInput from "../common/password-input";
|
||||||
|
import TotpModal from "../common/totp-modal";
|
||||||
|
import { UnreadCounterService } from "../../services";
|
||||||
|
|
||||||
|
interface LoginProps {
|
||||||
|
prev?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const getLoginQueryParams = () =>
|
||||||
|
getQueryParams<LoginProps>({
|
||||||
|
prev(param) {
|
||||||
|
return param ? decodeURIComponent(param) : undefined;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
loginRes: RequestState<LoginResponse>;
|
loginRes: RequestState<LoginResponse>;
|
||||||
form: {
|
form: {
|
||||||
username_or_email?: string;
|
username_or_email: string;
|
||||||
password?: string;
|
password: string;
|
||||||
totp_2fa_token?: string;
|
|
||||||
};
|
};
|
||||||
showTotp: boolean;
|
|
||||||
siteRes: GetSiteResponse;
|
siteRes: GetSiteResponse;
|
||||||
|
show2faModal: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Login extends Component<any, State> {
|
async function handleLoginSuccess(i: Login, loginRes: LoginResponse) {
|
||||||
|
UserService.Instance.login({
|
||||||
|
res: loginRes,
|
||||||
|
});
|
||||||
|
const site = await HttpService.client.getSite();
|
||||||
|
|
||||||
|
if (site.state === "success") {
|
||||||
|
UserService.Instance.myUserInfo = site.data.my_user;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { prev } = getLoginQueryParams();
|
||||||
|
|
||||||
|
prev
|
||||||
|
? i.props.history.replace(prev)
|
||||||
|
: i.props.history.action === "PUSH"
|
||||||
|
? i.props.history.back()
|
||||||
|
: i.props.history.replace("/");
|
||||||
|
|
||||||
|
UnreadCounterService.Instance.updateAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleLoginSubmit(i: Login, event: any) {
|
||||||
|
event.preventDefault();
|
||||||
|
const { password, username_or_email } = i.state.form;
|
||||||
|
|
||||||
|
if (username_or_email && password) {
|
||||||
|
i.setState({ loginRes: LOADING_REQUEST });
|
||||||
|
|
||||||
|
const loginRes = await HttpService.client.login({
|
||||||
|
username_or_email,
|
||||||
|
password,
|
||||||
|
});
|
||||||
|
switch (loginRes.state) {
|
||||||
|
case "failed": {
|
||||||
|
if (loginRes.msg === "missing_totp_token") {
|
||||||
|
i.setState({ show2faModal: true });
|
||||||
|
} else {
|
||||||
|
toast(I18NextService.i18n.t(loginRes.msg), "danger");
|
||||||
|
}
|
||||||
|
|
||||||
|
i.setState({ loginRes });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "success": {
|
||||||
|
handleLoginSuccess(i, loginRes.data);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleLoginUsernameChange(i: Login, event: any) {
|
||||||
|
i.setState(
|
||||||
|
prevState => (prevState.form.username_or_email = event.target.value.trim()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleLoginPasswordChange(i: Login, event: any) {
|
||||||
|
i.setState(prevState => (prevState.form.password = event.target.value));
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleClose2faModal(i: Login) {
|
||||||
|
i.setState({ show2faModal: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Login extends Component<
|
||||||
|
RouteComponentProps<Record<string, never>>,
|
||||||
|
State
|
||||||
|
> {
|
||||||
private isoData = setIsoData(this.context);
|
private isoData = setIsoData(this.context);
|
||||||
|
|
||||||
state: State = {
|
state: State = {
|
||||||
loginRes: { state: "empty" },
|
loginRes: EMPTY_REQUEST,
|
||||||
form: {},
|
form: {
|
||||||
showTotp: false,
|
username_or_email: "",
|
||||||
|
password: "",
|
||||||
|
},
|
||||||
siteRes: this.isoData.site_res,
|
siteRes: this.isoData.site_res,
|
||||||
|
show2faModal: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props: any, context: any) {
|
constructor(props: any, context: any) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
this.handleSubmitTotp = this.handleSubmitTotp.bind(this);
|
||||||
// Navigate to home if already logged in
|
|
||||||
if (UserService.Instance.myUserInfo) {
|
|
||||||
this.context.router.history.push("/");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get documentTitle(): string {
|
get documentTitle(): string {
|
||||||
|
@ -48,7 +134,7 @@ export class Login extends Component<any, State> {
|
||||||
}
|
}
|
||||||
|
|
||||||
get isLemmyMl(): boolean {
|
get isLemmyMl(): boolean {
|
||||||
return isBrowser() && window.location.hostname == "lemmy.ml";
|
return isBrowser() && window.location.hostname === "lemmy.ml";
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
@ -58,6 +144,12 @@ export class Login extends Component<any, State> {
|
||||||
title={this.documentTitle}
|
title={this.documentTitle}
|
||||||
path={this.context.router.route.match.url}
|
path={this.context.router.route.match.url}
|
||||||
/>
|
/>
|
||||||
|
<TotpModal
|
||||||
|
type="login"
|
||||||
|
onSubmit={this.handleSubmitTotp}
|
||||||
|
show={this.state.show2faModal}
|
||||||
|
onClose={linkEvent(this, handleClose2faModal)}
|
||||||
|
/>
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col-12 col-lg-6 offset-lg-3">{this.loginForm()}</div>
|
<div className="col-12 col-lg-6 offset-lg-3">{this.loginForm()}</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -65,10 +157,28 @@ export class Login extends Component<any, State> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async handleSubmitTotp(totp: string) {
|
||||||
|
const loginRes = await HttpService.client.login({
|
||||||
|
password: this.state.form.password,
|
||||||
|
username_or_email: this.state.form.username_or_email,
|
||||||
|
totp_2fa_token: totp,
|
||||||
|
});
|
||||||
|
|
||||||
|
const successful = loginRes.state === "success";
|
||||||
|
if (successful) {
|
||||||
|
this.setState({ show2faModal: false });
|
||||||
|
handleLoginSuccess(this, loginRes.data);
|
||||||
|
} else {
|
||||||
|
toast(I18NextService.i18n.t("incorrect_totp_code"), "danger");
|
||||||
|
}
|
||||||
|
|
||||||
|
return successful;
|
||||||
|
}
|
||||||
|
|
||||||
loginForm() {
|
loginForm() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<form onSubmit={linkEvent(this, this.handleLoginSubmit)}>
|
<form onSubmit={linkEvent(this, handleLoginSubmit)}>
|
||||||
<h1 className="h4 mb-4">{I18NextService.i18n.t("login")}</h1>
|
<h1 className="h4 mb-4">{I18NextService.i18n.t("login")}</h1>
|
||||||
<div className="mb-3 row">
|
<div className="mb-3 row">
|
||||||
<label
|
<label
|
||||||
|
@ -83,62 +193,26 @@ export class Login extends Component<any, State> {
|
||||||
className="form-control"
|
className="form-control"
|
||||||
id="login-email-or-username"
|
id="login-email-or-username"
|
||||||
value={this.state.form.username_or_email}
|
value={this.state.form.username_or_email}
|
||||||
onInput={linkEvent(this, this.handleLoginUsernameChange)}
|
onInput={linkEvent(this, handleLoginUsernameChange)}
|
||||||
autoComplete="email"
|
autoComplete="email"
|
||||||
required
|
required
|
||||||
minLength={3}
|
minLength={3}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-3 row">
|
<div className="mb-3">
|
||||||
<label className="col-sm-2 col-form-label" htmlFor="login-password">
|
<PasswordInput
|
||||||
{I18NextService.i18n.t("password")}
|
id="login-password"
|
||||||
</label>
|
value={this.state.form.password}
|
||||||
<div className="col-sm-10">
|
onInput={linkEvent(this, handleLoginPasswordChange)}
|
||||||
<input
|
label={I18NextService.i18n.t("password")}
|
||||||
type="password"
|
showForgotLink
|
||||||
id="login-password"
|
/>
|
||||||
value={this.state.form.password}
|
|
||||||
onInput={linkEvent(this, this.handleLoginPasswordChange)}
|
|
||||||
className="form-control"
|
|
||||||
autoComplete="current-password"
|
|
||||||
required
|
|
||||||
maxLength={60}
|
|
||||||
/>
|
|
||||||
<NavLink
|
|
||||||
className="btn p-0 btn-link d-inline-block float-right text-muted small font-weight-bold pointer-events not-allowed"
|
|
||||||
to="/login_reset"
|
|
||||||
>
|
|
||||||
{I18NextService.i18n.t("forgot_password")}
|
|
||||||
</NavLink>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{this.state.showTotp && (
|
|
||||||
<div className="mb-3 row">
|
|
||||||
<label
|
|
||||||
className="col-sm-6 col-form-label"
|
|
||||||
htmlFor="login-totp-token"
|
|
||||||
>
|
|
||||||
{I18NextService.i18n.t("two_factor_token")}
|
|
||||||
</label>
|
|
||||||
<div className="col-sm-6">
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
inputMode="numeric"
|
|
||||||
className="form-control"
|
|
||||||
id="login-totp-token"
|
|
||||||
pattern="[0-9]*"
|
|
||||||
autoComplete="one-time-code"
|
|
||||||
value={this.state.form.totp_2fa_token}
|
|
||||||
onInput={linkEvent(this, this.handleLoginTotpChange)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className="mb-3 row">
|
<div className="mb-3 row">
|
||||||
<div className="col-sm-10">
|
<div className="col-sm-10">
|
||||||
<button type="submit" className="btn btn-secondary">
|
<button type="submit" className="btn btn-secondary">
|
||||||
{this.state.loginRes.state == "loading" ? (
|
{this.state.loginRes.state === "loading" ? (
|
||||||
<Spinner />
|
<Spinner />
|
||||||
) : (
|
) : (
|
||||||
I18NextService.i18n.t("login")
|
I18NextService.i18n.t("login")
|
||||||
|
@ -150,64 +224,4 @@ export class Login extends Component<any, State> {
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleLoginSubmit(i: Login, event: any) {
|
|
||||||
event.preventDefault();
|
|
||||||
const { password, totp_2fa_token, username_or_email } = i.state.form;
|
|
||||||
|
|
||||||
if (username_or_email && password) {
|
|
||||||
i.setState({ loginRes: { state: "loading" } });
|
|
||||||
|
|
||||||
const loginRes = await HttpService.client.login({
|
|
||||||
username_or_email,
|
|
||||||
password,
|
|
||||||
totp_2fa_token,
|
|
||||||
});
|
|
||||||
switch (loginRes.state) {
|
|
||||||
case "failed": {
|
|
||||||
if (loginRes.msg === "missing_totp_token") {
|
|
||||||
i.setState({ showTotp: true });
|
|
||||||
toast(I18NextService.i18n.t("enter_two_factor_code"), "info");
|
|
||||||
}
|
|
||||||
|
|
||||||
i.setState({ loginRes: { state: "failed", msg: loginRes.msg } });
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case "success": {
|
|
||||||
UserService.Instance.login({
|
|
||||||
res: loginRes.data,
|
|
||||||
});
|
|
||||||
const site = await HttpService.client.getSite({
|
|
||||||
auth: myAuth(),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (site.state === "success") {
|
|
||||||
UserService.Instance.myUserInfo = site.data.my_user;
|
|
||||||
}
|
|
||||||
|
|
||||||
i.props.history.action === "PUSH"
|
|
||||||
? i.props.history.back()
|
|
||||||
: i.props.history.replace("/");
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleLoginUsernameChange(i: Login, event: any) {
|
|
||||||
i.state.form.username_or_email = event.target.value.trim();
|
|
||||||
i.setState(i.state);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleLoginTotpChange(i: Login, event: any) {
|
|
||||||
i.state.form.totp_2fa_token = event.target.value;
|
|
||||||
i.setState(i.state);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleLoginPasswordChange(i: Login, event: any) {
|
|
||||||
i.state.form.password = event.target.value;
|
|
||||||
i.setState(i.state);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import { myAuthRequired } from "@utils/app";
|
|
||||||
import { capitalizeFirstLetter } from "@utils/helpers";
|
import { capitalizeFirstLetter } from "@utils/helpers";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { Component, FormEventHandler, linkEvent } from "inferno";
|
import { Component, FormEventHandler, linkEvent } from "inferno";
|
||||||
|
@ -88,7 +87,7 @@ function RateLimits({
|
||||||
|
|
||||||
function handleRateLimitChange(
|
function handleRateLimitChange(
|
||||||
{ rateLimitType, ctx }: { rateLimitType: string; ctx: RateLimitsForm },
|
{ rateLimitType, ctx }: { rateLimitType: string; ctx: RateLimitsForm },
|
||||||
event: any
|
event: any,
|
||||||
) {
|
) {
|
||||||
ctx.setState(prev => ({
|
ctx.setState(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
|
@ -101,7 +100,7 @@ function handleRateLimitChange(
|
||||||
|
|
||||||
function handlePerSecondChange(
|
function handlePerSecondChange(
|
||||||
{ rateLimitType, ctx }: { rateLimitType: string; ctx: RateLimitsForm },
|
{ rateLimitType, ctx }: { rateLimitType: string; ctx: RateLimitsForm },
|
||||||
event: any
|
event: any,
|
||||||
) {
|
) {
|
||||||
ctx.setState(prev => ({
|
ctx.setState(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
|
@ -114,15 +113,12 @@ function handlePerSecondChange(
|
||||||
|
|
||||||
function submitRateLimitForm(i: RateLimitsForm, event: any) {
|
function submitRateLimitForm(i: RateLimitsForm, event: any) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const auth = myAuthRequired();
|
|
||||||
const form: EditSite = Object.entries(i.state.form).reduce(
|
const form: EditSite = Object.entries(i.state.form).reduce(
|
||||||
(acc, [key, val]) => {
|
(acc, [key, val]) => {
|
||||||
acc[`rate_limit_${key}`] = val;
|
acc[`rate_limit_${key}`] = val;
|
||||||
return acc;
|
return acc;
|
||||||
},
|
},
|
||||||
{
|
{},
|
||||||
auth,
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
i.props.onSaveSite(form);
|
i.props.onSaveSite(form);
|
||||||
|
@ -159,11 +155,11 @@ export default class RateLimitsForm extends Component<
|
||||||
})}
|
})}
|
||||||
handleRateLimit={linkEvent(
|
handleRateLimit={linkEvent(
|
||||||
{ rateLimitType, ctx: this },
|
{ rateLimitType, ctx: this },
|
||||||
handleRateLimitChange
|
handleRateLimitChange,
|
||||||
)}
|
)}
|
||||||
handleRateLimitPerSecond={linkEvent(
|
handleRateLimitPerSecond={linkEvent(
|
||||||
{ rateLimitType, ctx: this },
|
{ rateLimitType, ctx: this },
|
||||||
handlePerSecondChange
|
handlePerSecondChange,
|
||||||
)}
|
)}
|
||||||
rateLimitValue={this.state.form[rateLimitType]}
|
rateLimitValue={this.state.form[rateLimitType]}
|
||||||
rateLimitPerSecondValue={
|
rateLimitPerSecondValue={
|
||||||
|
|
|
@ -8,8 +8,14 @@ import {
|
||||||
Register,
|
Register,
|
||||||
} from "lemmy-js-client";
|
} from "lemmy-js-client";
|
||||||
import { I18NextService, UserService } from "../../services";
|
import { I18NextService, UserService } from "../../services";
|
||||||
import { HttpService, RequestState } from "../../services/HttpService";
|
import {
|
||||||
|
EMPTY_REQUEST,
|
||||||
|
HttpService,
|
||||||
|
LOADING_REQUEST,
|
||||||
|
RequestState,
|
||||||
|
} from "../../services/HttpService";
|
||||||
import { Spinner } from "../common/icon";
|
import { Spinner } from "../common/icon";
|
||||||
|
import PasswordInput from "../common/password-input";
|
||||||
import { SiteForm } from "./site-form";
|
import { SiteForm } from "./site-form";
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
|
@ -34,7 +40,7 @@ export class Setup extends Component<any, State> {
|
||||||
private isoData = setIsoData(this.context);
|
private isoData = setIsoData(this.context);
|
||||||
|
|
||||||
state: State = {
|
state: State = {
|
||||||
registerRes: { state: "empty" },
|
registerRes: EMPTY_REQUEST,
|
||||||
themeList: [],
|
themeList: [],
|
||||||
form: {
|
form: {
|
||||||
show_nsfw: true,
|
show_nsfw: true,
|
||||||
|
@ -121,46 +127,28 @@ export class Setup extends Component<any, State> {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-3 row">
|
<div className="mb-3">
|
||||||
<label className="col-sm-2 col-form-label" htmlFor="password">
|
<PasswordInput
|
||||||
{I18NextService.i18n.t("password")}
|
id="password"
|
||||||
</label>
|
value={this.state.form.password}
|
||||||
<div className="col-sm-10">
|
onInput={linkEvent(this, this.handleRegisterPasswordChange)}
|
||||||
<input
|
label={I18NextService.i18n.t("password")}
|
||||||
type="password"
|
isNew
|
||||||
id="password"
|
/>
|
||||||
value={this.state.form.password}
|
|
||||||
onInput={linkEvent(this, this.handleRegisterPasswordChange)}
|
|
||||||
className="form-control"
|
|
||||||
required
|
|
||||||
autoComplete="new-password"
|
|
||||||
minLength={10}
|
|
||||||
maxLength={60}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-3 row">
|
<div className="mb-3">
|
||||||
<label className="col-sm-2 col-form-label" htmlFor="verify-password">
|
<PasswordInput
|
||||||
{I18NextService.i18n.t("verify_password")}
|
id="verify-password"
|
||||||
</label>
|
value={this.state.form.password_verify}
|
||||||
<div className="col-sm-10">
|
onInput={linkEvent(this, this.handleRegisterPasswordVerifyChange)}
|
||||||
<input
|
label={I18NextService.i18n.t("verify_password")}
|
||||||
type="password"
|
isNew
|
||||||
id="verify-password"
|
/>
|
||||||
value={this.state.form.password_verify}
|
|
||||||
onInput={linkEvent(this, this.handleRegisterPasswordVerifyChange)}
|
|
||||||
className="form-control"
|
|
||||||
required
|
|
||||||
autoComplete="new-password"
|
|
||||||
minLength={10}
|
|
||||||
maxLength={60}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-3 row">
|
<div className="mb-3 row">
|
||||||
<div className="col-sm-10">
|
<div className="col-sm-10">
|
||||||
<button type="submit" className="btn btn-secondary">
|
<button type="submit" className="btn btn-secondary">
|
||||||
{this.state.registerRes.state == "loading" ? (
|
{this.state.registerRes.state === "loading" ? (
|
||||||
<Spinner />
|
<Spinner />
|
||||||
) : (
|
) : (
|
||||||
I18NextService.i18n.t("sign_up")
|
I18NextService.i18n.t("sign_up")
|
||||||
|
@ -174,7 +162,7 @@ export class Setup extends Component<any, State> {
|
||||||
|
|
||||||
async handleRegisterSubmit(i: Setup, event: any) {
|
async handleRegisterSubmit(i: Setup, event: any) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
i.setState({ registerRes: { state: "loading" } });
|
i.setState({ registerRes: LOADING_REQUEST });
|
||||||
const {
|
const {
|
||||||
username,
|
username,
|
||||||
password_verify,
|
password_verify,
|
||||||
|
@ -203,7 +191,7 @@ export class Setup extends Component<any, State> {
|
||||||
registerRes: await HttpService.client.register(form),
|
registerRes: await HttpService.client.register(form),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (i.state.registerRes.state == "success") {
|
if (i.state.registerRes.state === "success") {
|
||||||
const data = i.state.registerRes.data;
|
const data = i.state.registerRes.data;
|
||||||
|
|
||||||
UserService.Instance.login({ res: data });
|
UserService.Instance.login({ res: data });
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
import { myAuth, setIsoData } from "@utils/app";
|
import { setIsoData } from "@utils/app";
|
||||||
import { isBrowser } from "@utils/browser";
|
import { isBrowser } from "@utils/browser";
|
||||||
import { validEmail } from "@utils/helpers";
|
import { validEmail } from "@utils/helpers";
|
||||||
import { Options, passwordStrength } from "check-password-strength";
|
|
||||||
import { NoOptionI18nKeys } from "i18next";
|
|
||||||
import { Component, linkEvent } from "inferno";
|
import { Component, linkEvent } from "inferno";
|
||||||
import { T } from "inferno-i18next-dess";
|
import { T } from "inferno-i18next-dess";
|
||||||
import {
|
import {
|
||||||
|
@ -15,38 +13,17 @@ import {
|
||||||
import { joinLemmyUrl } from "../../config";
|
import { joinLemmyUrl } from "../../config";
|
||||||
import { mdToHtml } from "../../markdown";
|
import { mdToHtml } from "../../markdown";
|
||||||
import { I18NextService, UserService } from "../../services";
|
import { I18NextService, UserService } from "../../services";
|
||||||
import { HttpService, RequestState } from "../../services/HttpService";
|
import {
|
||||||
|
EMPTY_REQUEST,
|
||||||
|
HttpService,
|
||||||
|
LOADING_REQUEST,
|
||||||
|
RequestState,
|
||||||
|
} from "../../services/HttpService";
|
||||||
import { toast } from "../../toast";
|
import { toast } from "../../toast";
|
||||||
import { HtmlTags } from "../common/html-tags";
|
import { HtmlTags } from "../common/html-tags";
|
||||||
import { Icon, Spinner } from "../common/icon";
|
import { Icon, Spinner } from "../common/icon";
|
||||||
import { MarkdownTextArea } from "../common/markdown-textarea";
|
import { MarkdownTextArea } from "../common/markdown-textarea";
|
||||||
|
import PasswordInput from "../common/password-input";
|
||||||
const passwordStrengthOptions: Options<string> = [
|
|
||||||
{
|
|
||||||
id: 0,
|
|
||||||
value: "very_weak",
|
|
||||||
minDiversity: 0,
|
|
||||||
minLength: 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
value: "weak",
|
|
||||||
minDiversity: 2,
|
|
||||||
minLength: 10,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
value: "medium",
|
|
||||||
minDiversity: 3,
|
|
||||||
minLength: 12,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
value: "strong",
|
|
||||||
minDiversity: 4,
|
|
||||||
minLength: 14,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
registerRes: RequestState<LoginResponse>;
|
registerRes: RequestState<LoginResponse>;
|
||||||
|
@ -71,8 +48,8 @@ export class Signup extends Component<any, State> {
|
||||||
private audio?: HTMLAudioElement;
|
private audio?: HTMLAudioElement;
|
||||||
|
|
||||||
state: State = {
|
state: State = {
|
||||||
registerRes: { state: "empty" },
|
registerRes: EMPTY_REQUEST,
|
||||||
captchaRes: { state: "empty" },
|
captchaRes: EMPTY_REQUEST,
|
||||||
form: {
|
form: {
|
||||||
show_nsfw: false,
|
show_nsfw: false,
|
||||||
},
|
},
|
||||||
|
@ -93,13 +70,13 @@ export class Signup extends Component<any, State> {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchCaptcha() {
|
async fetchCaptcha() {
|
||||||
this.setState({ captchaRes: { state: "loading" } });
|
this.setState({ captchaRes: LOADING_REQUEST });
|
||||||
this.setState({
|
this.setState({
|
||||||
captchaRes: await HttpService.client.getCaptcha({}),
|
captchaRes: await HttpService.client.getCaptcha(),
|
||||||
});
|
});
|
||||||
|
|
||||||
this.setState(s => {
|
this.setState(s => {
|
||||||
if (s.captchaRes.state == "success") {
|
if (s.captchaRes.state === "success") {
|
||||||
s.form.captcha_uuid = s.captchaRes.data.ok?.uuid;
|
s.form.captcha_uuid = s.captchaRes.data.ok?.uuid;
|
||||||
}
|
}
|
||||||
return s;
|
return s;
|
||||||
|
@ -113,12 +90,12 @@ export class Signup extends Component<any, State> {
|
||||||
|
|
||||||
titleName(siteView: SiteView): string {
|
titleName(siteView: SiteView): string {
|
||||||
return I18NextService.i18n.t(
|
return I18NextService.i18n.t(
|
||||||
siteView.local_site.private_instance ? "apply_to_join" : "sign_up"
|
siteView.local_site.private_instance ? "apply_to_join" : "sign_up",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
get isLemmyMl(): boolean {
|
get isLemmyMl(): boolean {
|
||||||
return isBrowser() && window.location.hostname == "lemmy.ml";
|
return isBrowser() && window.location.hostname === "lemmy.ml";
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
@ -219,57 +196,28 @@ export class Signup extends Component<any, State> {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mb-3 row">
|
<div className="mb-3">
|
||||||
<label
|
<PasswordInput
|
||||||
className="col-sm-2 col-form-label"
|
id="register-password"
|
||||||
htmlFor="register-password"
|
value={this.state.form.password}
|
||||||
>
|
onInput={linkEvent(this, this.handleRegisterPasswordChange)}
|
||||||
{I18NextService.i18n.t("password")}
|
showStrength
|
||||||
</label>
|
label={I18NextService.i18n.t("password")}
|
||||||
<div className="col-sm-10">
|
isNew
|
||||||
<input
|
/>
|
||||||
type="password"
|
|
||||||
id="register-password"
|
|
||||||
value={this.state.form.password}
|
|
||||||
autoComplete="new-password"
|
|
||||||
onInput={linkEvent(this, this.handleRegisterPasswordChange)}
|
|
||||||
minLength={10}
|
|
||||||
maxLength={60}
|
|
||||||
className="form-control"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
{this.state.form.password && (
|
|
||||||
<div className={this.passwordColorClass}>
|
|
||||||
{I18NextService.i18n.t(
|
|
||||||
this.passwordStrength as NoOptionI18nKeys
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mb-3 row">
|
<div className="mb-3">
|
||||||
<label
|
<PasswordInput
|
||||||
className="col-sm-2 col-form-label"
|
id="register-verify-password"
|
||||||
htmlFor="register-verify-password"
|
value={this.state.form.password_verify}
|
||||||
>
|
onInput={linkEvent(this, this.handleRegisterPasswordVerifyChange)}
|
||||||
{I18NextService.i18n.t("verify_password")}
|
label={I18NextService.i18n.t("verify_password")}
|
||||||
</label>
|
isNew
|
||||||
<div className="col-sm-10">
|
/>
|
||||||
<input
|
|
||||||
type="password"
|
|
||||||
id="register-verify-password"
|
|
||||||
value={this.state.form.password_verify}
|
|
||||||
autoComplete="new-password"
|
|
||||||
onInput={linkEvent(this, this.handleRegisterPasswordVerifyChange)}
|
|
||||||
maxLength={60}
|
|
||||||
className="form-control"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{siteView.local_site.registration_mode == "RequireApplication" && (
|
{siteView.local_site.registration_mode === "RequireApplication" && (
|
||||||
<>
|
<>
|
||||||
<div className="mb-3 row">
|
<div className="mb-3 row">
|
||||||
<div className="offset-sm-2 col-sm-10">
|
<div className="offset-sm-2 col-sm-10">
|
||||||
|
@ -281,7 +229,7 @@ export class Signup extends Component<any, State> {
|
||||||
<div
|
<div
|
||||||
className="md-div"
|
className="md-div"
|
||||||
dangerouslySetInnerHTML={mdToHtml(
|
dangerouslySetInnerHTML={mdToHtml(
|
||||||
siteView.local_site.application_question
|
siteView.local_site.application_question,
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
@ -337,7 +285,7 @@ export class Signup extends Component<any, State> {
|
||||||
<div className="mb-3 row">
|
<div className="mb-3 row">
|
||||||
<div className="col-sm-10">
|
<div className="col-sm-10">
|
||||||
<button type="submit" className="btn btn-secondary">
|
<button type="submit" className="btn btn-secondary">
|
||||||
{this.state.registerRes.state == "loading" ? (
|
{this.state.registerRes.state === "loading" ? (
|
||||||
<Spinner />
|
<Spinner />
|
||||||
) : (
|
) : (
|
||||||
this.titleName(siteView)
|
this.titleName(siteView)
|
||||||
|
@ -379,7 +327,7 @@ export class Signup extends Component<any, State> {
|
||||||
value={this.state.form.captcha_answer}
|
value={this.state.form.captcha_answer}
|
||||||
onInput={linkEvent(
|
onInput={linkEvent(
|
||||||
this,
|
this,
|
||||||
this.handleRegisterCaptchaAnswerChange
|
this.handleRegisterCaptchaAnswerChange,
|
||||||
)}
|
)}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
|
@ -420,25 +368,6 @@ export class Signup extends Component<any, State> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
get passwordStrength(): string | undefined {
|
|
||||||
const password = this.state.form.password;
|
|
||||||
return password
|
|
||||||
? passwordStrength(password, passwordStrengthOptions).value
|
|
||||||
: undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
get passwordColorClass(): string {
|
|
||||||
const strength = this.passwordStrength;
|
|
||||||
|
|
||||||
if (strength && ["weak", "medium"].includes(strength)) {
|
|
||||||
return "text-warning";
|
|
||||||
} else if (strength == "strong") {
|
|
||||||
return "text-success";
|
|
||||||
} else {
|
|
||||||
return "text-danger";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async handleRegisterSubmit(i: Signup, event: any) {
|
async handleRegisterSubmit(i: Signup, event: any) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const {
|
const {
|
||||||
|
@ -453,7 +382,7 @@ export class Signup extends Component<any, State> {
|
||||||
username,
|
username,
|
||||||
} = i.state.form;
|
} = i.state.form;
|
||||||
if (username && password && password_verify) {
|
if (username && password && password_verify) {
|
||||||
i.setState({ registerRes: { state: "loading" } });
|
i.setState({ registerRes: LOADING_REQUEST });
|
||||||
|
|
||||||
const registerRes = await HttpService.client.register({
|
const registerRes = await HttpService.client.register({
|
||||||
username,
|
username,
|
||||||
|
@ -469,7 +398,7 @@ export class Signup extends Component<any, State> {
|
||||||
switch (registerRes.state) {
|
switch (registerRes.state) {
|
||||||
case "failed": {
|
case "failed": {
|
||||||
toast(registerRes.msg, "danger");
|
toast(registerRes.msg, "danger");
|
||||||
i.setState({ registerRes: { state: "empty" } });
|
i.setState({ registerRes: EMPTY_REQUEST });
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -482,7 +411,7 @@ export class Signup extends Component<any, State> {
|
||||||
res: data,
|
res: data,
|
||||||
});
|
});
|
||||||
|
|
||||||
const site = await HttpService.client.getSite({ auth: myAuth() });
|
const site = await HttpService.client.getSite();
|
||||||
|
|
||||||
if (site.state === "success") {
|
if (site.state === "success") {
|
||||||
UserService.Instance.myUserInfo = site.data.my_user;
|
UserService.Instance.myUserInfo = site.data.my_user;
|
||||||
|
@ -511,7 +440,7 @@ export class Signup extends Component<any, State> {
|
||||||
|
|
||||||
handleRegisterEmailChange(i: Signup, event: any) {
|
handleRegisterEmailChange(i: Signup, event: any) {
|
||||||
i.state.form.email = event.target.value;
|
i.state.form.email = event.target.value;
|
||||||
if (i.state.form.email == "") {
|
if (i.state.form.email === "") {
|
||||||
i.state.form.email = undefined;
|
i.state.form.email = undefined;
|
||||||
}
|
}
|
||||||
i.setState(i.state);
|
i.setState(i.state);
|
||||||
|
@ -556,7 +485,7 @@ export class Signup extends Component<any, State> {
|
||||||
// This was a bad bug, it should only build the new audio on a new file.
|
// This was a bad bug, it should only build the new audio on a new file.
|
||||||
// Replays would stop prematurely if this was rebuilt every time.
|
// Replays would stop prematurely if this was rebuilt every time.
|
||||||
|
|
||||||
if (i.state.captchaRes.state == "success" && i.state.captchaRes.data.ok) {
|
if (i.state.captchaRes.state === "success" && i.state.captchaRes.data.ok) {
|
||||||
const captchaRes = i.state.captchaRes.data.ok;
|
const captchaRes = i.state.captchaRes.data.ok;
|
||||||
if (!i.audio) {
|
if (!i.audio) {
|
||||||
const base64 = `data:audio/wav;base64,${captchaRes.wav}`;
|
const base64 = `data:audio/wav;base64,${captchaRes.wav}`;
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import { myAuthRequired } from "@utils/app";
|
|
||||||
import { capitalizeFirstLetter, validInstanceTLD } from "@utils/helpers";
|
import { capitalizeFirstLetter, validInstanceTLD } from "@utils/helpers";
|
||||||
import {
|
import {
|
||||||
Component,
|
Component,
|
||||||
|
@ -7,6 +6,7 @@ import {
|
||||||
InfernoNode,
|
InfernoNode,
|
||||||
linkEvent,
|
linkEvent,
|
||||||
} from "inferno";
|
} from "inferno";
|
||||||
|
import { Prompt } from "inferno-router";
|
||||||
import {
|
import {
|
||||||
CreateSite,
|
CreateSite,
|
||||||
EditSite,
|
EditSite,
|
||||||
|
@ -21,7 +21,6 @@ import { ImageUploadForm } from "../common/image-upload-form";
|
||||||
import { LanguageSelect } from "../common/language-select";
|
import { LanguageSelect } from "../common/language-select";
|
||||||
import { ListingTypeSelect } from "../common/listing-type-select";
|
import { ListingTypeSelect } from "../common/listing-type-select";
|
||||||
import { MarkdownTextArea } from "../common/markdown-textarea";
|
import { MarkdownTextArea } from "../common/markdown-textarea";
|
||||||
import NavigationPrompt from "../common/navigation-prompt";
|
|
||||||
|
|
||||||
interface SiteFormProps {
|
interface SiteFormProps {
|
||||||
blockedInstances?: Instance[];
|
blockedInstances?: Instance[];
|
||||||
|
@ -85,7 +84,6 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
|
||||||
captcha_difficulty: ls.captcha_difficulty,
|
captcha_difficulty: ls.captcha_difficulty,
|
||||||
allowed_instances: this.props.allowedInstances?.map(i => i.domain),
|
allowed_instances: this.props.allowedInstances?.map(i => i.domain),
|
||||||
blocked_instances: this.props.blockedInstances?.map(i => i.domain),
|
blocked_instances: this.props.blockedInstances?.map(i => i.domain),
|
||||||
auth: "TODO",
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,7 +121,8 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
|
||||||
className="site-form"
|
className="site-form"
|
||||||
onSubmit={linkEvent(this, this.handleSaveSiteSubmit)}
|
onSubmit={linkEvent(this, this.handleSaveSiteSubmit)}
|
||||||
>
|
>
|
||||||
<NavigationPrompt
|
<Prompt
|
||||||
|
message={I18NextService.i18n.t("block_leaving")}
|
||||||
when={
|
when={
|
||||||
!this.props.loading &&
|
!this.props.loading &&
|
||||||
!siteSetup &&
|
!siteSetup &&
|
||||||
|
@ -292,7 +291,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{this.state.siteForm.registration_mode == "RequireApplication" && (
|
{this.state.siteForm.registration_mode === "RequireApplication" && (
|
||||||
<div className="mb-3 row">
|
<div className="mb-3 row">
|
||||||
<label className="col-12 col-form-label">
|
<label className="col-12 col-form-label">
|
||||||
{I18NextService.i18n.t("application_questionnaire")}
|
{I18NextService.i18n.t("application_questionnaire")}
|
||||||
|
@ -318,7 +317,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
|
||||||
checked={this.state.siteForm.community_creation_admin_only}
|
checked={this.state.siteForm.community_creation_admin_only}
|
||||||
onChange={linkEvent(
|
onChange={linkEvent(
|
||||||
this,
|
this,
|
||||||
this.handleSiteCommunityCreationAdminOnly
|
this.handleSiteCommunityCreationAdminOnly,
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<label
|
<label
|
||||||
|
@ -340,7 +339,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
|
||||||
checked={this.state.siteForm.require_email_verification}
|
checked={this.state.siteForm.require_email_verification}
|
||||||
onChange={linkEvent(
|
onChange={linkEvent(
|
||||||
this,
|
this,
|
||||||
this.handleSiteRequireEmailVerification
|
this.handleSiteRequireEmailVerification,
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<label
|
<label
|
||||||
|
@ -362,7 +361,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
|
||||||
checked={this.state.siteForm.application_email_admins}
|
checked={this.state.siteForm.application_email_admins}
|
||||||
onChange={linkEvent(
|
onChange={linkEvent(
|
||||||
this,
|
this,
|
||||||
this.handleSiteApplicationEmailAdmins
|
this.handleSiteApplicationEmailAdmins,
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<label
|
<label
|
||||||
|
@ -410,6 +409,9 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
|
||||||
<option value="browser">
|
<option value="browser">
|
||||||
{I18NextService.i18n.t("browser_default")}
|
{I18NextService.i18n.t("browser_default")}
|
||||||
</option>
|
</option>
|
||||||
|
<option value="browser-compact">
|
||||||
|
{I18NextService.i18n.t("browser_default_compact")}
|
||||||
|
</option>
|
||||||
{this.props.themeList?.map(theme => (
|
{this.props.themeList?.map(theme => (
|
||||||
<option key={theme} value={theme}>
|
<option key={theme} value={theme}>
|
||||||
{theme}
|
{theme}
|
||||||
|
@ -627,7 +629,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(
|
componentDidUpdate(
|
||||||
prevProps: Readonly<{ children?: InfernoNode } & SiteFormProps>
|
prevProps: Readonly<{ children?: InfernoNode } & SiteFormProps>,
|
||||||
) {
|
) {
|
||||||
if (
|
if (
|
||||||
!(
|
!(
|
||||||
|
@ -690,7 +692,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
|
||||||
className="btn btn-sm bg-danger"
|
className="btn btn-sm bg-danger"
|
||||||
onClick={linkEvent(
|
onClick={linkEvent(
|
||||||
{ key, instance },
|
{ key, instance },
|
||||||
this.handleRemoveInstance
|
this.handleRemoveInstance,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Icon
|
<Icon
|
||||||
|
@ -718,7 +720,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
|
||||||
|
|
||||||
handleInstanceEnterPress(
|
handleInstanceEnterPress(
|
||||||
key: InstanceKey,
|
key: InstanceKey,
|
||||||
event: InfernoKeyboardEvent<HTMLInputElement>
|
event: InfernoKeyboardEvent<HTMLInputElement>,
|
||||||
) {
|
) {
|
||||||
if (event.code.toLowerCase() === "enter") {
|
if (event.code.toLowerCase() === "enter") {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
@ -729,8 +731,6 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
|
||||||
|
|
||||||
handleSaveSiteSubmit(i: SiteForm, event: any) {
|
handleSaveSiteSubmit(i: SiteForm, event: any) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const auth = myAuthRequired();
|
|
||||||
i.setState(s => ((s.siteForm.auth = auth), s));
|
|
||||||
i.setState({ submitted: true });
|
i.setState({ submitted: true });
|
||||||
|
|
||||||
const stateSiteForm = i.state.siteForm;
|
const stateSiteForm = i.state.siteForm;
|
||||||
|
@ -784,7 +784,6 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
|
||||||
allowed_instances: stateSiteForm.allowed_instances,
|
allowed_instances: stateSiteForm.allowed_instances,
|
||||||
blocked_instances: stateSiteForm.blocked_instances,
|
blocked_instances: stateSiteForm.blocked_instances,
|
||||||
discussion_languages: stateSiteForm.discussion_languages,
|
discussion_languages: stateSiteForm.discussion_languages,
|
||||||
auth,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -859,7 +858,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
|
||||||
handleDeleteTaglineClick(
|
handleDeleteTaglineClick(
|
||||||
i: SiteForm,
|
i: SiteForm,
|
||||||
index: number,
|
index: number,
|
||||||
event: InfernoMouseEvent<HTMLButtonElement>
|
event: InfernoMouseEvent<HTMLButtonElement>,
|
||||||
) {
|
) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const taglines = i.state.siteForm.taglines;
|
const taglines = i.state.siteForm.taglines;
|
||||||
|
@ -874,7 +873,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
|
||||||
|
|
||||||
handleAddTaglineClick(
|
handleAddTaglineClick(
|
||||||
i: SiteForm,
|
i: SiteForm,
|
||||||
event: InfernoMouseEvent<HTMLButtonElement>
|
event: InfernoMouseEvent<HTMLButtonElement>,
|
||||||
) {
|
) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (!i.state.siteForm.taglines) {
|
if (!i.state.siteForm.taglines) {
|
||||||
|
@ -965,7 +964,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
|
||||||
|
|
||||||
handleSiteActorNameMaxLength(i: SiteForm, event: any) {
|
handleSiteActorNameMaxLength(i: SiteForm, event: any) {
|
||||||
i.setState(
|
i.setState(
|
||||||
s => ((s.siteForm.actor_name_max_length = Number(event.target.value)), s)
|
s => ((s.siteForm.actor_name_max_length = Number(event.target.value)), s),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import classNames from "classnames";
|
||||||
import { Component, linkEvent } from "inferno";
|
import { Component, linkEvent } from "inferno";
|
||||||
import { PersonView, Site, SiteAggregates } from "lemmy-js-client";
|
import { PersonView, Site, SiteAggregates } from "lemmy-js-client";
|
||||||
import { mdToHtml } from "../../markdown";
|
import { mdToHtml } from "../../markdown";
|
||||||
|
@ -32,10 +33,7 @@ export class SiteSidebar extends Component<SiteSidebarProps, SiteSidebarState> {
|
||||||
return (
|
return (
|
||||||
<div className="site-sidebar accordion">
|
<div className="site-sidebar accordion">
|
||||||
<section id="sidebarInfo" className="card border-secondary mb-3">
|
<section id="sidebarInfo" className="card border-secondary mb-3">
|
||||||
<header
|
<header className="card-header" id="sidebarInfoHeader">
|
||||||
className="card-header d-flex align-items-center"
|
|
||||||
id="sidebarInfoHeader"
|
|
||||||
>
|
|
||||||
{this.siteName()}
|
{this.siteName()}
|
||||||
{!this.state.collapsed && (
|
{!this.state.collapsed && (
|
||||||
<BannerIconHeader banner={this.props.site.banner} />
|
<BannerIconHeader banner={this.props.site.banner} />
|
||||||
|
@ -54,7 +52,7 @@ export class SiteSidebar extends Component<SiteSidebarProps, SiteSidebarState> {
|
||||||
|
|
||||||
siteName() {
|
siteName() {
|
||||||
return (
|
return (
|
||||||
<>
|
<div className={classNames({ "mb-2": !this.state.collapsed })}>
|
||||||
<h5 className="mb-0 d-inline">{this.props.site.name}</h5>
|
<h5 className="mb-0 d-inline">{this.props.site.name}</h5>
|
||||||
{!this.props.isMobile && (
|
{!this.props.isMobile && (
|
||||||
<button
|
<button
|
||||||
|
@ -83,7 +81,7 @@ export class SiteSidebar extends Component<SiteSidebarProps, SiteSidebarState> {
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
import { myAuthRequired } from "@utils/app";
|
|
||||||
import { capitalizeFirstLetter } from "@utils/helpers";
|
import { capitalizeFirstLetter } from "@utils/helpers";
|
||||||
import { Component, InfernoMouseEvent, linkEvent } from "inferno";
|
import { Component, InfernoMouseEvent, linkEvent } from "inferno";
|
||||||
import { EditSite, Tagline } from "lemmy-js-client";
|
import { EditSite, Tagline } from "lemmy-js-client";
|
||||||
import { I18NextService } from "../../services";
|
import { I18NextService } from "../../services";
|
||||||
import { HtmlTags } from "../common/html-tags";
|
|
||||||
import { Icon, Spinner } from "../common/icon";
|
import { Icon, Spinner } from "../common/icon";
|
||||||
import { MarkdownTextArea } from "../common/markdown-textarea";
|
import { MarkdownTextArea } from "../common/markdown-textarea";
|
||||||
|
|
||||||
|
@ -26,17 +24,10 @@ export class TaglineForm extends Component<TaglineFormProps, TaglineFormState> {
|
||||||
constructor(props: any, context: any) {
|
constructor(props: any, context: any) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
}
|
}
|
||||||
get documentTitle(): string {
|
|
||||||
return I18NextService.i18n.t("taglines");
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className="tagline-form col-12">
|
<div className="tagline-form col-12">
|
||||||
<HtmlTags
|
|
||||||
title={this.documentTitle}
|
|
||||||
path={this.context.router.route.match.url}
|
|
||||||
/>
|
|
||||||
<h1 className="h4 mb-4">{I18NextService.i18n.t("taglines")}</h1>
|
<h1 className="h4 mb-4">{I18NextService.i18n.t("taglines")}</h1>
|
||||||
<div className="table-responsive col-12">
|
<div className="table-responsive col-12">
|
||||||
<table id="taglines_table" className="table table-sm table-hover">
|
<table id="taglines_table" className="table table-sm table-hover">
|
||||||
|
@ -48,7 +39,7 @@ export class TaglineForm extends Component<TaglineFormProps, TaglineFormState> {
|
||||||
{this.state.taglines.map((cv, index) => (
|
{this.state.taglines.map((cv, index) => (
|
||||||
<tr key={index}>
|
<tr key={index}>
|
||||||
<td>
|
<td>
|
||||||
{this.state.editingRow == index && (
|
{this.state.editingRow === index && (
|
||||||
<MarkdownTextArea
|
<MarkdownTextArea
|
||||||
initialContent={cv}
|
initialContent={cv}
|
||||||
onContentChange={s =>
|
onContentChange={s =>
|
||||||
|
@ -59,14 +50,14 @@ export class TaglineForm extends Component<TaglineFormProps, TaglineFormState> {
|
||||||
siteLanguages={[]}
|
siteLanguages={[]}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{this.state.editingRow != index && <div>{cv}</div>}
|
{this.state.editingRow !== index && <div>{cv}</div>}
|
||||||
</td>
|
</td>
|
||||||
<td className="text-right">
|
<td className="text-right">
|
||||||
<button
|
<button
|
||||||
className="btn btn-link btn-animate text-muted"
|
className="btn btn-link btn-animate text-muted"
|
||||||
onClick={linkEvent(
|
onClick={linkEvent(
|
||||||
{ i: this, index: index },
|
{ i: this, index: index },
|
||||||
this.handleEditTaglineClick
|
this.handleEditTaglineClick,
|
||||||
)}
|
)}
|
||||||
data-tippy-content={I18NextService.i18n.t("edit")}
|
data-tippy-content={I18NextService.i18n.t("edit")}
|
||||||
aria-label={I18NextService.i18n.t("edit")}
|
aria-label={I18NextService.i18n.t("edit")}
|
||||||
|
@ -78,7 +69,7 @@ export class TaglineForm extends Component<TaglineFormProps, TaglineFormState> {
|
||||||
className="btn btn-link btn-animate text-muted"
|
className="btn btn-link btn-animate text-muted"
|
||||||
onClick={linkEvent(
|
onClick={linkEvent(
|
||||||
{ i: this, index: index },
|
{ i: this, index: index },
|
||||||
this.handleDeleteTaglineClick
|
this.handleDeleteTaglineClick,
|
||||||
)}
|
)}
|
||||||
data-tippy-content={I18NextService.i18n.t("delete")}
|
data-tippy-content={I18NextService.i18n.t("delete")}
|
||||||
aria-label={I18NextService.i18n.t("delete")}
|
aria-label={I18NextService.i18n.t("delete")}
|
||||||
|
@ -141,7 +132,7 @@ export class TaglineForm extends Component<TaglineFormProps, TaglineFormState> {
|
||||||
|
|
||||||
handleEditTaglineClick(d: { i: TaglineForm; index: number }, event: any) {
|
handleEditTaglineClick(d: { i: TaglineForm; index: number }, event: any) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (d.i.state.editingRow == d.index) {
|
if (d.i.state.editingRow === d.index) {
|
||||||
d.i.setState({ editingRow: undefined });
|
d.i.setState({ editingRow: undefined });
|
||||||
} else {
|
} else {
|
||||||
d.i.setState({ editingRow: d.index });
|
d.i.setState({ editingRow: d.index });
|
||||||
|
@ -151,13 +142,12 @@ export class TaglineForm extends Component<TaglineFormProps, TaglineFormState> {
|
||||||
async handleSaveClick(i: TaglineForm) {
|
async handleSaveClick(i: TaglineForm) {
|
||||||
i.props.onSaveSite({
|
i.props.onSaveSite({
|
||||||
taglines: i.state.taglines,
|
taglines: i.state.taglines,
|
||||||
auth: myAuthRequired(),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
handleAddTaglineClick(
|
handleAddTaglineClick(
|
||||||
i: TaglineForm,
|
i: TaglineForm,
|
||||||
event: InfernoMouseEvent<HTMLButtonElement>
|
event: InfernoMouseEvent<HTMLButtonElement>,
|
||||||
) {
|
) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const newTaglines = [...i.state.taglines];
|
const newTaglines = [...i.state.taglines];
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import {
|
import {
|
||||||
fetchUsers,
|
fetchUsers,
|
||||||
getUpdatedSearchId,
|
getUpdatedSearchId,
|
||||||
myAuth,
|
|
||||||
personToChoice,
|
personToChoice,
|
||||||
setIsoData,
|
setIsoData,
|
||||||
} from "@utils/app";
|
} from "@utils/app";
|
||||||
|
@ -48,7 +47,12 @@ import {
|
||||||
import { fetchLimit } from "../config";
|
import { fetchLimit } from "../config";
|
||||||
import { InitialFetchRequest } from "../interfaces";
|
import { InitialFetchRequest } from "../interfaces";
|
||||||
import { FirstLoadService, I18NextService } from "../services";
|
import { FirstLoadService, I18NextService } from "../services";
|
||||||
import { HttpService, RequestState } from "../services/HttpService";
|
import {
|
||||||
|
EMPTY_REQUEST,
|
||||||
|
HttpService,
|
||||||
|
LOADING_REQUEST,
|
||||||
|
RequestState,
|
||||||
|
} from "../services/HttpService";
|
||||||
import { HtmlTags } from "./common/html-tags";
|
import { HtmlTags } from "./common/html-tags";
|
||||||
import { Icon, Spinner } from "./common/icon";
|
import { Icon, Spinner } from "./common/icon";
|
||||||
import { MomentTime } from "./common/moment-time";
|
import { MomentTime } from "./common/moment-time";
|
||||||
|
@ -121,7 +125,7 @@ function getActionFromString(action?: string): ModlogActionType {
|
||||||
const getModlogActionMapper =
|
const getModlogActionMapper =
|
||||||
(
|
(
|
||||||
actionType: ModlogActionType,
|
actionType: ModlogActionType,
|
||||||
getAction: (view: View) => { id: number; when_: string }
|
getAction: (view: View) => { id: number; when_: string },
|
||||||
) =>
|
) =>
|
||||||
(view: View & { moderator?: Person; admin?: Person }): ModlogType => {
|
(view: View & { moderator?: Person; admin?: Person }): ModlogType => {
|
||||||
const { id, when_ } = getAction(view);
|
const { id, when_ } = getAction(view);
|
||||||
|
@ -155,111 +159,111 @@ function buildCombined({
|
||||||
.map(
|
.map(
|
||||||
getModlogActionMapper(
|
getModlogActionMapper(
|
||||||
"ModRemovePost",
|
"ModRemovePost",
|
||||||
({ mod_remove_post }: ModRemovePostView) => mod_remove_post
|
({ mod_remove_post }: ModRemovePostView) => mod_remove_post,
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
.concat(
|
.concat(
|
||||||
locked_posts.map(
|
locked_posts.map(
|
||||||
getModlogActionMapper(
|
getModlogActionMapper(
|
||||||
"ModLockPost",
|
"ModLockPost",
|
||||||
({ mod_lock_post }: ModLockPostView) => mod_lock_post
|
({ mod_lock_post }: ModLockPostView) => mod_lock_post,
|
||||||
)
|
),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
.concat(
|
.concat(
|
||||||
featured_posts.map(
|
featured_posts.map(
|
||||||
getModlogActionMapper(
|
getModlogActionMapper(
|
||||||
"ModFeaturePost",
|
"ModFeaturePost",
|
||||||
({ mod_feature_post }: ModFeaturePostView) => mod_feature_post
|
({ mod_feature_post }: ModFeaturePostView) => mod_feature_post,
|
||||||
)
|
),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
.concat(
|
.concat(
|
||||||
removed_comments.map(
|
removed_comments.map(
|
||||||
getModlogActionMapper(
|
getModlogActionMapper(
|
||||||
"ModRemoveComment",
|
"ModRemoveComment",
|
||||||
({ mod_remove_comment }: ModRemoveCommentView) => mod_remove_comment
|
({ mod_remove_comment }: ModRemoveCommentView) => mod_remove_comment,
|
||||||
)
|
),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
.concat(
|
.concat(
|
||||||
removed_communities.map(
|
removed_communities.map(
|
||||||
getModlogActionMapper(
|
getModlogActionMapper(
|
||||||
"ModRemoveCommunity",
|
"ModRemoveCommunity",
|
||||||
({ mod_remove_community }: ModRemoveCommunityView) =>
|
({ mod_remove_community }: ModRemoveCommunityView) =>
|
||||||
mod_remove_community
|
mod_remove_community,
|
||||||
)
|
),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
.concat(
|
.concat(
|
||||||
banned_from_community.map(
|
banned_from_community.map(
|
||||||
getModlogActionMapper(
|
getModlogActionMapper(
|
||||||
"ModBanFromCommunity",
|
"ModBanFromCommunity",
|
||||||
({ mod_ban_from_community }: ModBanFromCommunityView) =>
|
({ mod_ban_from_community }: ModBanFromCommunityView) =>
|
||||||
mod_ban_from_community
|
mod_ban_from_community,
|
||||||
)
|
),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
.concat(
|
.concat(
|
||||||
added_to_community.map(
|
added_to_community.map(
|
||||||
getModlogActionMapper(
|
getModlogActionMapper(
|
||||||
"ModAddCommunity",
|
"ModAddCommunity",
|
||||||
({ mod_add_community }: ModAddCommunityView) => mod_add_community
|
({ mod_add_community }: ModAddCommunityView) => mod_add_community,
|
||||||
)
|
),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
.concat(
|
.concat(
|
||||||
transferred_to_community.map(
|
transferred_to_community.map(
|
||||||
getModlogActionMapper(
|
getModlogActionMapper(
|
||||||
"ModTransferCommunity",
|
"ModTransferCommunity",
|
||||||
({ mod_transfer_community }: ModTransferCommunityView) =>
|
({ mod_transfer_community }: ModTransferCommunityView) =>
|
||||||
mod_transfer_community
|
mod_transfer_community,
|
||||||
)
|
),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
.concat(
|
.concat(
|
||||||
added.map(
|
added.map(
|
||||||
getModlogActionMapper("ModAdd", ({ mod_add }: ModAddView) => mod_add)
|
getModlogActionMapper("ModAdd", ({ mod_add }: ModAddView) => mod_add),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
.concat(
|
.concat(
|
||||||
banned.map(
|
banned.map(
|
||||||
getModlogActionMapper("ModBan", ({ mod_ban }: ModBanView) => mod_ban)
|
getModlogActionMapper("ModBan", ({ mod_ban }: ModBanView) => mod_ban),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
.concat(
|
.concat(
|
||||||
admin_purged_persons.map(
|
admin_purged_persons.map(
|
||||||
getModlogActionMapper(
|
getModlogActionMapper(
|
||||||
"AdminPurgePerson",
|
"AdminPurgePerson",
|
||||||
({ admin_purge_person }: AdminPurgePersonView) => admin_purge_person
|
({ admin_purge_person }: AdminPurgePersonView) => admin_purge_person,
|
||||||
)
|
),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
.concat(
|
.concat(
|
||||||
admin_purged_communities.map(
|
admin_purged_communities.map(
|
||||||
getModlogActionMapper(
|
getModlogActionMapper(
|
||||||
"AdminPurgeCommunity",
|
"AdminPurgeCommunity",
|
||||||
({ admin_purge_community }: AdminPurgeCommunityView) =>
|
({ admin_purge_community }: AdminPurgeCommunityView) =>
|
||||||
admin_purge_community
|
admin_purge_community,
|
||||||
)
|
),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
.concat(
|
.concat(
|
||||||
admin_purged_posts.map(
|
admin_purged_posts.map(
|
||||||
getModlogActionMapper(
|
getModlogActionMapper(
|
||||||
"AdminPurgePost",
|
"AdminPurgePost",
|
||||||
({ admin_purge_post }: AdminPurgePostView) => admin_purge_post
|
({ admin_purge_post }: AdminPurgePostView) => admin_purge_post,
|
||||||
)
|
),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
.concat(
|
.concat(
|
||||||
admin_purged_comments.map(
|
admin_purged_comments.map(
|
||||||
getModlogActionMapper(
|
getModlogActionMapper(
|
||||||
"AdminPurgeComment",
|
"AdminPurgeComment",
|
||||||
({ admin_purge_comment }: AdminPurgeCommentView) =>
|
({ admin_purge_comment }: AdminPurgeCommentView) =>
|
||||||
admin_purge_comment
|
admin_purge_comment,
|
||||||
)
|
),
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Sort them by time
|
// Sort them by time
|
||||||
|
@ -312,6 +316,7 @@ function renderModlogType({ type_, view }: ModlogType) {
|
||||||
const {
|
const {
|
||||||
mod_feature_post: { featured, is_featured_community },
|
mod_feature_post: { featured, is_featured_community },
|
||||||
post: { id, name },
|
post: { id, name },
|
||||||
|
community,
|
||||||
} = view as ModFeaturePostView;
|
} = view as ModFeaturePostView;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -320,7 +325,12 @@ function renderModlogType({ type_, view }: ModlogType) {
|
||||||
<span>
|
<span>
|
||||||
Post <Link to={`/post/${id}`}>{name}</Link>
|
Post <Link to={`/post/${id}`}>{name}</Link>
|
||||||
</span>
|
</span>
|
||||||
<span>{is_featured_community ? " In Community" : " In Local"}</span>
|
<span>
|
||||||
|
{is_featured_community
|
||||||
|
? " in community "
|
||||||
|
: " in Local, from community "}
|
||||||
|
</span>
|
||||||
|
<CommunityLink community={community} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -354,7 +364,7 @@ function renderModlogType({ type_, view }: ModlogType) {
|
||||||
case "ModRemoveCommunity": {
|
case "ModRemoveCommunity": {
|
||||||
const mrco = view as ModRemoveCommunityView;
|
const mrco = view as ModRemoveCommunityView;
|
||||||
const {
|
const {
|
||||||
mod_remove_community: { reason, expires, removed },
|
mod_remove_community: { reason, removed },
|
||||||
community,
|
community,
|
||||||
} = mrco;
|
} = mrco;
|
||||||
|
|
||||||
|
@ -369,11 +379,6 @@ function renderModlogType({ type_, view }: ModlogType) {
|
||||||
<div>reason: {reason}</div>
|
<div>reason: {reason}</div>
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{expires && (
|
|
||||||
<span>
|
|
||||||
<div>expires: {formatPastDate(expires)}</div>
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -532,7 +537,7 @@ function renderModlogType({ type_, view }: ModlogType) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<span>Purged a Post from from </span>
|
<span>Purged a Post from </span>
|
||||||
<CommunityLink community={community} />
|
<CommunityLink community={community} />
|
||||||
{reason && (
|
{reason && (
|
||||||
<span>
|
<span>
|
||||||
|
@ -616,7 +621,7 @@ async function createNewOptions({
|
||||||
|
|
||||||
if (id) {
|
if (id) {
|
||||||
const selectedUser = oldOptions.find(
|
const selectedUser = oldOptions.find(
|
||||||
({ value }) => value === id.toString()
|
({ value }) => value === id.toString(),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (selectedUser) {
|
if (selectedUser) {
|
||||||
|
@ -628,7 +633,7 @@ async function createNewOptions({
|
||||||
newOptions.push(
|
newOptions.push(
|
||||||
...(await fetchUsers(text))
|
...(await fetchUsers(text))
|
||||||
.slice(0, Number(fetchLimit))
|
.slice(0, Number(fetchLimit))
|
||||||
.map<Choice>(personToChoice)
|
.map<Choice>(personToChoice),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -642,8 +647,8 @@ export class Modlog extends Component<
|
||||||
private isoData = setIsoData<ModlogData>(this.context);
|
private isoData = setIsoData<ModlogData>(this.context);
|
||||||
|
|
||||||
state: ModlogState = {
|
state: ModlogState = {
|
||||||
res: { state: "empty" },
|
res: EMPTY_REQUEST,
|
||||||
communityRes: { state: "empty" },
|
communityRes: EMPTY_REQUEST,
|
||||||
loadingModSearch: false,
|
loadingModSearch: false,
|
||||||
loadingUserSearch: false,
|
loadingUserSearch: false,
|
||||||
userSearchOptions: [],
|
userSearchOptions: [],
|
||||||
|
@ -652,7 +657,7 @@ export class Modlog extends Component<
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
props: RouteComponentProps<{ communityId?: string }>,
|
props: RouteComponentProps<{ communityId?: string }>,
|
||||||
context: any
|
context: any,
|
||||||
) {
|
) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
this.handlePageChange = this.handlePageChange.bind(this);
|
this.handlePageChange = this.handlePageChange.bind(this);
|
||||||
|
@ -692,7 +697,7 @@ export class Modlog extends Component<
|
||||||
|
|
||||||
get combined() {
|
get combined() {
|
||||||
const res = this.state.res;
|
const res = this.state.res;
|
||||||
const combined = res.state == "success" ? buildCombined(res.data) : [];
|
const combined = res.state === "success" ? buildCombined(res.data) : [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<tbody>
|
<tbody>
|
||||||
|
@ -717,7 +722,7 @@ export class Modlog extends Component<
|
||||||
|
|
||||||
get amAdminOrMod(): boolean {
|
get amAdminOrMod(): boolean {
|
||||||
const amMod_ =
|
const amMod_ =
|
||||||
this.state.communityRes.state == "success" &&
|
this.state.communityRes.state === "success" &&
|
||||||
amMod(this.state.communityRes.data.moderators);
|
amMod(this.state.communityRes.data.moderators);
|
||||||
return amAdmin() || amMod_;
|
return amAdmin() || amMod_;
|
||||||
}
|
}
|
||||||
|
@ -725,7 +730,7 @@ export class Modlog extends Component<
|
||||||
modOrAdminText(person?: Person): string {
|
modOrAdminText(person?: Person): string {
|
||||||
return person &&
|
return person &&
|
||||||
this.isoData.site_res.admins.some(
|
this.isoData.site_res.admins.some(
|
||||||
({ person: { id } }) => id === person.id
|
({ person: { id } }) => id === person.id,
|
||||||
)
|
)
|
||||||
? I18NextService.i18n.t("admin")
|
? I18NextService.i18n.t("admin")
|
||||||
: I18NextService.i18n.t("mod");
|
: I18NextService.i18n.t("mod");
|
||||||
|
@ -854,7 +859,11 @@ export class Modlog extends Component<
|
||||||
</thead>
|
</thead>
|
||||||
{this.combined}
|
{this.combined}
|
||||||
</table>
|
</table>
|
||||||
<Paginator page={page} onChange={this.handlePageChange} />
|
<Paginator
|
||||||
|
page={page}
|
||||||
|
onChange={this.handlePageChange}
|
||||||
|
nextDisabled={false}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -933,20 +942,19 @@ export class Modlog extends Component<
|
||||||
|
|
||||||
this.props.history.push(
|
this.props.history.push(
|
||||||
`/modlog${communityId ? `/${communityId}` : ""}${getQueryString(
|
`/modlog${communityId ? `/${communityId}` : ""}${getQueryString(
|
||||||
queryParams
|
queryParams,
|
||||||
)}`
|
)}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
await this.refetch();
|
await this.refetch();
|
||||||
}
|
}
|
||||||
|
|
||||||
async refetch() {
|
async refetch() {
|
||||||
const auth = myAuth();
|
|
||||||
const { actionType, page, modId, userId } = getModlogQueryParams();
|
const { actionType, page, modId, userId } = getModlogQueryParams();
|
||||||
const { communityId: urlCommunityId } = this.props.match.params;
|
const { communityId: urlCommunityId } = this.props.match.params;
|
||||||
const communityId = getIdFromString(urlCommunityId);
|
const communityId = getIdFromString(urlCommunityId);
|
||||||
|
|
||||||
this.setState({ res: { state: "loading" } });
|
this.setState({ res: LOADING_REQUEST });
|
||||||
this.setState({
|
this.setState({
|
||||||
res: await HttpService.client.getModlog({
|
res: await HttpService.client.getModlog({
|
||||||
community_id: communityId,
|
community_id: communityId,
|
||||||
|
@ -958,16 +966,14 @@ export class Modlog extends Component<
|
||||||
.hide_modlog_mod_names
|
.hide_modlog_mod_names
|
||||||
? modId ?? undefined
|
? modId ?? undefined
|
||||||
: undefined,
|
: undefined,
|
||||||
auth,
|
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (communityId) {
|
if (communityId) {
|
||||||
this.setState({ communityRes: { state: "loading" } });
|
this.setState({ communityRes: LOADING_REQUEST });
|
||||||
this.setState({
|
this.setState({
|
||||||
communityRes: await HttpService.client.getCommunity({
|
communityRes: await HttpService.client.getCommunity({
|
||||||
id: communityId,
|
id: communityId,
|
||||||
auth,
|
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -977,7 +983,6 @@ export class Modlog extends Component<
|
||||||
client,
|
client,
|
||||||
path,
|
path,
|
||||||
query: { modId: urlModId, page, userId: urlUserId, actionType },
|
query: { modId: urlModId, page, userId: urlUserId, actionType },
|
||||||
auth,
|
|
||||||
site,
|
site,
|
||||||
}: InitialFetchRequest<QueryParams<ModlogProps>>): Promise<ModlogData> {
|
}: InitialFetchRequest<QueryParams<ModlogProps>>): Promise<ModlogData> {
|
||||||
const pathSplit = path.split("/");
|
const pathSplit = path.split("/");
|
||||||
|
@ -994,43 +999,33 @@ export class Modlog extends Component<
|
||||||
type_: getActionFromString(actionType),
|
type_: getActionFromString(actionType),
|
||||||
mod_person_id: modId,
|
mod_person_id: modId,
|
||||||
other_person_id: userId,
|
other_person_id: userId,
|
||||||
auth,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let communityResponse: RequestState<GetCommunityResponse> = {
|
let communityResponse: RequestState<GetCommunityResponse> = EMPTY_REQUEST;
|
||||||
state: "empty",
|
|
||||||
};
|
|
||||||
|
|
||||||
if (communityId) {
|
if (communityId) {
|
||||||
const communityForm: GetCommunity = {
|
const communityForm: GetCommunity = {
|
||||||
id: communityId,
|
id: communityId,
|
||||||
auth,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
communityResponse = await client.getCommunity(communityForm);
|
communityResponse = await client.getCommunity(communityForm);
|
||||||
}
|
}
|
||||||
|
|
||||||
let modUserResponse: RequestState<GetPersonDetailsResponse> = {
|
let modUserResponse: RequestState<GetPersonDetailsResponse> = EMPTY_REQUEST;
|
||||||
state: "empty",
|
|
||||||
};
|
|
||||||
|
|
||||||
if (modId) {
|
if (modId) {
|
||||||
const getPersonForm: GetPersonDetails = {
|
const getPersonForm: GetPersonDetails = {
|
||||||
person_id: modId,
|
person_id: modId,
|
||||||
auth,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
modUserResponse = await client.getPersonDetails(getPersonForm);
|
modUserResponse = await client.getPersonDetails(getPersonForm);
|
||||||
}
|
}
|
||||||
|
|
||||||
let userResponse: RequestState<GetPersonDetailsResponse> = {
|
let userResponse: RequestState<GetPersonDetailsResponse> = EMPTY_REQUEST;
|
||||||
state: "empty",
|
|
||||||
};
|
|
||||||
|
|
||||||
if (userId) {
|
if (userId) {
|
||||||
const getPersonForm: GetPersonDetails = {
|
const getPersonForm: GetPersonDetails = {
|
||||||
person_id: userId,
|
person_id: userId,
|
||||||
auth,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
userResponse = await client.getPersonDetails(getPersonForm);
|
userResponse = await client.getPersonDetails(getPersonForm);
|
||||||
|
|
|
@ -7,7 +7,6 @@ import {
|
||||||
enableDownvotes,
|
enableDownvotes,
|
||||||
getCommentParentId,
|
getCommentParentId,
|
||||||
myAuth,
|
myAuth,
|
||||||
myAuthRequired,
|
|
||||||
setIsoData,
|
setIsoData,
|
||||||
updatePersonBlock,
|
updatePersonBlock,
|
||||||
} from "@utils/app";
|
} from "@utils/app";
|
||||||
|
@ -63,7 +62,14 @@ import {
|
||||||
import { fetchLimit, relTags } from "../../config";
|
import { fetchLimit, relTags } from "../../config";
|
||||||
import { CommentViewType, InitialFetchRequest } from "../../interfaces";
|
import { CommentViewType, InitialFetchRequest } from "../../interfaces";
|
||||||
import { FirstLoadService, I18NextService, UserService } from "../../services";
|
import { FirstLoadService, I18NextService, UserService } from "../../services";
|
||||||
import { HttpService, RequestState } from "../../services/HttpService";
|
import { UnreadCounterService } from "../../services";
|
||||||
|
import {
|
||||||
|
EMPTY_REQUEST,
|
||||||
|
EmptyRequestState,
|
||||||
|
HttpService,
|
||||||
|
LOADING_REQUEST,
|
||||||
|
RequestState,
|
||||||
|
} from "../../services/HttpService";
|
||||||
import { toast } from "../../toast";
|
import { toast } from "../../toast";
|
||||||
import { CommentNodes } from "../comment/comment-nodes";
|
import { CommentNodes } from "../comment/comment-nodes";
|
||||||
import { CommentSortSelect } from "../common/comment-sort-select";
|
import { CommentSortSelect } from "../common/comment-sort-select";
|
||||||
|
@ -125,10 +131,10 @@ export class Inbox extends Component<any, InboxState> {
|
||||||
sort: "New",
|
sort: "New",
|
||||||
page: 1,
|
page: 1,
|
||||||
siteRes: this.isoData.site_res,
|
siteRes: this.isoData.site_res,
|
||||||
repliesRes: { state: "empty" },
|
repliesRes: EMPTY_REQUEST,
|
||||||
mentionsRes: { state: "empty" },
|
mentionsRes: EMPTY_REQUEST,
|
||||||
messagesRes: { state: "empty" },
|
messagesRes: EMPTY_REQUEST,
|
||||||
markAllAsReadRes: { state: "empty" },
|
markAllAsReadRes: EMPTY_REQUEST,
|
||||||
finished: new Map(),
|
finished: new Map(),
|
||||||
isIsomorphic: false,
|
isIsomorphic: false,
|
||||||
};
|
};
|
||||||
|
@ -188,20 +194,20 @@ export class Inbox extends Component<any, InboxState> {
|
||||||
const mui = UserService.Instance.myUserInfo;
|
const mui = UserService.Instance.myUserInfo;
|
||||||
return mui
|
return mui
|
||||||
? `@${mui.local_user_view.person.name} ${I18NextService.i18n.t(
|
? `@${mui.local_user_view.person.name} ${I18NextService.i18n.t(
|
||||||
"inbox"
|
"inbox",
|
||||||
)} - ${this.state.siteRes.site_view.site.name}`
|
)} - ${this.state.siteRes.site_view.site.name}`
|
||||||
: "";
|
: "";
|
||||||
}
|
}
|
||||||
|
|
||||||
get hasUnreads(): boolean {
|
get hasUnreads(): boolean {
|
||||||
if (this.state.unreadOrAll == UnreadOrAll.Unread) {
|
if (this.state.unreadOrAll === UnreadOrAll.Unread) {
|
||||||
const { repliesRes, mentionsRes, messagesRes } = this.state;
|
const { repliesRes, mentionsRes, messagesRes } = this.state;
|
||||||
const replyCount =
|
const replyCount =
|
||||||
repliesRes.state == "success" ? repliesRes.data.replies.length : 0;
|
repliesRes.state === "success" ? repliesRes.data.replies.length : 0;
|
||||||
const mentionCount =
|
const mentionCount =
|
||||||
mentionsRes.state == "success" ? mentionsRes.data.mentions.length : 0;
|
mentionsRes.state === "success" ? mentionsRes.data.mentions.length : 0;
|
||||||
const messageCount =
|
const messageCount =
|
||||||
messagesRes.state == "success"
|
messagesRes.state === "success"
|
||||||
? messagesRes.data.private_messages.length
|
? messagesRes.data.private_messages.length
|
||||||
: 0;
|
: 0;
|
||||||
|
|
||||||
|
@ -242,11 +248,11 @@ export class Inbox extends Component<any, InboxState> {
|
||||||
className="btn btn-secondary mb-2 mb-sm-3"
|
className="btn btn-secondary mb-2 mb-sm-3"
|
||||||
onClick={linkEvent(this, this.handleMarkAllAsRead)}
|
onClick={linkEvent(this, this.handleMarkAllAsRead)}
|
||||||
>
|
>
|
||||||
{this.state.markAllAsReadRes.state == "loading" ? (
|
{this.state.markAllAsReadRes.state === "loading" ? (
|
||||||
<Spinner />
|
<Spinner />
|
||||||
) : (
|
) : (
|
||||||
capitalizeFirstLetter(
|
capitalizeFirstLetter(
|
||||||
I18NextService.i18n.t("mark_all_as_read")
|
I18NextService.i18n.t("mark_all_as_read"),
|
||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
|
@ -256,6 +262,7 @@ export class Inbox extends Component<any, InboxState> {
|
||||||
<Paginator
|
<Paginator
|
||||||
page={this.state.page}
|
page={this.state.page}
|
||||||
onChange={this.handlePageChange}
|
onChange={this.handlePageChange}
|
||||||
|
nextDisabled={false}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -445,22 +452,22 @@ export class Inbox extends Component<any, InboxState> {
|
||||||
|
|
||||||
buildCombined(): ReplyType[] {
|
buildCombined(): ReplyType[] {
|
||||||
const replies: ReplyType[] =
|
const replies: ReplyType[] =
|
||||||
this.state.repliesRes.state == "success"
|
this.state.repliesRes.state === "success"
|
||||||
? this.state.repliesRes.data.replies.map(this.replyToReplyType)
|
? this.state.repliesRes.data.replies.map(this.replyToReplyType)
|
||||||
: [];
|
: [];
|
||||||
const mentions: ReplyType[] =
|
const mentions: ReplyType[] =
|
||||||
this.state.mentionsRes.state == "success"
|
this.state.mentionsRes.state === "success"
|
||||||
? this.state.mentionsRes.data.mentions.map(this.mentionToReplyType)
|
? this.state.mentionsRes.data.mentions.map(this.mentionToReplyType)
|
||||||
: [];
|
: [];
|
||||||
const messages: ReplyType[] =
|
const messages: ReplyType[] =
|
||||||
this.state.messagesRes.state == "success"
|
this.state.messagesRes.state === "success"
|
||||||
? this.state.messagesRes.data.private_messages.map(
|
? this.state.messagesRes.data.private_messages.map(
|
||||||
this.messageToReplyType
|
this.messageToReplyType,
|
||||||
)
|
)
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
return [...replies, ...mentions, ...messages].sort((a, b) =>
|
return [...replies, ...mentions, ...messages].sort((a, b) =>
|
||||||
b.published.localeCompare(a.published)
|
b.published.localeCompare(a.published),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -559,9 +566,9 @@ export class Inbox extends Component<any, InboxState> {
|
||||||
|
|
||||||
all() {
|
all() {
|
||||||
if (
|
if (
|
||||||
this.state.repliesRes.state == "loading" ||
|
this.state.repliesRes.state === "loading" ||
|
||||||
this.state.mentionsRes.state == "loading" ||
|
this.state.mentionsRes.state === "loading" ||
|
||||||
this.state.messagesRes.state == "loading"
|
this.state.messagesRes.state === "loading"
|
||||||
) {
|
) {
|
||||||
return (
|
return (
|
||||||
<h1 className="h4">
|
<h1 className="h4">
|
||||||
|
@ -718,78 +725,77 @@ export class Inbox extends Component<any, InboxState> {
|
||||||
|
|
||||||
static async fetchInitialData({
|
static async fetchInitialData({
|
||||||
client,
|
client,
|
||||||
auth,
|
|
||||||
}: InitialFetchRequest): Promise<InboxData> {
|
}: InitialFetchRequest): Promise<InboxData> {
|
||||||
const sort: CommentSortType = "New";
|
const sort: CommentSortType = "New";
|
||||||
|
const empty: EmptyRequestState = EMPTY_REQUEST;
|
||||||
return {
|
let inboxData: InboxData = {
|
||||||
mentionsRes: auth
|
mentionsRes: empty,
|
||||||
? await client.getPersonMentions({
|
messagesRes: empty,
|
||||||
sort,
|
repliesRes: empty,
|
||||||
unread_only: true,
|
|
||||||
page: 1,
|
|
||||||
limit: fetchLimit,
|
|
||||||
auth,
|
|
||||||
})
|
|
||||||
: { state: "empty" },
|
|
||||||
messagesRes: auth
|
|
||||||
? await client.getPrivateMessages({
|
|
||||||
unread_only: true,
|
|
||||||
page: 1,
|
|
||||||
limit: fetchLimit,
|
|
||||||
auth,
|
|
||||||
})
|
|
||||||
: { state: "empty" },
|
|
||||||
repliesRes: auth
|
|
||||||
? await client.getReplies({
|
|
||||||
sort,
|
|
||||||
unread_only: true,
|
|
||||||
page: 1,
|
|
||||||
limit: fetchLimit,
|
|
||||||
auth,
|
|
||||||
})
|
|
||||||
: { state: "empty" },
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (myAuth()) {
|
||||||
|
const [mentionsRes, messagesRes, repliesRes] = await Promise.all([
|
||||||
|
client.getPersonMentions({
|
||||||
|
sort,
|
||||||
|
unread_only: true,
|
||||||
|
page: 1,
|
||||||
|
limit: fetchLimit,
|
||||||
|
}),
|
||||||
|
client.getPrivateMessages({
|
||||||
|
unread_only: true,
|
||||||
|
page: 1,
|
||||||
|
limit: fetchLimit,
|
||||||
|
}),
|
||||||
|
client.getReplies({
|
||||||
|
sort,
|
||||||
|
unread_only: true,
|
||||||
|
page: 1,
|
||||||
|
limit: fetchLimit,
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
inboxData = { mentionsRes, messagesRes, repliesRes };
|
||||||
|
}
|
||||||
|
|
||||||
|
return inboxData;
|
||||||
}
|
}
|
||||||
|
|
||||||
async refetch() {
|
async refetch() {
|
||||||
const sort = this.state.sort;
|
const sort = this.state.sort;
|
||||||
const unread_only = this.state.unreadOrAll == UnreadOrAll.Unread;
|
const unread_only = this.state.unreadOrAll === UnreadOrAll.Unread;
|
||||||
const page = this.state.page;
|
const page = this.state.page;
|
||||||
const limit = fetchLimit;
|
const limit = fetchLimit;
|
||||||
const auth = myAuthRequired();
|
|
||||||
|
|
||||||
this.setState({ repliesRes: { state: "loading" } });
|
this.setState({ repliesRes: LOADING_REQUEST });
|
||||||
this.setState({
|
this.setState({
|
||||||
repliesRes: await HttpService.client.getReplies({
|
repliesRes: await HttpService.client.getReplies({
|
||||||
sort,
|
sort,
|
||||||
unread_only,
|
unread_only,
|
||||||
page,
|
page,
|
||||||
limit,
|
limit,
|
||||||
auth,
|
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
this.setState({ mentionsRes: { state: "loading" } });
|
this.setState({ mentionsRes: LOADING_REQUEST });
|
||||||
this.setState({
|
this.setState({
|
||||||
mentionsRes: await HttpService.client.getPersonMentions({
|
mentionsRes: await HttpService.client.getPersonMentions({
|
||||||
sort,
|
sort,
|
||||||
unread_only,
|
unread_only,
|
||||||
page,
|
page,
|
||||||
limit,
|
limit,
|
||||||
auth,
|
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
this.setState({ messagesRes: { state: "loading" } });
|
this.setState({ messagesRes: LOADING_REQUEST });
|
||||||
this.setState({
|
this.setState({
|
||||||
messagesRes: await HttpService.client.getPrivateMessages({
|
messagesRes: await HttpService.client.getPrivateMessages({
|
||||||
unread_only,
|
unread_only,
|
||||||
page,
|
page,
|
||||||
limit,
|
limit,
|
||||||
auth,
|
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
UnreadCounterService.Instance.updateInboxCounts();
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleSortChange(val: CommentSortType) {
|
async handleSortChange(val: CommentSortType) {
|
||||||
|
@ -798,19 +804,17 @@ export class Inbox extends Component<any, InboxState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleMarkAllAsRead(i: Inbox) {
|
async handleMarkAllAsRead(i: Inbox) {
|
||||||
i.setState({ markAllAsReadRes: { state: "loading" } });
|
i.setState({ markAllAsReadRes: LOADING_REQUEST });
|
||||||
|
|
||||||
i.setState({
|
i.setState({
|
||||||
markAllAsReadRes: await HttpService.client.markAllAsRead({
|
markAllAsReadRes: await HttpService.client.markAllAsRead(),
|
||||||
auth: myAuthRequired(),
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (i.state.markAllAsReadRes.state == "success") {
|
if (i.state.markAllAsReadRes.state === "success") {
|
||||||
i.setState({
|
i.setState({
|
||||||
repliesRes: { state: "empty" },
|
repliesRes: EMPTY_REQUEST,
|
||||||
mentionsRes: { state: "empty" },
|
mentionsRes: EMPTY_REQUEST,
|
||||||
messagesRes: { state: "empty" },
|
messagesRes: EMPTY_REQUEST,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -837,7 +841,7 @@ export class Inbox extends Component<any, InboxState> {
|
||||||
|
|
||||||
async handleBlockPerson(form: BlockPerson) {
|
async handleBlockPerson(form: BlockPerson) {
|
||||||
const blockPersonRes = await HttpService.client.blockPerson(form);
|
const blockPersonRes = await HttpService.client.blockPerson(form);
|
||||||
if (blockPersonRes.state == "success") {
|
if (blockPersonRes.state === "success") {
|
||||||
updatePersonBlock(blockPersonRes.data);
|
updatePersonBlock(blockPersonRes.data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -868,7 +872,7 @@ export class Inbox extends Component<any, InboxState> {
|
||||||
|
|
||||||
async handleDeleteComment(form: DeleteComment) {
|
async handleDeleteComment(form: DeleteComment) {
|
||||||
const res = await HttpService.client.deleteComment(form);
|
const res = await HttpService.client.deleteComment(form);
|
||||||
if (res.state == "success") {
|
if (res.state === "success") {
|
||||||
toast(I18NextService.i18n.t("deleted"));
|
toast(I18NextService.i18n.t("deleted"));
|
||||||
this.findAndUpdateComment(res);
|
this.findAndUpdateComment(res);
|
||||||
}
|
}
|
||||||
|
@ -876,7 +880,7 @@ export class Inbox extends Component<any, InboxState> {
|
||||||
|
|
||||||
async handleRemoveComment(form: RemoveComment) {
|
async handleRemoveComment(form: RemoveComment) {
|
||||||
const res = await HttpService.client.removeComment(form);
|
const res = await HttpService.client.removeComment(form);
|
||||||
if (res.state == "success") {
|
if (res.state === "success") {
|
||||||
toast(I18NextService.i18n.t("remove_comment"));
|
toast(I18NextService.i18n.t("remove_comment"));
|
||||||
this.findAndUpdateComment(res);
|
this.findAndUpdateComment(res);
|
||||||
}
|
}
|
||||||
|
@ -917,12 +921,20 @@ export class Inbox extends Component<any, InboxState> {
|
||||||
|
|
||||||
async handleCommentReplyRead(form: MarkCommentReplyAsRead) {
|
async handleCommentReplyRead(form: MarkCommentReplyAsRead) {
|
||||||
const res = await HttpService.client.markCommentReplyAsRead(form);
|
const res = await HttpService.client.markCommentReplyAsRead(form);
|
||||||
this.findAndUpdateCommentReply(res);
|
if (this.state.unreadOrAll === UnreadOrAll.All) {
|
||||||
|
this.findAndUpdateCommentReply(res);
|
||||||
|
} else {
|
||||||
|
await this.refetch();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handlePersonMentionRead(form: MarkPersonMentionAsRead) {
|
async handlePersonMentionRead(form: MarkPersonMentionAsRead) {
|
||||||
const res = await HttpService.client.markPersonMentionAsRead(form);
|
const res = await HttpService.client.markPersonMentionAsRead(form);
|
||||||
this.findAndUpdateMention(res);
|
if (this.state.unreadOrAll === UnreadOrAll.All) {
|
||||||
|
this.findAndUpdateMention(res);
|
||||||
|
} else {
|
||||||
|
await this.refetch();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleBanFromCommunity(form: BanFromCommunity) {
|
async handleBanFromCommunity(form: BanFromCommunity) {
|
||||||
|
@ -947,7 +959,11 @@ export class Inbox extends Component<any, InboxState> {
|
||||||
|
|
||||||
async handleMarkMessageAsRead(form: MarkPrivateMessageAsRead) {
|
async handleMarkMessageAsRead(form: MarkPrivateMessageAsRead) {
|
||||||
const res = await HttpService.client.markPrivateMessageAsRead(form);
|
const res = await HttpService.client.markPrivateMessageAsRead(form);
|
||||||
this.findAndUpdateMessage(res);
|
if (this.state.unreadOrAll === UnreadOrAll.All) {
|
||||||
|
this.findAndUpdateMessage(res);
|
||||||
|
} else {
|
||||||
|
await this.refetch();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleMessageReport(form: CreatePrivateMessageReport) {
|
async handleMessageReport(form: CreatePrivateMessageReport) {
|
||||||
|
@ -958,9 +974,9 @@ export class Inbox extends Component<any, InboxState> {
|
||||||
async handleCreateMessage(form: CreatePrivateMessage) {
|
async handleCreateMessage(form: CreatePrivateMessage) {
|
||||||
const res = await HttpService.client.createPrivateMessage(form);
|
const res = await HttpService.client.createPrivateMessage(form);
|
||||||
this.setState(s => {
|
this.setState(s => {
|
||||||
if (s.messagesRes.state == "success" && res.state == "success") {
|
if (s.messagesRes.state === "success" && res.state === "success") {
|
||||||
s.messagesRes.data.private_messages.unshift(
|
s.messagesRes.data.private_messages.unshift(
|
||||||
res.data.private_message_view
|
res.data.private_message_view,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -973,7 +989,7 @@ export class Inbox extends Component<any, InboxState> {
|
||||||
if (s.messagesRes.state === "success" && res.state === "success") {
|
if (s.messagesRes.state === "success" && res.state === "success") {
|
||||||
s.messagesRes.data.private_messages = editPrivateMessage(
|
s.messagesRes.data.private_messages = editPrivateMessage(
|
||||||
res.data.private_message_view,
|
res.data.private_message_view,
|
||||||
s.messagesRes.data.private_messages
|
s.messagesRes.data.private_messages,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return s;
|
return s;
|
||||||
|
@ -982,20 +998,20 @@ export class Inbox extends Component<any, InboxState> {
|
||||||
|
|
||||||
updateBanFromCommunity(banRes: RequestState<BanFromCommunityResponse>) {
|
updateBanFromCommunity(banRes: RequestState<BanFromCommunityResponse>) {
|
||||||
// Maybe not necessary
|
// Maybe not necessary
|
||||||
if (banRes.state == "success") {
|
if (banRes.state === "success") {
|
||||||
this.setState(s => {
|
this.setState(s => {
|
||||||
if (s.repliesRes.state == "success") {
|
if (s.repliesRes.state === "success") {
|
||||||
s.repliesRes.data.replies
|
s.repliesRes.data.replies
|
||||||
.filter(c => c.creator.id == banRes.data.person_view.person.id)
|
.filter(c => c.creator.id === banRes.data.person_view.person.id)
|
||||||
.forEach(
|
.forEach(
|
||||||
c => (c.creator_banned_from_community = banRes.data.banned)
|
c => (c.creator_banned_from_community = banRes.data.banned),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (s.mentionsRes.state == "success") {
|
if (s.mentionsRes.state === "success") {
|
||||||
s.mentionsRes.data.mentions
|
s.mentionsRes.data.mentions
|
||||||
.filter(c => c.creator.id == banRes.data.person_view.person.id)
|
.filter(c => c.creator.id === banRes.data.person_view.person.id)
|
||||||
.forEach(
|
.forEach(
|
||||||
c => (c.creator_banned_from_community = banRes.data.banned)
|
c => (c.creator_banned_from_community = banRes.data.banned),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return s;
|
return s;
|
||||||
|
@ -1005,16 +1021,16 @@ export class Inbox extends Component<any, InboxState> {
|
||||||
|
|
||||||
updateBan(banRes: RequestState<BanPersonResponse>) {
|
updateBan(banRes: RequestState<BanPersonResponse>) {
|
||||||
// Maybe not necessary
|
// Maybe not necessary
|
||||||
if (banRes.state == "success") {
|
if (banRes.state === "success") {
|
||||||
this.setState(s => {
|
this.setState(s => {
|
||||||
if (s.repliesRes.state == "success") {
|
if (s.repliesRes.state === "success") {
|
||||||
s.repliesRes.data.replies
|
s.repliesRes.data.replies
|
||||||
.filter(c => c.creator.id == banRes.data.person_view.person.id)
|
.filter(c => c.creator.id === banRes.data.person_view.person.id)
|
||||||
.forEach(c => (c.creator.banned = banRes.data.banned));
|
.forEach(c => (c.creator.banned = banRes.data.banned));
|
||||||
}
|
}
|
||||||
if (s.mentionsRes.state == "success") {
|
if (s.mentionsRes.state === "success") {
|
||||||
s.mentionsRes.data.mentions
|
s.mentionsRes.data.mentions
|
||||||
.filter(c => c.creator.id == banRes.data.person_view.person.id)
|
.filter(c => c.creator.id === banRes.data.person_view.person.id)
|
||||||
.forEach(c => (c.creator.banned = banRes.data.banned));
|
.forEach(c => (c.creator.banned = banRes.data.banned));
|
||||||
}
|
}
|
||||||
return s;
|
return s;
|
||||||
|
@ -1023,40 +1039,40 @@ export class Inbox extends Component<any, InboxState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
purgeItem(purgeRes: RequestState<PurgeItemResponse>) {
|
purgeItem(purgeRes: RequestState<PurgeItemResponse>) {
|
||||||
if (purgeRes.state == "success") {
|
if (purgeRes.state === "success") {
|
||||||
toast(I18NextService.i18n.t("purge_success"));
|
toast(I18NextService.i18n.t("purge_success"));
|
||||||
this.context.router.history.push(`/`);
|
this.context.router.history.push(`/`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
reportToast(
|
reportToast(
|
||||||
res: RequestState<PrivateMessageReportResponse | CommentReportResponse>
|
res: RequestState<PrivateMessageReportResponse | CommentReportResponse>,
|
||||||
) {
|
) {
|
||||||
if (res.state == "success") {
|
if (res.state === "success") {
|
||||||
toast(I18NextService.i18n.t("report_created"));
|
toast(I18NextService.i18n.t("report_created"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// A weird case, since you have only replies and mentions, not comment responses
|
// A weird case, since you have only replies and mentions, not comment responses
|
||||||
findAndUpdateComment(res: RequestState<CommentResponse>) {
|
findAndUpdateComment(res: RequestState<CommentResponse>) {
|
||||||
if (res.state == "success") {
|
if (res.state === "success") {
|
||||||
this.setState(s => {
|
this.setState(s => {
|
||||||
if (s.repliesRes.state == "success") {
|
if (s.repliesRes.state === "success") {
|
||||||
s.repliesRes.data.replies = editWith(
|
s.repliesRes.data.replies = editWith(
|
||||||
res.data.comment_view,
|
res.data.comment_view,
|
||||||
s.repliesRes.data.replies
|
s.repliesRes.data.replies,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (s.mentionsRes.state == "success") {
|
if (s.mentionsRes.state === "success") {
|
||||||
s.mentionsRes.data.mentions = editWith(
|
s.mentionsRes.data.mentions = editWith(
|
||||||
res.data.comment_view,
|
res.data.comment_view,
|
||||||
s.mentionsRes.data.mentions
|
s.mentionsRes.data.mentions,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// Set finished for the parent
|
// Set finished for the parent
|
||||||
s.finished.set(
|
s.finished.set(
|
||||||
getCommentParentId(res.data.comment_view.comment) ?? 0,
|
getCommentParentId(res.data.comment_view.comment) ?? 0,
|
||||||
true
|
true,
|
||||||
);
|
);
|
||||||
return s;
|
return s;
|
||||||
});
|
});
|
||||||
|
@ -1065,10 +1081,10 @@ export class Inbox extends Component<any, InboxState> {
|
||||||
|
|
||||||
findAndUpdateCommentReply(res: RequestState<CommentReplyResponse>) {
|
findAndUpdateCommentReply(res: RequestState<CommentReplyResponse>) {
|
||||||
this.setState(s => {
|
this.setState(s => {
|
||||||
if (s.repliesRes.state == "success" && res.state == "success") {
|
if (s.repliesRes.state === "success" && res.state === "success") {
|
||||||
s.repliesRes.data.replies = editCommentReply(
|
s.repliesRes.data.replies = editCommentReply(
|
||||||
res.data.comment_reply_view,
|
res.data.comment_reply_view,
|
||||||
s.repliesRes.data.replies
|
s.repliesRes.data.replies,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return s;
|
return s;
|
||||||
|
@ -1077,10 +1093,10 @@ export class Inbox extends Component<any, InboxState> {
|
||||||
|
|
||||||
findAndUpdateMention(res: RequestState<PersonMentionResponse>) {
|
findAndUpdateMention(res: RequestState<PersonMentionResponse>) {
|
||||||
this.setState(s => {
|
this.setState(s => {
|
||||||
if (s.mentionsRes.state == "success" && res.state == "success") {
|
if (s.mentionsRes.state === "success" && res.state === "success") {
|
||||||
s.mentionsRes.data.mentions = editMention(
|
s.mentionsRes.data.mentions = editMention(
|
||||||
res.data.person_mention_view,
|
res.data.person_mention_view,
|
||||||
s.mentionsRes.data.mentions
|
s.mentionsRes.data.mentions,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return s;
|
return s;
|
||||||
|
|
|
@ -1,11 +1,16 @@
|
||||||
import { myAuth, setIsoData } from "@utils/app";
|
import { setIsoData } from "@utils/app";
|
||||||
import { capitalizeFirstLetter } from "@utils/helpers";
|
import { capitalizeFirstLetter } from "@utils/helpers";
|
||||||
import { Component, linkEvent } from "inferno";
|
import { Component, linkEvent } from "inferno";
|
||||||
import { GetSiteResponse, LoginResponse } from "lemmy-js-client";
|
import { GetSiteResponse, LoginResponse } from "lemmy-js-client";
|
||||||
import { HttpService, I18NextService, UserService } from "../../services";
|
import { HttpService, I18NextService, UserService } from "../../services";
|
||||||
import { RequestState } from "../../services/HttpService";
|
import {
|
||||||
|
EMPTY_REQUEST,
|
||||||
|
LOADING_REQUEST,
|
||||||
|
RequestState,
|
||||||
|
} from "../../services/HttpService";
|
||||||
import { HtmlTags } from "../common/html-tags";
|
import { HtmlTags } from "../common/html-tags";
|
||||||
import { Spinner } from "../common/icon";
|
import { Spinner } from "../common/icon";
|
||||||
|
import PasswordInput from "../common/password-input";
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
passwordChangeRes: RequestState<LoginResponse>;
|
passwordChangeRes: RequestState<LoginResponse>;
|
||||||
|
@ -21,7 +26,7 @@ export class PasswordChange extends Component<any, State> {
|
||||||
private isoData = setIsoData(this.context);
|
private isoData = setIsoData(this.context);
|
||||||
|
|
||||||
state: State = {
|
state: State = {
|
||||||
passwordChangeRes: { state: "empty" },
|
passwordChangeRes: EMPTY_REQUEST,
|
||||||
siteRes: this.isoData.site_res,
|
siteRes: this.isoData.site_res,
|
||||||
form: {
|
form: {
|
||||||
token: this.props.match.params.token,
|
token: this.props.match.params.token,
|
||||||
|
@ -60,42 +65,28 @@ export class PasswordChange extends Component<any, State> {
|
||||||
passwordChangeForm() {
|
passwordChangeForm() {
|
||||||
return (
|
return (
|
||||||
<form onSubmit={linkEvent(this, this.handlePasswordChangeSubmit)}>
|
<form onSubmit={linkEvent(this, this.handlePasswordChangeSubmit)}>
|
||||||
<div className="mb-3 row">
|
<div className="mb-3">
|
||||||
<label className="col-sm-2 col-form-label" htmlFor="new-password">
|
<PasswordInput
|
||||||
{I18NextService.i18n.t("new_password")}
|
id="new-password"
|
||||||
</label>
|
value={this.state.form.password}
|
||||||
<div className="col-sm-10">
|
onInput={linkEvent(this, this.handlePasswordChange)}
|
||||||
<input
|
showStrength
|
||||||
id="new-password"
|
label={I18NextService.i18n.t("new_password")}
|
||||||
type="password"
|
isNew
|
||||||
value={this.state.form.password}
|
/>
|
||||||
onInput={linkEvent(this, this.handlePasswordChange)}
|
|
||||||
className="form-control"
|
|
||||||
required
|
|
||||||
maxLength={60}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-3 row">
|
<div className="mb-3">
|
||||||
<label className="col-sm-2 col-form-label" htmlFor="verify-password">
|
<PasswordInput
|
||||||
{I18NextService.i18n.t("verify_password")}
|
id="password"
|
||||||
</label>
|
value={this.state.form.password_verify}
|
||||||
<div className="col-sm-10">
|
onInput={linkEvent(this, this.handleVerifyPasswordChange)}
|
||||||
<input
|
label={I18NextService.i18n.t("verify_password")}
|
||||||
id="verify-password"
|
/>
|
||||||
type="password"
|
|
||||||
value={this.state.form.password_verify}
|
|
||||||
onInput={linkEvent(this, this.handleVerifyPasswordChange)}
|
|
||||||
className="form-control"
|
|
||||||
required
|
|
||||||
maxLength={60}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-3 row">
|
<div className="mb-3 row">
|
||||||
<div className="col-sm-10">
|
<div className="col-sm-10">
|
||||||
<button type="submit" className="btn btn-secondary">
|
<button type="submit" className="btn btn-secondary">
|
||||||
{this.state.passwordChangeRes.state == "loading" ? (
|
{this.state.passwordChangeRes.state === "loading" ? (
|
||||||
<Spinner />
|
<Spinner />
|
||||||
) : (
|
) : (
|
||||||
capitalizeFirstLetter(I18NextService.i18n.t("save"))
|
capitalizeFirstLetter(I18NextService.i18n.t("save"))
|
||||||
|
@ -119,7 +110,7 @@ export class PasswordChange extends Component<any, State> {
|
||||||
|
|
||||||
async handlePasswordChangeSubmit(i: PasswordChange, event: any) {
|
async handlePasswordChangeSubmit(i: PasswordChange, event: any) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
i.setState({ passwordChangeRes: { state: "loading" } });
|
i.setState({ passwordChangeRes: LOADING_REQUEST });
|
||||||
|
|
||||||
const password = i.state.form.password;
|
const password = i.state.form.password;
|
||||||
const password_verify = i.state.form.password_verify;
|
const password_verify = i.state.form.password_verify;
|
||||||
|
@ -139,7 +130,7 @@ export class PasswordChange extends Component<any, State> {
|
||||||
res: data,
|
res: data,
|
||||||
});
|
});
|
||||||
|
|
||||||
const site = await HttpService.client.getSite({ auth: myAuth() });
|
const site = await HttpService.client.getSite();
|
||||||
if (site.state === "success") {
|
if (site.state === "success") {
|
||||||
UserService.Instance.myUserInfo = site.data.my_user;
|
UserService.Instance.myUserInfo = site.data.my_user;
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ import {
|
||||||
LockPost,
|
LockPost,
|
||||||
MarkCommentReplyAsRead,
|
MarkCommentReplyAsRead,
|
||||||
MarkPersonMentionAsRead,
|
MarkPersonMentionAsRead,
|
||||||
|
MarkPostAsRead,
|
||||||
PersonView,
|
PersonView,
|
||||||
PostView,
|
PostView,
|
||||||
PurgeComment,
|
PurgeComment,
|
||||||
|
@ -84,6 +85,7 @@ interface PersonDetailsProps {
|
||||||
onSavePost(form: SavePost): void;
|
onSavePost(form: SavePost): void;
|
||||||
onFeaturePost(form: FeaturePost): void;
|
onFeaturePost(form: FeaturePost): void;
|
||||||
onPurgePost(form: PurgePost): void;
|
onPurgePost(form: PurgePost): void;
|
||||||
|
onMarkPostAsRead(form: MarkPostAsRead): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ItemEnum {
|
enum ItemEnum {
|
||||||
|
@ -113,7 +115,16 @@ export class PersonDetails extends Component<PersonDetailsProps, any> {
|
||||||
<div className="person-details">
|
<div className="person-details">
|
||||||
{this.viewSelector(this.props.view)}
|
{this.viewSelector(this.props.view)}
|
||||||
|
|
||||||
<Paginator page={this.props.page} onChange={this.handlePageChange} />
|
<Paginator
|
||||||
|
page={this.props.page}
|
||||||
|
onChange={this.handlePageChange}
|
||||||
|
nextDisabled={
|
||||||
|
(this.props.view === PersonDetailsView.Comments &&
|
||||||
|
this.props.limit > this.props.personRes.comments.length) ||
|
||||||
|
(this.props.view === PersonDetailsView.Posts &&
|
||||||
|
this.props.limit > this.props.personRes.posts.length)
|
||||||
|
}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -200,6 +211,7 @@ export class PersonDetails extends Component<PersonDetailsProps, any> {
|
||||||
onAddModToCommunity={this.props.onAddModToCommunity}
|
onAddModToCommunity={this.props.onAddModToCommunity}
|
||||||
onAddAdmin={this.props.onAddAdmin}
|
onAddAdmin={this.props.onAddAdmin}
|
||||||
onTransferCommunity={this.props.onTransferCommunity}
|
onTransferCommunity={this.props.onTransferCommunity}
|
||||||
|
onMarkPostAsRead={this.props.onMarkPostAsRead}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -252,7 +264,7 @@ export class PersonDetails extends Component<PersonDetailsProps, any> {
|
||||||
viewType={CommentViewType.Flat}
|
viewType={CommentViewType.Flat}
|
||||||
admins={this.props.admins}
|
admins={this.props.admins}
|
||||||
finished={this.props.finished}
|
finished={this.props.finished}
|
||||||
noIndent
|
isTopLevel
|
||||||
showCommunity
|
showCommunity
|
||||||
showContext
|
showContext
|
||||||
enableDownvotes={this.props.enableDownvotes}
|
enableDownvotes={this.props.enableDownvotes}
|
||||||
|
@ -311,6 +323,7 @@ export class PersonDetails extends Component<PersonDetailsProps, any> {
|
||||||
onAddModToCommunity={this.props.onAddModToCommunity}
|
onAddModToCommunity={this.props.onAddModToCommunity}
|
||||||
onAddAdmin={this.props.onAddAdmin}
|
onAddAdmin={this.props.onAddAdmin}
|
||||||
onTransferCommunity={this.props.onTransferCommunity}
|
onTransferCommunity={this.props.onTransferCommunity}
|
||||||
|
onMarkPostAsRead={this.props.onMarkPostAsRead}
|
||||||
/>
|
/>
|
||||||
<hr className="my-3" />
|
<hr className="my-3" />
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -57,7 +57,7 @@ export class PersonListing extends Component<PersonListingProps, any> {
|
||||||
{
|
{
|
||||||
"text-muted": this.props.muted,
|
"text-muted": this.props.muted,
|
||||||
"text-info": !this.props.muted,
|
"text-info": !this.props.muted,
|
||||||
}
|
},
|
||||||
)}
|
)}
|
||||||
to={link}
|
to={link}
|
||||||
>
|
>
|
||||||
|
|
|
@ -5,8 +5,6 @@ import {
|
||||||
enableDownvotes,
|
enableDownvotes,
|
||||||
enableNsfw,
|
enableNsfw,
|
||||||
getCommentParentId,
|
getCommentParentId,
|
||||||
myAuth,
|
|
||||||
myAuthRequired,
|
|
||||||
setIsoData,
|
setIsoData,
|
||||||
updatePersonBlock,
|
updatePersonBlock,
|
||||||
} from "@utils/app";
|
} from "@utils/app";
|
||||||
|
@ -60,6 +58,7 @@ import {
|
||||||
LockPost,
|
LockPost,
|
||||||
MarkCommentReplyAsRead,
|
MarkCommentReplyAsRead,
|
||||||
MarkPersonMentionAsRead,
|
MarkPersonMentionAsRead,
|
||||||
|
MarkPostAsRead,
|
||||||
PersonView,
|
PersonView,
|
||||||
PostResponse,
|
PostResponse,
|
||||||
PurgeComment,
|
PurgeComment,
|
||||||
|
@ -77,7 +76,12 @@ import { fetchLimit, relTags } from "../../config";
|
||||||
import { InitialFetchRequest, PersonDetailsView } from "../../interfaces";
|
import { InitialFetchRequest, PersonDetailsView } from "../../interfaces";
|
||||||
import { mdToHtml } from "../../markdown";
|
import { mdToHtml } from "../../markdown";
|
||||||
import { FirstLoadService, I18NextService, UserService } from "../../services";
|
import { FirstLoadService, I18NextService, UserService } from "../../services";
|
||||||
import { HttpService, RequestState } from "../../services/HttpService";
|
import {
|
||||||
|
EMPTY_REQUEST,
|
||||||
|
HttpService,
|
||||||
|
LOADING_REQUEST,
|
||||||
|
RequestState,
|
||||||
|
} from "../../services/HttpService";
|
||||||
import { setupTippy } from "../../tippy";
|
import { setupTippy } from "../../tippy";
|
||||||
import { toast } from "../../toast";
|
import { toast } from "../../toast";
|
||||||
import { BannerIconHeader } from "../common/banner-icon-header";
|
import { BannerIconHeader } from "../common/banner-icon-header";
|
||||||
|
@ -132,7 +136,7 @@ function getViewFromProps(view?: string): PersonDetailsView {
|
||||||
|
|
||||||
const getCommunitiesListing = (
|
const getCommunitiesListing = (
|
||||||
translationKey: NoOptionI18nKeys,
|
translationKey: NoOptionI18nKeys,
|
||||||
communityViews?: { community: Community }[]
|
communityViews?: { community: Community }[],
|
||||||
) =>
|
) =>
|
||||||
communityViews &&
|
communityViews &&
|
||||||
communityViews.length > 0 && (
|
communityViews.length > 0 && (
|
||||||
|
@ -156,13 +160,23 @@ const Moderates = ({ moderates }: { moderates?: CommunityModeratorView[] }) =>
|
||||||
const Follows = () =>
|
const Follows = () =>
|
||||||
getCommunitiesListing("subscribed", UserService.Instance.myUserInfo?.follows);
|
getCommunitiesListing("subscribed", UserService.Instance.myUserInfo?.follows);
|
||||||
|
|
||||||
|
function isPersonBlocked(personRes: RequestState<GetPersonDetailsResponse>) {
|
||||||
|
return (
|
||||||
|
(personRes.state === "success" &&
|
||||||
|
UserService.Instance.myUserInfo?.person_blocks.some(
|
||||||
|
({ target: { id } }) => id === personRes.data.person_view.person.id,
|
||||||
|
)) ??
|
||||||
|
false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export class Profile extends Component<
|
export class Profile extends Component<
|
||||||
RouteComponentProps<{ username: string }>,
|
RouteComponentProps<{ username: string }>,
|
||||||
ProfileState
|
ProfileState
|
||||||
> {
|
> {
|
||||||
private isoData = setIsoData<ProfileData>(this.context);
|
private isoData = setIsoData<ProfileData>(this.context);
|
||||||
state: ProfileState = {
|
state: ProfileState = {
|
||||||
personRes: { state: "empty" },
|
personRes: EMPTY_REQUEST,
|
||||||
personBlocked: false,
|
personBlocked: false,
|
||||||
siteRes: this.isoData.site_res,
|
siteRes: this.isoData.site_res,
|
||||||
showBanDialog: false,
|
showBanDialog: false,
|
||||||
|
@ -208,13 +222,16 @@ export class Profile extends Component<
|
||||||
this.handlePurgePost = this.handlePurgePost.bind(this);
|
this.handlePurgePost = this.handlePurgePost.bind(this);
|
||||||
this.handleFeaturePost = this.handleFeaturePost.bind(this);
|
this.handleFeaturePost = this.handleFeaturePost.bind(this);
|
||||||
this.handleModBanSubmit = this.handleModBanSubmit.bind(this);
|
this.handleModBanSubmit = this.handleModBanSubmit.bind(this);
|
||||||
|
this.handleMarkPostAsRead = this.handleMarkPostAsRead.bind(this);
|
||||||
|
|
||||||
// Only fetch the data if coming from another route
|
// Only fetch the data if coming from another route
|
||||||
if (FirstLoadService.isFirstLoad) {
|
if (FirstLoadService.isFirstLoad) {
|
||||||
|
const personRes = this.isoData.routeData.personResponse;
|
||||||
this.state = {
|
this.state = {
|
||||||
...this.state,
|
...this.state,
|
||||||
personRes: this.isoData.routeData.personResponse,
|
personRes,
|
||||||
isIsomorphic: true,
|
isIsomorphic: true,
|
||||||
|
personBlocked: isPersonBlocked(personRes),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -233,19 +250,19 @@ export class Profile extends Component<
|
||||||
async fetchUserData() {
|
async fetchUserData() {
|
||||||
const { page, sort, view } = getProfileQueryParams();
|
const { page, sort, view } = getProfileQueryParams();
|
||||||
|
|
||||||
this.setState({ personRes: { state: "loading" } });
|
this.setState({ personRes: LOADING_REQUEST });
|
||||||
|
const personRes = await HttpService.client.getPersonDetails({
|
||||||
|
username: this.props.match.params.username,
|
||||||
|
sort,
|
||||||
|
saved_only: view === PersonDetailsView.Saved,
|
||||||
|
page,
|
||||||
|
limit: fetchLimit,
|
||||||
|
});
|
||||||
this.setState({
|
this.setState({
|
||||||
personRes: await HttpService.client.getPersonDetails({
|
personRes,
|
||||||
username: this.props.match.params.username,
|
personBlocked: isPersonBlocked(personRes),
|
||||||
sort,
|
|
||||||
saved_only: view === PersonDetailsView.Saved,
|
|
||||||
page,
|
|
||||||
limit: fetchLimit,
|
|
||||||
auth: myAuth(),
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
restoreScrollPosition(this.context);
|
restoreScrollPosition(this.context);
|
||||||
this.setPersonBlock();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get amCurrentUser() {
|
get amCurrentUser() {
|
||||||
|
@ -259,24 +276,10 @@ export class Profile extends Component<
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setPersonBlock() {
|
|
||||||
const mui = UserService.Instance.myUserInfo;
|
|
||||||
const res = this.state.personRes;
|
|
||||||
|
|
||||||
if (mui && res.state === "success") {
|
|
||||||
this.setState({
|
|
||||||
personBlocked: mui.person_blocks.some(
|
|
||||||
({ target: { id } }) => id === res.data.person_view.person.id
|
|
||||||
),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static async fetchInitialData({
|
static async fetchInitialData({
|
||||||
client,
|
client,
|
||||||
path,
|
path,
|
||||||
query: { page, sort, view: urlView },
|
query: { page, sort, view: urlView },
|
||||||
auth,
|
|
||||||
}: InitialFetchRequest<QueryParams<ProfileProps>>): Promise<ProfileData> {
|
}: InitialFetchRequest<QueryParams<ProfileProps>>): Promise<ProfileData> {
|
||||||
const pathSplit = path.split("/");
|
const pathSplit = path.split("/");
|
||||||
|
|
||||||
|
@ -289,7 +292,6 @@ export class Profile extends Component<
|
||||||
saved_only: view === PersonDetailsView.Saved,
|
saved_only: view === PersonDetailsView.Saved,
|
||||||
page: getPageFromString(page),
|
page: getPageFromString(page),
|
||||||
limit: fetchLimit,
|
limit: fetchLimit,
|
||||||
auth,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -300,7 +302,7 @@ export class Profile extends Component<
|
||||||
get documentTitle(): string {
|
get documentTitle(): string {
|
||||||
const siteName = this.state.siteRes.site_view.site.name;
|
const siteName = this.state.siteRes.site_view.site.name;
|
||||||
const res = this.state.personRes;
|
const res = this.state.personRes;
|
||||||
return res.state == "success"
|
return res.state === "success"
|
||||||
? `@${res.data.person_view.person.name} - ${siteName}`
|
? `@${res.data.person_view.person.name} - ${siteName}`
|
||||||
: siteName;
|
: siteName;
|
||||||
}
|
}
|
||||||
|
@ -324,6 +326,7 @@ export class Profile extends Component<
|
||||||
<HtmlTags
|
<HtmlTags
|
||||||
title={this.documentTitle}
|
title={this.documentTitle}
|
||||||
path={this.context.router.route.match.url}
|
path={this.context.router.route.match.url}
|
||||||
|
canonicalPath={personRes.person_view.person.actor_id}
|
||||||
description={personRes.person_view.person.bio}
|
description={personRes.person_view.person.bio}
|
||||||
image={personRes.person_view.person.avatar}
|
image={personRes.person_view.person.avatar}
|
||||||
/>
|
/>
|
||||||
|
@ -375,6 +378,7 @@ export class Profile extends Component<
|
||||||
onSavePost={this.handleSavePost}
|
onSavePost={this.handleSavePost}
|
||||||
onPurgePost={this.handlePurgePost}
|
onPurgePost={this.handlePurgePost}
|
||||||
onFeaturePost={this.handleFeaturePost}
|
onFeaturePost={this.handleFeaturePost}
|
||||||
|
onMarkPostAsRead={this.handleMarkPostAsRead}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -495,7 +499,7 @@ export class Profile extends Component<
|
||||||
classNames="ms-1"
|
classNames="ms-1"
|
||||||
isBanned={isBanned(pv.person)}
|
isBanned={isBanned(pv.person)}
|
||||||
isDeleted={pv.person.deleted}
|
isDeleted={pv.person.deleted}
|
||||||
isAdmin={pv.person.admin}
|
isAdmin={isAdmin(pv.person.id, admins)}
|
||||||
isBot={pv.person.bot_account}
|
isBot={pv.person.bot_account}
|
||||||
/>
|
/>
|
||||||
</li>
|
</li>
|
||||||
|
@ -529,7 +533,7 @@ export class Profile extends Component<
|
||||||
}
|
}
|
||||||
onClick={linkEvent(
|
onClick={linkEvent(
|
||||||
pv.person.id,
|
pv.person.id,
|
||||||
this.handleUnblockPerson
|
this.handleUnblockPerson,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{I18NextService.i18n.t("unblock_user")}
|
{I18NextService.i18n.t("unblock_user")}
|
||||||
|
@ -541,7 +545,7 @@ export class Profile extends Component<
|
||||||
}
|
}
|
||||||
onClick={linkEvent(
|
onClick={linkEvent(
|
||||||
pv.person.id,
|
pv.person.id,
|
||||||
this.handleBlockPerson
|
this.handleBlockPerson,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{I18NextService.i18n.t("block_user")}
|
{I18NextService.i18n.t("block_user")}
|
||||||
|
@ -763,7 +767,7 @@ export class Profile extends Component<
|
||||||
|
|
||||||
const personRes = i.state.personRes;
|
const personRes = i.state.personRes;
|
||||||
|
|
||||||
if (personRes.state == "success") {
|
if (personRes.state === "success") {
|
||||||
const person = personRes.data.person_view.person;
|
const person = personRes.data.person_view.person;
|
||||||
const ban = !person.banned;
|
const ban = !person.banned;
|
||||||
|
|
||||||
|
@ -778,7 +782,6 @@ export class Profile extends Component<
|
||||||
remove_data: removeData,
|
remove_data: removeData,
|
||||||
reason: banReason,
|
reason: banReason,
|
||||||
expires: futureDaysToUnixTime(banExpireDays),
|
expires: futureDaysToUnixTime(banExpireDays),
|
||||||
auth: myAuthRequired(),
|
|
||||||
});
|
});
|
||||||
// TODO
|
// TODO
|
||||||
this.updateBan(res);
|
this.updateBan(res);
|
||||||
|
@ -790,10 +793,10 @@ export class Profile extends Component<
|
||||||
const res = await HttpService.client.blockPerson({
|
const res = await HttpService.client.blockPerson({
|
||||||
person_id: recipientId,
|
person_id: recipientId,
|
||||||
block,
|
block,
|
||||||
auth: myAuthRequired(),
|
|
||||||
});
|
});
|
||||||
if (res.state == "success") {
|
if (res.state === "success") {
|
||||||
updatePersonBlock(res.data);
|
updatePersonBlock(res.data);
|
||||||
|
this.setState({ personBlocked: res.data.blocked });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -829,6 +832,7 @@ export class Profile extends Component<
|
||||||
const blockPersonRes = await HttpService.client.blockPerson(form);
|
const blockPersonRes = await HttpService.client.blockPerson(form);
|
||||||
if (blockPersonRes.state === "success") {
|
if (blockPersonRes.state === "success") {
|
||||||
updatePersonBlock(blockPersonRes.data);
|
updatePersonBlock(blockPersonRes.data);
|
||||||
|
this.setState({ personBlocked: blockPersonRes.data.blocked });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -841,7 +845,7 @@ export class Profile extends Component<
|
||||||
|
|
||||||
async handleEditComment(form: EditComment) {
|
async handleEditComment(form: EditComment) {
|
||||||
const editCommentRes = await HttpService.client.editComment(form);
|
const editCommentRes = await HttpService.client.editComment(form);
|
||||||
this.findAndUpdateComment(editCommentRes);
|
this.findAndUpdateCommentEdit(editCommentRes);
|
||||||
|
|
||||||
return editCommentRes;
|
return editCommentRes;
|
||||||
}
|
}
|
||||||
|
@ -923,7 +927,7 @@ export class Profile extends Component<
|
||||||
async handleAddAdmin(form: AddAdmin) {
|
async handleAddAdmin(form: AddAdmin) {
|
||||||
const addAdminRes = await HttpService.client.addAdmin(form);
|
const addAdminRes = await HttpService.client.addAdmin(form);
|
||||||
|
|
||||||
if (addAdminRes.state == "success") {
|
if (addAdminRes.state === "success") {
|
||||||
this.setState(s => ((s.siteRes.admins = addAdminRes.data.admins), s));
|
this.setState(s => ((s.siteRes.admins = addAdminRes.data.admins), s));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -943,6 +947,11 @@ export class Profile extends Component<
|
||||||
await HttpService.client.markPersonMentionAsRead(form);
|
await HttpService.client.markPersonMentionAsRead(form);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async handleMarkPostAsRead(form: MarkPostAsRead) {
|
||||||
|
const res = await HttpService.client.markPostAsRead(form);
|
||||||
|
this.findAndUpdatePost(res);
|
||||||
|
}
|
||||||
|
|
||||||
async handleBanFromCommunity(form: BanFromCommunity) {
|
async handleBanFromCommunity(form: BanFromCommunity) {
|
||||||
const banRes = await HttpService.client.banFromCommunity(form);
|
const banRes = await HttpService.client.banFromCommunity(form);
|
||||||
this.updateBanFromCommunity(banRes);
|
this.updateBanFromCommunity(banRes);
|
||||||
|
@ -957,17 +966,17 @@ export class Profile extends Component<
|
||||||
// Maybe not necessary
|
// Maybe not necessary
|
||||||
if (banRes.state === "success") {
|
if (banRes.state === "success") {
|
||||||
this.setState(s => {
|
this.setState(s => {
|
||||||
if (s.personRes.state == "success") {
|
if (s.personRes.state === "success") {
|
||||||
s.personRes.data.posts
|
s.personRes.data.posts
|
||||||
.filter(c => c.creator.id === banRes.data.person_view.person.id)
|
.filter(c => c.creator.id === banRes.data.person_view.person.id)
|
||||||
.forEach(
|
.forEach(
|
||||||
c => (c.creator_banned_from_community = banRes.data.banned)
|
c => (c.creator_banned_from_community = banRes.data.banned),
|
||||||
);
|
);
|
||||||
|
|
||||||
s.personRes.data.comments
|
s.personRes.data.comments
|
||||||
.filter(c => c.creator.id === banRes.data.person_view.person.id)
|
.filter(c => c.creator.id === banRes.data.person_view.person.id)
|
||||||
.forEach(
|
.forEach(
|
||||||
c => (c.creator_banned_from_community = banRes.data.banned)
|
c => (c.creator_banned_from_community = banRes.data.banned),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return s;
|
return s;
|
||||||
|
@ -977,14 +986,14 @@ export class Profile extends Component<
|
||||||
|
|
||||||
updateBan(banRes: RequestState<BanPersonResponse>) {
|
updateBan(banRes: RequestState<BanPersonResponse>) {
|
||||||
// Maybe not necessary
|
// Maybe not necessary
|
||||||
if (banRes.state == "success") {
|
if (banRes.state === "success") {
|
||||||
this.setState(s => {
|
this.setState(s => {
|
||||||
if (s.personRes.state == "success") {
|
if (s.personRes.state === "success") {
|
||||||
s.personRes.data.posts
|
s.personRes.data.posts
|
||||||
.filter(c => c.creator.id == banRes.data.person_view.person.id)
|
.filter(c => c.creator.id === banRes.data.person_view.person.id)
|
||||||
.forEach(c => (c.creator.banned = banRes.data.banned));
|
.forEach(c => (c.creator.banned = banRes.data.banned));
|
||||||
s.personRes.data.comments
|
s.personRes.data.comments
|
||||||
.filter(c => c.creator.id == banRes.data.person_view.person.id)
|
.filter(c => c.creator.id === banRes.data.person_view.person.id)
|
||||||
.forEach(c => (c.creator.banned = banRes.data.banned));
|
.forEach(c => (c.creator.banned = banRes.data.banned));
|
||||||
s.personRes.data.person_view.person.banned = banRes.data.banned;
|
s.personRes.data.person_view.person.banned = banRes.data.banned;
|
||||||
}
|
}
|
||||||
|
@ -994,18 +1003,18 @@ export class Profile extends Component<
|
||||||
}
|
}
|
||||||
|
|
||||||
purgeItem(purgeRes: RequestState<PurgeItemResponse>) {
|
purgeItem(purgeRes: RequestState<PurgeItemResponse>) {
|
||||||
if (purgeRes.state == "success") {
|
if (purgeRes.state === "success") {
|
||||||
toast(I18NextService.i18n.t("purge_success"));
|
toast(I18NextService.i18n.t("purge_success"));
|
||||||
this.context.router.history.push(`/`);
|
this.context.router.history.push(`/`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
findAndUpdateComment(res: RequestState<CommentResponse>) {
|
findAndUpdateCommentEdit(res: RequestState<CommentResponse>) {
|
||||||
this.setState(s => {
|
this.setState(s => {
|
||||||
if (s.personRes.state == "success" && res.state == "success") {
|
if (s.personRes.state === "success" && res.state === "success") {
|
||||||
s.personRes.data.comments = editComment(
|
s.personRes.data.comments = editComment(
|
||||||
res.data.comment_view,
|
res.data.comment_view,
|
||||||
s.personRes.data.comments
|
s.personRes.data.comments,
|
||||||
);
|
);
|
||||||
s.finished.set(res.data.comment_view.comment.id, true);
|
s.finished.set(res.data.comment_view.comment.id, true);
|
||||||
}
|
}
|
||||||
|
@ -1013,14 +1022,26 @@ export class Profile extends Component<
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
findAndUpdateComment(res: RequestState<CommentResponse>) {
|
||||||
|
this.setState(s => {
|
||||||
|
if (s.personRes.state === "success" && res.state === "success") {
|
||||||
|
s.personRes.data.comments = editComment(
|
||||||
|
res.data.comment_view,
|
||||||
|
s.personRes.data.comments,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
createAndUpdateComments(res: RequestState<CommentResponse>) {
|
createAndUpdateComments(res: RequestState<CommentResponse>) {
|
||||||
this.setState(s => {
|
this.setState(s => {
|
||||||
if (s.personRes.state == "success" && res.state == "success") {
|
if (s.personRes.state === "success" && res.state === "success") {
|
||||||
s.personRes.data.comments.unshift(res.data.comment_view);
|
s.personRes.data.comments.unshift(res.data.comment_view);
|
||||||
// Set finished for the parent
|
// Set finished for the parent
|
||||||
s.finished.set(
|
s.finished.set(
|
||||||
getCommentParentId(res.data.comment_view.comment) ?? 0,
|
getCommentParentId(res.data.comment_view.comment) ?? 0,
|
||||||
true
|
true,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return s;
|
return s;
|
||||||
|
@ -1029,10 +1050,10 @@ export class Profile extends Component<
|
||||||
|
|
||||||
findAndUpdateCommentReply(res: RequestState<CommentReplyResponse>) {
|
findAndUpdateCommentReply(res: RequestState<CommentReplyResponse>) {
|
||||||
this.setState(s => {
|
this.setState(s => {
|
||||||
if (s.personRes.state == "success" && res.state == "success") {
|
if (s.personRes.state === "success" && res.state === "success") {
|
||||||
s.personRes.data.comments = editWith(
|
s.personRes.data.comments = editWith(
|
||||||
res.data.comment_reply_view,
|
res.data.comment_reply_view,
|
||||||
s.personRes.data.comments
|
s.personRes.data.comments,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return s;
|
return s;
|
||||||
|
@ -1041,10 +1062,10 @@ export class Profile extends Component<
|
||||||
|
|
||||||
findAndUpdatePost(res: RequestState<PostResponse>) {
|
findAndUpdatePost(res: RequestState<PostResponse>) {
|
||||||
this.setState(s => {
|
this.setState(s => {
|
||||||
if (s.personRes.state == "success" && res.state == "success") {
|
if (s.personRes.state === "success" && res.state === "success") {
|
||||||
s.personRes.data.posts = editPost(
|
s.personRes.data.posts = editPost(
|
||||||
res.data.post_view,
|
res.data.post_view,
|
||||||
s.personRes.data.posts
|
s.personRes.data.posts,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return s;
|
return s;
|
||||||
|
|
|
@ -1,8 +1,4 @@
|
||||||
import {
|
import { editRegistrationApplication, myAuth, setIsoData } from "@utils/app";
|
||||||
editRegistrationApplication,
|
|
||||||
myAuthRequired,
|
|
||||||
setIsoData,
|
|
||||||
} from "@utils/app";
|
|
||||||
import { randomStr } from "@utils/helpers";
|
import { randomStr } from "@utils/helpers";
|
||||||
import { RouteDataResponse } from "@utils/types";
|
import { RouteDataResponse } from "@utils/types";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
|
@ -16,12 +12,18 @@ import {
|
||||||
import { fetchLimit } from "../../config";
|
import { fetchLimit } from "../../config";
|
||||||
import { InitialFetchRequest } from "../../interfaces";
|
import { InitialFetchRequest } from "../../interfaces";
|
||||||
import { FirstLoadService, I18NextService, UserService } from "../../services";
|
import { FirstLoadService, I18NextService, UserService } from "../../services";
|
||||||
import { HttpService, RequestState } from "../../services/HttpService";
|
import {
|
||||||
|
EMPTY_REQUEST,
|
||||||
|
HttpService,
|
||||||
|
LOADING_REQUEST,
|
||||||
|
RequestState,
|
||||||
|
} from "../../services/HttpService";
|
||||||
import { setupTippy } from "../../tippy";
|
import { setupTippy } from "../../tippy";
|
||||||
import { HtmlTags } from "../common/html-tags";
|
import { HtmlTags } from "../common/html-tags";
|
||||||
import { Spinner } from "../common/icon";
|
import { Spinner } from "../common/icon";
|
||||||
import { Paginator } from "../common/paginator";
|
import { Paginator } from "../common/paginator";
|
||||||
import { RegistrationApplication } from "../common/registration-application";
|
import { RegistrationApplication } from "../common/registration-application";
|
||||||
|
import { UnreadCounterService } from "../../services";
|
||||||
|
|
||||||
enum UnreadOrAll {
|
enum UnreadOrAll {
|
||||||
Unread,
|
Unread,
|
||||||
|
@ -46,7 +48,7 @@ export class RegistrationApplications extends Component<
|
||||||
> {
|
> {
|
||||||
private isoData = setIsoData<RegistrationApplicationsData>(this.context);
|
private isoData = setIsoData<RegistrationApplicationsData>(this.context);
|
||||||
state: RegistrationApplicationsState = {
|
state: RegistrationApplicationsState = {
|
||||||
appsRes: { state: "empty" },
|
appsRes: EMPTY_REQUEST,
|
||||||
siteRes: this.isoData.site_res,
|
siteRes: this.isoData.site_res,
|
||||||
unreadOrAll: UnreadOrAll.Unread,
|
unreadOrAll: UnreadOrAll.Unread,
|
||||||
page: 1,
|
page: 1,
|
||||||
|
@ -80,7 +82,7 @@ export class RegistrationApplications extends Component<
|
||||||
const mui = UserService.Instance.myUserInfo;
|
const mui = UserService.Instance.myUserInfo;
|
||||||
return mui
|
return mui
|
||||||
? `@${mui.local_user_view.person.name} ${I18NextService.i18n.t(
|
? `@${mui.local_user_view.person.name} ${I18NextService.i18n.t(
|
||||||
"registration_applications"
|
"registration_applications",
|
||||||
)} - ${this.state.siteRes.site_view.site.name}`
|
)} - ${this.state.siteRes.site_view.site.name}`
|
||||||
: "";
|
: "";
|
||||||
}
|
}
|
||||||
|
@ -110,6 +112,7 @@ export class RegistrationApplications extends Component<
|
||||||
<Paginator
|
<Paginator
|
||||||
page={this.state.page}
|
page={this.state.page}
|
||||||
onChange={this.handlePageChange}
|
onChange={this.handlePageChange}
|
||||||
|
nextDisabled={fetchLimit > apps.length}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -204,46 +207,47 @@ export class RegistrationApplications extends Component<
|
||||||
}
|
}
|
||||||
|
|
||||||
static async fetchInitialData({
|
static async fetchInitialData({
|
||||||
auth,
|
|
||||||
client,
|
client,
|
||||||
}: InitialFetchRequest): Promise<RegistrationApplicationsData> {
|
}: InitialFetchRequest): Promise<RegistrationApplicationsData> {
|
||||||
return {
|
return {
|
||||||
listRegistrationApplicationsResponse: auth
|
listRegistrationApplicationsResponse: myAuth()
|
||||||
? await client.listRegistrationApplications({
|
? await client.listRegistrationApplications({
|
||||||
unread_only: true,
|
unread_only: true,
|
||||||
page: 1,
|
page: 1,
|
||||||
limit: fetchLimit,
|
limit: fetchLimit,
|
||||||
auth: auth as string,
|
|
||||||
})
|
})
|
||||||
: { state: "empty" },
|
: EMPTY_REQUEST,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async refetch() {
|
async refetch() {
|
||||||
const unread_only = this.state.unreadOrAll == UnreadOrAll.Unread;
|
const unread_only = this.state.unreadOrAll === UnreadOrAll.Unread;
|
||||||
this.setState({
|
this.setState({
|
||||||
appsRes: { state: "loading" },
|
appsRes: LOADING_REQUEST,
|
||||||
});
|
});
|
||||||
this.setState({
|
this.setState({
|
||||||
appsRes: await HttpService.client.listRegistrationApplications({
|
appsRes: await HttpService.client.listRegistrationApplications({
|
||||||
unread_only: unread_only,
|
unread_only: unread_only,
|
||||||
page: this.state.page,
|
page: this.state.page,
|
||||||
limit: fetchLimit,
|
limit: fetchLimit,
|
||||||
auth: myAuthRequired(),
|
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleApproveApplication(form: ApproveRegistrationApplication) {
|
async handleApproveApplication(form: ApproveRegistrationApplication) {
|
||||||
const approveRes = await HttpService.client.approveRegistrationApplication(
|
const approveRes = await HttpService.client.approveRegistrationApplication(
|
||||||
form
|
form,
|
||||||
);
|
);
|
||||||
this.setState(s => {
|
this.setState(s => {
|
||||||
if (s.appsRes.state == "success" && approveRes.state == "success") {
|
if (s.appsRes.state === "success" && approveRes.state === "success") {
|
||||||
s.appsRes.data.registration_applications = editRegistrationApplication(
|
s.appsRes.data.registration_applications = editRegistrationApplication(
|
||||||
approveRes.data.registration_application,
|
approveRes.data.registration_application,
|
||||||
s.appsRes.data.registration_applications
|
s.appsRes.data.registration_applications,
|
||||||
);
|
);
|
||||||
|
if (this.state.unreadOrAll === UnreadOrAll.Unread) {
|
||||||
|
this.refetch();
|
||||||
|
UnreadCounterService.Instance.updateApplications();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return s;
|
return s;
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,7 +2,6 @@ import {
|
||||||
editCommentReport,
|
editCommentReport,
|
||||||
editPostReport,
|
editPostReport,
|
||||||
editPrivateMessageReport,
|
editPrivateMessageReport,
|
||||||
myAuthRequired,
|
|
||||||
setIsoData,
|
setIsoData,
|
||||||
} from "@utils/app";
|
} from "@utils/app";
|
||||||
import { randomStr } from "@utils/helpers";
|
import { randomStr } from "@utils/helpers";
|
||||||
|
@ -36,13 +35,18 @@ import {
|
||||||
I18NextService,
|
I18NextService,
|
||||||
UserService,
|
UserService,
|
||||||
} from "../../services";
|
} from "../../services";
|
||||||
import { RequestState } from "../../services/HttpService";
|
import {
|
||||||
|
EMPTY_REQUEST,
|
||||||
|
LOADING_REQUEST,
|
||||||
|
RequestState,
|
||||||
|
} from "../../services/HttpService";
|
||||||
import { CommentReport } from "../comment/comment-report";
|
import { CommentReport } from "../comment/comment-report";
|
||||||
import { HtmlTags } from "../common/html-tags";
|
import { HtmlTags } from "../common/html-tags";
|
||||||
import { Spinner } from "../common/icon";
|
import { Spinner } from "../common/icon";
|
||||||
import { Paginator } from "../common/paginator";
|
import { Paginator } from "../common/paginator";
|
||||||
import { PostReport } from "../post/post-report";
|
import { PostReport } from "../post/post-report";
|
||||||
import { PrivateMessageReport } from "../private_message/private-message-report";
|
import { PrivateMessageReport } from "../private_message/private-message-report";
|
||||||
|
import { UnreadCounterService } from "../../services";
|
||||||
|
|
||||||
enum UnreadOrAll {
|
enum UnreadOrAll {
|
||||||
Unread,
|
Unread,
|
||||||
|
@ -89,9 +93,9 @@ interface ReportsState {
|
||||||
export class Reports extends Component<any, ReportsState> {
|
export class Reports extends Component<any, ReportsState> {
|
||||||
private isoData = setIsoData<ReportsData>(this.context);
|
private isoData = setIsoData<ReportsData>(this.context);
|
||||||
state: ReportsState = {
|
state: ReportsState = {
|
||||||
commentReportsRes: { state: "empty" },
|
commentReportsRes: EMPTY_REQUEST,
|
||||||
postReportsRes: { state: "empty" },
|
postReportsRes: EMPTY_REQUEST,
|
||||||
messageReportsRes: { state: "empty" },
|
messageReportsRes: EMPTY_REQUEST,
|
||||||
unreadOrAll: UnreadOrAll.Unread,
|
unreadOrAll: UnreadOrAll.Unread,
|
||||||
messageType: MessageType.All,
|
messageType: MessageType.All,
|
||||||
page: 1,
|
page: 1,
|
||||||
|
@ -140,7 +144,7 @@ export class Reports extends Component<any, ReportsState> {
|
||||||
const mui = UserService.Instance.myUserInfo;
|
const mui = UserService.Instance.myUserInfo;
|
||||||
return mui
|
return mui
|
||||||
? `@${mui.local_user_view.person.name} ${I18NextService.i18n.t(
|
? `@${mui.local_user_view.person.name} ${I18NextService.i18n.t(
|
||||||
"reports"
|
"reports",
|
||||||
)} - ${this.state.siteRes.site_view.site.name}`
|
)} - ${this.state.siteRes.site_view.site.name}`
|
||||||
: "";
|
: "";
|
||||||
}
|
}
|
||||||
|
@ -160,6 +164,16 @@ export class Reports extends Component<any, ReportsState> {
|
||||||
<Paginator
|
<Paginator
|
||||||
page={this.state.page}
|
page={this.state.page}
|
||||||
onChange={this.handlePageChange}
|
onChange={this.handlePageChange}
|
||||||
|
nextDisabled={
|
||||||
|
(this.state.messageType === MessageType.All &&
|
||||||
|
fetchLimit > this.buildCombined.length) ||
|
||||||
|
(this.state.messageType === MessageType.CommentReport &&
|
||||||
|
fetchLimit > this.commentReports.length) ||
|
||||||
|
(this.state.messageType === MessageType.PostReport &&
|
||||||
|
fetchLimit > this.postReports.length) ||
|
||||||
|
(this.state.messageType === MessageType.PrivateMessageReport &&
|
||||||
|
fetchLimit > this.privateMessageReports.length)
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -352,25 +366,25 @@ export class Reports extends Component<any, ReportsState> {
|
||||||
get buildCombined(): ItemType[] {
|
get buildCombined(): ItemType[] {
|
||||||
const commentRes = this.state.commentReportsRes;
|
const commentRes = this.state.commentReportsRes;
|
||||||
const comments =
|
const comments =
|
||||||
commentRes.state == "success"
|
commentRes.state === "success"
|
||||||
? commentRes.data.comment_reports.map(this.commentReportToItemType)
|
? commentRes.data.comment_reports.map(this.commentReportToItemType)
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
const postRes = this.state.postReportsRes;
|
const postRes = this.state.postReportsRes;
|
||||||
const posts =
|
const posts =
|
||||||
postRes.state == "success"
|
postRes.state === "success"
|
||||||
? postRes.data.post_reports.map(this.postReportToItemType)
|
? postRes.data.post_reports.map(this.postReportToItemType)
|
||||||
: [];
|
: [];
|
||||||
const pmRes = this.state.messageReportsRes;
|
const pmRes = this.state.messageReportsRes;
|
||||||
const privateMessages =
|
const privateMessages =
|
||||||
pmRes.state == "success"
|
pmRes.state === "success"
|
||||||
? pmRes.data.private_message_reports.map(
|
? pmRes.data.private_message_reports.map(
|
||||||
this.privateMessageReportToItemType
|
this.privateMessageReportToItemType,
|
||||||
)
|
)
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
return [...comments, ...posts, ...privateMessages].sort((a, b) =>
|
return [...comments, ...posts, ...privateMessages].sort((a, b) =>
|
||||||
b.published.localeCompare(a.published)
|
b.published.localeCompare(a.published),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -521,7 +535,6 @@ export class Reports extends Component<any, ReportsState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
static async fetchInitialData({
|
static async fetchInitialData({
|
||||||
auth,
|
|
||||||
client,
|
client,
|
||||||
}: InitialFetchRequest): Promise<ReportsData> {
|
}: InitialFetchRequest): Promise<ReportsData> {
|
||||||
const unresolved_only = true;
|
const unresolved_only = true;
|
||||||
|
@ -532,20 +545,18 @@ export class Reports extends Component<any, ReportsState> {
|
||||||
unresolved_only,
|
unresolved_only,
|
||||||
page,
|
page,
|
||||||
limit,
|
limit,
|
||||||
auth: auth as string,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const postReportsForm: ListPostReports = {
|
const postReportsForm: ListPostReports = {
|
||||||
unresolved_only,
|
unresolved_only,
|
||||||
page,
|
page,
|
||||||
limit,
|
limit,
|
||||||
auth: auth as string,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const data: ReportsData = {
|
const data: ReportsData = {
|
||||||
commentReportsRes: await client.listCommentReports(commentReportsForm),
|
commentReportsRes: await client.listCommentReports(commentReportsForm),
|
||||||
postReportsRes: await client.listPostReports(postReportsForm),
|
postReportsRes: await client.listPostReports(postReportsForm),
|
||||||
messageReportsRes: { state: "empty" },
|
messageReportsRes: EMPTY_REQUEST,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (amAdmin()) {
|
if (amAdmin()) {
|
||||||
|
@ -553,11 +564,10 @@ export class Reports extends Component<any, ReportsState> {
|
||||||
unresolved_only,
|
unresolved_only,
|
||||||
page,
|
page,
|
||||||
limit,
|
limit,
|
||||||
auth: auth as string,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
data.messageReportsRes = await client.listPrivateMessageReports(
|
data.messageReportsRes = await client.listPrivateMessageReports(
|
||||||
privateMessageReportsForm
|
privateMessageReportsForm,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -565,15 +575,14 @@ export class Reports extends Component<any, ReportsState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
async refetch() {
|
async refetch() {
|
||||||
const unresolved_only = this.state.unreadOrAll == UnreadOrAll.Unread;
|
const unresolved_only = this.state.unreadOrAll === UnreadOrAll.Unread;
|
||||||
const page = this.state.page;
|
const page = this.state.page;
|
||||||
const limit = fetchLimit;
|
const limit = fetchLimit;
|
||||||
const auth = myAuthRequired();
|
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
commentReportsRes: { state: "loading" },
|
commentReportsRes: LOADING_REQUEST,
|
||||||
postReportsRes: { state: "loading" },
|
postReportsRes: LOADING_REQUEST,
|
||||||
messageReportsRes: { state: "loading" },
|
messageReportsRes: LOADING_REQUEST,
|
||||||
});
|
});
|
||||||
|
|
||||||
const form:
|
const form:
|
||||||
|
@ -583,7 +592,6 @@ export class Reports extends Component<any, ReportsState> {
|
||||||
unresolved_only,
|
unresolved_only,
|
||||||
page,
|
page,
|
||||||
limit,
|
limit,
|
||||||
auth,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
|
@ -594,7 +602,7 @@ export class Reports extends Component<any, ReportsState> {
|
||||||
if (amAdmin()) {
|
if (amAdmin()) {
|
||||||
this.setState({
|
this.setState({
|
||||||
messageReportsRes: await HttpService.client.listPrivateMessageReports(
|
messageReportsRes: await HttpService.client.listPrivateMessageReports(
|
||||||
form
|
form,
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -603,24 +611,36 @@ export class Reports extends Component<any, ReportsState> {
|
||||||
async handleResolveCommentReport(form: ResolveCommentReport) {
|
async handleResolveCommentReport(form: ResolveCommentReport) {
|
||||||
const res = await HttpService.client.resolveCommentReport(form);
|
const res = await HttpService.client.resolveCommentReport(form);
|
||||||
this.findAndUpdateCommentReport(res);
|
this.findAndUpdateCommentReport(res);
|
||||||
|
if (this.state.unreadOrAll === UnreadOrAll.Unread) {
|
||||||
|
this.refetch();
|
||||||
|
UnreadCounterService.Instance.updateReports();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleResolvePostReport(form: ResolvePostReport) {
|
async handleResolvePostReport(form: ResolvePostReport) {
|
||||||
const res = await HttpService.client.resolvePostReport(form);
|
const res = await HttpService.client.resolvePostReport(form);
|
||||||
this.findAndUpdatePostReport(res);
|
this.findAndUpdatePostReport(res);
|
||||||
|
if (this.state.unreadOrAll === UnreadOrAll.Unread) {
|
||||||
|
this.refetch();
|
||||||
|
UnreadCounterService.Instance.updateReports();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleResolvePrivateMessageReport(form: ResolvePrivateMessageReport) {
|
async handleResolvePrivateMessageReport(form: ResolvePrivateMessageReport) {
|
||||||
const res = await HttpService.client.resolvePrivateMessageReport(form);
|
const res = await HttpService.client.resolvePrivateMessageReport(form);
|
||||||
this.findAndUpdatePrivateMessageReport(res);
|
this.findAndUpdatePrivateMessageReport(res);
|
||||||
|
if (this.state.unreadOrAll === UnreadOrAll.Unread) {
|
||||||
|
this.refetch();
|
||||||
|
UnreadCounterService.Instance.updateReports();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
findAndUpdateCommentReport(res: RequestState<CommentReportResponse>) {
|
findAndUpdateCommentReport(res: RequestState<CommentReportResponse>) {
|
||||||
this.setState(s => {
|
this.setState(s => {
|
||||||
if (s.commentReportsRes.state == "success" && res.state == "success") {
|
if (s.commentReportsRes.state === "success" && res.state === "success") {
|
||||||
s.commentReportsRes.data.comment_reports = editCommentReport(
|
s.commentReportsRes.data.comment_reports = editCommentReport(
|
||||||
res.data.comment_report_view,
|
res.data.comment_report_view,
|
||||||
s.commentReportsRes.data.comment_reports
|
s.commentReportsRes.data.comment_reports,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return s;
|
return s;
|
||||||
|
@ -629,10 +649,10 @@ export class Reports extends Component<any, ReportsState> {
|
||||||
|
|
||||||
findAndUpdatePostReport(res: RequestState<PostReportResponse>) {
|
findAndUpdatePostReport(res: RequestState<PostReportResponse>) {
|
||||||
this.setState(s => {
|
this.setState(s => {
|
||||||
if (s.postReportsRes.state == "success" && res.state == "success") {
|
if (s.postReportsRes.state === "success" && res.state === "success") {
|
||||||
s.postReportsRes.data.post_reports = editPostReport(
|
s.postReportsRes.data.post_reports = editPostReport(
|
||||||
res.data.post_report_view,
|
res.data.post_report_view,
|
||||||
s.postReportsRes.data.post_reports
|
s.postReportsRes.data.post_reports,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return s;
|
return s;
|
||||||
|
@ -640,14 +660,14 @@ export class Reports extends Component<any, ReportsState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
findAndUpdatePrivateMessageReport(
|
findAndUpdatePrivateMessageReport(
|
||||||
res: RequestState<PrivateMessageReportResponse>
|
res: RequestState<PrivateMessageReportResponse>,
|
||||||
) {
|
) {
|
||||||
this.setState(s => {
|
this.setState(s => {
|
||||||
if (s.messageReportsRes.state == "success" && res.state == "success") {
|
if (s.messageReportsRes.state === "success" && res.state === "success") {
|
||||||
s.messageReportsRes.data.private_message_reports =
|
s.messageReportsRes.data.private_message_reports =
|
||||||
editPrivateMessageReport(
|
editPrivateMessageReport(
|
||||||
res.data.private_message_report_view,
|
res.data.private_message_report_view,
|
||||||
s.messageReportsRes.data.private_message_reports
|
s.messageReportsRes.data.private_message_reports,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return s;
|
return s;
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -2,7 +2,12 @@ import { setIsoData } from "@utils/app";
|
||||||
import { Component } from "inferno";
|
import { Component } from "inferno";
|
||||||
import { GetSiteResponse, VerifyEmailResponse } from "lemmy-js-client";
|
import { GetSiteResponse, VerifyEmailResponse } from "lemmy-js-client";
|
||||||
import { I18NextService } from "../../services";
|
import { I18NextService } from "../../services";
|
||||||
import { HttpService, RequestState } from "../../services/HttpService";
|
import {
|
||||||
|
EMPTY_REQUEST,
|
||||||
|
HttpService,
|
||||||
|
LOADING_REQUEST,
|
||||||
|
RequestState,
|
||||||
|
} from "../../services/HttpService";
|
||||||
import { toast } from "../../toast";
|
import { toast } from "../../toast";
|
||||||
import { HtmlTags } from "../common/html-tags";
|
import { HtmlTags } from "../common/html-tags";
|
||||||
import { Spinner } from "../common/icon";
|
import { Spinner } from "../common/icon";
|
||||||
|
@ -16,7 +21,7 @@ export class VerifyEmail extends Component<any, State> {
|
||||||
private isoData = setIsoData(this.context);
|
private isoData = setIsoData(this.context);
|
||||||
|
|
||||||
state: State = {
|
state: State = {
|
||||||
verifyRes: { state: "empty" },
|
verifyRes: EMPTY_REQUEST,
|
||||||
siteRes: this.isoData.site_res,
|
siteRes: this.isoData.site_res,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -26,7 +31,7 @@ export class VerifyEmail extends Component<any, State> {
|
||||||
|
|
||||||
async verify() {
|
async verify() {
|
||||||
this.setState({
|
this.setState({
|
||||||
verifyRes: { state: "loading" },
|
verifyRes: LOADING_REQUEST,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
|
@ -35,7 +40,7 @@ export class VerifyEmail extends Component<any, State> {
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this.state.verifyRes.state == "success") {
|
if (this.state.verifyRes.state === "success") {
|
||||||
toast(I18NextService.i18n.t("email_verified"));
|
toast(I18NextService.i18n.t("email_verified"));
|
||||||
this.props.history.push("/login");
|
this.props.history.push("/login");
|
||||||
}
|
}
|
||||||
|
@ -61,7 +66,7 @@ export class VerifyEmail extends Component<any, State> {
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col-12 col-lg-6 offset-lg-3 mb-4">
|
<div className="col-12 col-lg-6 offset-lg-3 mb-4">
|
||||||
<h1 className="h4 mb-4">{I18NextService.i18n.t("verify_email")}</h1>
|
<h1 className="h4 mb-4">{I18NextService.i18n.t("verify_email")}</h1>
|
||||||
{this.state.verifyRes.state == "loading" && (
|
{this.state.verifyRes.state === "loading" && (
|
||||||
<h5>
|
<h5>
|
||||||
<Spinner large />
|
<Spinner large />
|
||||||
</h5>
|
</h5>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { enableDownvotes, enableNsfw, myAuth, setIsoData } from "@utils/app";
|
import { enableDownvotes, enableNsfw, setIsoData } from "@utils/app";
|
||||||
import { getIdFromString, getQueryParams } from "@utils/helpers";
|
import { getIdFromString, getQueryParams } from "@utils/helpers";
|
||||||
import type { QueryParams } from "@utils/types";
|
import type { QueryParams } from "@utils/types";
|
||||||
import { Choice, RouteDataResponse } from "@utils/types";
|
import { Choice, RouteDataResponse } from "@utils/types";
|
||||||
|
@ -14,6 +14,7 @@ import {
|
||||||
import { InitialFetchRequest, PostFormParams } from "../../interfaces";
|
import { InitialFetchRequest, PostFormParams } from "../../interfaces";
|
||||||
import { FirstLoadService, I18NextService } from "../../services";
|
import { FirstLoadService, I18NextService } from "../../services";
|
||||||
import {
|
import {
|
||||||
|
EMPTY_REQUEST,
|
||||||
HttpService,
|
HttpService,
|
||||||
RequestState,
|
RequestState,
|
||||||
WrappedLemmyHttp,
|
WrappedLemmyHttp,
|
||||||
|
@ -57,7 +58,7 @@ export class CreatePost extends Component<
|
||||||
state: CreatePostState = {
|
state: CreatePostState = {
|
||||||
siteRes: this.isoData.site_res,
|
siteRes: this.isoData.site_res,
|
||||||
loading: true,
|
loading: true,
|
||||||
initialCommunitiesRes: { state: "empty" },
|
initialCommunitiesRes: EMPTY_REQUEST,
|
||||||
isIsomorphic: false,
|
isIsomorphic: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -96,12 +97,10 @@ export class CreatePost extends Component<
|
||||||
|
|
||||||
async fetchCommunity() {
|
async fetchCommunity() {
|
||||||
const { communityId } = getCreatePostQueryParams();
|
const { communityId } = getCreatePostQueryParams();
|
||||||
const auth = myAuth();
|
|
||||||
|
|
||||||
if (communityId) {
|
if (communityId) {
|
||||||
const res = await HttpService.client.getCommunity({
|
const res = await HttpService.client.getCommunity({
|
||||||
id: communityId,
|
id: communityId,
|
||||||
auth,
|
|
||||||
});
|
});
|
||||||
if (res.state === "success") {
|
if (res.state === "success") {
|
||||||
this.setState({
|
this.setState({
|
||||||
|
@ -121,7 +120,7 @@ export class CreatePost extends Component<
|
||||||
const { communityId } = getCreatePostQueryParams();
|
const { communityId } = getCreatePostQueryParams();
|
||||||
|
|
||||||
const initialCommunitiesRes = await fetchCommunitiesForOptions(
|
const initialCommunitiesRes = await fetchCommunitiesForOptions(
|
||||||
HttpService.client
|
HttpService.client,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
|
@ -239,18 +238,16 @@ export class CreatePost extends Component<
|
||||||
static async fetchInitialData({
|
static async fetchInitialData({
|
||||||
client,
|
client,
|
||||||
query: { communityId },
|
query: { communityId },
|
||||||
auth,
|
|
||||||
}: InitialFetchRequest<
|
}: InitialFetchRequest<
|
||||||
QueryParams<CreatePostProps>
|
QueryParams<CreatePostProps>
|
||||||
>): Promise<CreatePostData> {
|
>): Promise<CreatePostData> {
|
||||||
const data: CreatePostData = {
|
const data: CreatePostData = {
|
||||||
initialCommunitiesRes: await fetchCommunitiesForOptions(client),
|
initialCommunitiesRes: await fetchCommunitiesForOptions(client),
|
||||||
communityResponse: { state: "empty" },
|
communityResponse: EMPTY_REQUEST,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (communityId) {
|
if (communityId) {
|
||||||
const form: GetCommunity = {
|
const form: GetCommunity = {
|
||||||
auth,
|
|
||||||
id: getIdFromString(communityId),
|
id: getIdFromString(communityId),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,4 @@
|
||||||
import {
|
import { communityToChoice, fetchCommunities } from "@utils/app";
|
||||||
communityToChoice,
|
|
||||||
fetchCommunities,
|
|
||||||
myAuth,
|
|
||||||
myAuthRequired,
|
|
||||||
} from "@utils/app";
|
|
||||||
import {
|
import {
|
||||||
capitalizeFirstLetter,
|
capitalizeFirstLetter,
|
||||||
debounce,
|
debounce,
|
||||||
|
@ -15,6 +10,7 @@ import { isImage } from "@utils/media";
|
||||||
import { Choice } from "@utils/types";
|
import { Choice } from "@utils/types";
|
||||||
import autosize from "autosize";
|
import autosize from "autosize";
|
||||||
import { Component, InfernoNode, linkEvent } from "inferno";
|
import { Component, InfernoNode, linkEvent } from "inferno";
|
||||||
|
import { Prompt } from "inferno-router";
|
||||||
import {
|
import {
|
||||||
CommunityView,
|
CommunityView,
|
||||||
CreatePost,
|
CreatePost,
|
||||||
|
@ -33,13 +29,17 @@ import {
|
||||||
} from "../../config";
|
} from "../../config";
|
||||||
import { PostFormParams } from "../../interfaces";
|
import { PostFormParams } from "../../interfaces";
|
||||||
import { I18NextService, UserService } from "../../services";
|
import { I18NextService, UserService } from "../../services";
|
||||||
import { HttpService, RequestState } from "../../services/HttpService";
|
import {
|
||||||
|
EMPTY_REQUEST,
|
||||||
|
HttpService,
|
||||||
|
LOADING_REQUEST,
|
||||||
|
RequestState,
|
||||||
|
} from "../../services/HttpService";
|
||||||
import { setupTippy } from "../../tippy";
|
import { setupTippy } from "../../tippy";
|
||||||
import { toast } from "../../toast";
|
import { toast } from "../../toast";
|
||||||
import { Icon, Spinner } from "../common/icon";
|
import { Icon, Spinner } from "../common/icon";
|
||||||
import { LanguageSelect } from "../common/language-select";
|
import { LanguageSelect } from "../common/language-select";
|
||||||
import { MarkdownTextArea } from "../common/markdown-textarea";
|
import { MarkdownTextArea } from "../common/markdown-textarea";
|
||||||
import NavigationPrompt from "../common/navigation-prompt";
|
|
||||||
import { SearchableSelect } from "../common/searchable-select";
|
import { SearchableSelect } from "../common/searchable-select";
|
||||||
import { PostListings } from "./post-listings";
|
import { PostListings } from "./post-listings";
|
||||||
|
|
||||||
|
@ -89,7 +89,6 @@ function handlePostSubmit(i: PostForm, event: any) {
|
||||||
i.setState(s => ((s.form.url = undefined), s));
|
i.setState(s => ((s.form.url = undefined), s));
|
||||||
}
|
}
|
||||||
i.setState({ loading: true, submitted: true });
|
i.setState({ loading: true, submitted: true });
|
||||||
const auth = myAuthRequired();
|
|
||||||
|
|
||||||
const pForm = i.state.form;
|
const pForm = i.state.form;
|
||||||
const pv = i.props.post_view;
|
const pv = i.props.post_view;
|
||||||
|
@ -102,7 +101,6 @@ function handlePostSubmit(i: PostForm, event: any) {
|
||||||
nsfw: pForm.nsfw,
|
nsfw: pForm.nsfw,
|
||||||
post_id: pv.post.id,
|
post_id: pv.post.id,
|
||||||
language_id: pForm.language_id,
|
language_id: pForm.language_id,
|
||||||
auth,
|
|
||||||
});
|
});
|
||||||
} else if (pForm.name && pForm.community_id) {
|
} else if (pForm.name && pForm.community_id) {
|
||||||
i.props.onCreate?.({
|
i.props.onCreate?.({
|
||||||
|
@ -113,7 +111,6 @@ function handlePostSubmit(i: PostForm, event: any) {
|
||||||
nsfw: pForm.nsfw,
|
nsfw: pForm.nsfw,
|
||||||
language_id: pForm.language_id,
|
language_id: pForm.language_id,
|
||||||
honeypot: pForm.honeypot,
|
honeypot: pForm.honeypot,
|
||||||
auth,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -122,9 +119,9 @@ function copySuggestedTitle(d: { i: PostForm; suggestedTitle?: string }) {
|
||||||
const sTitle = d.suggestedTitle;
|
const sTitle = d.suggestedTitle;
|
||||||
if (sTitle) {
|
if (sTitle) {
|
||||||
d.i.setState(
|
d.i.setState(
|
||||||
s => ((s.form.name = sTitle?.substring(0, MAX_POST_TITLE_LENGTH)), s)
|
s => ((s.form.name = sTitle?.substring(0, MAX_POST_TITLE_LENGTH)), s),
|
||||||
);
|
);
|
||||||
d.i.setState({ suggestedPostsRes: { state: "empty" } });
|
d.i.setState({ suggestedPostsRes: EMPTY_REQUEST });
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const textarea: any = document.getElementById("post-title");
|
const textarea: any = document.getElementById("post-title");
|
||||||
autosize.update(textarea);
|
autosize.update(textarea);
|
||||||
|
@ -223,8 +220,8 @@ function handleImageDelete(i: PostForm) {
|
||||||
|
|
||||||
export class PostForm extends Component<PostFormProps, PostFormState> {
|
export class PostForm extends Component<PostFormProps, PostFormState> {
|
||||||
state: PostFormState = {
|
state: PostFormState = {
|
||||||
suggestedPostsRes: { state: "empty" },
|
suggestedPostsRes: EMPTY_REQUEST,
|
||||||
metadataRes: { state: "empty" },
|
metadataRes: EMPTY_REQUEST,
|
||||||
form: {},
|
form: {},
|
||||||
loading: false,
|
loading: false,
|
||||||
imageLoading: false,
|
imageLoading: false,
|
||||||
|
@ -271,9 +268,9 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
||||||
({ community: { id, title } }) => ({
|
({ community: { id, title } }) => ({
|
||||||
label: title,
|
label: title,
|
||||||
value: id.toString(),
|
value: id.toString(),
|
||||||
})
|
}),
|
||||||
) ?? []
|
) ?? []
|
||||||
).filter(option => option.value !== selectedCommunityChoice.value)
|
).filter(option => option.value !== selectedCommunityChoice.value),
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
|
@ -284,7 +281,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
||||||
({ community: { id, title } }) => ({
|
({ community: { id, title } }) => ({
|
||||||
label: title,
|
label: title,
|
||||||
value: id.toString(),
|
value: id.toString(),
|
||||||
})
|
}),
|
||||||
) ?? [],
|
) ?? [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -310,16 +307,16 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(
|
componentWillReceiveProps(
|
||||||
nextProps: Readonly<{ children?: InfernoNode } & PostFormProps>
|
nextProps: Readonly<{ children?: InfernoNode } & PostFormProps>,
|
||||||
): void {
|
): void {
|
||||||
if (this.props != nextProps) {
|
if (this.props !== nextProps) {
|
||||||
this.setState(
|
this.setState(
|
||||||
s => (
|
s => (
|
||||||
(s.form.community_id = getIdFromString(
|
(s.form.community_id = getIdFromString(
|
||||||
nextProps.selectedCommunityChoice?.value
|
nextProps.selectedCommunityChoice?.value,
|
||||||
)),
|
)),
|
||||||
s
|
s
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -332,7 +329,8 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form className="post-form" onSubmit={linkEvent(this, handlePostSubmit)}>
|
<form className="post-form" onSubmit={linkEvent(this, handlePostSubmit)}>
|
||||||
<NavigationPrompt
|
<Prompt
|
||||||
|
message={I18NextService.i18n.t("block_leaving")}
|
||||||
when={
|
when={
|
||||||
!!(
|
!!(
|
||||||
this.state.form.name ||
|
this.state.form.name ||
|
||||||
|
@ -341,6 +339,32 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
||||||
) && !this.state.submitted
|
) && !this.state.submitted
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
<div className="mb-3 row">
|
||||||
|
<label className="col-sm-2 col-form-label" htmlFor="post-title">
|
||||||
|
{I18NextService.i18n.t("title")}
|
||||||
|
</label>
|
||||||
|
<div className="col-sm-10">
|
||||||
|
<textarea
|
||||||
|
value={this.state.form.name}
|
||||||
|
id="post-title"
|
||||||
|
onInput={linkEvent(this, handlePostNameChange)}
|
||||||
|
className={`form-control ${
|
||||||
|
!validTitle(this.state.form.name) && "is-invalid"
|
||||||
|
}`}
|
||||||
|
required
|
||||||
|
rows={1}
|
||||||
|
minLength={3}
|
||||||
|
maxLength={MAX_POST_TITLE_LENGTH}
|
||||||
|
/>
|
||||||
|
{!validTitle(this.state.form.name) && (
|
||||||
|
<div className="invalid-feedback">
|
||||||
|
{I18NextService.i18n.t("invalid_post_title")}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{this.renderSuggestedPosts()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="mb-3 row">
|
<div className="mb-3 row">
|
||||||
<label className="col-sm-2 col-form-label" htmlFor="post-url">
|
<label className="col-sm-2 col-form-label" htmlFor="post-url">
|
||||||
{I18NextService.i18n.t("url")}
|
{I18NextService.i18n.t("url")}
|
||||||
|
@ -366,7 +390,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
href={`${ghostArchiveUrl}/search?term=${encodeURIComponent(
|
href={`${ghostArchiveUrl}/search?term=${encodeURIComponent(
|
||||||
url
|
url,
|
||||||
)}`}
|
)}`}
|
||||||
className="me-2 d-inline-block float-right text-muted small fw-bold"
|
className="me-2 d-inline-block float-right text-muted small fw-bold"
|
||||||
rel={relTags}
|
rel={relTags}
|
||||||
|
@ -375,7 +399,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
href={`${archiveTodayUrl}/?run=1&url=${encodeURIComponent(
|
href={`${archiveTodayUrl}/?run=1&url=${encodeURIComponent(
|
||||||
url
|
url,
|
||||||
)}`}
|
)}`}
|
||||||
className="me-2 d-inline-block float-right text-muted small fw-bold"
|
className="me-2 d-inline-block float-right text-muted small fw-bold"
|
||||||
rel={relTags}
|
rel={relTags}
|
||||||
|
@ -446,37 +470,12 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
||||||
onAddModToCommunity={() => {}}
|
onAddModToCommunity={() => {}}
|
||||||
onAddAdmin={() => {}}
|
onAddAdmin={() => {}}
|
||||||
onTransferCommunity={() => {}}
|
onTransferCommunity={() => {}}
|
||||||
|
onMarkPostAsRead={() => {}}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mb-3 row">
|
|
||||||
<label className="col-sm-2 col-form-label" htmlFor="post-title">
|
|
||||||
{I18NextService.i18n.t("title")}
|
|
||||||
</label>
|
|
||||||
<div className="col-sm-10">
|
|
||||||
<textarea
|
|
||||||
value={this.state.form.name}
|
|
||||||
id="post-title"
|
|
||||||
onInput={linkEvent(this, handlePostNameChange)}
|
|
||||||
className={`form-control ${
|
|
||||||
!validTitle(this.state.form.name) && "is-invalid"
|
|
||||||
}`}
|
|
||||||
required
|
|
||||||
rows={1}
|
|
||||||
minLength={3}
|
|
||||||
maxLength={MAX_POST_TITLE_LENGTH}
|
|
||||||
/>
|
|
||||||
{!validTitle(this.state.form.name) && (
|
|
||||||
<div className="invalid-feedback">
|
|
||||||
{I18NextService.i18n.t("invalid_post_title")}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{this.renderSuggestedPosts()}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="mb-3 row">
|
<div className="mb-3 row">
|
||||||
<label className="col-sm-2 col-form-label">
|
<label className="col-sm-2 col-form-label">
|
||||||
{I18NextService.i18n.t("body")}
|
{I18NextService.i18n.t("body")}
|
||||||
|
@ -580,8 +579,10 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
||||||
case "loading":
|
case "loading":
|
||||||
return <Spinner />;
|
return <Spinner />;
|
||||||
case "success": {
|
case "success": {
|
||||||
const suggestedTitle = this.state.metadataRes.data.metadata.title;
|
// Clean up the title of any extra whitespace and replace with a space
|
||||||
|
const suggestedTitle = this.state.metadataRes.data.metadata.title
|
||||||
|
?.trim()
|
||||||
|
.replace(/\s+/g, " ");
|
||||||
return (
|
return (
|
||||||
suggestedTitle && (
|
suggestedTitle && (
|
||||||
<button
|
<button
|
||||||
|
@ -589,7 +590,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
||||||
className="mt-1 small border-0 bg-transparent p-0 d-block text-muted fw-bold pointer"
|
className="mt-1 small border-0 bg-transparent p-0 d-block text-muted fw-bold pointer"
|
||||||
onClick={linkEvent(
|
onClick={linkEvent(
|
||||||
{ i: this, suggestedTitle },
|
{ i: this, suggestedTitle },
|
||||||
copySuggestedTitle
|
copySuggestedTitle,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{I18NextService.i18n.t("copy_suggested_title", { title: "" })}{" "}
|
{I18NextService.i18n.t("copy_suggested_title", { title: "" })}{" "}
|
||||||
|
@ -640,6 +641,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
||||||
onAddModToCommunity={() => {}}
|
onAddModToCommunity={() => {}}
|
||||||
onAddAdmin={() => {}}
|
onAddAdmin={() => {}}
|
||||||
onTransferCommunity={() => {}}
|
onTransferCommunity={() => {}}
|
||||||
|
onMarkPostAsRead={() => {}}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
@ -651,7 +653,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
||||||
async fetchPageTitle() {
|
async fetchPageTitle() {
|
||||||
const url = this.state.form.url;
|
const url = this.state.form.url;
|
||||||
if (url && validURL(url)) {
|
if (url && validURL(url)) {
|
||||||
this.setState({ metadataRes: { state: "loading" } });
|
this.setState({ metadataRes: LOADING_REQUEST });
|
||||||
this.setState({
|
this.setState({
|
||||||
metadataRes: await HttpService.client.getSiteMetadata({ url }),
|
metadataRes: await HttpService.client.getSiteMetadata({ url }),
|
||||||
});
|
});
|
||||||
|
@ -661,7 +663,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
||||||
async fetchSimilarPosts() {
|
async fetchSimilarPosts() {
|
||||||
const q = this.state.form.name;
|
const q = this.state.form.name;
|
||||||
if (q && q !== "") {
|
if (q && q !== "") {
|
||||||
this.setState({ suggestedPostsRes: { state: "loading" } });
|
this.setState({ suggestedPostsRes: LOADING_REQUEST });
|
||||||
this.setState({
|
this.setState({
|
||||||
suggestedPostsRes: await HttpService.client.search({
|
suggestedPostsRes: await HttpService.client.search({
|
||||||
q,
|
q,
|
||||||
|
@ -671,7 +673,6 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
||||||
community_id: this.state.form.community_id,
|
community_id: this.state.form.community_id,
|
||||||
page: 1,
|
page: 1,
|
||||||
limit: trendingFetchLimit,
|
limit: trendingFetchLimit,
|
||||||
auth: myAuth(),
|
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue