mirror of
https://github.com/LemmyNet/lemmy-ui.git
synced 2024-12-22 19:01:26 +00:00
Merge branch 'main' into federation_disclaimer
This commit is contained in:
commit
7b8ce16407
189 changed files with 6967 additions and 4756 deletions
|
@ -20,10 +20,11 @@
|
|||
"@typescript-eslint/no-explicit-any": 0,
|
||||
"@typescript-eslint/explicit-module-boundary-types": 0,
|
||||
"@typescript-eslint/no-empty-function": 0,
|
||||
"@typescript-eslint/no-non-null-assertion": 0,
|
||||
"arrow-body-style": 0,
|
||||
"curly": 0,
|
||||
"eol-last": 0,
|
||||
"eqeqeq": 0,
|
||||
"eqeqeq": "error",
|
||||
"func-style": 0,
|
||||
"import/no-duplicates": 0,
|
||||
"max-statements": 0,
|
||||
|
@ -39,7 +40,7 @@
|
|||
"no-useless-constructor": 0,
|
||||
"no-useless-escape": 0,
|
||||
"no-var": 0,
|
||||
"prefer-const": 1,
|
||||
"prefer-const": "error",
|
||||
"prefer-rest-params": 0,
|
||||
"prettier/prettier": "error",
|
||||
"quote-props": 0,
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
src/shared/translations
|
||||
lemmy-translations
|
||||
src/assets/css/themes/*.css
|
||||
src/assets/css/code-themes/*.css
|
||||
stats.json
|
||||
dist
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
pipeline:
|
||||
steps:
|
||||
fetch_git_submodules:
|
||||
image: node:alpine
|
||||
image: node:20-alpine
|
||||
commands:
|
||||
- apk add git
|
||||
- git submodule init
|
||||
|
@ -8,17 +8,17 @@ pipeline:
|
|||
# - git fetch --tags
|
||||
|
||||
yarn:
|
||||
image: node:alpine
|
||||
image: node:20-alpine
|
||||
commands:
|
||||
- yarn
|
||||
|
||||
yarn_lint:
|
||||
image: node:alpine
|
||||
image: node:20-alpine
|
||||
commands:
|
||||
- yarn lint
|
||||
|
||||
yarn_build_dev:
|
||||
image: node:alpine
|
||||
image: node:20-alpine
|
||||
commands:
|
||||
- yarn build:dev
|
||||
|
||||
|
@ -29,7 +29,7 @@ pipeline:
|
|||
repo: dessalines/lemmy-ui
|
||||
dockerfile: Dockerfile
|
||||
platforms: linux/amd64
|
||||
auto_tag: true
|
||||
tag: ${CI_COMMIT_TAG}
|
||||
when:
|
||||
event: tag
|
||||
|
||||
|
@ -43,3 +43,19 @@ pipeline:
|
|||
tag: dev
|
||||
when:
|
||||
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 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.*'
|
||||
|
||||
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/node_modules /app/node_modules
|
||||
|
||||
RUN chown -R node:node /app
|
||||
|
||||
USER node
|
||||
EXPOSE 1234
|
||||
WORKDIR /app
|
||||
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
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
|
@ -28,7 +28,7 @@ RUN echo "export const VERSION = 'dev';" > "src/shared/version.ts"
|
|||
RUN yarn --prefer-offline
|
||||
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/node_modules /app/node_modules
|
||||
|
||||
|
|
|
@ -8,12 +8,12 @@ fs.readdir(translationDir, (_err, files) => {
|
|||
const lang = filename.split(".")[0];
|
||||
try {
|
||||
const json = JSON.parse(
|
||||
fs.readFileSync(translationDir + filename, "utf8")
|
||||
fs.readFileSync(translationDir + filename, "utf8"),
|
||||
);
|
||||
let data = `export const ${lang} = {\n translation: {`;
|
||||
for (const 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}",`;
|
||||
}
|
||||
}
|
||||
|
@ -67,14 +67,14 @@ ${optionKeys.map(key => `${indent}| "${key}"`).join("\n")};
|
|||
export type I18nKeys = NoOptionI18nKeys | OptionI18nKeys;
|
||||
|
||||
export type TTypedOptions<TKey extends OptionI18nKeys> =${Array.from(
|
||||
optionMap.entries()
|
||||
optionMap.entries(),
|
||||
).reduce(
|
||||
(acc, [key, options]) =>
|
||||
`${acc} TKey extends \"${key}\" ? ${
|
||||
options.reduce((acc, cur) => acc + `${cur}: string | number; `, "{ ") +
|
||||
"}"
|
||||
} :\n${indent}`,
|
||||
""
|
||||
"",
|
||||
)} (Record<string, unknown> | string);
|
||||
|
||||
export interface TFunctionTyped {
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit a241fe1255a6363c7ae1ec5a09520c066745e6ce
|
||||
Subproject commit 6fbc86932a03c4d40829ee4a3395259b2a7660e5
|
77
package.json
77
package.json
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "lemmy-ui",
|
||||
"version": "0.18.1-rc.11",
|
||||
"version": "0.19.0-rc.3",
|
||||
"description": "An isomorphic UI for lemmy",
|
||||
"repository": "https://github.com/LemmyNet/lemmy-ui",
|
||||
"license": "AGPL-3.0",
|
||||
|
@ -15,6 +15,7 @@
|
|||
"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}\"",
|
||||
"prepare": "husky install",
|
||||
"postinstall": "husky install",
|
||||
"themes:build": "sass 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",
|
||||
|
@ -34,23 +35,24 @@
|
|||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/plugin-proposal-decorators": "^7.21.0",
|
||||
"@babel/plugin-transform-runtime": "^7.21.4",
|
||||
"@babel/plugin-transform-typescript": "^7.21.3",
|
||||
"@babel/plugin-proposal-decorators": "^7.21.5",
|
||||
"@babel/plugin-transform-runtime": "^7.21.5",
|
||||
"@babel/plugin-transform-typescript": "^7.21.5",
|
||||
"@babel/preset-env": "7.21.5",
|
||||
"@babel/preset-typescript": "^7.21.5",
|
||||
"@babel/runtime": "^7.21.5",
|
||||
"@emoji-mart/data": "^1.1.0",
|
||||
"@shortcm/qr-image": "^9.0.2",
|
||||
"autosize": "^6.0.1",
|
||||
"babel-loader": "^9.1.2",
|
||||
"babel-plugin-inferno": "^6.6.0",
|
||||
"bootstrap": "^5.2.3",
|
||||
"bootstrap": "^5.3.1",
|
||||
"check-password-strength": "^2.0.7",
|
||||
"classnames": "^2.3.1",
|
||||
"clean-webpack-plugin": "^4.0.0",
|
||||
"cookie": "^0.5.0",
|
||||
"copy-webpack-plugin": "^11.0.0",
|
||||
"cross-fetch": "^3.1.5",
|
||||
"cross-fetch": "^4.0.0",
|
||||
"css-loader": "^6.7.3",
|
||||
"date-fns": "^2.30.0",
|
||||
"emoji-mart": "^5.4.0",
|
||||
|
@ -58,22 +60,24 @@
|
|||
"express": "~4.18.2",
|
||||
"history": "^5.3.0",
|
||||
"html-to-text": "^9.0.5",
|
||||
"i18next": "^22.4.15",
|
||||
"inferno": "^8.1.1",
|
||||
"inferno-create-element": "^8.1.1",
|
||||
"husky": "^8.0.3",
|
||||
"i18next": "^23.3.0",
|
||||
"inferno": "^8.2.2",
|
||||
"inferno-create-element": "^8.2.2",
|
||||
"inferno-helmet": "^5.2.1",
|
||||
"inferno-hydrate": "^8.1.1",
|
||||
"inferno-hydrate": "^8.2.2",
|
||||
"inferno-i18next-dess": "0.0.2",
|
||||
"inferno-router": "^8.1.1",
|
||||
"inferno-server": "^8.1.1",
|
||||
"inferno-router": "^8.2.2",
|
||||
"inferno-server": "^8.2.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.merge": "^4.6.2",
|
||||
"markdown-it": "^13.0.1",
|
||||
"markdown-it-bidi": "^0.1.0",
|
||||
"markdown-it-container": "^3.0.0",
|
||||
"markdown-it-emoji": "^2.0.2",
|
||||
"markdown-it-footnote": "^3.0.3",
|
||||
"markdown-it-highlightjs": "^4.0.1",
|
||||
"markdown-it-html5-embed": "^1.0.0",
|
||||
"markdown-it-ruby": "^0.1.1",
|
||||
"markdown-it-sub": "^1.0.0",
|
||||
|
@ -81,21 +85,23 @@
|
|||
"mini-css-extract-plugin": "^2.7.5",
|
||||
"register-service-worker": "^1.7.2",
|
||||
"run-node-webpack-plugin": "^1.3.0",
|
||||
"sanitize-html": "^2.10.0",
|
||||
"sass": "^1.62.1",
|
||||
"sass-loader": "^13.2.2",
|
||||
"rxjs": "^7.8.1",
|
||||
"sanitize-html": "^2.11.0",
|
||||
"sass": "^1.64.1",
|
||||
"sass-loader": "^13.3.2",
|
||||
"serialize-javascript": "^6.0.1",
|
||||
"service-worker-webpack": "^1.0.0",
|
||||
"sharp": "^0.32.1",
|
||||
"sharp": "^0.32.4",
|
||||
"tippy.js": "^6.3.7",
|
||||
"toastify-js": "^1.12.0",
|
||||
"tributejs": "^5.1.3",
|
||||
"webpack": "5.82.1",
|
||||
"webpack-cli": "^5.1.1",
|
||||
"webpack": "5.88.2",
|
||||
"webpack-cli": "^5.1.4",
|
||||
"webpack-node-externals": "^3.0.0"
|
||||
},
|
||||
"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/bootstrap": "^5.2.6",
|
||||
"@types/cookie": "^0.5.1",
|
||||
|
@ -103,33 +109,32 @@
|
|||
"@types/html-to-text": "^9.0.0",
|
||||
"@types/lodash.isequal": "^4.5.6",
|
||||
"@types/markdown-it": "^12.2.3",
|
||||
"@types/markdown-it-container": "^2.0.5",
|
||||
"@types/node": "^20.1.2",
|
||||
"@types/markdown-it-container": "^2.0.6",
|
||||
"@types/node": "^20.4.5",
|
||||
"@types/path-browserify": "^1.0.0",
|
||||
"@types/sanitize-html": "^2.9.0",
|
||||
"@types/serialize-javascript": "^5.0.1",
|
||||
"@types/toastify-js": "^1.11.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.59.5",
|
||||
"@typescript-eslint/parser": "^5.59.5",
|
||||
"eslint": "^8.40.0",
|
||||
"@types/toastify-js": "^1.12.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.2.0",
|
||||
"@typescript-eslint/parser": "^6.2.0",
|
||||
"eslint": "^8.45.0",
|
||||
"eslint-plugin-inferno": "^7.32.2",
|
||||
"eslint-plugin-jsx-a11y": "^6.7.1",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"husky": "^8.0.3",
|
||||
"eslint-plugin-prettier": "^5.0.0",
|
||||
"import-sort-style-module": "^6.0.0",
|
||||
"lint-staged": "^13.2.2",
|
||||
"prettier": "^2.8.8",
|
||||
"lint-staged": "^13.2.3",
|
||||
"prettier": "^3.0.0",
|
||||
"prettier-plugin-import-sort": "^0.0.7",
|
||||
"prettier-plugin-organize-imports": "^3.2.2",
|
||||
"prettier-plugin-packagejson": "^2.4.3",
|
||||
"prettier-plugin-organize-imports": "^3.2.3",
|
||||
"prettier-plugin-packagejson": "^2.4.5",
|
||||
"rimraf": "^5.0.0",
|
||||
"sortpack": "^2.3.4",
|
||||
"style-loader": "^3.3.2",
|
||||
"terser": "^5.17.3",
|
||||
"typescript": "^5.0.4",
|
||||
"terser": "^5.19.2",
|
||||
"typescript": "^5.1.6",
|
||||
"typescript-language-server": "^3.3.2",
|
||||
"webpack-bundle-analyzer": "^4.9.0",
|
||||
"webpack-dev-server": "4.15.0"
|
||||
"webpack-dev-server": "4.15.1"
|
||||
},
|
||||
"packageManager": "yarn@1.22.19",
|
||||
"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;
|
||||
}
|
||||
|
||||
.img-blur {
|
||||
.img-blur-thumb {
|
||||
filter: blur(10px);
|
||||
-webkit-filter: blur(10px);
|
||||
-moz-filter: blur(10px);
|
||||
|
@ -259,6 +259,18 @@ hr {
|
|||
-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 {
|
||||
max-height: 90vh;
|
||||
}
|
||||
|
@ -436,3 +448,7 @@ br.big {
|
|||
.skip-link:focus {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.totp-link {
|
||||
width: fit-content;
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ $green: #00bc8c;
|
|||
$cyan: #3498db;
|
||||
|
||||
$primary: $green;
|
||||
$secondary: $gray-700;
|
||||
$secondary: $gray-600;
|
||||
$success: $green;
|
||||
$dark: $gray-300;
|
||||
|
||||
|
@ -30,9 +30,18 @@ $mark-bg: $gray-900;
|
|||
$text-muted: $gray-600;
|
||||
$yiq-contrasted-threshold: 175;
|
||||
|
||||
$font-family-sans-serif: "Lato", -apple-system, BlinkMacSystemFont, "Segoe UI",
|
||||
Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji",
|
||||
"Segoe UI Emoji", "Segoe UI Symbol";
|
||||
$font-family-sans-serif:
|
||||
"Lato",
|
||||
-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;
|
||||
$h1-font-size: 3rem;
|
||||
$h2-font-size: 2.5rem;
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
@import "variables.darkly";
|
||||
|
||||
$primary: $blue;
|
||||
$secondary: #444;
|
||||
$light: $gray-800;
|
||||
|
||||
$link-color: $red;
|
||||
|
|
|
@ -17,7 +17,7 @@ $green: #00bc8c;
|
|||
$cyan: #3498db;
|
||||
|
||||
$primary: $green;
|
||||
$secondary: $gray-700;
|
||||
$secondary: $gray-500;
|
||||
$success: $green;
|
||||
$dark: $gray-300;
|
||||
|
||||
|
@ -29,9 +29,18 @@ $mark-bg: #333;
|
|||
$text-muted: $gray-600;
|
||||
$yiq-contrasted-threshold: 175;
|
||||
|
||||
$font-family-sans-serif: "Lato", -apple-system, BlinkMacSystemFont, "Segoe UI",
|
||||
Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji",
|
||||
"Segoe UI Emoji", "Segoe UI Symbol";
|
||||
$font-family-sans-serif:
|
||||
"Lato",
|
||||
-apple-system,
|
||||
BlinkMacSystemFont,
|
||||
"Segoe UI",
|
||||
Roboto,
|
||||
"Helvetica Neue",
|
||||
Arial,
|
||||
sans-serif,
|
||||
"Apple Color Emoji",
|
||||
"Segoe UI Emoji",
|
||||
"Segoe UI Symbol";
|
||||
$h1-font-size: 3rem;
|
||||
$h2-font-size: 2.5rem;
|
||||
$h3-font-size: 2rem;
|
||||
|
|
|
@ -26,7 +26,7 @@ $danger: #aa0000;
|
|||
$info: #00aaaa;
|
||||
$warning: #aa00aa;
|
||||
$light: $gray-800;
|
||||
$dark: black;
|
||||
$dark: $gray-300;
|
||||
|
||||
$body-bg: #000084;
|
||||
$body-color: $gray-300;
|
||||
|
|
|
@ -9,6 +9,7 @@ $gray-800: #303030;
|
|||
$gray-900: #222;
|
||||
|
||||
$light: $gray-700;
|
||||
$dark: $gray-200;
|
||||
|
||||
$body-bg: $gray-900;
|
||||
$body-color: $gray-200;
|
||||
|
|
|
@ -70,7 +70,7 @@ hr.my-3 {
|
|||
--bs-gray-800: #303030;
|
||||
--bs-gray-900: #222;
|
||||
--bs-primary: #00bc8c;
|
||||
--bs-secondary: #444;
|
||||
--bs-secondary: #adb5bd;
|
||||
--bs-success: #00bc8c;
|
||||
--bs-info: #3498db;
|
||||
--bs-warning: #f39c12;
|
||||
|
@ -78,7 +78,7 @@ hr.my-3 {
|
|||
--bs-light: #303030;
|
||||
--bs-dark: #dee2e6;
|
||||
--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-info-rgb: 52, 152, 219;
|
||||
--bs-warning-rgb: 243, 156, 18;
|
||||
|
@ -86,7 +86,7 @@ hr.my-3 {
|
|||
--bs-light-rgb: 48, 48, 48;
|
||||
--bs-dark-rgb: 222, 226, 230;
|
||||
--bs-primary-text-emphasis: #004b38;
|
||||
--bs-secondary-text-emphasis: #1b1b1b;
|
||||
--bs-secondary-text-emphasis: #45484c;
|
||||
--bs-success-text-emphasis: #004b38;
|
||||
--bs-info-text-emphasis: #153d58;
|
||||
--bs-warning-text-emphasis: #613e07;
|
||||
|
@ -94,7 +94,7 @@ hr.my-3 {
|
|||
--bs-light-text-emphasis: #444;
|
||||
--bs-dark-text-emphasis: #444;
|
||||
--bs-primary-bg-subtle: #ccf2e8;
|
||||
--bs-secondary-bg-subtle: #dadada;
|
||||
--bs-secondary-bg-subtle: #eff0f2;
|
||||
--bs-success-bg-subtle: #ccf2e8;
|
||||
--bs-info-bg-subtle: #d6eaf8;
|
||||
--bs-warning-bg-subtle: #fdebd0;
|
||||
|
@ -102,7 +102,7 @@ hr.my-3 {
|
|||
--bs-light-bg-subtle: #fcfcfd;
|
||||
--bs-dark-bg-subtle: #ced4da;
|
||||
--bs-primary-border-subtle: #99e4d1;
|
||||
--bs-secondary-border-subtle: #b4b4b4;
|
||||
--bs-secondary-border-subtle: #dee1e5;
|
||||
--bs-success-border-subtle: #99e4d1;
|
||||
--bs-info-border-subtle: #aed6f1;
|
||||
--bs-warning-border-subtle: #fad7a0;
|
||||
|
@ -182,7 +182,7 @@ hr.my-3 {
|
|||
--bs-tertiary-bg: #292929;
|
||||
--bs-tertiary-bg-rgb: 41, 41, 41;
|
||||
--bs-primary-text-emphasis: #66d7ba;
|
||||
--bs-secondary-text-emphasis: #8f8f8f;
|
||||
--bs-secondary-text-emphasis: #ced3d7;
|
||||
--bs-success-text-emphasis: #66d7ba;
|
||||
--bs-info-text-emphasis: #85c1e9;
|
||||
--bs-warning-text-emphasis: #f8c471;
|
||||
|
@ -190,7 +190,7 @@ hr.my-3 {
|
|||
--bs-light-text-emphasis: #f8f9fa;
|
||||
--bs-dark-text-emphasis: #dee2e6;
|
||||
--bs-primary-bg-subtle: #00261c;
|
||||
--bs-secondary-bg-subtle: #0e0e0e;
|
||||
--bs-secondary-bg-subtle: #232426;
|
||||
--bs-success-bg-subtle: #00261c;
|
||||
--bs-info-bg-subtle: #0a1e2c;
|
||||
--bs-warning-bg-subtle: #311f04;
|
||||
|
@ -198,7 +198,7 @@ hr.my-3 {
|
|||
--bs-light-bg-subtle: #303030;
|
||||
--bs-dark-bg-subtle: #181818;
|
||||
--bs-primary-border-subtle: #007154;
|
||||
--bs-secondary-border-subtle: #292929;
|
||||
--bs-secondary-border-subtle: #686d71;
|
||||
--bs-success-border-subtle: #007154;
|
||||
--bs-info-border-subtle: #1f5b83;
|
||||
--bs-warning-border-subtle: #925e0b;
|
||||
|
@ -1961,13 +1961,13 @@ progress {
|
|||
|
||||
.table-secondary {
|
||||
--bs-table-color: #000;
|
||||
--bs-table-bg: #dadada;
|
||||
--bs-table-border-color: #c4c4c4;
|
||||
--bs-table-striped-bg: #cfcfcf;
|
||||
--bs-table-bg: #eff0f2;
|
||||
--bs-table-border-color: #d7d8da;
|
||||
--bs-table-striped-bg: #e3e4e6;
|
||||
--bs-table-striped-color: #000;
|
||||
--bs-table-active-bg: #c4c4c4;
|
||||
--bs-table-active-bg: #d7d8da;
|
||||
--bs-table-active-color: #000;
|
||||
--bs-table-hover-bg: #cacaca;
|
||||
--bs-table-hover-bg: #dddee0;
|
||||
--bs-table-hover-color: #000;
|
||||
color: var(--bs-table-color);
|
||||
border-color: var(--bs-table-border-color);
|
||||
|
@ -2994,20 +2994,20 @@ textarea.form-control-lg {
|
|||
}
|
||||
|
||||
.btn-secondary {
|
||||
--bs-btn-color: #fff;
|
||||
--bs-btn-bg: #444;
|
||||
--bs-btn-border-color: #444;
|
||||
--bs-btn-hover-color: #fff;
|
||||
--bs-btn-hover-bg: #3a3a3a;
|
||||
--bs-btn-hover-border-color: #363636;
|
||||
--bs-btn-focus-shadow-rgb: 96, 96, 96;
|
||||
--bs-btn-active-color: #fff;
|
||||
--bs-btn-active-bg: #363636;
|
||||
--bs-btn-active-border-color: #333333;
|
||||
--bs-btn-color: #000;
|
||||
--bs-btn-bg: #adb5bd;
|
||||
--bs-btn-border-color: #adb5bd;
|
||||
--bs-btn-hover-color: #000;
|
||||
--bs-btn-hover-bg: #b9c0c7;
|
||||
--bs-btn-hover-border-color: #b5bcc4;
|
||||
--bs-btn-focus-shadow-rgb: 147, 154, 161;
|
||||
--bs-btn-active-color: #000;
|
||||
--bs-btn-active-bg: #bdc4ca;
|
||||
--bs-btn-active-border-color: #b5bcc4;
|
||||
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
|
||||
--bs-btn-disabled-color: #fff;
|
||||
--bs-btn-disabled-bg: #444;
|
||||
--bs-btn-disabled-border-color: #444;
|
||||
--bs-btn-disabled-color: #000;
|
||||
--bs-btn-disabled-bg: #adb5bd;
|
||||
--bs-btn-disabled-border-color: #adb5bd;
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
|
@ -3130,19 +3130,19 @@ textarea.form-control-lg {
|
|||
}
|
||||
|
||||
.btn-outline-secondary {
|
||||
--bs-btn-color: #444;
|
||||
--bs-btn-border-color: #444;
|
||||
--bs-btn-hover-color: #fff;
|
||||
--bs-btn-hover-bg: #444;
|
||||
--bs-btn-hover-border-color: #444;
|
||||
--bs-btn-focus-shadow-rgb: 68, 68, 68;
|
||||
--bs-btn-active-color: #fff;
|
||||
--bs-btn-active-bg: #444;
|
||||
--bs-btn-active-border-color: #444;
|
||||
--bs-btn-color: #adb5bd;
|
||||
--bs-btn-border-color: #adb5bd;
|
||||
--bs-btn-hover-color: #000;
|
||||
--bs-btn-hover-bg: #adb5bd;
|
||||
--bs-btn-hover-border-color: #adb5bd;
|
||||
--bs-btn-focus-shadow-rgb: 173, 181, 189;
|
||||
--bs-btn-active-color: #000;
|
||||
--bs-btn-active-bg: #adb5bd;
|
||||
--bs-btn-active-border-color: #adb5bd;
|
||||
--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-border-color: #444;
|
||||
--bs-btn-disabled-border-color: #adb5bd;
|
||||
--bs-gradient: none;
|
||||
}
|
||||
|
||||
|
@ -6777,8 +6777,8 @@ textarea.form-control-lg {
|
|||
}
|
||||
|
||||
.text-bg-secondary {
|
||||
color: #fff !important;
|
||||
background-color: RGBA(68, 68, 68, var(--bs-bg-opacity, 1)) !important;
|
||||
color: #000 !important;
|
||||
background-color: RGBA(173, 181, 189, var(--bs-bg-opacity, 1)) !important;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
.link-secondary:hover, .link-secondary:focus {
|
||||
color: RGBA(54, 54, 54, var(--bs-link-opacity, 1)) !important;
|
||||
text-decoration-color: RGBA(54, 54, 54, var(--bs-link-underline-opacity, 1)) !important;
|
||||
color: RGBA(189, 196, 202, var(--bs-link-opacity, 1)) !important;
|
||||
text-decoration-color: RGBA(189, 196, 202, var(--bs-link-underline-opacity, 1)) !important;
|
||||
}
|
||||
|
||||
.link-success {
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
--bs-gray-800: #202020;
|
||||
--bs-gray-900: #111;
|
||||
--bs-primary: #00bc8c;
|
||||
--bs-secondary: #333;
|
||||
--bs-secondary: #666;
|
||||
--bs-success: #00bc8c;
|
||||
--bs-info: #3498db;
|
||||
--bs-warning: #f39c12;
|
||||
|
@ -38,7 +38,7 @@
|
|||
--bs-light: #111;
|
||||
--bs-dark: #dee2e6;
|
||||
--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-info-rgb: 52, 152, 219;
|
||||
--bs-warning-rgb: 243, 156, 18;
|
||||
|
@ -46,7 +46,7 @@
|
|||
--bs-light-rgb: 17, 17, 17;
|
||||
--bs-dark-rgb: 222, 226, 230;
|
||||
--bs-primary-text-emphasis: #004b38;
|
||||
--bs-secondary-text-emphasis: #141414;
|
||||
--bs-secondary-text-emphasis: #292929;
|
||||
--bs-success-text-emphasis: #004b38;
|
||||
--bs-info-text-emphasis: #153d58;
|
||||
--bs-warning-text-emphasis: #613e07;
|
||||
|
@ -54,7 +54,7 @@
|
|||
--bs-light-text-emphasis: #333;
|
||||
--bs-dark-text-emphasis: #333;
|
||||
--bs-primary-bg-subtle: #ccf2e8;
|
||||
--bs-secondary-bg-subtle: #d6d6d6;
|
||||
--bs-secondary-bg-subtle: #e0e0e0;
|
||||
--bs-success-bg-subtle: #ccf2e8;
|
||||
--bs-info-bg-subtle: #d6eaf8;
|
||||
--bs-warning-bg-subtle: #fdebd0;
|
||||
|
@ -62,7 +62,7 @@
|
|||
--bs-light-bg-subtle: #f6f6f7;
|
||||
--bs-dark-bg-subtle: #ced4da;
|
||||
--bs-primary-border-subtle: #99e4d1;
|
||||
--bs-secondary-border-subtle: #adadad;
|
||||
--bs-secondary-border-subtle: #c2c2c2;
|
||||
--bs-success-border-subtle: #99e4d1;
|
||||
--bs-info-border-subtle: #aed6f1;
|
||||
--bs-warning-border-subtle: #fad7a0;
|
||||
|
@ -142,7 +142,7 @@
|
|||
--bs-tertiary-bg: #191919;
|
||||
--bs-tertiary-bg-rgb: 25, 25, 25;
|
||||
--bs-primary-text-emphasis: #66d7ba;
|
||||
--bs-secondary-text-emphasis: #858585;
|
||||
--bs-secondary-text-emphasis: #a3a3a3;
|
||||
--bs-success-text-emphasis: #66d7ba;
|
||||
--bs-info-text-emphasis: #85c1e9;
|
||||
--bs-warning-text-emphasis: #f8c471;
|
||||
|
@ -150,7 +150,7 @@
|
|||
--bs-light-text-emphasis: #f8f9fa;
|
||||
--bs-dark-text-emphasis: #dee2e6;
|
||||
--bs-primary-bg-subtle: #00261c;
|
||||
--bs-secondary-bg-subtle: #0a0a0a;
|
||||
--bs-secondary-bg-subtle: #141414;
|
||||
--bs-success-bg-subtle: #00261c;
|
||||
--bs-info-bg-subtle: #0a1e2c;
|
||||
--bs-warning-bg-subtle: #311f04;
|
||||
|
@ -158,7 +158,7 @@
|
|||
--bs-light-bg-subtle: #202020;
|
||||
--bs-dark-bg-subtle: #101010;
|
||||
--bs-primary-border-subtle: #007154;
|
||||
--bs-secondary-border-subtle: #1f1f1f;
|
||||
--bs-secondary-border-subtle: #3d3d3d;
|
||||
--bs-success-border-subtle: #007154;
|
||||
--bs-info-border-subtle: #1f5b83;
|
||||
--bs-warning-border-subtle: #925e0b;
|
||||
|
@ -1945,13 +1945,13 @@ progress {
|
|||
|
||||
.table-secondary {
|
||||
--bs-table-color: #000;
|
||||
--bs-table-bg: #d6d6d6;
|
||||
--bs-table-border-color: #c1c1c1;
|
||||
--bs-table-striped-bg: #cbcbcb;
|
||||
--bs-table-bg: #e0e0e0;
|
||||
--bs-table-border-color: #cacaca;
|
||||
--bs-table-striped-bg: #d5d5d5;
|
||||
--bs-table-striped-color: #000;
|
||||
--bs-table-active-bg: #c1c1c1;
|
||||
--bs-table-active-bg: #cacaca;
|
||||
--bs-table-active-color: #000;
|
||||
--bs-table-hover-bg: #c6c6c6;
|
||||
--bs-table-hover-bg: #cfcfcf;
|
||||
--bs-table-hover-color: #000;
|
||||
color: var(--bs-table-color);
|
||||
border-color: var(--bs-table-border-color);
|
||||
|
@ -2979,19 +2979,19 @@ textarea.form-control-lg {
|
|||
|
||||
.btn-secondary {
|
||||
--bs-btn-color: #f3f3f3;
|
||||
--bs-btn-bg: #333;
|
||||
--bs-btn-border-color: #333;
|
||||
--bs-btn-bg: #666;
|
||||
--bs-btn-border-color: #666;
|
||||
--bs-btn-hover-color: #f3f3f3;
|
||||
--bs-btn-hover-bg: #2b2b2b;
|
||||
--bs-btn-hover-border-color: #292929;
|
||||
--bs-btn-focus-shadow-rgb: 80, 80, 80;
|
||||
--bs-btn-hover-bg: #575757;
|
||||
--bs-btn-hover-border-color: #525252;
|
||||
--bs-btn-focus-shadow-rgb: 123, 123, 123;
|
||||
--bs-btn-active-color: #f3f3f3;
|
||||
--bs-btn-active-bg: #292929;
|
||||
--bs-btn-active-border-color: #262626;
|
||||
--bs-btn-active-bg: #525252;
|
||||
--bs-btn-active-border-color: #4d4d4d;
|
||||
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
|
||||
--bs-btn-disabled-color: #f3f3f3;
|
||||
--bs-btn-disabled-bg: #333;
|
||||
--bs-btn-disabled-border-color: #333;
|
||||
--bs-btn-disabled-bg: #666;
|
||||
--bs-btn-disabled-border-color: #666;
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
|
@ -3114,19 +3114,19 @@ textarea.form-control-lg {
|
|||
}
|
||||
|
||||
.btn-outline-secondary {
|
||||
--bs-btn-color: #333;
|
||||
--bs-btn-border-color: #333;
|
||||
--bs-btn-color: #666;
|
||||
--bs-btn-border-color: #666;
|
||||
--bs-btn-hover-color: #f3f3f3;
|
||||
--bs-btn-hover-bg: #333;
|
||||
--bs-btn-hover-border-color: #333;
|
||||
--bs-btn-focus-shadow-rgb: 51, 51, 51;
|
||||
--bs-btn-hover-bg: #666;
|
||||
--bs-btn-hover-border-color: #666;
|
||||
--bs-btn-focus-shadow-rgb: 102, 102, 102;
|
||||
--bs-btn-active-color: #f3f3f3;
|
||||
--bs-btn-active-bg: #333;
|
||||
--bs-btn-active-border-color: #333;
|
||||
--bs-btn-active-bg: #666;
|
||||
--bs-btn-active-border-color: #666;
|
||||
--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-border-color: #333;
|
||||
--bs-btn-disabled-border-color: #666;
|
||||
--bs-gradient: none;
|
||||
}
|
||||
|
||||
|
@ -6766,7 +6766,7 @@ textarea.form-control-lg {
|
|||
|
||||
.text-bg-secondary {
|
||||
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 {
|
||||
|
@ -6813,8 +6813,8 @@ textarea.form-control-lg {
|
|||
text-decoration-color: RGBA(var(--bs-secondary-rgb), var(--bs-link-underline-opacity, 1)) !important;
|
||||
}
|
||||
.link-secondary:hover, .link-secondary:focus {
|
||||
color: RGBA(41, 41, 41, var(--bs-link-opacity, 1)) !important;
|
||||
text-decoration-color: RGBA(41, 41, 41, var(--bs-link-underline-opacity, 1)) !important;
|
||||
color: RGBA(82, 82, 82, var(--bs-link-opacity, 1)) !important;
|
||||
text-decoration-color: RGBA(82, 82, 82, var(--bs-link-underline-opacity, 1)) !important;
|
||||
}
|
||||
|
||||
.link-success {
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
--bs-gray-800: #303030;
|
||||
--bs-gray-900: #222;
|
||||
--bs-primary: #375a7f;
|
||||
--bs-secondary: #444;
|
||||
--bs-secondary: #adb5bd;
|
||||
--bs-success: #00bc8c;
|
||||
--bs-info: #3498db;
|
||||
--bs-warning: #f39c12;
|
||||
|
@ -38,7 +38,7 @@
|
|||
--bs-light: #303030;
|
||||
--bs-dark: #dee2e6;
|
||||
--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-info-rgb: 52, 152, 219;
|
||||
--bs-warning-rgb: 243, 156, 18;
|
||||
|
@ -46,7 +46,7 @@
|
|||
--bs-light-rgb: 48, 48, 48;
|
||||
--bs-dark-rgb: 222, 226, 230;
|
||||
--bs-primary-text-emphasis: #162433;
|
||||
--bs-secondary-text-emphasis: #1b1b1b;
|
||||
--bs-secondary-text-emphasis: #45484c;
|
||||
--bs-success-text-emphasis: #004b38;
|
||||
--bs-info-text-emphasis: #153d58;
|
||||
--bs-warning-text-emphasis: #613e07;
|
||||
|
@ -54,7 +54,7 @@
|
|||
--bs-light-text-emphasis: #444;
|
||||
--bs-dark-text-emphasis: #444;
|
||||
--bs-primary-bg-subtle: #d7dee5;
|
||||
--bs-secondary-bg-subtle: #dadada;
|
||||
--bs-secondary-bg-subtle: #eff0f2;
|
||||
--bs-success-bg-subtle: #ccf2e8;
|
||||
--bs-info-bg-subtle: #d6eaf8;
|
||||
--bs-warning-bg-subtle: #fdebd0;
|
||||
|
@ -62,7 +62,7 @@
|
|||
--bs-light-bg-subtle: #fcfcfd;
|
||||
--bs-dark-bg-subtle: #ced4da;
|
||||
--bs-primary-border-subtle: #afbdcc;
|
||||
--bs-secondary-border-subtle: #b4b4b4;
|
||||
--bs-secondary-border-subtle: #dee1e5;
|
||||
--bs-success-border-subtle: #99e4d1;
|
||||
--bs-info-border-subtle: #aed6f1;
|
||||
--bs-warning-border-subtle: #fad7a0;
|
||||
|
@ -142,7 +142,7 @@
|
|||
--bs-tertiary-bg: #292929;
|
||||
--bs-tertiary-bg-rgb: 41, 41, 41;
|
||||
--bs-primary-text-emphasis: #879cb2;
|
||||
--bs-secondary-text-emphasis: #8f8f8f;
|
||||
--bs-secondary-text-emphasis: #ced3d7;
|
||||
--bs-success-text-emphasis: #66d7ba;
|
||||
--bs-info-text-emphasis: #85c1e9;
|
||||
--bs-warning-text-emphasis: #f8c471;
|
||||
|
@ -150,7 +150,7 @@
|
|||
--bs-light-text-emphasis: #f8f9fa;
|
||||
--bs-dark-text-emphasis: #dee2e6;
|
||||
--bs-primary-bg-subtle: #0b1219;
|
||||
--bs-secondary-bg-subtle: #0e0e0e;
|
||||
--bs-secondary-bg-subtle: #232426;
|
||||
--bs-success-bg-subtle: #00261c;
|
||||
--bs-info-bg-subtle: #0a1e2c;
|
||||
--bs-warning-bg-subtle: #311f04;
|
||||
|
@ -158,7 +158,7 @@
|
|||
--bs-light-bg-subtle: #303030;
|
||||
--bs-dark-bg-subtle: #181818;
|
||||
--bs-primary-border-subtle: #21364c;
|
||||
--bs-secondary-border-subtle: #292929;
|
||||
--bs-secondary-border-subtle: #686d71;
|
||||
--bs-success-border-subtle: #007154;
|
||||
--bs-info-border-subtle: #1f5b83;
|
||||
--bs-warning-border-subtle: #925e0b;
|
||||
|
@ -1945,13 +1945,13 @@ progress {
|
|||
|
||||
.table-secondary {
|
||||
--bs-table-color: #000;
|
||||
--bs-table-bg: #dadada;
|
||||
--bs-table-border-color: #c4c4c4;
|
||||
--bs-table-striped-bg: #cfcfcf;
|
||||
--bs-table-bg: #eff0f2;
|
||||
--bs-table-border-color: #d7d8da;
|
||||
--bs-table-striped-bg: #e3e4e6;
|
||||
--bs-table-striped-color: #000;
|
||||
--bs-table-active-bg: #c4c4c4;
|
||||
--bs-table-active-bg: #d7d8da;
|
||||
--bs-table-active-color: #000;
|
||||
--bs-table-hover-bg: #cacaca;
|
||||
--bs-table-hover-bg: #dddee0;
|
||||
--bs-table-hover-color: #000;
|
||||
color: var(--bs-table-color);
|
||||
border-color: var(--bs-table-border-color);
|
||||
|
@ -2978,20 +2978,20 @@ textarea.form-control-lg {
|
|||
}
|
||||
|
||||
.btn-secondary {
|
||||
--bs-btn-color: #fff;
|
||||
--bs-btn-bg: #444;
|
||||
--bs-btn-border-color: #444;
|
||||
--bs-btn-hover-color: #fff;
|
||||
--bs-btn-hover-bg: #3a3a3a;
|
||||
--bs-btn-hover-border-color: #363636;
|
||||
--bs-btn-focus-shadow-rgb: 96, 96, 96;
|
||||
--bs-btn-active-color: #fff;
|
||||
--bs-btn-active-bg: #363636;
|
||||
--bs-btn-active-border-color: #333333;
|
||||
--bs-btn-color: #000;
|
||||
--bs-btn-bg: #adb5bd;
|
||||
--bs-btn-border-color: #adb5bd;
|
||||
--bs-btn-hover-color: #000;
|
||||
--bs-btn-hover-bg: #b9c0c7;
|
||||
--bs-btn-hover-border-color: #b5bcc4;
|
||||
--bs-btn-focus-shadow-rgb: 147, 154, 161;
|
||||
--bs-btn-active-color: #000;
|
||||
--bs-btn-active-bg: #bdc4ca;
|
||||
--bs-btn-active-border-color: #b5bcc4;
|
||||
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
|
||||
--bs-btn-disabled-color: #fff;
|
||||
--bs-btn-disabled-bg: #444;
|
||||
--bs-btn-disabled-border-color: #444;
|
||||
--bs-btn-disabled-color: #000;
|
||||
--bs-btn-disabled-bg: #adb5bd;
|
||||
--bs-btn-disabled-border-color: #adb5bd;
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
|
@ -3114,19 +3114,19 @@ textarea.form-control-lg {
|
|||
}
|
||||
|
||||
.btn-outline-secondary {
|
||||
--bs-btn-color: #444;
|
||||
--bs-btn-border-color: #444;
|
||||
--bs-btn-hover-color: #fff;
|
||||
--bs-btn-hover-bg: #444;
|
||||
--bs-btn-hover-border-color: #444;
|
||||
--bs-btn-focus-shadow-rgb: 68, 68, 68;
|
||||
--bs-btn-active-color: #fff;
|
||||
--bs-btn-active-bg: #444;
|
||||
--bs-btn-active-border-color: #444;
|
||||
--bs-btn-color: #adb5bd;
|
||||
--bs-btn-border-color: #adb5bd;
|
||||
--bs-btn-hover-color: #000;
|
||||
--bs-btn-hover-bg: #adb5bd;
|
||||
--bs-btn-hover-border-color: #adb5bd;
|
||||
--bs-btn-focus-shadow-rgb: 173, 181, 189;
|
||||
--bs-btn-active-color: #000;
|
||||
--bs-btn-active-bg: #adb5bd;
|
||||
--bs-btn-active-border-color: #adb5bd;
|
||||
--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-border-color: #444;
|
||||
--bs-btn-disabled-border-color: #adb5bd;
|
||||
--bs-gradient: none;
|
||||
}
|
||||
|
||||
|
@ -6765,8 +6765,8 @@ textarea.form-control-lg {
|
|||
}
|
||||
|
||||
.text-bg-secondary {
|
||||
color: #fff !important;
|
||||
background-color: RGBA(68, 68, 68, var(--bs-bg-opacity, 1)) !important;
|
||||
color: #000 !important;
|
||||
background-color: RGBA(173, 181, 189, var(--bs-bg-opacity, 1)) !important;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
.link-secondary:hover, .link-secondary:focus {
|
||||
color: RGBA(54, 54, 54, var(--bs-link-opacity, 1)) !important;
|
||||
text-decoration-color: RGBA(54, 54, 54, var(--bs-link-underline-opacity, 1)) !important;
|
||||
color: RGBA(189, 196, 202, var(--bs-link-opacity, 1)) !important;
|
||||
text-decoration-color: RGBA(189, 196, 202, var(--bs-link-underline-opacity, 1)) !important;
|
||||
}
|
||||
|
||||
.link-success {
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
--bs-gray-800: #303030;
|
||||
--bs-gray-900: #222;
|
||||
--bs-primary: #00bc8c;
|
||||
--bs-secondary: #444;
|
||||
--bs-secondary: #adb5bd;
|
||||
--bs-success: #00bc8c;
|
||||
--bs-info: #3498db;
|
||||
--bs-warning: #f39c12;
|
||||
|
@ -38,7 +38,7 @@
|
|||
--bs-light: #303030;
|
||||
--bs-dark: #dee2e6;
|
||||
--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-info-rgb: 52, 152, 219;
|
||||
--bs-warning-rgb: 243, 156, 18;
|
||||
|
@ -46,7 +46,7 @@
|
|||
--bs-light-rgb: 48, 48, 48;
|
||||
--bs-dark-rgb: 222, 226, 230;
|
||||
--bs-primary-text-emphasis: #004b38;
|
||||
--bs-secondary-text-emphasis: #1b1b1b;
|
||||
--bs-secondary-text-emphasis: #45484c;
|
||||
--bs-success-text-emphasis: #004b38;
|
||||
--bs-info-text-emphasis: #153d58;
|
||||
--bs-warning-text-emphasis: #613e07;
|
||||
|
@ -54,7 +54,7 @@
|
|||
--bs-light-text-emphasis: #444;
|
||||
--bs-dark-text-emphasis: #444;
|
||||
--bs-primary-bg-subtle: #ccf2e8;
|
||||
--bs-secondary-bg-subtle: #dadada;
|
||||
--bs-secondary-bg-subtle: #eff0f2;
|
||||
--bs-success-bg-subtle: #ccf2e8;
|
||||
--bs-info-bg-subtle: #d6eaf8;
|
||||
--bs-warning-bg-subtle: #fdebd0;
|
||||
|
@ -62,7 +62,7 @@
|
|||
--bs-light-bg-subtle: #fcfcfd;
|
||||
--bs-dark-bg-subtle: #ced4da;
|
||||
--bs-primary-border-subtle: #99e4d1;
|
||||
--bs-secondary-border-subtle: #b4b4b4;
|
||||
--bs-secondary-border-subtle: #dee1e5;
|
||||
--bs-success-border-subtle: #99e4d1;
|
||||
--bs-info-border-subtle: #aed6f1;
|
||||
--bs-warning-border-subtle: #fad7a0;
|
||||
|
@ -142,7 +142,7 @@
|
|||
--bs-tertiary-bg: #292929;
|
||||
--bs-tertiary-bg-rgb: 41, 41, 41;
|
||||
--bs-primary-text-emphasis: #66d7ba;
|
||||
--bs-secondary-text-emphasis: #8f8f8f;
|
||||
--bs-secondary-text-emphasis: #ced3d7;
|
||||
--bs-success-text-emphasis: #66d7ba;
|
||||
--bs-info-text-emphasis: #85c1e9;
|
||||
--bs-warning-text-emphasis: #f8c471;
|
||||
|
@ -150,7 +150,7 @@
|
|||
--bs-light-text-emphasis: #f8f9fa;
|
||||
--bs-dark-text-emphasis: #dee2e6;
|
||||
--bs-primary-bg-subtle: #00261c;
|
||||
--bs-secondary-bg-subtle: #0e0e0e;
|
||||
--bs-secondary-bg-subtle: #232426;
|
||||
--bs-success-bg-subtle: #00261c;
|
||||
--bs-info-bg-subtle: #0a1e2c;
|
||||
--bs-warning-bg-subtle: #311f04;
|
||||
|
@ -158,7 +158,7 @@
|
|||
--bs-light-bg-subtle: #303030;
|
||||
--bs-dark-bg-subtle: #181818;
|
||||
--bs-primary-border-subtle: #007154;
|
||||
--bs-secondary-border-subtle: #292929;
|
||||
--bs-secondary-border-subtle: #686d71;
|
||||
--bs-success-border-subtle: #007154;
|
||||
--bs-info-border-subtle: #1f5b83;
|
||||
--bs-warning-border-subtle: #925e0b;
|
||||
|
@ -1945,13 +1945,13 @@ progress {
|
|||
|
||||
.table-secondary {
|
||||
--bs-table-color: #000;
|
||||
--bs-table-bg: #dadada;
|
||||
--bs-table-border-color: #c4c4c4;
|
||||
--bs-table-striped-bg: #cfcfcf;
|
||||
--bs-table-bg: #eff0f2;
|
||||
--bs-table-border-color: #d7d8da;
|
||||
--bs-table-striped-bg: #e3e4e6;
|
||||
--bs-table-striped-color: #000;
|
||||
--bs-table-active-bg: #c4c4c4;
|
||||
--bs-table-active-bg: #d7d8da;
|
||||
--bs-table-active-color: #000;
|
||||
--bs-table-hover-bg: #cacaca;
|
||||
--bs-table-hover-bg: #dddee0;
|
||||
--bs-table-hover-color: #000;
|
||||
color: var(--bs-table-color);
|
||||
border-color: var(--bs-table-border-color);
|
||||
|
@ -2978,20 +2978,20 @@ textarea.form-control-lg {
|
|||
}
|
||||
|
||||
.btn-secondary {
|
||||
--bs-btn-color: #fff;
|
||||
--bs-btn-bg: #444;
|
||||
--bs-btn-border-color: #444;
|
||||
--bs-btn-hover-color: #fff;
|
||||
--bs-btn-hover-bg: #3a3a3a;
|
||||
--bs-btn-hover-border-color: #363636;
|
||||
--bs-btn-focus-shadow-rgb: 96, 96, 96;
|
||||
--bs-btn-active-color: #fff;
|
||||
--bs-btn-active-bg: #363636;
|
||||
--bs-btn-active-border-color: #333333;
|
||||
--bs-btn-color: #000;
|
||||
--bs-btn-bg: #adb5bd;
|
||||
--bs-btn-border-color: #adb5bd;
|
||||
--bs-btn-hover-color: #000;
|
||||
--bs-btn-hover-bg: #b9c0c7;
|
||||
--bs-btn-hover-border-color: #b5bcc4;
|
||||
--bs-btn-focus-shadow-rgb: 147, 154, 161;
|
||||
--bs-btn-active-color: #000;
|
||||
--bs-btn-active-bg: #bdc4ca;
|
||||
--bs-btn-active-border-color: #b5bcc4;
|
||||
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
|
||||
--bs-btn-disabled-color: #fff;
|
||||
--bs-btn-disabled-bg: #444;
|
||||
--bs-btn-disabled-border-color: #444;
|
||||
--bs-btn-disabled-color: #000;
|
||||
--bs-btn-disabled-bg: #adb5bd;
|
||||
--bs-btn-disabled-border-color: #adb5bd;
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
|
@ -3114,19 +3114,19 @@ textarea.form-control-lg {
|
|||
}
|
||||
|
||||
.btn-outline-secondary {
|
||||
--bs-btn-color: #444;
|
||||
--bs-btn-border-color: #444;
|
||||
--bs-btn-hover-color: #fff;
|
||||
--bs-btn-hover-bg: #444;
|
||||
--bs-btn-hover-border-color: #444;
|
||||
--bs-btn-focus-shadow-rgb: 68, 68, 68;
|
||||
--bs-btn-active-color: #fff;
|
||||
--bs-btn-active-bg: #444;
|
||||
--bs-btn-active-border-color: #444;
|
||||
--bs-btn-color: #adb5bd;
|
||||
--bs-btn-border-color: #adb5bd;
|
||||
--bs-btn-hover-color: #000;
|
||||
--bs-btn-hover-bg: #adb5bd;
|
||||
--bs-btn-hover-border-color: #adb5bd;
|
||||
--bs-btn-focus-shadow-rgb: 173, 181, 189;
|
||||
--bs-btn-active-color: #000;
|
||||
--bs-btn-active-bg: #adb5bd;
|
||||
--bs-btn-active-border-color: #adb5bd;
|
||||
--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-border-color: #444;
|
||||
--bs-btn-disabled-border-color: #adb5bd;
|
||||
--bs-gradient: none;
|
||||
}
|
||||
|
||||
|
@ -6765,8 +6765,8 @@ textarea.form-control-lg {
|
|||
}
|
||||
|
||||
.text-bg-secondary {
|
||||
color: #fff !important;
|
||||
background-color: RGBA(68, 68, 68, var(--bs-bg-opacity, 1)) !important;
|
||||
color: #000 !important;
|
||||
background-color: RGBA(173, 181, 189, var(--bs-bg-opacity, 1)) !important;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
.link-secondary:hover, .link-secondary:focus {
|
||||
color: RGBA(54, 54, 54, var(--bs-link-opacity, 1)) !important;
|
||||
text-decoration-color: RGBA(54, 54, 54, var(--bs-link-underline-opacity, 1)) !important;
|
||||
color: RGBA(189, 196, 202, var(--bs-link-opacity, 1)) !important;
|
||||
text-decoration-color: RGBA(189, 196, 202, var(--bs-link-underline-opacity, 1)) !important;
|
||||
}
|
||||
|
||||
.link-success {
|
||||
|
|
|
@ -26,35 +26,35 @@
|
|||
--bs-gray-400: #ced4da;
|
||||
--bs-gray-500: #adb5bd;
|
||||
--bs-gray-600: #6c757d;
|
||||
--bs-gray-700: #495057;
|
||||
--bs-gray-700: #444;
|
||||
--bs-gray-800: #303030;
|
||||
--bs-gray-900: #222;
|
||||
--bs-gray-900: #2f2f2f;
|
||||
--bs-primary: #fefe54;
|
||||
--bs-secondary: #222;
|
||||
--bs-secondary: #303030;
|
||||
--bs-success: #00aa00;
|
||||
--bs-info: #00aaaa;
|
||||
--bs-warning: #aa00aa;
|
||||
--bs-danger: #aa0000;
|
||||
--bs-light: #303030;
|
||||
--bs-dark: black;
|
||||
--bs-light: #444;
|
||||
--bs-dark: #bbb;
|
||||
--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-info-rgb: 0, 170, 170;
|
||||
--bs-warning-rgb: 170, 0, 170;
|
||||
--bs-danger-rgb: 170, 0, 0;
|
||||
--bs-light-rgb: 48, 48, 48;
|
||||
--bs-dark-rgb: 0, 0, 0;
|
||||
--bs-light-rgb: 68, 68, 68;
|
||||
--bs-dark-rgb: 187, 187, 187;
|
||||
--bs-primary-text-emphasis: #666622;
|
||||
--bs-secondary-text-emphasis: #0e0e0e;
|
||||
--bs-secondary-text-emphasis: #131313;
|
||||
--bs-success-text-emphasis: #004400;
|
||||
--bs-info-text-emphasis: #004444;
|
||||
--bs-warning-text-emphasis: #440044;
|
||||
--bs-danger-text-emphasis: #440000;
|
||||
--bs-light-text-emphasis: #495057;
|
||||
--bs-dark-text-emphasis: #495057;
|
||||
--bs-light-text-emphasis: #444;
|
||||
--bs-dark-text-emphasis: #444;
|
||||
--bs-primary-bg-subtle: #ffffdd;
|
||||
--bs-secondary-bg-subtle: lightgray;
|
||||
--bs-secondary-bg-subtle: #d6d6d6;
|
||||
--bs-success-bg-subtle: #cceecc;
|
||||
--bs-info-bg-subtle: #cceeee;
|
||||
--bs-warning-bg-subtle: #eeccee;
|
||||
|
@ -62,7 +62,7 @@
|
|||
--bs-light-bg-subtle: #fcfcfd;
|
||||
--bs-dark-bg-subtle: #ced4da;
|
||||
--bs-primary-border-subtle: #ffffbb;
|
||||
--bs-secondary-border-subtle: #a7a7a7;
|
||||
--bs-secondary-border-subtle: #acacac;
|
||||
--bs-success-border-subtle: #99dd99;
|
||||
--bs-info-border-subtle: #99dddd;
|
||||
--bs-warning-border-subtle: #dd99dd;
|
||||
|
@ -129,8 +129,8 @@
|
|||
color-scheme: dark;
|
||||
--bs-body-color: #adb5bd;
|
||||
--bs-body-color-rgb: 173, 181, 189;
|
||||
--bs-body-bg: #222;
|
||||
--bs-body-bg-rgb: 34, 34, 34;
|
||||
--bs-body-bg: #2f2f2f;
|
||||
--bs-body-bg-rgb: 47, 47, 47;
|
||||
--bs-emphasis-color: #fff;
|
||||
--bs-emphasis-color-rgb: 255, 255, 255;
|
||||
--bs-secondary-color: rgba(173, 181, 189, 0.75);
|
||||
|
@ -139,10 +139,10 @@
|
|||
--bs-secondary-bg-rgb: 48, 48, 48;
|
||||
--bs-tertiary-color: rgba(173, 181, 189, 0.5);
|
||||
--bs-tertiary-color-rgb: 173, 181, 189;
|
||||
--bs-tertiary-bg: #292929;
|
||||
--bs-tertiary-bg-rgb: 41, 41, 41;
|
||||
--bs-tertiary-bg: #303030;
|
||||
--bs-tertiary-bg-rgb: 48, 48, 48;
|
||||
--bs-primary-text-emphasis: #fefe98;
|
||||
--bs-secondary-text-emphasis: #7a7a7a;
|
||||
--bs-secondary-text-emphasis: #838383;
|
||||
--bs-success-text-emphasis: #66cc66;
|
||||
--bs-info-text-emphasis: #66cccc;
|
||||
--bs-warning-text-emphasis: #cc66cc;
|
||||
|
@ -150,7 +150,7 @@
|
|||
--bs-light-text-emphasis: #f8f9fa;
|
||||
--bs-dark-text-emphasis: #bbb;
|
||||
--bs-primary-bg-subtle: #333311;
|
||||
--bs-secondary-bg-subtle: #070707;
|
||||
--bs-secondary-bg-subtle: #0a0a0a;
|
||||
--bs-success-bg-subtle: #002200;
|
||||
--bs-info-bg-subtle: #002222;
|
||||
--bs-warning-bg-subtle: #220022;
|
||||
|
@ -158,12 +158,12 @@
|
|||
--bs-light-bg-subtle: #303030;
|
||||
--bs-dark-bg-subtle: #181818;
|
||||
--bs-primary-border-subtle: #989832;
|
||||
--bs-secondary-border-subtle: #141414;
|
||||
--bs-secondary-border-subtle: #1d1d1d;
|
||||
--bs-success-border-subtle: #006600;
|
||||
--bs-info-border-subtle: #006666;
|
||||
--bs-warning-border-subtle: #660066;
|
||||
--bs-danger-border-subtle: #660000;
|
||||
--bs-light-border-subtle: #495057;
|
||||
--bs-light-border-subtle: #444;
|
||||
--bs-dark-border-subtle: #303030;
|
||||
--bs-heading-color: inherit;
|
||||
--bs-link-color: #fefe98;
|
||||
|
@ -171,7 +171,7 @@
|
|||
--bs-link-color-rgb: 254, 254, 152;
|
||||
--bs-link-hover-color-rgb: 254, 254, 173;
|
||||
--bs-code-color: #fe98fe;
|
||||
--bs-border-color: #495057;
|
||||
--bs-border-color: #444;
|
||||
--bs-border-color-translucent: rgba(255, 255, 255, 0.15);
|
||||
--bs-form-valid-color: #99ff99;
|
||||
--bs-form-valid-border-color: #99ff99;
|
||||
|
@ -1942,13 +1942,13 @@ progress {
|
|||
|
||||
.table-secondary {
|
||||
--bs-table-color: #000;
|
||||
--bs-table-bg: lightgray;
|
||||
--bs-table-border-color: #bebebe;
|
||||
--bs-table-striped-bg: #c8c8c8;
|
||||
--bs-table-bg: #d6d6d6;
|
||||
--bs-table-border-color: #c1c1c1;
|
||||
--bs-table-striped-bg: #cbcbcb;
|
||||
--bs-table-striped-color: #000;
|
||||
--bs-table-active-bg: #bebebe;
|
||||
--bs-table-active-bg: #c1c1c1;
|
||||
--bs-table-active-color: #000;
|
||||
--bs-table-hover-bg: #c3c3c3;
|
||||
--bs-table-hover-bg: #c6c6c6;
|
||||
--bs-table-hover-color: #000;
|
||||
color: var(--bs-table-color);
|
||||
border-color: var(--bs-table-border-color);
|
||||
|
@ -2012,28 +2012,28 @@ progress {
|
|||
|
||||
.table-light {
|
||||
--bs-table-color: #fff;
|
||||
--bs-table-bg: #303030;
|
||||
--bs-table-border-color: #454545;
|
||||
--bs-table-striped-bg: #3a3a3a;
|
||||
--bs-table-bg: #444;
|
||||
--bs-table-border-color: #575757;
|
||||
--bs-table-striped-bg: #4d4d4d;
|
||||
--bs-table-striped-color: #fff;
|
||||
--bs-table-active-bg: #454545;
|
||||
--bs-table-active-bg: #575757;
|
||||
--bs-table-active-color: #fff;
|
||||
--bs-table-hover-bg: #404040;
|
||||
--bs-table-hover-bg: #525252;
|
||||
--bs-table-hover-color: #fff;
|
||||
color: var(--bs-table-color);
|
||||
border-color: var(--bs-table-border-color);
|
||||
}
|
||||
|
||||
.table-dark {
|
||||
--bs-table-color: #fff;
|
||||
--bs-table-bg: black;
|
||||
--bs-table-border-color: #1a1a1a;
|
||||
--bs-table-striped-bg: #0d0d0d;
|
||||
--bs-table-striped-color: #fff;
|
||||
--bs-table-active-bg: #1a1a1a;
|
||||
--bs-table-active-color: #fff;
|
||||
--bs-table-hover-bg: #131313;
|
||||
--bs-table-hover-color: #fff;
|
||||
--bs-table-color: #000;
|
||||
--bs-table-bg: #bbb;
|
||||
--bs-table-border-color: #a8a8a8;
|
||||
--bs-table-striped-bg: #b2b2b2;
|
||||
--bs-table-striped-color: #000;
|
||||
--bs-table-active-bg: #a8a8a8;
|
||||
--bs-table-active-color: #000;
|
||||
--bs-table-hover-bg: #adadad;
|
||||
--bs-table-hover-color: #000;
|
||||
color: var(--bs-table-color);
|
||||
border-color: var(--bs-table-border-color);
|
||||
}
|
||||
|
@ -2933,19 +2933,19 @@ textarea.form-control-lg {
|
|||
|
||||
.btn-secondary {
|
||||
--bs-btn-color: #fff;
|
||||
--bs-btn-bg: #222;
|
||||
--bs-btn-border-color: #222;
|
||||
--bs-btn-bg: #303030;
|
||||
--bs-btn-border-color: #303030;
|
||||
--bs-btn-hover-color: #fff;
|
||||
--bs-btn-hover-bg: #1d1d1d;
|
||||
--bs-btn-hover-border-color: #1b1b1b;
|
||||
--bs-btn-focus-shadow-rgb: 67, 67, 67;
|
||||
--bs-btn-hover-bg: #292929;
|
||||
--bs-btn-hover-border-color: #262626;
|
||||
--bs-btn-focus-shadow-rgb: 79, 79, 79;
|
||||
--bs-btn-active-color: #fff;
|
||||
--bs-btn-active-bg: #1b1b1b;
|
||||
--bs-btn-active-border-color: #1a1a1a;
|
||||
--bs-btn-active-bg: #262626;
|
||||
--bs-btn-active-border-color: #242424;
|
||||
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
|
||||
--bs-btn-disabled-color: #fff;
|
||||
--bs-btn-disabled-bg: #222;
|
||||
--bs-btn-disabled-border-color: #222;
|
||||
--bs-btn-disabled-bg: #303030;
|
||||
--bs-btn-disabled-border-color: #303030;
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
|
@ -3018,36 +3018,36 @@ textarea.form-control-lg {
|
|||
|
||||
.btn-light {
|
||||
--bs-btn-color: #fff;
|
||||
--bs-btn-bg: #303030;
|
||||
--bs-btn-border-color: #303030;
|
||||
--bs-btn-bg: #444;
|
||||
--bs-btn-border-color: #444;
|
||||
--bs-btn-hover-color: #fff;
|
||||
--bs-btn-hover-bg: #292929;
|
||||
--bs-btn-hover-border-color: #262626;
|
||||
--bs-btn-focus-shadow-rgb: 79, 79, 79;
|
||||
--bs-btn-hover-bg: #3a3a3a;
|
||||
--bs-btn-hover-border-color: #363636;
|
||||
--bs-btn-focus-shadow-rgb: 96, 96, 96;
|
||||
--bs-btn-active-color: #fff;
|
||||
--bs-btn-active-bg: #262626;
|
||||
--bs-btn-active-border-color: #242424;
|
||||
--bs-btn-active-bg: #363636;
|
||||
--bs-btn-active-border-color: #333333;
|
||||
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
|
||||
--bs-btn-disabled-color: #fff;
|
||||
--bs-btn-disabled-bg: #303030;
|
||||
--bs-btn-disabled-border-color: #303030;
|
||||
--bs-btn-disabled-bg: #444;
|
||||
--bs-btn-disabled-border-color: #444;
|
||||
}
|
||||
|
||||
.btn-dark {
|
||||
--bs-btn-color: #fff;
|
||||
--bs-btn-bg: black;
|
||||
--bs-btn-border-color: black;
|
||||
--bs-btn-hover-color: #fff;
|
||||
--bs-btn-hover-bg: #262626;
|
||||
--bs-btn-hover-border-color: #1a1a1a;
|
||||
--bs-btn-focus-shadow-rgb: 38, 38, 38;
|
||||
--bs-btn-active-color: #fff;
|
||||
--bs-btn-active-bg: #333333;
|
||||
--bs-btn-active-border-color: #1a1a1a;
|
||||
--bs-btn-color: #000;
|
||||
--bs-btn-bg: #bbb;
|
||||
--bs-btn-border-color: #bbb;
|
||||
--bs-btn-hover-color: #000;
|
||||
--bs-btn-hover-bg: #c5c5c5;
|
||||
--bs-btn-hover-border-color: #c2c2c2;
|
||||
--bs-btn-focus-shadow-rgb: 159, 159, 159;
|
||||
--bs-btn-active-color: #000;
|
||||
--bs-btn-active-bg: #c9c9c9;
|
||||
--bs-btn-active-border-color: #c2c2c2;
|
||||
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
|
||||
--bs-btn-disabled-color: #fff;
|
||||
--bs-btn-disabled-bg: black;
|
||||
--bs-btn-disabled-border-color: black;
|
||||
--bs-btn-disabled-color: #000;
|
||||
--bs-btn-disabled-bg: #bbb;
|
||||
--bs-btn-disabled-border-color: #bbb;
|
||||
}
|
||||
|
||||
.btn-outline-primary {
|
||||
|
@ -3068,19 +3068,19 @@ textarea.form-control-lg {
|
|||
}
|
||||
|
||||
.btn-outline-secondary {
|
||||
--bs-btn-color: #222;
|
||||
--bs-btn-border-color: #222;
|
||||
--bs-btn-color: #303030;
|
||||
--bs-btn-border-color: #303030;
|
||||
--bs-btn-hover-color: #fff;
|
||||
--bs-btn-hover-bg: #222;
|
||||
--bs-btn-hover-border-color: #222;
|
||||
--bs-btn-focus-shadow-rgb: 34, 34, 34;
|
||||
--bs-btn-hover-bg: #303030;
|
||||
--bs-btn-hover-border-color: #303030;
|
||||
--bs-btn-focus-shadow-rgb: 48, 48, 48;
|
||||
--bs-btn-active-color: #fff;
|
||||
--bs-btn-active-bg: #222;
|
||||
--bs-btn-active-border-color: #222;
|
||||
--bs-btn-active-bg: #303030;
|
||||
--bs-btn-active-border-color: #303030;
|
||||
--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-border-color: #222;
|
||||
--bs-btn-disabled-border-color: #303030;
|
||||
--bs-gradient: none;
|
||||
}
|
||||
|
||||
|
@ -3153,36 +3153,36 @@ textarea.form-control-lg {
|
|||
}
|
||||
|
||||
.btn-outline-light {
|
||||
--bs-btn-color: #303030;
|
||||
--bs-btn-border-color: #303030;
|
||||
--bs-btn-color: #444;
|
||||
--bs-btn-border-color: #444;
|
||||
--bs-btn-hover-color: #fff;
|
||||
--bs-btn-hover-bg: #303030;
|
||||
--bs-btn-hover-border-color: #303030;
|
||||
--bs-btn-focus-shadow-rgb: 48, 48, 48;
|
||||
--bs-btn-hover-bg: #444;
|
||||
--bs-btn-hover-border-color: #444;
|
||||
--bs-btn-focus-shadow-rgb: 68, 68, 68;
|
||||
--bs-btn-active-color: #fff;
|
||||
--bs-btn-active-bg: #303030;
|
||||
--bs-btn-active-border-color: #303030;
|
||||
--bs-btn-active-bg: #444;
|
||||
--bs-btn-active-border-color: #444;
|
||||
--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-border-color: #303030;
|
||||
--bs-btn-disabled-border-color: #444;
|
||||
--bs-gradient: none;
|
||||
}
|
||||
|
||||
.btn-outline-dark {
|
||||
--bs-btn-color: black;
|
||||
--bs-btn-border-color: black;
|
||||
--bs-btn-hover-color: #fff;
|
||||
--bs-btn-hover-bg: black;
|
||||
--bs-btn-hover-border-color: black;
|
||||
--bs-btn-focus-shadow-rgb: 0, 0, 0;
|
||||
--bs-btn-active-color: #fff;
|
||||
--bs-btn-active-bg: black;
|
||||
--bs-btn-active-border-color: black;
|
||||
--bs-btn-color: #bbb;
|
||||
--bs-btn-border-color: #bbb;
|
||||
--bs-btn-hover-color: #000;
|
||||
--bs-btn-hover-bg: #bbb;
|
||||
--bs-btn-hover-border-color: #bbb;
|
||||
--bs-btn-focus-shadow-rgb: 187, 187, 187;
|
||||
--bs-btn-active-color: #000;
|
||||
--bs-btn-active-bg: #bbb;
|
||||
--bs-btn-active-border-color: #bbb;
|
||||
--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-border-color: black;
|
||||
--bs-btn-disabled-border-color: #bbb;
|
||||
--bs-gradient: none;
|
||||
}
|
||||
|
||||
|
@ -6490,7 +6490,7 @@ textarea.form-control-lg {
|
|||
|
||||
.text-bg-secondary {
|
||||
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 {
|
||||
|
@ -6515,12 +6515,12 @@ textarea.form-control-lg {
|
|||
|
||||
.text-bg-light {
|
||||
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 {
|
||||
color: #fff !important;
|
||||
background-color: RGBA(0, 0, 0, var(--bs-bg-opacity, 1)) !important;
|
||||
color: #000 !important;
|
||||
background-color: RGBA(187, 187, 187, var(--bs-bg-opacity, 1)) !important;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
.link-secondary:hover, .link-secondary:focus {
|
||||
color: RGBA(27, 27, 27, var(--bs-link-opacity, 1)) !important;
|
||||
text-decoration-color: RGBA(27, 27, 27, var(--bs-link-underline-opacity, 1)) !important;
|
||||
color: RGBA(38, 38, 38, var(--bs-link-opacity, 1)) !important;
|
||||
text-decoration-color: RGBA(38, 38, 38, var(--bs-link-underline-opacity, 1)) !important;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
.link-light:hover, .link-light:focus {
|
||||
color: RGBA(38, 38, 38, var(--bs-link-opacity, 1)) !important;
|
||||
text-decoration-color: RGBA(38, 38, 38, var(--bs-link-underline-opacity, 1)) !important;
|
||||
color: RGBA(54, 54, 54, var(--bs-link-opacity, 1)) !important;
|
||||
text-decoration-color: RGBA(54, 54, 54, var(--bs-link-underline-opacity, 1)) !important;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
.link-dark:hover, .link-dark:focus {
|
||||
color: RGBA(0, 0, 0, var(--bs-link-opacity, 1)) !important;
|
||||
text-decoration-color: RGBA(0, 0, 0, var(--bs-link-underline-opacity, 1)) !important;
|
||||
color: RGBA(201, 201, 201, var(--bs-link-opacity, 1)) !important;
|
||||
text-decoration-color: RGBA(201, 201, 201, var(--bs-link-underline-opacity, 1)) !important;
|
||||
}
|
||||
|
||||
.link-body-emphasis {
|
||||
|
@ -11588,7 +11588,7 @@ textarea.form-control-lg {
|
|||
.dropdown-item.active,
|
||||
.dropdown-item:hover,
|
||||
option:disabled {
|
||||
color: #222;
|
||||
color: #303030;
|
||||
}
|
||||
|
||||
.input-group-text {
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
--bs-warning: #fffb96;
|
||||
--bs-danger: rgb(255, 95, 110);
|
||||
--bs-light: #444;
|
||||
--bs-dark: #222;
|
||||
--bs-dark: #ebebeb;
|
||||
--bs-primary-rgb: 255, 64, 186;
|
||||
--bs-secondary-rgb: 1, 205, 254;
|
||||
--bs-success-rgb: 5, 255, 161;
|
||||
|
@ -44,7 +44,7 @@
|
|||
--bs-warning-rgb: 255, 251, 150;
|
||||
--bs-danger-rgb: 255, 95, 110;
|
||||
--bs-light-rgb: 68, 68, 68;
|
||||
--bs-dark-rgb: 34, 34, 34;
|
||||
--bs-dark-rgb: 235, 235, 235;
|
||||
--bs-primary-text-emphasis: #661a4a;
|
||||
--bs-secondary-text-emphasis: #005266;
|
||||
--bs-success-text-emphasis: #026640;
|
||||
|
@ -74,8 +74,9 @@
|
|||
--bs-font-sans-serif: "Lucida Console", Monaco, monospace;
|
||||
--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-root-font-size: 93.75%;
|
||||
--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-line-height: 1.5;
|
||||
--bs-body-color: #ebebeb;
|
||||
|
@ -184,6 +185,9 @@
|
|||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
:root {
|
||||
font-size: var(--bs-root-font-size);
|
||||
}
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
:root {
|
||||
scroll-behavior: smooth;
|
||||
|
@ -220,47 +224,47 @@ h6, .h6, h5, .h5, h4, .h4, h3, .h3, h2, .h2, h1, .h1 {
|
|||
}
|
||||
|
||||
h1, .h1 {
|
||||
font-size: calc(1.34375rem + 1.125vw);
|
||||
font-size: calc(1.375rem + 1.5vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h1, .h1 {
|
||||
font-size: 2.1875rem;
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
h2, .h2 {
|
||||
font-size: calc(1.3rem + 0.6vw);
|
||||
font-size: calc(1.325rem + 0.9vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h2, .h2 {
|
||||
font-size: 1.75rem;
|
||||
font-size: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
h3, .h3 {
|
||||
font-size: calc(1.278125rem + 0.3375vw);
|
||||
font-size: calc(1.3rem + 0.6vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h3, .h3 {
|
||||
font-size: 1.53125rem;
|
||||
font-size: 1.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
h4, .h4 {
|
||||
font-size: calc(1.25625rem + 0.075vw);
|
||||
font-size: calc(1.275rem + 0.3vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h4, .h4 {
|
||||
font-size: 1.3125rem;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
h5, .h5 {
|
||||
font-size: 1.09375rem;
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
h6, .h6 {
|
||||
font-size: 0.875rem;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
p {
|
||||
|
@ -586,7 +590,7 @@ progress {
|
|||
}
|
||||
|
||||
.lead {
|
||||
font-size: 1.09375rem;
|
||||
font-size: 1.25rem;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
|
@ -680,7 +684,7 @@ progress {
|
|||
|
||||
.blockquote {
|
||||
margin-bottom: 1rem;
|
||||
font-size: 1.09375rem;
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
.blockquote > :last-child {
|
||||
margin-bottom: 0;
|
||||
|
@ -2025,15 +2029,15 @@ progress {
|
|||
}
|
||||
|
||||
.table-dark {
|
||||
--bs-table-color: #fff;
|
||||
--bs-table-bg: #222;
|
||||
--bs-table-border-color: #383838;
|
||||
--bs-table-striped-bg: #2d2d2d;
|
||||
--bs-table-striped-color: #fff;
|
||||
--bs-table-active-bg: #383838;
|
||||
--bs-table-active-color: #fff;
|
||||
--bs-table-hover-bg: #333333;
|
||||
--bs-table-hover-color: #fff;
|
||||
--bs-table-color: #000;
|
||||
--bs-table-bg: #ebebeb;
|
||||
--bs-table-border-color: #d4d4d4;
|
||||
--bs-table-striped-bg: #dfdfdf;
|
||||
--bs-table-striped-color: #000;
|
||||
--bs-table-active-bg: #d4d4d4;
|
||||
--bs-table-active-color: #000;
|
||||
--bs-table-hover-bg: #d9d9d9;
|
||||
--bs-table-hover-color: #000;
|
||||
color: var(--bs-table-color);
|
||||
border-color: var(--bs-table-border-color);
|
||||
}
|
||||
|
@ -2088,13 +2092,13 @@ progress {
|
|||
.col-form-label-lg {
|
||||
padding-top: 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 {
|
||||
padding-top: 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 {
|
||||
|
@ -2107,7 +2111,7 @@ progress {
|
|||
display: block;
|
||||
width: 100%;
|
||||
padding: 0.375rem 0.75rem;
|
||||
font-size: 0.875rem;
|
||||
font-size: 1rem;
|
||||
font-weight: 400;
|
||||
line-height: 1.5;
|
||||
color: #fff;
|
||||
|
@ -2200,7 +2204,7 @@ progress {
|
|||
.form-control-sm {
|
||||
min-height: calc(1.5em + 0.5rem + calc(var(--bs-border-width) * 2));
|
||||
padding: 0.25rem 0.5rem;
|
||||
font-size: 0.765625rem;
|
||||
font-size: 0.875rem;
|
||||
border-radius: var(--bs-border-radius-sm);
|
||||
}
|
||||
.form-control-sm::file-selector-button {
|
||||
|
@ -2212,7 +2216,7 @@ progress {
|
|||
.form-control-lg {
|
||||
min-height: calc(1.5em + 1rem + calc(var(--bs-border-width) * 2));
|
||||
padding: 0.5rem 1rem;
|
||||
font-size: 1.09375rem;
|
||||
font-size: 1.25rem;
|
||||
border-radius: var(--bs-border-radius-lg);
|
||||
}
|
||||
.form-control-lg::file-selector-button {
|
||||
|
@ -2259,7 +2263,7 @@ textarea.form-control-lg {
|
|||
display: block;
|
||||
width: 100%;
|
||||
padding: 0.375rem 2.25rem 0.375rem 0.75rem;
|
||||
font-size: 0.875rem;
|
||||
font-size: 1rem;
|
||||
font-weight: 400;
|
||||
line-height: 1.5;
|
||||
color: #fff;
|
||||
|
@ -2300,7 +2304,7 @@ textarea.form-control-lg {
|
|||
padding-top: 0.25rem;
|
||||
padding-bottom: 0.25rem;
|
||||
padding-left: 0.5rem;
|
||||
font-size: 0.765625rem;
|
||||
font-size: 0.875rem;
|
||||
border-radius: var(--bs-border-radius-sm);
|
||||
}
|
||||
|
||||
|
@ -2308,7 +2312,7 @@ textarea.form-control-lg {
|
|||
padding-top: 0.5rem;
|
||||
padding-bottom: 0.5rem;
|
||||
padding-left: 1rem;
|
||||
font-size: 1.09375rem;
|
||||
font-size: 1.25rem;
|
||||
border-radius: var(--bs-border-radius-lg);
|
||||
}
|
||||
|
||||
|
@ -2318,7 +2322,7 @@ textarea.form-control-lg {
|
|||
|
||||
.form-check {
|
||||
display: block;
|
||||
min-height: 1.3125rem;
|
||||
min-height: 1.5rem;
|
||||
padding-left: 1.5em;
|
||||
margin-bottom: 0.125rem;
|
||||
}
|
||||
|
@ -2654,7 +2658,7 @@ textarea.form-control-lg {
|
|||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.375rem 0.75rem;
|
||||
font-size: 0.875rem;
|
||||
font-size: 1rem;
|
||||
font-weight: 400;
|
||||
line-height: 1.5;
|
||||
color: #fff;
|
||||
|
@ -2670,7 +2674,7 @@ textarea.form-control-lg {
|
|||
.input-group-lg > .input-group-text,
|
||||
.input-group-lg > .btn {
|
||||
padding: 0.5rem 1rem;
|
||||
font-size: 1.09375rem;
|
||||
font-size: 1.25rem;
|
||||
border-radius: var(--bs-border-radius-lg);
|
||||
}
|
||||
|
||||
|
@ -2679,7 +2683,7 @@ textarea.form-control-lg {
|
|||
.input-group-sm > .input-group-text,
|
||||
.input-group-sm > .btn {
|
||||
padding: 0.25rem 0.5rem;
|
||||
font-size: 0.765625rem;
|
||||
font-size: 0.875rem;
|
||||
border-radius: var(--bs-border-radius-sm);
|
||||
}
|
||||
|
||||
|
@ -2729,7 +2733,7 @@ textarea.form-control-lg {
|
|||
max-width: 100%;
|
||||
padding: 0.25rem 0.5rem;
|
||||
margin-top: 0.1rem;
|
||||
font-size: 0.765625rem;
|
||||
font-size: 0.875rem;
|
||||
color: #fff;
|
||||
background-color: var(--bs-success);
|
||||
border-radius: var(--bs-border-radius);
|
||||
|
@ -2819,7 +2823,7 @@ textarea.form-control-lg {
|
|||
max-width: 100%;
|
||||
padding: 0.25rem 0.5rem;
|
||||
margin-top: 0.1rem;
|
||||
font-size: 0.765625rem;
|
||||
font-size: 0.875rem;
|
||||
color: #fff;
|
||||
background-color: var(--bs-danger);
|
||||
border-radius: var(--bs-border-radius);
|
||||
|
@ -2897,7 +2901,7 @@ textarea.form-control-lg {
|
|||
--bs-btn-padding-x: 0.75rem;
|
||||
--bs-btn-padding-y: 0.375rem;
|
||||
--bs-btn-font-family: ;
|
||||
--bs-btn-font-size: 0.875rem;
|
||||
--bs-btn-font-size: 1rem;
|
||||
--bs-btn-font-weight: 400;
|
||||
--bs-btn-line-height: 1.5;
|
||||
--bs-btn-color: var(--bs-body-color);
|
||||
|
@ -3095,20 +3099,20 @@ textarea.form-control-lg {
|
|||
}
|
||||
|
||||
.btn-dark {
|
||||
--bs-btn-color: #fff;
|
||||
--bs-btn-bg: #222;
|
||||
--bs-btn-border-color: #222;
|
||||
--bs-btn-hover-color: #fff;
|
||||
--bs-btn-hover-bg: #434343;
|
||||
--bs-btn-hover-border-color: #383838;
|
||||
--bs-btn-focus-shadow-rgb: 67, 67, 67;
|
||||
--bs-btn-active-color: #fff;
|
||||
--bs-btn-active-bg: #4e4e4e;
|
||||
--bs-btn-active-border-color: #383838;
|
||||
--bs-btn-color: #000;
|
||||
--bs-btn-bg: #ebebeb;
|
||||
--bs-btn-border-color: #ebebeb;
|
||||
--bs-btn-hover-color: #000;
|
||||
--bs-btn-hover-bg: #eeeeee;
|
||||
--bs-btn-hover-border-color: #ededed;
|
||||
--bs-btn-focus-shadow-rgb: 200, 200, 200;
|
||||
--bs-btn-active-color: #000;
|
||||
--bs-btn-active-bg: #efefef;
|
||||
--bs-btn-active-border-color: #ededed;
|
||||
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
|
||||
--bs-btn-disabled-color: #fff;
|
||||
--bs-btn-disabled-bg: #222;
|
||||
--bs-btn-disabled-border-color: #222;
|
||||
--bs-btn-disabled-color: #000;
|
||||
--bs-btn-disabled-bg: #ebebeb;
|
||||
--bs-btn-disabled-border-color: #ebebeb;
|
||||
}
|
||||
|
||||
.btn-outline-primary {
|
||||
|
@ -3231,19 +3235,19 @@ textarea.form-control-lg {
|
|||
}
|
||||
|
||||
.btn-outline-dark {
|
||||
--bs-btn-color: #222;
|
||||
--bs-btn-border-color: #222;
|
||||
--bs-btn-hover-color: #fff;
|
||||
--bs-btn-hover-bg: #222;
|
||||
--bs-btn-hover-border-color: #222;
|
||||
--bs-btn-focus-shadow-rgb: 34, 34, 34;
|
||||
--bs-btn-active-color: #fff;
|
||||
--bs-btn-active-bg: #222;
|
||||
--bs-btn-active-border-color: #222;
|
||||
--bs-btn-color: #ebebeb;
|
||||
--bs-btn-border-color: #ebebeb;
|
||||
--bs-btn-hover-color: #000;
|
||||
--bs-btn-hover-bg: #ebebeb;
|
||||
--bs-btn-hover-border-color: #ebebeb;
|
||||
--bs-btn-focus-shadow-rgb: 235, 235, 235;
|
||||
--bs-btn-active-color: #000;
|
||||
--bs-btn-active-bg: #ebebeb;
|
||||
--bs-btn-active-border-color: #ebebeb;
|
||||
--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-border-color: #222;
|
||||
--bs-btn-disabled-border-color: #ebebeb;
|
||||
--bs-gradient: none;
|
||||
}
|
||||
|
||||
|
@ -3273,14 +3277,14 @@ textarea.form-control-lg {
|
|||
.btn-lg, .btn-group-lg > .btn {
|
||||
--bs-btn-padding-y: 0.5rem;
|
||||
--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);
|
||||
}
|
||||
|
||||
.btn-sm, .btn-group-sm > .btn {
|
||||
--bs-btn-padding-y: 0.25rem;
|
||||
--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);
|
||||
}
|
||||
|
||||
|
@ -3353,7 +3357,7 @@ textarea.form-control-lg {
|
|||
--bs-dropdown-padding-x: 0;
|
||||
--bs-dropdown-padding-y: 0.5rem;
|
||||
--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-bg: var(--bs-body-bg);
|
||||
--bs-dropdown-border-color: var(--bs-border-color-translucent);
|
||||
|
@ -3615,7 +3619,7 @@ textarea.form-control-lg {
|
|||
display: block;
|
||||
padding: var(--bs-dropdown-header-padding-y) var(--bs-dropdown-header-padding-x);
|
||||
margin-bottom: 0;
|
||||
font-size: 0.765625rem;
|
||||
font-size: 0.875rem;
|
||||
color: var(--bs-dropdown-header-color);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
@ -3900,15 +3904,15 @@ textarea.form-control-lg {
|
|||
--bs-navbar-hover-color: rgba(255, 64, 186, 0.7);
|
||||
--bs-navbar-disabled-color: rgba(235, 235, 235, 0.3);
|
||||
--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-font-size: 1.09375rem;
|
||||
--bs-navbar-brand-font-size: 1.25rem;
|
||||
--bs-navbar-brand-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-toggler-padding-y: 0.25rem;
|
||||
--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-border-color: rgba(var(--bs-emphasis-color-rgb), 0.15);
|
||||
--bs-navbar-toggler-border-radius: var(--bs-border-radius);
|
||||
|
@ -4545,7 +4549,7 @@ textarea.form-control-lg {
|
|||
align-items: center;
|
||||
width: 100%;
|
||||
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);
|
||||
text-align: left;
|
||||
background-color: var(--bs-accordion-btn-bg);
|
||||
|
@ -4689,7 +4693,7 @@ textarea.form-control-lg {
|
|||
.pagination {
|
||||
--bs-pagination-padding-x: 0.75rem;
|
||||
--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-bg: var(--bs-body-bg);
|
||||
--bs-pagination-border-width: var(--bs-border-width);
|
||||
|
@ -4769,14 +4773,14 @@ textarea.form-control-lg {
|
|||
.pagination-lg {
|
||||
--bs-pagination-padding-x: 1.5rem;
|
||||
--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);
|
||||
}
|
||||
|
||||
.pagination-sm {
|
||||
--bs-pagination-padding-x: 0.5rem;
|
||||
--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);
|
||||
}
|
||||
|
||||
|
@ -4911,7 +4915,7 @@ textarea.form-control-lg {
|
|||
.progress,
|
||||
.progress-stacked {
|
||||
--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-border-radius: var(--bs-border-radius);
|
||||
--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-y: 0.25rem;
|
||||
--bs-tooltip-margin: ;
|
||||
--bs-tooltip-font-size: 0.765625rem;
|
||||
--bs-tooltip-font-size: 0.875rem;
|
||||
--bs-tooltip-color: var(--bs-body-bg);
|
||||
--bs-tooltip-bg: var(--bs-emphasis-color);
|
||||
--bs-tooltip-border-radius: var(--bs-border-radius);
|
||||
|
@ -5816,7 +5820,7 @@ textarea.form-control-lg {
|
|||
.popover {
|
||||
--bs-popover-zindex: 1070;
|
||||
--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-border-width: var(--bs-border-width);
|
||||
--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-header-padding-x: 1rem;
|
||||
--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-bg: var(--bs-secondary-bg);
|
||||
--bs-popover-body-padding-x: 1rem;
|
||||
|
@ -6844,8 +6848,8 @@ textarea.form-control-lg {
|
|||
}
|
||||
|
||||
.text-bg-dark {
|
||||
color: #fff !important;
|
||||
background-color: RGBA(34, 34, 34, var(--bs-bg-opacity, 1)) !important;
|
||||
color: #000 !important;
|
||||
background-color: RGBA(235, 235, 235, var(--bs-bg-opacity, 1)) !important;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
.link-dark:hover, .link-dark:focus {
|
||||
color: RGBA(27, 27, 27, var(--bs-link-opacity, 1)) !important;
|
||||
text-decoration-color: RGBA(27, 27, 27, var(--bs-link-underline-opacity, 1)) !important;
|
||||
color: RGBA(239, 239, 239, var(--bs-link-opacity, 1)) !important;
|
||||
text-decoration-color: RGBA(239, 239, 239, var(--bs-link-underline-opacity, 1)) !important;
|
||||
}
|
||||
|
||||
.link-body-emphasis {
|
||||
|
@ -8296,27 +8300,27 @@ textarea.form-control-lg {
|
|||
}
|
||||
|
||||
.fs-1 {
|
||||
font-size: calc(1.34375rem + 1.125vw) !important;
|
||||
font-size: calc(1.375rem + 1.5vw) !important;
|
||||
}
|
||||
|
||||
.fs-2 {
|
||||
font-size: calc(1.3rem + 0.6vw) !important;
|
||||
font-size: calc(1.325rem + 0.9vw) !important;
|
||||
}
|
||||
|
||||
.fs-3 {
|
||||
font-size: calc(1.278125rem + 0.3375vw) !important;
|
||||
font-size: calc(1.3rem + 0.6vw) !important;
|
||||
}
|
||||
|
||||
.fs-4 {
|
||||
font-size: calc(1.25625rem + 0.075vw) !important;
|
||||
font-size: calc(1.275rem + 0.3vw) !important;
|
||||
}
|
||||
|
||||
.fs-5 {
|
||||
font-size: 1.09375rem !important;
|
||||
font-size: 1.25rem !important;
|
||||
}
|
||||
|
||||
.fs-6 {
|
||||
font-size: 0.875rem !important;
|
||||
font-size: 1rem !important;
|
||||
}
|
||||
|
||||
.fst-italic {
|
||||
|
@ -11859,16 +11863,16 @@ textarea.form-control-lg {
|
|||
}
|
||||
@media (min-width: 1200px) {
|
||||
.fs-1 {
|
||||
font-size: 2.1875rem !important;
|
||||
font-size: 2.5rem !important;
|
||||
}
|
||||
.fs-2 {
|
||||
font-size: 1.75rem !important;
|
||||
font-size: 2rem !important;
|
||||
}
|
||||
.fs-3 {
|
||||
font-size: 1.53125rem !important;
|
||||
font-size: 1.75rem !important;
|
||||
}
|
||||
.fs-4 {
|
||||
font-size: 1.3125rem !important;
|
||||
font-size: 1.5rem !important;
|
||||
}
|
||||
}
|
||||
@media print {
|
||||
|
|
|
@ -23,8 +23,11 @@ option:disabled {
|
|||
}
|
||||
|
||||
.form-control::placeholder {
|
||||
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;
|
||||
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;
|
||||
}
|
||||
|
||||
.input-group-text {
|
||||
|
|
|
@ -74,8 +74,9 @@
|
|||
--bs-font-sans-serif: "Lucida Console", Monaco, monospace;
|
||||
--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-root-font-size: 93.75%;
|
||||
--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-line-height: 1.5;
|
||||
--bs-body-color: #495057;
|
||||
|
@ -184,6 +185,9 @@
|
|||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
:root {
|
||||
font-size: var(--bs-root-font-size);
|
||||
}
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
:root {
|
||||
scroll-behavior: smooth;
|
||||
|
@ -220,47 +224,47 @@ h6, .h6, h5, .h5, h4, .h4, h3, .h3, h2, .h2, h1, .h1 {
|
|||
}
|
||||
|
||||
h1, .h1 {
|
||||
font-size: calc(1.34375rem + 1.125vw);
|
||||
font-size: calc(1.375rem + 1.5vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h1, .h1 {
|
||||
font-size: 2.1875rem;
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
h2, .h2 {
|
||||
font-size: calc(1.3rem + 0.6vw);
|
||||
font-size: calc(1.325rem + 0.9vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h2, .h2 {
|
||||
font-size: 1.75rem;
|
||||
font-size: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
h3, .h3 {
|
||||
font-size: calc(1.278125rem + 0.3375vw);
|
||||
font-size: calc(1.3rem + 0.6vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h3, .h3 {
|
||||
font-size: 1.53125rem;
|
||||
font-size: 1.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
h4, .h4 {
|
||||
font-size: calc(1.25625rem + 0.075vw);
|
||||
font-size: calc(1.275rem + 0.3vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h4, .h4 {
|
||||
font-size: 1.3125rem;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
h5, .h5 {
|
||||
font-size: 1.09375rem;
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
h6, .h6 {
|
||||
font-size: 0.875rem;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
p {
|
||||
|
@ -585,7 +589,7 @@ progress {
|
|||
}
|
||||
|
||||
.lead {
|
||||
font-size: 1.09375rem;
|
||||
font-size: 1.25rem;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
|
@ -679,7 +683,7 @@ progress {
|
|||
|
||||
.blockquote {
|
||||
margin-bottom: 1rem;
|
||||
font-size: 1.09375rem;
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
.blockquote > :last-child {
|
||||
margin-bottom: 0;
|
||||
|
@ -2087,13 +2091,13 @@ progress {
|
|||
.col-form-label-lg {
|
||||
padding-top: 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 {
|
||||
padding-top: 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 {
|
||||
|
@ -2106,7 +2110,7 @@ progress {
|
|||
display: block;
|
||||
width: 100%;
|
||||
padding: 0.375rem 0.75rem;
|
||||
font-size: 0.875rem;
|
||||
font-size: 1rem;
|
||||
font-weight: 400;
|
||||
line-height: 1.5;
|
||||
color: var(--bs-body-color);
|
||||
|
@ -2199,7 +2203,7 @@ progress {
|
|||
.form-control-sm {
|
||||
min-height: calc(1.5em + 0.5rem + calc(var(--bs-border-width) * 2));
|
||||
padding: 0.25rem 0.5rem;
|
||||
font-size: 0.765625rem;
|
||||
font-size: 0.875rem;
|
||||
border-radius: var(--bs-border-radius-sm);
|
||||
}
|
||||
.form-control-sm::file-selector-button {
|
||||
|
@ -2211,7 +2215,7 @@ progress {
|
|||
.form-control-lg {
|
||||
min-height: calc(1.5em + 1rem + calc(var(--bs-border-width) * 2));
|
||||
padding: 0.5rem 1rem;
|
||||
font-size: 1.09375rem;
|
||||
font-size: 1.25rem;
|
||||
border-radius: var(--bs-border-radius-lg);
|
||||
}
|
||||
.form-control-lg::file-selector-button {
|
||||
|
@ -2258,7 +2262,7 @@ textarea.form-control-lg {
|
|||
display: block;
|
||||
width: 100%;
|
||||
padding: 0.375rem 2.25rem 0.375rem 0.75rem;
|
||||
font-size: 0.875rem;
|
||||
font-size: 1rem;
|
||||
font-weight: 400;
|
||||
line-height: 1.5;
|
||||
color: var(--bs-body-color);
|
||||
|
@ -2299,7 +2303,7 @@ textarea.form-control-lg {
|
|||
padding-top: 0.25rem;
|
||||
padding-bottom: 0.25rem;
|
||||
padding-left: 0.5rem;
|
||||
font-size: 0.765625rem;
|
||||
font-size: 0.875rem;
|
||||
border-radius: var(--bs-border-radius-sm);
|
||||
}
|
||||
|
||||
|
@ -2307,7 +2311,7 @@ textarea.form-control-lg {
|
|||
padding-top: 0.5rem;
|
||||
padding-bottom: 0.5rem;
|
||||
padding-left: 1rem;
|
||||
font-size: 1.09375rem;
|
||||
font-size: 1.25rem;
|
||||
border-radius: var(--bs-border-radius-lg);
|
||||
}
|
||||
|
||||
|
@ -2317,7 +2321,7 @@ textarea.form-control-lg {
|
|||
|
||||
.form-check {
|
||||
display: block;
|
||||
min-height: 1.3125rem;
|
||||
min-height: 1.5rem;
|
||||
padding-left: 1.5em;
|
||||
margin-bottom: 0.125rem;
|
||||
}
|
||||
|
@ -2653,7 +2657,7 @@ textarea.form-control-lg {
|
|||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.375rem 0.75rem;
|
||||
font-size: 0.875rem;
|
||||
font-size: 1rem;
|
||||
font-weight: 400;
|
||||
line-height: 1.5;
|
||||
color: var(--bs-body-color);
|
||||
|
@ -2669,7 +2673,7 @@ textarea.form-control-lg {
|
|||
.input-group-lg > .input-group-text,
|
||||
.input-group-lg > .btn {
|
||||
padding: 0.5rem 1rem;
|
||||
font-size: 1.09375rem;
|
||||
font-size: 1.25rem;
|
||||
border-radius: var(--bs-border-radius-lg);
|
||||
}
|
||||
|
||||
|
@ -2678,7 +2682,7 @@ textarea.form-control-lg {
|
|||
.input-group-sm > .input-group-text,
|
||||
.input-group-sm > .btn {
|
||||
padding: 0.25rem 0.5rem;
|
||||
font-size: 0.765625rem;
|
||||
font-size: 0.875rem;
|
||||
border-radius: var(--bs-border-radius-sm);
|
||||
}
|
||||
|
||||
|
@ -2728,7 +2732,7 @@ textarea.form-control-lg {
|
|||
max-width: 100%;
|
||||
padding: 0.25rem 0.5rem;
|
||||
margin-top: 0.1rem;
|
||||
font-size: 0.765625rem;
|
||||
font-size: 0.875rem;
|
||||
color: #fff;
|
||||
background-color: var(--bs-success);
|
||||
border-radius: var(--bs-border-radius);
|
||||
|
@ -2818,7 +2822,7 @@ textarea.form-control-lg {
|
|||
max-width: 100%;
|
||||
padding: 0.25rem 0.5rem;
|
||||
margin-top: 0.1rem;
|
||||
font-size: 0.765625rem;
|
||||
font-size: 0.875rem;
|
||||
color: #fff;
|
||||
background-color: var(--bs-danger);
|
||||
border-radius: var(--bs-border-radius);
|
||||
|
@ -2896,7 +2900,7 @@ textarea.form-control-lg {
|
|||
--bs-btn-padding-x: 0.75rem;
|
||||
--bs-btn-padding-y: 0.375rem;
|
||||
--bs-btn-font-family: ;
|
||||
--bs-btn-font-size: 0.875rem;
|
||||
--bs-btn-font-size: 1rem;
|
||||
--bs-btn-font-weight: 400;
|
||||
--bs-btn-line-height: 1.5;
|
||||
--bs-btn-color: var(--bs-body-color);
|
||||
|
@ -3272,14 +3276,14 @@ textarea.form-control-lg {
|
|||
.btn-lg, .btn-group-lg > .btn {
|
||||
--bs-btn-padding-y: 0.5rem;
|
||||
--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);
|
||||
}
|
||||
|
||||
.btn-sm, .btn-group-sm > .btn {
|
||||
--bs-btn-padding-y: 0.25rem;
|
||||
--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);
|
||||
}
|
||||
|
||||
|
@ -3352,7 +3356,7 @@ textarea.form-control-lg {
|
|||
--bs-dropdown-padding-x: 0;
|
||||
--bs-dropdown-padding-y: 0.5rem;
|
||||
--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-bg: var(--bs-body-bg);
|
||||
--bs-dropdown-border-color: var(--bs-border-color-translucent);
|
||||
|
@ -3614,7 +3618,7 @@ textarea.form-control-lg {
|
|||
display: block;
|
||||
padding: var(--bs-dropdown-header-padding-y) var(--bs-dropdown-header-padding-x);
|
||||
margin-bottom: 0;
|
||||
font-size: 0.765625rem;
|
||||
font-size: 0.875rem;
|
||||
color: var(--bs-dropdown-header-color);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
@ -3899,15 +3903,15 @@ textarea.form-control-lg {
|
|||
--bs-navbar-hover-color: rgba(255, 64, 186, 0.7);
|
||||
--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-brand-padding-y: 0.3359375rem;
|
||||
--bs-navbar-brand-padding-y: 0.3125rem;
|
||||
--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-hover-color: rgba(var(--bs-emphasis-color-rgb), 1);
|
||||
--bs-navbar-nav-link-padding-x: 0.5rem;
|
||||
--bs-navbar-toggler-padding-y: 0.25rem;
|
||||
--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-border-color: rgba(var(--bs-emphasis-color-rgb), 0.15);
|
||||
--bs-navbar-toggler-border-radius: var(--bs-border-radius);
|
||||
|
@ -4544,7 +4548,7 @@ textarea.form-control-lg {
|
|||
align-items: center;
|
||||
width: 100%;
|
||||
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);
|
||||
text-align: left;
|
||||
background-color: var(--bs-accordion-btn-bg);
|
||||
|
@ -4688,7 +4692,7 @@ textarea.form-control-lg {
|
|||
.pagination {
|
||||
--bs-pagination-padding-x: 0.75rem;
|
||||
--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-bg: var(--bs-body-bg);
|
||||
--bs-pagination-border-width: var(--bs-border-width);
|
||||
|
@ -4768,14 +4772,14 @@ textarea.form-control-lg {
|
|||
.pagination-lg {
|
||||
--bs-pagination-padding-x: 1.5rem;
|
||||
--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);
|
||||
}
|
||||
|
||||
.pagination-sm {
|
||||
--bs-pagination-padding-x: 0.5rem;
|
||||
--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);
|
||||
}
|
||||
|
||||
|
@ -4910,7 +4914,7 @@ textarea.form-control-lg {
|
|||
.progress,
|
||||
.progress-stacked {
|
||||
--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-border-radius: var(--bs-border-radius);
|
||||
--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-y: 0.25rem;
|
||||
--bs-tooltip-margin: ;
|
||||
--bs-tooltip-font-size: 0.765625rem;
|
||||
--bs-tooltip-font-size: 0.875rem;
|
||||
--bs-tooltip-color: var(--bs-body-bg);
|
||||
--bs-tooltip-bg: var(--bs-emphasis-color);
|
||||
--bs-tooltip-border-radius: var(--bs-border-radius);
|
||||
|
@ -5815,7 +5819,7 @@ textarea.form-control-lg {
|
|||
.popover {
|
||||
--bs-popover-zindex: 1070;
|
||||
--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-border-width: var(--bs-border-width);
|
||||
--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-header-padding-x: 1rem;
|
||||
--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-bg: var(--bs-secondary-bg);
|
||||
--bs-popover-body-padding-x: 1rem;
|
||||
|
@ -8295,27 +8299,27 @@ textarea.form-control-lg {
|
|||
}
|
||||
|
||||
.fs-1 {
|
||||
font-size: calc(1.34375rem + 1.125vw) !important;
|
||||
font-size: calc(1.375rem + 1.5vw) !important;
|
||||
}
|
||||
|
||||
.fs-2 {
|
||||
font-size: calc(1.3rem + 0.6vw) !important;
|
||||
font-size: calc(1.325rem + 0.9vw) !important;
|
||||
}
|
||||
|
||||
.fs-3 {
|
||||
font-size: calc(1.278125rem + 0.3375vw) !important;
|
||||
font-size: calc(1.3rem + 0.6vw) !important;
|
||||
}
|
||||
|
||||
.fs-4 {
|
||||
font-size: calc(1.25625rem + 0.075vw) !important;
|
||||
font-size: calc(1.275rem + 0.3vw) !important;
|
||||
}
|
||||
|
||||
.fs-5 {
|
||||
font-size: 1.09375rem !important;
|
||||
font-size: 1.25rem !important;
|
||||
}
|
||||
|
||||
.fs-6 {
|
||||
font-size: 0.875rem !important;
|
||||
font-size: 1rem !important;
|
||||
}
|
||||
|
||||
.fst-italic {
|
||||
|
@ -11858,16 +11862,16 @@ textarea.form-control-lg {
|
|||
}
|
||||
@media (min-width: 1200px) {
|
||||
.fs-1 {
|
||||
font-size: 2.1875rem !important;
|
||||
font-size: 2.5rem !important;
|
||||
}
|
||||
.fs-2 {
|
||||
font-size: 1.75rem !important;
|
||||
font-size: 2rem !important;
|
||||
}
|
||||
.fs-3 {
|
||||
font-size: 1.53125rem !important;
|
||||
font-size: 1.75rem !important;
|
||||
}
|
||||
.fs-4 {
|
||||
font-size: 1.3125rem !important;
|
||||
font-size: 1.5rem !important;
|
||||
}
|
||||
}
|
||||
@media print {
|
||||
|
|
|
@ -1,118 +1,27 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<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:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="1024"
|
||||
height="1024"
|
||||
viewBox="0 0 1024 1024"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
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">
|
||||
version="1.1">
|
||||
<g transform="translate(0,-26.066658)">
|
||||
<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"
|
||||
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" />
|
||||
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" />
|
||||
<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"
|
||||
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"
|
||||
id="path969"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="szszs"
|
||||
inkscape:label="head"
|
||||
sodipodi:insensitive="true" />
|
||||
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" />
|
||||
<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"
|
||||
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" />
|
||||
<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"
|
||||
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" />
|
||||
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" />
|
||||
<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"
|
||||
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" />
|
||||
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" />
|
||||
<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"
|
||||
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" />
|
||||
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" />
|
||||
</g>
|
||||
</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">
|
||||
<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 id="icon-alert-triangle" viewBox="0 0 24 24">
|
||||
<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>
|
||||
<symbol id="icon-alert-triangle" viewBox="0 -960 960 960">
|
||||
<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 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>
|
||||
|
@ -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.70605 13.353L15 16.5" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</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>
|
||||
</svg>
|
||||
|
|
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 54 KiB |
|
@ -1,11 +1,12 @@
|
|||
import { initializeSite, setupDateFns } from "@utils/app";
|
||||
import { hydrate } from "inferno-hydrate";
|
||||
import { Router } from "inferno-router";
|
||||
import { BrowserRouter } from "inferno-router";
|
||||
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/dropdown";
|
||||
import "bootstrap/js/dist/modal";
|
||||
|
||||
async function startClient() {
|
||||
initializeSite(window.isoData.site_res);
|
||||
|
@ -13,9 +14,9 @@ async function startClient() {
|
|||
await setupDateFns();
|
||||
|
||||
const wrapper = (
|
||||
<Router history={HistoryService.history}>
|
||||
<BrowserRouter>
|
||||
<App user={UserService.Instance.myUserInfo} />
|
||||
</Router>
|
||||
</BrowserRouter>
|
||||
);
|
||||
|
||||
const root = document.getElementById("root");
|
||||
|
|
|
@ -6,7 +6,7 @@ import fetch from "cross-fetch";
|
|||
import type { Request, Response } from "express";
|
||||
import { StaticRouter, matchPath } from "inferno-router";
|
||||
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 {
|
||||
InitialFetchRequest,
|
||||
|
@ -26,18 +26,19 @@ export default async (req: Request, res: Response) => {
|
|||
try {
|
||||
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 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;
|
||||
|
||||
// Get site data first
|
||||
|
@ -46,19 +47,18 @@ export default async (req: Request, res: Response) => {
|
|||
let site: GetSiteResponse | undefined = undefined;
|
||||
let routeData: RouteData = {};
|
||||
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(
|
||||
"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;
|
||||
auth = undefined;
|
||||
try_site = await client.getSite(getSiteForm);
|
||||
client.setHeaders({});
|
||||
try_site = await client.getSite();
|
||||
}
|
||||
|
||||
if (!auth && isAuthPath(path)) {
|
||||
return res.redirect("/login");
|
||||
return res.redirect(`/login?prev=${encodeURIComponent(url)}`);
|
||||
}
|
||||
|
||||
if (try_site.state === "success") {
|
||||
|
@ -72,7 +72,6 @@ export default async (req: Request, res: Response) => {
|
|||
if (site && activeRoute?.fetchInitialData) {
|
||||
const initialFetchReq: InitialFetchRequest = {
|
||||
client,
|
||||
auth,
|
||||
path,
|
||||
query,
|
||||
site,
|
||||
|
@ -90,7 +89,7 @@ export default async (req: Request, res: Response) => {
|
|||
}
|
||||
|
||||
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;
|
||||
|
||||
// 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);
|
||||
|
||||
res.send(await createSsrHtml(root, isoData));
|
||||
res.send(await createSsrHtml(root, isoData, res.locals.cspNonce));
|
||||
} catch (err) {
|
||||
// If an error is caught here, the error page couldn't even be rendered
|
||||
console.error(err);
|
||||
res.statusCode = 500;
|
||||
|
||||
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()) {
|
||||
const headers = setForwardedHeaders(req.headers);
|
||||
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") {
|
||||
manifest = await generateManifestJson(site.data);
|
||||
|
|
|
@ -15,5 +15,6 @@ export default async ({ res }: { res: Response }) => {
|
|||
Disallow: /admin
|
||||
Disallow: /password_change
|
||||
Disallow: /search/
|
||||
Disallow: /modlog
|
||||
`);
|
||||
};
|
||||
|
|
|
@ -12,6 +12,6 @@ export default async ({ res }: { res: Response }) => {
|
|||
process.env.LEMMY_UI_LEMMY_EXTERNAL_HOST +
|
||||
`
|
||||
Expires: 2024-01-01T04:59:00.000Z
|
||||
`
|
||||
`,
|
||||
);
|
||||
};
|
||||
|
|
|
@ -8,7 +8,7 @@ export default async ({ res }: { res: Response }) => {
|
|||
path.resolve(
|
||||
`./dist/service-worker${
|
||||
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 ThemesListHandler from "./handlers/themes-list-handler";
|
||||
import { setCacheControl, setDefaultCsp } from "./middleware";
|
||||
import CodeThemeHandler from "./handlers/code-theme-handler";
|
||||
|
||||
const server = express();
|
||||
|
||||
|
@ -20,17 +21,26 @@ const [hostname, port] = process.env["LEMMY_UI_HOST"]
|
|||
|
||||
server.use(express.json());
|
||||
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(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);
|
||||
|
@ -38,6 +48,7 @@ server.get("/robots.txt", RobotsHandler);
|
|||
server.get("/service-worker.js", ServiceWorkerHandler);
|
||||
server.get("/manifest.webmanifest", ManifestHandler);
|
||||
server.get("/css/themes/:name", ThemeHandler);
|
||||
server.get("/css/code-themes/:name", CodeThemeHandler);
|
||||
server.get("/css/themelist", ThemesListHandler);
|
||||
server.get("/*", CatchAllHandler);
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import * as crypto from "crypto";
|
||||
import type { NextFunction, Request, Response } from "express";
|
||||
import { hasJwtCookie } from "./utils/has-jwt-cookie";
|
||||
|
||||
|
@ -8,9 +9,20 @@ export function setDefaultCsp({
|
|||
res: Response;
|
||||
next: NextFunction;
|
||||
}) {
|
||||
res.locals.cspNonce = crypto.randomBytes(16).toString("hex");
|
||||
|
||||
res.setHeader(
|
||||
"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();
|
||||
|
@ -25,7 +37,7 @@ export function setDefaultCsp({
|
|||
export function setCacheControl(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
next: NextFunction,
|
||||
) {
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
return next();
|
||||
|
@ -43,7 +55,7 @@ export function setCacheControl(
|
|||
if (hasJwtCookie(req)) {
|
||||
caching = "private";
|
||||
} 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 sharp from "sharp";
|
||||
import { favIconPngUrl, favIconUrl } from "../../shared/config";
|
||||
import { ILemmyConfig, IsoDataOptionalSite } from "../../shared/interfaces";
|
||||
import { IsoDataOptionalSite } from "../../shared/interfaces";
|
||||
import { buildThemeList } from "./build-themes-list";
|
||||
import { fetchIconPng } from "./fetch-icon-png";
|
||||
|
||||
|
@ -14,7 +14,8 @@ let appleTouchIcon: string | undefined = undefined;
|
|||
|
||||
export async function createSsrHtml(
|
||||
root: string,
|
||||
isoData: IsoDataOptionalSite
|
||||
isoData: IsoDataOptionalSite,
|
||||
cspNonce: string,
|
||||
) {
|
||||
const site = isoData.site_res;
|
||||
|
||||
|
@ -22,10 +23,16 @@ export async function createSsrHtml(
|
|||
(await buildThemeList())[0]
|
||||
}.css" />`;
|
||||
|
||||
const customHtmlHeaderScriptTag = new RegExp("<script", "g");
|
||||
const customHtmlHeaderWithNonce = customHtmlHeader.replace(
|
||||
customHtmlHeaderScriptTag,
|
||||
`<script nonce="${cspNonce}"`,
|
||||
);
|
||||
|
||||
if (!appleTouchIcon) {
|
||||
appleTouchIcon = site?.site_view.site.icon
|
||||
? `data:image/png;base64,${await sharp(
|
||||
await fetchIconPng(site.site_view.site.icon)
|
||||
await fetchIconPng(site.site_view.site.icon),
|
||||
)
|
||||
.resize(180, 180)
|
||||
.extend({
|
||||
|
@ -45,28 +52,28 @@ export async function createSsrHtml(
|
|||
process.env["LEMMY_UI_DEBUG"] === "true"
|
||||
? renderToString(
|
||||
<>
|
||||
<script src="//cdn.jsdelivr.net/npm/eruda"></script>
|
||||
<script>eruda.init();</script>
|
||||
</>
|
||||
<script
|
||||
nonce={cspNonce}
|
||||
src="//cdn.jsdelivr.net/npm/eruda"
|
||||
></script>
|
||||
<script nonce={cspNonce}>eruda.init();</script>
|
||||
</>,
|
||||
)
|
||||
: "";
|
||||
|
||||
const helmet = Helmet.renderStatic();
|
||||
|
||||
const config: ILemmyConfig = { wsHost: process.env.LEMMY_UI_LEMMY_WS_HOST };
|
||||
|
||||
return `
|
||||
<!DOCTYPE html>
|
||||
<html ${helmet.htmlAttributes.toString()}>
|
||||
<head>
|
||||
<script>window.isoData = ${serialize(isoData)}</script>
|
||||
<script>window.lemmyConfig = ${serialize(config)}</script>
|
||||
<script nonce="${cspNonce}">window.isoData = ${serialize(isoData)}</script>
|
||||
|
||||
<!-- A remote debugging utility for mobile -->
|
||||
${erudaStr}
|
||||
|
||||
<!-- Custom injected script -->
|
||||
${customHtmlHeader}
|
||||
${customHtmlHeaderWithNonce}
|
||||
|
||||
${helmet.title.toString()}
|
||||
${helmet.meta.toString()}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { getHttpBaseExternal } from "@utils/env";
|
||||
import { readFile } from "fs/promises";
|
||||
import { GetSiteResponse } from "lemmy-js-client";
|
||||
import path from "path";
|
||||
|
@ -11,7 +10,7 @@ const defaultLogoPathDirectory = path.join(
|
|||
process.cwd(),
|
||||
"dist",
|
||||
"assets",
|
||||
"icons"
|
||||
"icons",
|
||||
);
|
||||
|
||||
export default async function ({
|
||||
|
@ -21,15 +20,13 @@ export default async function ({
|
|||
local_site: { community_creation_admin_only },
|
||||
},
|
||||
}: GetSiteResponse) {
|
||||
const url = getHttpBaseExternal();
|
||||
|
||||
const icon = site.icon ? await fetchIconPng(site.icon) : null;
|
||||
|
||||
return {
|
||||
name: site.name,
|
||||
description: site.description ?? "A link aggregator for the fediverse",
|
||||
start_url: url,
|
||||
scope: url,
|
||||
start_url: "/",
|
||||
scope: "/",
|
||||
display: "standalone",
|
||||
id: "/",
|
||||
background_color: "#222222",
|
||||
|
@ -37,7 +34,7 @@ export default async function ({
|
|||
icons: await Promise.all(
|
||||
iconSizes.map(async size => {
|
||||
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"));
|
||||
|
||||
if (icon) {
|
||||
|
@ -54,7 +51,7 @@ export default async function ({
|
|||
src: `data:image/png;base64,${src}`,
|
||||
purpose: "any maskable",
|
||||
};
|
||||
})
|
||||
}),
|
||||
),
|
||||
shortcuts: [
|
||||
{
|
||||
|
@ -76,7 +73,8 @@ export default async function ({
|
|||
description: "Create a post.",
|
||||
},
|
||||
].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",
|
||||
|
@ -85,7 +83,7 @@ export default async function ({
|
|||
description: "Create a community",
|
||||
},
|
||||
]
|
||||
: []
|
||||
: [],
|
||||
),
|
||||
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 { Component, RefObject, createRef, linkEvent } from "inferno";
|
||||
import { Provider } from "inferno-i18next-dess";
|
||||
|
@ -14,6 +14,8 @@ import { Footer } from "./footer";
|
|||
import { Navbar } from "./navbar";
|
||||
import "./styles.scss";
|
||||
import { Theme } from "./theme";
|
||||
import AnonymousGuard from "../common/anonymous-guard";
|
||||
import { CodeTheme } from "./code-theme";
|
||||
|
||||
interface AppProps {
|
||||
user?: MyUserInfo;
|
||||
|
@ -54,7 +56,10 @@ export class App extends Component<AppProps, any> {
|
|||
{I18NextService.i18n.t("jump_to_content", "Jump to content")}
|
||||
</button>
|
||||
{siteView && (
|
||||
<Theme defaultTheme={siteView.local_site.default_theme} />
|
||||
<>
|
||||
<Theme defaultTheme={siteView.local_site.default_theme} />
|
||||
<CodeTheme />
|
||||
</>
|
||||
)}
|
||||
<Navbar siteRes={siteRes} />
|
||||
<div className="mt-4 p-0 fl-1">
|
||||
|
@ -75,9 +80,13 @@ export class App extends Component<AppProps, any> {
|
|||
<div tabIndex={-1}>
|
||||
{RouteComponent &&
|
||||
(isAuthPath(path ?? "") ? (
|
||||
<AuthGuard>
|
||||
<AuthGuard {...routeProps}>
|
||||
<RouteComponent {...routeProps} />
|
||||
</AuthGuard>
|
||||
) : isAnonymousPath(path ?? "") ? (
|
||||
<AnonymousGuard>
|
||||
<RouteComponent {...routeProps} />
|
||||
</AnonymousGuard>
|
||||
) : (
|
||||
<RouteComponent {...routeProps} />
|
||||
))}
|
||||
|
@ -86,7 +95,7 @@ export class App extends Component<AppProps, any> {
|
|||
);
|
||||
}}
|
||||
/>
|
||||
)
|
||||
),
|
||||
)}
|
||||
<Route component={ErrorPage} />
|
||||
</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 { removeAuthParam } from "@utils/helpers";
|
||||
import { Component } from "inferno";
|
||||
import { T } from "inferno-i18next-dess";
|
||||
import { Link } from "inferno-router";
|
||||
|
@ -56,11 +55,7 @@ export class ErrorPage extends Component<any, any> {
|
|||
</>
|
||||
)}
|
||||
{errorPageData?.error && (
|
||||
<T
|
||||
i18nKey="error_code_message"
|
||||
parent="p"
|
||||
interpolation={{ error: removeAuthParam(errorPageData.error) }}
|
||||
>
|
||||
<T i18nKey="error_code_message" parent="p">
|
||||
#<strong className="text-danger">#</strong>#
|
||||
</T>
|
||||
)}
|
||||
|
|
|
@ -1,31 +1,30 @@
|
|||
import { myAuth, showAvatars } from "@utils/app";
|
||||
import { showAvatars } from "@utils/app";
|
||||
import { isBrowser } from "@utils/browser";
|
||||
import { numToSI, poll } from "@utils/helpers";
|
||||
import { numToSI } from "@utils/helpers";
|
||||
import { amAdmin, canCreateCommunity } from "@utils/roles";
|
||||
import { Component, createRef, linkEvent } from "inferno";
|
||||
import { NavLink } from "inferno-router";
|
||||
import { GetSiteResponse } from "lemmy-js-client";
|
||||
import { donateLemmyUrl } from "../../config";
|
||||
import {
|
||||
GetReportCountResponse,
|
||||
GetSiteResponse,
|
||||
GetUnreadCountResponse,
|
||||
GetUnreadRegistrationApplicationCountResponse,
|
||||
} from "lemmy-js-client";
|
||||
import { donateLemmyUrl, updateUnreadCountsInterval } from "../../config";
|
||||
import { I18NextService, UserService } from "../../services";
|
||||
import { HttpService, RequestState } from "../../services/HttpService";
|
||||
I18NextService,
|
||||
UserService,
|
||||
UnreadCounterService,
|
||||
} from "../../services";
|
||||
import { toast } from "../../toast";
|
||||
import { Icon } from "../common/icon";
|
||||
import { PictrsImage } from "../common/pictrs-image";
|
||||
import { Subscription } from "rxjs";
|
||||
|
||||
interface NavbarProps {
|
||||
siteRes?: GetSiteResponse;
|
||||
}
|
||||
|
||||
interface NavbarState {
|
||||
unreadInboxCountRes: RequestState<GetUnreadCountResponse>;
|
||||
unreadReportCountRes: RequestState<GetReportCountResponse>;
|
||||
unreadApplicationCountRes: RequestState<GetUnreadRegistrationApplicationCountResponse>;
|
||||
onSiteBanner?(url: string): any;
|
||||
unreadInboxCount: number;
|
||||
unreadReportCount: number;
|
||||
unreadApplicationCount: number;
|
||||
}
|
||||
|
||||
function handleCollapseClick(i: Navbar) {
|
||||
|
@ -44,13 +43,17 @@ function handleLogOut(i: Navbar) {
|
|||
}
|
||||
|
||||
export class Navbar extends Component<NavbarProps, NavbarState> {
|
||||
state: NavbarState = {
|
||||
unreadInboxCountRes: { state: "empty" },
|
||||
unreadReportCountRes: { state: "empty" },
|
||||
unreadApplicationCountRes: { state: "empty" },
|
||||
};
|
||||
collapseButtonRef = createRef<HTMLButtonElement>();
|
||||
mobileMenuRef = createRef<HTMLDivElement>();
|
||||
unreadInboxCountSubscription: Subscription;
|
||||
unreadReportCountSubscription: Subscription;
|
||||
unreadApplicationCountSubscription: Subscription;
|
||||
|
||||
state: NavbarState = {
|
||||
unreadInboxCount: 0,
|
||||
unreadReportCount: 0,
|
||||
unreadApplicationCount: 0,
|
||||
};
|
||||
|
||||
constructor(props: any, context: any) {
|
||||
super(props, context);
|
||||
|
@ -63,7 +66,18 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
|
|||
if (isBrowser()) {
|
||||
// On the first load, check the unreads
|
||||
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();
|
||||
|
||||
document.addEventListener("mouseup", this.handleOutsideMenuClick);
|
||||
|
@ -72,6 +86,9 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
|
|||
|
||||
componentWillUnmount() {
|
||||
document.removeEventListener("mouseup", this.handleOutsideMenuClick);
|
||||
this.unreadInboxCountSubscription.unsubscribe();
|
||||
this.unreadReportCountSubscription.unsubscribe();
|
||||
this.unreadApplicationCountSubscription.unsubscribe();
|
||||
}
|
||||
|
||||
// TODO class active corresponding to current pages
|
||||
|
@ -103,34 +120,34 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
|
|||
to="/inbox"
|
||||
className="p-1 nav-link border-0 nav-messages"
|
||||
title={I18NextService.i18n.t("unread_messages", {
|
||||
count: Number(this.state.unreadApplicationCountRes.state),
|
||||
formattedCount: numToSI(this.unreadInboxCount),
|
||||
count: Number(this.state.unreadInboxCount),
|
||||
formattedCount: numToSI(this.state.unreadInboxCount),
|
||||
})}
|
||||
onMouseUp={linkEvent(this, handleCollapseClick)}
|
||||
>
|
||||
<Icon icon="bell" />
|
||||
{this.unreadInboxCount > 0 && (
|
||||
{this.state.unreadInboxCount > 0 && (
|
||||
<span className="mx-1 badge text-bg-light">
|
||||
{numToSI(this.unreadInboxCount)}
|
||||
{numToSI(this.state.unreadInboxCount)}
|
||||
</span>
|
||||
)}
|
||||
</NavLink>
|
||||
</li>
|
||||
{this.moderatesSomething && (
|
||||
{UserService.Instance.moderatesSomething && (
|
||||
<li className="nav-item nav-item-icon">
|
||||
<NavLink
|
||||
to="/reports"
|
||||
className="p-1 nav-link border-0"
|
||||
title={I18NextService.i18n.t("unread_reports", {
|
||||
count: Number(this.unreadReportCount),
|
||||
formattedCount: numToSI(this.unreadReportCount),
|
||||
count: Number(this.state.unreadReportCount),
|
||||
formattedCount: numToSI(this.state.unreadReportCount),
|
||||
})}
|
||||
onMouseUp={linkEvent(this, handleCollapseClick)}
|
||||
>
|
||||
<Icon icon="shield" />
|
||||
{this.unreadReportCount > 0 && (
|
||||
{this.state.unreadReportCount > 0 && (
|
||||
<span className="mx-1 badge text-bg-light">
|
||||
{numToSI(this.unreadReportCount)}
|
||||
{numToSI(this.state.unreadReportCount)}
|
||||
</span>
|
||||
)}
|
||||
</NavLink>
|
||||
|
@ -144,16 +161,18 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
|
|||
title={I18NextService.i18n.t(
|
||||
"unread_registration_applications",
|
||||
{
|
||||
count: Number(this.unreadApplicationCount),
|
||||
formattedCount: numToSI(this.unreadApplicationCount),
|
||||
}
|
||||
count: Number(this.state.unreadApplicationCount),
|
||||
formattedCount: numToSI(
|
||||
this.state.unreadApplicationCount,
|
||||
),
|
||||
},
|
||||
)}
|
||||
onMouseUp={linkEvent(this, handleCollapseClick)}
|
||||
>
|
||||
<Icon icon="clipboard" />
|
||||
{this.unreadApplicationCount > 0 && (
|
||||
{this.state.unreadApplicationCount > 0 && (
|
||||
<span className="mx-1 badge text-bg-light">
|
||||
{numToSI(this.unreadApplicationCount)}
|
||||
{numToSI(this.state.unreadApplicationCount)}
|
||||
</span>
|
||||
)}
|
||||
</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"
|
||||
to="/inbox"
|
||||
title={I18NextService.i18n.t("unread_messages", {
|
||||
count: Number(this.unreadInboxCount),
|
||||
formattedCount: numToSI(this.unreadInboxCount),
|
||||
count: Number(this.state.unreadInboxCount),
|
||||
formattedCount: numToSI(this.state.unreadInboxCount),
|
||||
})}
|
||||
onMouseUp={linkEvent(this, handleCollapseClick)}
|
||||
>
|
||||
<Icon icon="bell" />
|
||||
<span className="badge text-bg-light d-inline ms-1 d-md-none ms-md-0">
|
||||
{I18NextService.i18n.t("unread_messages", {
|
||||
count: Number(this.unreadInboxCount),
|
||||
formattedCount: numToSI(this.unreadInboxCount),
|
||||
count: Number(this.state.unreadInboxCount),
|
||||
formattedCount: numToSI(this.state.unreadInboxCount),
|
||||
})}
|
||||
</span>
|
||||
{this.unreadInboxCount > 0 && (
|
||||
{this.state.unreadInboxCount > 0 && (
|
||||
<span className="mx-1 badge text-bg-light">
|
||||
{numToSI(this.unreadInboxCount)}
|
||||
{numToSI(this.state.unreadInboxCount)}
|
||||
</span>
|
||||
)}
|
||||
</NavLink>
|
||||
</li>
|
||||
{this.moderatesSomething && (
|
||||
{UserService.Instance.moderatesSomething && (
|
||||
<li id="navModeration" className="nav-item">
|
||||
<NavLink
|
||||
className="nav-link d-inline-flex align-items-center d-md-inline-block"
|
||||
to="/reports"
|
||||
title={I18NextService.i18n.t("unread_reports", {
|
||||
count: Number(this.unreadReportCount),
|
||||
formattedCount: numToSI(this.unreadReportCount),
|
||||
count: Number(this.state.unreadReportCount),
|
||||
formattedCount: numToSI(this.state.unreadReportCount),
|
||||
})}
|
||||
onMouseUp={linkEvent(this, handleCollapseClick)}
|
||||
>
|
||||
<Icon icon="shield" />
|
||||
<span className="badge text-bg-light d-inline ms-1 d-md-none ms-md-0">
|
||||
{I18NextService.i18n.t("unread_reports", {
|
||||
count: Number(this.unreadReportCount),
|
||||
formattedCount: numToSI(this.unreadReportCount),
|
||||
count: Number(this.state.unreadReportCount),
|
||||
formattedCount: numToSI(
|
||||
this.state.unreadReportCount,
|
||||
),
|
||||
})}
|
||||
</span>
|
||||
{this.unreadReportCount > 0 && (
|
||||
{this.state.unreadReportCount > 0 && (
|
||||
<span className="mx-1 badge text-bg-light">
|
||||
{numToSI(this.unreadReportCount)}
|
||||
{numToSI(this.state.unreadReportCount)}
|
||||
</span>
|
||||
)}
|
||||
</NavLink>
|
||||
|
@ -321,11 +342,11 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
|
|||
title={I18NextService.i18n.t(
|
||||
"unread_registration_applications",
|
||||
{
|
||||
count: Number(this.unreadApplicationCount),
|
||||
count: Number(this.state.unreadApplicationCount),
|
||||
formattedCount: numToSI(
|
||||
this.unreadApplicationCount
|
||||
this.state.unreadApplicationCount,
|
||||
),
|
||||
}
|
||||
},
|
||||
)}
|
||||
onMouseUp={linkEvent(this, handleCollapseClick)}
|
||||
>
|
||||
|
@ -334,16 +355,16 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
|
|||
{I18NextService.i18n.t(
|
||||
"unread_registration_applications",
|
||||
{
|
||||
count: Number(this.unreadApplicationCount),
|
||||
count: Number(this.state.unreadApplicationCount),
|
||||
formattedCount: numToSI(
|
||||
this.unreadApplicationCount
|
||||
this.state.unreadApplicationCount,
|
||||
),
|
||||
}
|
||||
},
|
||||
)}
|
||||
</span>
|
||||
{this.unreadApplicationCount > 0 && (
|
||||
{this.state.unreadApplicationCount > 0 && (
|
||||
<span className="mx-1 badge text-bg-light">
|
||||
{numToSI(this.unreadApplicationCount)}
|
||||
{numToSI(this.state.unreadApplicationCount)}
|
||||
</span>
|
||||
)}
|
||||
</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() {
|
||||
return this.context.router.history.location.pathname;
|
||||
}
|
||||
|
|
|
@ -21,7 +21,10 @@ export class Theme extends Component<Props> {
|
|||
/>
|
||||
</Helmet>
|
||||
);
|
||||
} else if (this.props.defaultTheme != "browser") {
|
||||
} else if (
|
||||
this.props.defaultTheme !== "browser" &&
|
||||
this.props.defaultTheme !== "browser-compact"
|
||||
) {
|
||||
return (
|
||||
<Helmet>
|
||||
<link
|
||||
|
@ -31,6 +34,25 @@ export class Theme extends Component<Props> {
|
|||
/>
|
||||
</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 {
|
||||
return (
|
||||
<Helmet>
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { myAuthRequired } from "@utils/app";
|
||||
import { capitalizeFirstLetter } from "@utils/helpers";
|
||||
import { Component } from "inferno";
|
||||
import { T } from "inferno-i18next-dess";
|
||||
|
@ -43,7 +42,7 @@ export class CommentForm extends Component<CommentFormProps, any> {
|
|||
return (
|
||||
<div
|
||||
className={["comment-form", "mb-3", this.props.containerClass].join(
|
||||
" "
|
||||
" ",
|
||||
)}
|
||||
>
|
||||
{UserService.Instance.myUserInfo ? (
|
||||
|
@ -84,7 +83,7 @@ export class CommentForm extends Component<CommentFormProps, any> {
|
|||
: 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;
|
||||
if (typeof node === "number") {
|
||||
const post_id = node;
|
||||
|
@ -92,8 +91,6 @@ export class CommentForm extends Component<CommentFormProps, any> {
|
|||
content,
|
||||
post_id,
|
||||
language_id,
|
||||
form_id,
|
||||
auth: myAuthRequired(),
|
||||
});
|
||||
} else {
|
||||
if (edit) {
|
||||
|
@ -101,9 +98,7 @@ export class CommentForm extends Component<CommentFormProps, any> {
|
|||
onUpsertComment({
|
||||
content,
|
||||
comment_id,
|
||||
form_id,
|
||||
language_id,
|
||||
auth: myAuthRequired(),
|
||||
});
|
||||
} else {
|
||||
const post_id = node.comment_view.post.id;
|
||||
|
@ -112,9 +107,7 @@ export class CommentForm extends Component<CommentFormProps, any> {
|
|||
content,
|
||||
parent_id,
|
||||
post_id,
|
||||
form_id,
|
||||
language_id,
|
||||
auth: myAuthRequired(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,4 @@
|
|||
import {
|
||||
colorList,
|
||||
getCommentParentId,
|
||||
myAuth,
|
||||
myAuthRequired,
|
||||
showScores,
|
||||
} from "@utils/app";
|
||||
import { colorList, getCommentParentId, showScores } from "@utils/app";
|
||||
import { futureDaysToUnixTime, numToSI } from "@utils/helpers";
|
||||
import {
|
||||
amCommunityCreator,
|
||||
|
@ -68,6 +62,7 @@ import { CommunityLink } from "../community/community-link";
|
|||
import { PersonListing } from "../person/person-listing";
|
||||
import { CommentForm } from "./comment-form";
|
||||
import { CommentNodes } from "./comment-nodes";
|
||||
import ReportForm from "../common/report-form";
|
||||
|
||||
interface CommentNodeState {
|
||||
showReply: boolean;
|
||||
|
@ -90,7 +85,6 @@ interface CommentNodeState {
|
|||
viewSource: boolean;
|
||||
showAdvanced: boolean;
|
||||
showReportDialog: boolean;
|
||||
reportReason?: string;
|
||||
createOrEditCommentLoading: boolean;
|
||||
upvoteLoading: boolean;
|
||||
downvoteLoading: boolean;
|
||||
|
@ -105,7 +99,6 @@ interface CommentNodeState {
|
|||
addAdminLoading: boolean;
|
||||
transferCommunityLoading: boolean;
|
||||
fetchChildrenLoading: boolean;
|
||||
reportLoading: boolean;
|
||||
purgeLoading: boolean;
|
||||
}
|
||||
|
||||
|
@ -114,7 +107,7 @@ interface CommentNodeProps {
|
|||
moderators?: CommunityModeratorView[];
|
||||
admins?: PersonView[];
|
||||
noBorder?: boolean;
|
||||
noIndent?: boolean;
|
||||
isTopLevel?: boolean;
|
||||
viewOnly?: boolean;
|
||||
locked?: boolean;
|
||||
markable?: boolean;
|
||||
|
@ -179,7 +172,6 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
addAdminLoading: false,
|
||||
transferCommunityLoading: false,
|
||||
fetchChildrenLoading: false,
|
||||
reportLoading: false,
|
||||
purgeLoading: false,
|
||||
};
|
||||
|
||||
|
@ -187,6 +179,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
super(props, context);
|
||||
|
||||
this.handleReplyCancel = this.handleReplyCancel.bind(this);
|
||||
this.handleReportComment = this.handleReportComment.bind(this);
|
||||
}
|
||||
|
||||
get commentView(): CommentView {
|
||||
|
@ -198,11 +191,10 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
}
|
||||
|
||||
componentWillReceiveProps(
|
||||
nextProps: Readonly<{ children?: InfernoNode } & CommentNodeProps>
|
||||
nextProps: Readonly<{ children?: InfernoNode } & CommentNodeProps>,
|
||||
): void {
|
||||
if (!deepEqual(this.props, nextProps)) {
|
||||
this.setState({
|
||||
showReply: false,
|
||||
showEdit: false,
|
||||
showRemoveDialog: false,
|
||||
showBanDialog: false,
|
||||
|
@ -232,7 +224,6 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
addAdminLoading: false,
|
||||
transferCommunityLoading: false,
|
||||
fetchChildrenLoading: false,
|
||||
reportLoading: false,
|
||||
purgeLoading: false,
|
||||
});
|
||||
}
|
||||
|
@ -243,34 +234,34 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
const cv = this.commentView;
|
||||
|
||||
const purgeTypeText =
|
||||
this.state.purgeType == PurgeType.Comment
|
||||
this.state.purgeType === PurgeType.Comment
|
||||
? I18NextService.i18n.t("purge_comment")
|
||||
: `${I18NextService.i18n.t("purge")} ${cv.creator.name}`;
|
||||
|
||||
const canMod_ = canMod(
|
||||
cv.creator.id,
|
||||
this.props.moderators,
|
||||
this.props.admins
|
||||
this.props.admins,
|
||||
);
|
||||
const canModOnSelf = canMod(
|
||||
cv.creator.id,
|
||||
this.props.moderators,
|
||||
this.props.admins,
|
||||
UserService.Instance.myUserInfo,
|
||||
true
|
||||
true,
|
||||
);
|
||||
const canAdmin_ = canAdmin(cv.creator.id, this.props.admins);
|
||||
const canAdminOnSelf = canAdmin(
|
||||
cv.creator.id,
|
||||
this.props.admins,
|
||||
UserService.Instance.myUserInfo,
|
||||
true
|
||||
true,
|
||||
);
|
||||
const isMod_ = isMod(cv.creator.id, this.props.moderators);
|
||||
const isAdmin_ = isAdmin(cv.creator.id, this.props.admins);
|
||||
const amCommunityCreator_ = amCommunityCreator(
|
||||
cv.creator.id,
|
||||
this.props.moderators
|
||||
this.props.moderators,
|
||||
);
|
||||
|
||||
const moreRepliesBorderColor = this.props.node.depth
|
||||
|
@ -278,13 +269,13 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
: colorList[0];
|
||||
|
||||
const showMoreChildren =
|
||||
this.props.viewType == CommentViewType.Tree &&
|
||||
this.props.viewType === CommentViewType.Tree &&
|
||||
!this.state.collapsed &&
|
||||
node.children.length == 0 &&
|
||||
node.children.length === 0 &&
|
||||
node.comment_view.counts.child_count > 0;
|
||||
|
||||
return (
|
||||
<li className="comment">
|
||||
<li className="comment list-unstyled">
|
||||
<article
|
||||
id={`comment-${cv.comment.id}`}
|
||||
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,
|
||||
})}
|
||||
>
|
||||
<div
|
||||
className={classNames({
|
||||
"ms-2": !this.props.noIndent,
|
||||
})}
|
||||
>
|
||||
<div className="ms-2">
|
||||
<div className="d-flex flex-wrap align-items-center text-muted small">
|
||||
<button
|
||||
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">
|
||||
{
|
||||
this.props.allLanguages.find(
|
||||
lang => lang.id === cv.comment.language_id
|
||||
lang => lang.id === cv.comment.language_id,
|
||||
)?.name
|
||||
}
|
||||
</span>
|
||||
|
@ -352,7 +339,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
{showScores() && (
|
||||
<>
|
||||
<span
|
||||
className="me-1 fw-bold"
|
||||
className={`me-1 fw-bold ${this.scoreColor}`}
|
||||
aria-label={I18NextService.i18n.t("number_of_points", {
|
||||
count: Number(this.commentView.counts.score),
|
||||
formattedCount: numToSI(this.commentView.counts.score),
|
||||
|
@ -378,7 +365,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
onReplyCancel={this.handleReplyCancel}
|
||||
disabled={this.props.locked}
|
||||
finished={this.props.finished.get(
|
||||
this.props.node.comment_view.comment.id
|
||||
this.props.node.comment_view.comment.id,
|
||||
)}
|
||||
focus
|
||||
allLanguages={this.props.allLanguages}
|
||||
|
@ -388,20 +375,22 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
/>
|
||||
)}
|
||||
{!this.state.showEdit && !this.state.collapsed && (
|
||||
<div>
|
||||
{this.state.viewSource ? (
|
||||
<pre>{this.commentUnlessRemoved}</pre>
|
||||
) : (
|
||||
<div
|
||||
className="md-div"
|
||||
dangerouslySetInnerHTML={
|
||||
this.props.hideImages
|
||||
? mdToHtmlNoImages(this.commentUnlessRemoved)
|
||||
: mdToHtml(this.commentUnlessRemoved)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<div className="d-flex justify-content-between justify-content-lg-start flex-wrap text-muted fw-bold">
|
||||
<>
|
||||
<div className="comment-content">
|
||||
{this.state.viewSource ? (
|
||||
<pre>{this.commentUnlessRemoved}</pre>
|
||||
) : (
|
||||
<div
|
||||
className="md-div"
|
||||
dangerouslySetInnerHTML={
|
||||
this.props.hideImages
|
||||
? mdToHtmlNoImages(this.commentUnlessRemoved)
|
||||
: mdToHtml(this.commentUnlessRemoved)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</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.markable && (
|
||||
<button
|
||||
|
@ -474,13 +463,13 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
className="btn btn-link btn-animate text-muted"
|
||||
onClick={linkEvent(
|
||||
this,
|
||||
this.handleShowReportDialog
|
||||
this.handleShowReportDialog,
|
||||
)}
|
||||
data-tippy-content={I18NextService.i18n.t(
|
||||
"show_report_dialog"
|
||||
"show_report_dialog",
|
||||
)}
|
||||
aria-label={I18NextService.i18n.t(
|
||||
"show_report_dialog"
|
||||
"show_report_dialog",
|
||||
)}
|
||||
>
|
||||
<Icon icon="flag" />
|
||||
|
@ -489,10 +478,10 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
className="btn btn-link btn-animate text-muted"
|
||||
onClick={linkEvent(
|
||||
this,
|
||||
this.handleBlockPerson
|
||||
this.handleBlockPerson,
|
||||
)}
|
||||
data-tippy-content={I18NextService.i18n.t(
|
||||
"block_user"
|
||||
"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"
|
||||
onClick={linkEvent(this, this.handleViewSource)}
|
||||
data-tippy-content={I18NextService.i18n.t(
|
||||
"view_source"
|
||||
"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"
|
||||
onClick={linkEvent(this, this.handleEditClick)}
|
||||
data-tippy-content={I18NextService.i18n.t(
|
||||
"edit"
|
||||
"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"
|
||||
onClick={linkEvent(
|
||||
this,
|
||||
this.handleDeleteComment
|
||||
this.handleDeleteComment,
|
||||
)}
|
||||
data-tippy-content={
|
||||
!cv.comment.deleted
|
||||
|
@ -590,7 +579,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
className="btn btn-link btn-animate text-muted"
|
||||
onClick={linkEvent(
|
||||
this,
|
||||
this.handleDistinguishComment
|
||||
this.handleDistinguishComment,
|
||||
)}
|
||||
data-tippy-content={
|
||||
!cv.comment.distinguished
|
||||
|
@ -621,7 +610,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
className="btn btn-link btn-animate text-muted"
|
||||
onClick={linkEvent(
|
||||
this,
|
||||
this.handleModRemoveShow
|
||||
this.handleModRemoveShow,
|
||||
)}
|
||||
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"
|
||||
onClick={linkEvent(
|
||||
this,
|
||||
this.handleRemoveComment
|
||||
this.handleRemoveComment,
|
||||
)}
|
||||
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"
|
||||
onClick={linkEvent(
|
||||
this,
|
||||
this.handleModBanFromCommunityShow
|
||||
this.handleModBanFromCommunityShow,
|
||||
)}
|
||||
aria-label={I18NextService.i18n.t(
|
||||
"ban_from_community"
|
||||
"ban_from_community",
|
||||
)}
|
||||
>
|
||||
{I18NextService.i18n.t(
|
||||
"ban_from_community"
|
||||
"ban_from_community",
|
||||
)}
|
||||
</button>
|
||||
) : (
|
||||
|
@ -669,7 +658,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
className="btn btn-link btn-animate text-muted"
|
||||
onClick={linkEvent(
|
||||
this,
|
||||
this.handleBanPersonFromCommunity
|
||||
this.handleBanPersonFromCommunity,
|
||||
)}
|
||||
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"
|
||||
onClick={linkEvent(
|
||||
this,
|
||||
this.handleShowConfirmAppointAsMod
|
||||
this.handleShowConfirmAppointAsMod,
|
||||
)}
|
||||
aria-label={
|
||||
isMod_
|
||||
? I18NextService.i18n.t("remove_as_mod")
|
||||
: I18NextService.i18n.t(
|
||||
"appoint_as_mod"
|
||||
"appoint_as_mod",
|
||||
)
|
||||
}
|
||||
>
|
||||
|
@ -705,7 +694,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
<button
|
||||
className="btn btn-link btn-animate text-muted"
|
||||
aria-label={I18NextService.i18n.t(
|
||||
"are_you_sure"
|
||||
"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"
|
||||
onClick={linkEvent(
|
||||
this,
|
||||
this.handleAddModToCommunity
|
||||
this.handleAddModToCommunity,
|
||||
)}
|
||||
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"
|
||||
onClick={linkEvent(
|
||||
this,
|
||||
this.handleCancelConfirmAppointAsMod
|
||||
this.handleCancelConfirmAppointAsMod,
|
||||
)}
|
||||
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"
|
||||
onClick={linkEvent(
|
||||
this,
|
||||
this.handleShowConfirmTransferCommunity
|
||||
this.handleShowConfirmTransferCommunity,
|
||||
)}
|
||||
aria-label={I18NextService.i18n.t(
|
||||
"transfer_community"
|
||||
"transfer_community",
|
||||
)}
|
||||
>
|
||||
{I18NextService.i18n.t("transfer_community")}
|
||||
|
@ -760,7 +749,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
<button
|
||||
className="btn btn-link btn-animate text-muted"
|
||||
aria-label={I18NextService.i18n.t(
|
||||
"are_you_sure"
|
||||
"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"
|
||||
onClick={linkEvent(
|
||||
this,
|
||||
this.handleTransferCommunity
|
||||
this.handleTransferCommunity,
|
||||
)}
|
||||
aria-label={I18NextService.i18n.t("yes")}
|
||||
>
|
||||
|
@ -784,7 +773,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
onClick={linkEvent(
|
||||
this,
|
||||
this
|
||||
.handleCancelShowConfirmTransferCommunity
|
||||
.handleCancelShowConfirmTransferCommunity,
|
||||
)}
|
||||
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"
|
||||
onClick={linkEvent(
|
||||
this,
|
||||
this.handlePurgePersonShow
|
||||
this.handlePurgePersonShow,
|
||||
)}
|
||||
aria-label={I18NextService.i18n.t(
|
||||
"purge_user"
|
||||
"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"
|
||||
onClick={linkEvent(
|
||||
this,
|
||||
this.handlePurgeCommentShow
|
||||
this.handlePurgeCommentShow,
|
||||
)}
|
||||
aria-label={I18NextService.i18n.t(
|
||||
"purge_comment"
|
||||
"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"
|
||||
onClick={linkEvent(
|
||||
this,
|
||||
this.handleModBanShow
|
||||
this.handleModBanShow,
|
||||
)}
|
||||
aria-label={I18NextService.i18n.t(
|
||||
"ban_from_site"
|
||||
"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"
|
||||
onClick={linkEvent(
|
||||
this,
|
||||
this.handleBanPerson
|
||||
this.handleBanPerson,
|
||||
)}
|
||||
aria-label={I18NextService.i18n.t(
|
||||
"unban_from_site"
|
||||
"unban_from_site",
|
||||
)}
|
||||
>
|
||||
{this.state.banLoading ? (
|
||||
|
@ -862,22 +851,22 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
className="btn btn-link btn-animate text-muted"
|
||||
onClick={linkEvent(
|
||||
this,
|
||||
this.handleShowConfirmAppointAsAdmin
|
||||
this.handleShowConfirmAppointAsAdmin,
|
||||
)}
|
||||
aria-label={
|
||||
isAdmin_
|
||||
? I18NextService.i18n.t(
|
||||
"remove_as_admin"
|
||||
"remove_as_admin",
|
||||
)
|
||||
: I18NextService.i18n.t(
|
||||
"appoint_as_admin"
|
||||
"appoint_as_admin",
|
||||
)
|
||||
}
|
||||
>
|
||||
{isAdmin_
|
||||
? I18NextService.i18n.t("remove_as_admin")
|
||||
: I18NextService.i18n.t(
|
||||
"appoint_as_admin"
|
||||
"appoint_as_admin",
|
||||
)}
|
||||
</button>
|
||||
) : (
|
||||
|
@ -889,7 +878,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
className="btn btn-link btn-animate text-muted"
|
||||
onClick={linkEvent(
|
||||
this,
|
||||
this.handleAddAdmin
|
||||
this.handleAddAdmin,
|
||||
)}
|
||||
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"
|
||||
onClick={linkEvent(
|
||||
this,
|
||||
this.handleCancelConfirmAppointAsAdmin
|
||||
this.handleCancelConfirmAppointAsAdmin,
|
||||
)}
|
||||
aria-label={I18NextService.i18n.t("no")}
|
||||
>
|
||||
|
@ -919,7 +908,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
)}
|
||||
</div>
|
||||
{/* end of button group */}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</article>
|
||||
|
@ -941,7 +930,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
{I18NextService.i18n.t("x_more_replies", {
|
||||
count: node.comment_view.counts.child_count,
|
||||
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>
|
||||
)}
|
||||
{this.state.showReportDialog && (
|
||||
<form
|
||||
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>
|
||||
<ReportForm onSubmit={this.handleReportComment} />
|
||||
)}
|
||||
{this.state.showBanDialog && (
|
||||
<form onSubmit={linkEvent(this, this.handleModBanBothSubmit)}>
|
||||
|
@ -1116,7 +1079,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
onReplyCancel={this.handleReplyCancel}
|
||||
disabled={this.props.locked}
|
||||
finished={this.props.finished.get(
|
||||
this.props.node.comment_view.comment.id
|
||||
this.props.node.comment_view.comment.id,
|
||||
)}
|
||||
focus
|
||||
allLanguages={this.props.allLanguages}
|
||||
|
@ -1136,7 +1099,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
allLanguages={this.props.allLanguages}
|
||||
siteLanguages={this.props.siteLanguages}
|
||||
hideImages={this.props.hideImages}
|
||||
isChild={!this.props.noIndent}
|
||||
isChild={!this.props.isTopLevel}
|
||||
depth={this.props.node.depth + 1}
|
||||
finished={this.props.finished}
|
||||
onCommentReplyRead={this.props.onCommentReplyRead}
|
||||
|
@ -1212,19 +1175,19 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
|
||||
get myComment(): boolean {
|
||||
return (
|
||||
UserService.Instance.myUserInfo?.local_user_view.person.id ==
|
||||
UserService.Instance.myUserInfo?.local_user_view.person.id ===
|
||||
this.commentView.creator.id
|
||||
);
|
||||
}
|
||||
|
||||
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() {
|
||||
if (this.commentView.my_vote == 1) {
|
||||
if (this.commentView.my_vote === 1) {
|
||||
return "text-info";
|
||||
} else if (this.commentView.my_vote == -1) {
|
||||
} else if (this.commentView.my_vote === -1) {
|
||||
return "text-danger";
|
||||
} else {
|
||||
return "text-muted";
|
||||
|
@ -1281,10 +1244,6 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
i.setState({ showReportDialog: !i.state.showReportDialog });
|
||||
}
|
||||
|
||||
handleReportReasonChange(i: CommentNode, event: any) {
|
||||
i.setState({ reportReason: event.target.value });
|
||||
}
|
||||
|
||||
handleModRemoveShow(i: CommentNode) {
|
||||
i.setState({
|
||||
showRemoveDialog: !i.state.showRemoveDialog,
|
||||
|
@ -1301,13 +1260,13 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
}
|
||||
|
||||
isPersonMentionType(
|
||||
item: CommentView | PersonMentionView | CommentReplyView
|
||||
item: CommentView | PersonMentionView | CommentReplyView,
|
||||
): item is PersonMentionView {
|
||||
return (item as PersonMentionView).person_mention?.id !== undefined;
|
||||
}
|
||||
|
||||
isCommentReplyType(
|
||||
item: CommentView | PersonMentionView | CommentReplyView
|
||||
item: CommentView | PersonMentionView | CommentReplyView,
|
||||
): item is CommentReplyView {
|
||||
return (item as CommentReplyView).comment_reply?.id !== undefined;
|
||||
}
|
||||
|
@ -1414,7 +1373,6 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
i.props.onSaveComment({
|
||||
comment_id: i.commentView.comment.id,
|
||||
save: !i.commentView.saved,
|
||||
auth: myAuthRequired(),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1423,7 +1381,6 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
i.props.onBlockPerson({
|
||||
person_id: i.commentView.creator.id,
|
||||
block: true,
|
||||
auth: myAuthRequired(),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1434,13 +1391,11 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
i.props.onPersonMentionRead({
|
||||
person_mention_id: cv.person_mention.id,
|
||||
read: !cv.person_mention.read,
|
||||
auth: myAuthRequired(),
|
||||
});
|
||||
} else if (i.isCommentReplyType(cv)) {
|
||||
i.props.onCommentReplyRead({
|
||||
comment_reply_id: cv.comment_reply.id,
|
||||
read: !cv.comment_reply.read,
|
||||
auth: myAuthRequired(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1450,7 +1405,6 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
i.props.onDeleteComment({
|
||||
comment_id: i.commentId,
|
||||
deleted: !i.commentView.comment.deleted,
|
||||
auth: myAuthRequired(),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1460,7 +1414,6 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
i.props.onRemoveComment({
|
||||
comment_id: i.commentId,
|
||||
removed: !i.commentView.comment.removed,
|
||||
auth: myAuthRequired(),
|
||||
reason: i.state.removeReason,
|
||||
});
|
||||
}
|
||||
|
@ -1470,7 +1423,6 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
i.props.onDistinguishComment({
|
||||
comment_id: i.commentId,
|
||||
distinguished: !i.commentView.comment.distinguished,
|
||||
auth: myAuthRequired(),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1483,7 +1435,6 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
reason: i.state.banReason,
|
||||
remove_data: i.state.removeData,
|
||||
expires: futureDaysToUnixTime(i.state.banExpireDays),
|
||||
auth: myAuthRequired(),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1495,13 +1446,12 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
reason: i.state.banReason,
|
||||
remove_data: i.state.removeData,
|
||||
expires: futureDaysToUnixTime(i.state.banExpireDays),
|
||||
auth: myAuthRequired(),
|
||||
});
|
||||
}
|
||||
|
||||
handleModBanBothSubmit(i: CommentNode, event: any) {
|
||||
event.preventDefault();
|
||||
if (i.state.banType == BanType.Community) {
|
||||
if (i.state.banType === BanType.Community) {
|
||||
i.handleBanPersonFromCommunity(i);
|
||||
} else {
|
||||
i.handleBanPerson(i);
|
||||
|
@ -1516,7 +1466,6 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
community_id: i.commentView.community.id,
|
||||
person_id: i.commentView.creator.id,
|
||||
added,
|
||||
auth: myAuthRequired(),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1527,7 +1476,6 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
i.props.onAddAdmin({
|
||||
person_id: i.commentView.creator.id,
|
||||
added,
|
||||
auth: myAuthRequired(),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1536,17 +1484,17 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
i.props.onTransferCommunity({
|
||||
community_id: i.commentView.community.id,
|
||||
person_id: i.commentView.creator.id,
|
||||
auth: myAuthRequired(),
|
||||
});
|
||||
}
|
||||
|
||||
handleReportComment(i: CommentNode, event: any) {
|
||||
event.preventDefault();
|
||||
i.setState({ reportLoading: true });
|
||||
i.props.onCommentReport({
|
||||
comment_id: i.commentId,
|
||||
reason: i.state.reportReason ?? "",
|
||||
auth: myAuthRequired(),
|
||||
handleReportComment(reason: string) {
|
||||
this.props.onCommentReport({
|
||||
comment_id: this.commentId,
|
||||
reason,
|
||||
});
|
||||
|
||||
this.setState({
|
||||
showReportDialog: false,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1554,17 +1502,15 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
event.preventDefault();
|
||||
i.setState({ purgeLoading: true });
|
||||
|
||||
if (i.state.purgeType == PurgeType.Person) {
|
||||
if (i.state.purgeType === PurgeType.Person) {
|
||||
i.props.onPurgePerson({
|
||||
person_id: i.commentView.creator.id,
|
||||
reason: i.state.purgeReason,
|
||||
auth: myAuthRequired(),
|
||||
});
|
||||
} else {
|
||||
i.props.onPurgeComment({
|
||||
comment_id: i.commentId,
|
||||
reason: i.state.purgeReason,
|
||||
auth: myAuthRequired(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1577,7 +1523,6 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
limit: 999, // TODO
|
||||
type_: "All",
|
||||
saved_only: false,
|
||||
auth: myAuth(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ interface CommentNodesProps {
|
|||
admins?: PersonView[];
|
||||
maxCommentsShown?: number;
|
||||
noBorder?: boolean;
|
||||
noIndent?: boolean;
|
||||
isTopLevel?: boolean;
|
||||
viewOnly?: boolean;
|
||||
locked?: boolean;
|
||||
markable?: boolean;
|
||||
|
@ -86,7 +86,7 @@ export class CommentNodes extends Component<CommentNodesProps, any> {
|
|||
this.props.nodes.length > 0 && (
|
||||
<ul
|
||||
className={classNames("comments", {
|
||||
"ms-1": !!this.props.isChild,
|
||||
"ms-1": this.props.depth && this.props.depth > 1,
|
||||
"border-top border-light": !this.props.noBorder,
|
||||
})}
|
||||
style={
|
||||
|
@ -100,7 +100,7 @@ export class CommentNodes extends Component<CommentNodesProps, any> {
|
|||
key={node.comment_view.comment.id}
|
||||
node={node}
|
||||
noBorder={this.props.noBorder}
|
||||
noIndent={this.props.noIndent}
|
||||
isTopLevel={this.props.isTopLevel}
|
||||
viewOnly={this.props.viewOnly}
|
||||
locked={this.props.locked}
|
||||
moderators={this.props.moderators}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { myAuthRequired } from "@utils/app";
|
||||
import { Component, InfernoNode, linkEvent } from "inferno";
|
||||
import { T } from "inferno-i18next-dess";
|
||||
import {
|
||||
|
@ -11,6 +10,7 @@ import { I18NextService } from "../../services";
|
|||
import { Icon, Spinner } from "../common/icon";
|
||||
import { PersonListing } from "../person/person-listing";
|
||||
import { CommentNode } from "./comment-node";
|
||||
import { EMPTY_REQUEST } from "../../services/HttpService";
|
||||
|
||||
interface CommentReportProps {
|
||||
report: CommentReportView;
|
||||
|
@ -33,9 +33,9 @@ export class CommentReport extends Component<
|
|||
}
|
||||
|
||||
componentWillReceiveProps(
|
||||
nextProps: Readonly<{ children?: InfernoNode } & CommentReportProps>
|
||||
nextProps: Readonly<{ children?: InfernoNode } & CommentReportProps>,
|
||||
): void {
|
||||
if (this.props != nextProps) {
|
||||
if (this.props !== nextProps) {
|
||||
this.setState({ loading: false });
|
||||
}
|
||||
}
|
||||
|
@ -44,7 +44,7 @@ export class CommentReport extends Component<
|
|||
const r = this.props.report;
|
||||
const comment = r.comment;
|
||||
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 )
|
||||
|
@ -98,8 +98,8 @@ export class CommentReport extends Component<
|
|||
onPersonMentionRead={() => {}}
|
||||
onBanPersonFromCommunity={() => {}}
|
||||
onBanPerson={() => {}}
|
||||
onCreateComment={() => Promise.resolve({ state: "empty" })}
|
||||
onEditComment={() => Promise.resolve({ state: "empty" })}
|
||||
onCreateComment={() => Promise.resolve(EMPTY_REQUEST)}
|
||||
onEditComment={() => Promise.resolve(EMPTY_REQUEST)}
|
||||
/>
|
||||
<div>
|
||||
{I18NextService.i18n.t("reporter")}:{" "}
|
||||
|
@ -149,7 +149,6 @@ export class CommentReport extends Component<
|
|||
i.props.onResolveReport({
|
||||
report_id: i.props.report.comment_report.id,
|
||||
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 { Redirect } from "inferno-router";
|
||||
import { Component } from "inferno";
|
||||
import { RouteComponentProps } from "inferno-router/dist/Route";
|
||||
import { UserService } from "../../services";
|
||||
import { Spinner } from "./icon";
|
||||
|
||||
function AuthGuard(props: { children?: InfernoNode }) {
|
||||
if (!UserService.Instance.myUserInfo) {
|
||||
return <Redirect to="/login" />;
|
||||
} else {
|
||||
return props.children;
|
||||
interface AuthGuardState {
|
||||
hasRedirected: boolean;
|
||||
}
|
||||
|
||||
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 = (
|
||||
counts: CommunityAggregates | SiteAggregates
|
||||
counts: CommunityAggregates | SiteAggregates,
|
||||
): counts is CommunityAggregates => {
|
||||
return "subscribers" in counts;
|
||||
};
|
||||
|
||||
const isSiteAggregates = (
|
||||
counts: CommunityAggregates | SiteAggregates
|
||||
counts: CommunityAggregates | SiteAggregates,
|
||||
): counts is SiteAggregates => {
|
||||
return "communities" in counts;
|
||||
};
|
||||
|
@ -34,7 +34,7 @@ export const Badges = ({ counts, communityId }: BadgesProps) => {
|
|||
{
|
||||
count: Number(counts.users_active_day),
|
||||
formattedCount: numToSI(counts.users_active_day),
|
||||
}
|
||||
},
|
||||
)}
|
||||
>
|
||||
{I18NextService.i18n.t("number_of_users", {
|
||||
|
@ -50,7 +50,7 @@ export const Badges = ({ counts, communityId }: BadgesProps) => {
|
|||
{
|
||||
count: Number(counts.users_active_week),
|
||||
formattedCount: numToSI(counts.users_active_week),
|
||||
}
|
||||
},
|
||||
)}
|
||||
>
|
||||
{I18NextService.i18n.t("number_of_users", {
|
||||
|
@ -66,7 +66,7 @@ export const Badges = ({ counts, communityId }: BadgesProps) => {
|
|||
{
|
||||
count: Number(counts.users_active_month),
|
||||
formattedCount: numToSI(counts.users_active_month),
|
||||
}
|
||||
},
|
||||
)}
|
||||
>
|
||||
{I18NextService.i18n.t("number_of_users", {
|
||||
|
@ -82,7 +82,7 @@ export const Badges = ({ counts, communityId }: BadgesProps) => {
|
|||
{
|
||||
count: Number(counts.users_active_half_year),
|
||||
formattedCount: numToSI(counts.users_active_half_year),
|
||||
}
|
||||
},
|
||||
)}
|
||||
>
|
||||
{I18NextService.i18n.t("number_of_users", {
|
||||
|
|
|
@ -48,6 +48,9 @@ export class CommentSortSelect extends Component<
|
|||
{I18NextService.i18n.t("sort_type")}
|
||||
</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={"New"}>{I18NextService.i18n.t("new")}</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";
|
||||
|
||||
interface EmojiMartProps {
|
||||
|
@ -7,21 +7,24 @@ interface EmojiMartProps {
|
|||
}
|
||||
|
||||
export class EmojiMart extends Component<EmojiMartProps> {
|
||||
div: RefObject<HTMLDivElement>;
|
||||
|
||||
constructor(props: any, context: any) {
|
||||
super(props, context);
|
||||
|
||||
this.div = createRef();
|
||||
|
||||
this.handleEmojiClick = this.handleEmojiClick.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const div: any = document.getElementById("emoji-picker");
|
||||
if (div) {
|
||||
div.appendChild(
|
||||
getEmojiMart(this.handleEmojiClick, this.props.pickerOptions)
|
||||
);
|
||||
}
|
||||
this.div.current?.appendChild(
|
||||
getEmojiMart(this.handleEmojiClick, this.props.pickerOptions) as any,
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div id="emoji-picker"></div>;
|
||||
return <div id="emoji-picker" ref={this.div} />;
|
||||
}
|
||||
|
||||
handleEmojiClick(e: any) {
|
||||
|
|
|
@ -77,5 +77,6 @@ export class EmojiPicker extends Component<EmojiPickerProps, EmojiPickerState> {
|
|||
|
||||
handleEmojiClick(e: any) {
|
||||
this.props.onEmojiClick?.(e);
|
||||
this.setState({ showPicker: false });
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import { I18NextService } from "../../services";
|
|||
interface HtmlTagsProps {
|
||||
title: string;
|
||||
path: string;
|
||||
canonicalPath?: string;
|
||||
description?: string;
|
||||
image?: string;
|
||||
}
|
||||
|
@ -16,6 +17,8 @@ interface HtmlTagsProps {
|
|||
export class HtmlTags extends Component<HtmlTagsProps, any> {
|
||||
render() {
|
||||
const url = httpExternalPath(this.props.path);
|
||||
const canonicalUrl =
|
||||
this.props.canonicalPath ?? httpExternalPath(this.props.path);
|
||||
const desc = this.props.description;
|
||||
const image = this.props.image;
|
||||
|
||||
|
@ -30,6 +33,8 @@ export class HtmlTags extends Component<HtmlTagsProps, any> {
|
|||
<meta key={u} property={u} content={url} />
|
||||
))}
|
||||
|
||||
<link rel="canonical" href={canonicalUrl} />
|
||||
|
||||
{/* Open Graph / Facebook */}
|
||||
<meta property="og:type" content="website" />
|
||||
|
||||
|
@ -45,10 +50,10 @@ export class HtmlTags extends Component<HtmlTagsProps, any> {
|
|||
name={n}
|
||||
content={htmlToText(md.renderInline(desc))}
|
||||
/>
|
||||
)
|
||||
),
|
||||
)}
|
||||
{["og:image", "twitter:image"].map(
|
||||
p => image && <meta key={p} property={p} content={image} />
|
||||
p => image && <meta key={p} property={p} content={image} />,
|
||||
)}
|
||||
</Helmet>
|
||||
);
|
||||
|
|
|
@ -53,12 +53,12 @@ export class LanguageSelect extends Component<LanguageSelectProps, any> {
|
|||
<label
|
||||
className={classNames(
|
||||
"col-form-label",
|
||||
`col-sm-${this.props.multiple ? 3 : 2}`
|
||||
`col-sm-${this.props.multiple ? 3 : 2}`,
|
||||
)}
|
||||
htmlFor={this.id}
|
||||
>
|
||||
{I18NextService.i18n.t(
|
||||
this.props.multiple ? "language_plural" : "language"
|
||||
this.props.multiple ? "language_plural" : "language",
|
||||
)}
|
||||
</label>
|
||||
{this.props.multiple && this.props.showLanguageWarning && (
|
||||
|
@ -97,7 +97,7 @@ export class LanguageSelect extends Component<LanguageSelectProps, any> {
|
|||
this.props.siteLanguages,
|
||||
this.props.showAll,
|
||||
this.props.showSite,
|
||||
UserService.Instance.myUserInfo
|
||||
UserService.Instance.myUserInfo,
|
||||
);
|
||||
|
||||
return (
|
||||
|
|
|
@ -30,7 +30,7 @@ export class ListingTypeSelect extends Component<
|
|||
}
|
||||
|
||||
static getDerivedStateFromProps(
|
||||
props: ListingTypeSelectProps
|
||||
props: ListingTypeSelectProps,
|
||||
): ListingTypeSelectState {
|
||||
return {
|
||||
type_: props.type_,
|
||||
|
@ -107,6 +107,27 @@ export class ListingTypeSelect extends Component<
|
|||
>
|
||||
{I18NextService.i18n.t("all")}
|
||||
</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>
|
||||
);
|
||||
}
|
||||
|
|
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 autosize from "autosize";
|
||||
import classNames from "classnames";
|
||||
import { NoOptionI18nKeys } from "i18next";
|
||||
import { Component, linkEvent } from "inferno";
|
||||
import { Prompt } from "inferno-router";
|
||||
import { Language } from "lemmy-js-client";
|
||||
import {
|
||||
concurrentImageUpload,
|
||||
|
@ -19,9 +20,8 @@ import { pictrsDeleteToast, toast } from "../../toast";
|
|||
import { EmojiPicker } from "./emoji-picker";
|
||||
import { Icon, Spinner } from "./icon";
|
||||
import { LanguageSelect } from "./language-select";
|
||||
import NavigationPrompt from "./navigation-prompt";
|
||||
import ProgressBar from "./progress-bar";
|
||||
|
||||
import validUrl from "@utils/helpers/valid-url";
|
||||
interface MarkdownTextAreaProps {
|
||||
/**
|
||||
* Initial content inside the textarea
|
||||
|
@ -49,7 +49,7 @@ interface MarkdownTextAreaProps {
|
|||
hideNavigationWarnings?: boolean;
|
||||
onContentChange?(val: string): void;
|
||||
onReplyCancel?(): void;
|
||||
onSubmit?(content: string, formId: string, languageId?: number): void;
|
||||
onSubmit?(content: string, languageId?: number): void;
|
||||
allLanguages: Language[]; // TODO should probably be nullable
|
||||
siteLanguages: number[]; // TODO same
|
||||
}
|
||||
|
@ -89,6 +89,7 @@ export class MarkdownTextArea extends Component<
|
|||
super(props, context);
|
||||
|
||||
this.handleLanguageChange = this.handleLanguageChange.bind(this);
|
||||
this.handleEmoji = this.handleEmoji.bind(this);
|
||||
|
||||
if (isBrowser()) {
|
||||
this.tribute = setupTribute();
|
||||
|
@ -138,18 +139,14 @@ export class MarkdownTextArea extends Component<
|
|||
render() {
|
||||
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 (
|
||||
<form
|
||||
className="markdown-textarea"
|
||||
id={this.formId}
|
||||
onSubmit={linkEvent(this, this.handleSubmit)}
|
||||
>
|
||||
<NavigationPrompt
|
||||
<Prompt
|
||||
message={I18NextService.i18n.t("block_leaving")}
|
||||
when={
|
||||
!this.props.hideNavigationWarnings &&
|
||||
!!this.state.content &&
|
||||
|
@ -167,9 +164,7 @@ export class MarkdownTextArea extends Component<
|
|||
{this.getFormatButton("bold", this.handleInsertBold)}
|
||||
{this.getFormatButton("italic", this.handleInsertItalic)}
|
||||
{this.getFormatButton("link", this.handleInsertLink)}
|
||||
<EmojiPicker
|
||||
onEmojiClick={e => this.handleEmoji(this, e)}
|
||||
></EmojiPicker>
|
||||
<EmojiPicker onEmojiClick={this.handleEmoji}></EmojiPicker>
|
||||
<label
|
||||
htmlFor={`file-upload-${this.id}`}
|
||||
className={classNames("mb-0", {
|
||||
|
@ -206,7 +201,7 @@ export class MarkdownTextArea extends Component<
|
|||
{this.getFormatButton("header", this.handleInsertHeader)}
|
||||
{this.getFormatButton(
|
||||
"strikethrough",
|
||||
this.handleInsertStrikethrough
|
||||
this.handleInsertStrikethrough,
|
||||
)}
|
||||
{this.getFormatButton("quote", this.handleInsertQuote)}
|
||||
{this.getFormatButton("list", this.handleInsertList)}
|
||||
|
@ -214,7 +209,7 @@ export class MarkdownTextArea extends Component<
|
|||
{this.getFormatButton("subscript", this.handleInsertSubscript)}
|
||||
{this.getFormatButton(
|
||||
"superscript",
|
||||
this.handleInsertSuperscript
|
||||
this.handleInsertSuperscript,
|
||||
)}
|
||||
{this.getFormatButton("spoiler", this.handleInsertSpoiler)}
|
||||
<a
|
||||
|
@ -234,11 +229,11 @@ export class MarkdownTextArea extends Component<
|
|||
"form-control border-0 rounded-top-0 rounded-bottom",
|
||||
{
|
||||
"d-none": this.state.previewMode,
|
||||
}
|
||||
},
|
||||
)}
|
||||
value={this.state.content}
|
||||
onInput={linkEvent(this, this.handleContentChange)}
|
||||
onPaste={linkEvent(this, this.handleImageUploadPaste)}
|
||||
onPaste={linkEvent(this, this.handlePaste)}
|
||||
onKeyDown={linkEvent(this, this.handleKeyBinds)}
|
||||
required
|
||||
disabled={this.isDisabled}
|
||||
|
@ -263,7 +258,7 @@ export class MarkdownTextArea extends Component<
|
|||
value={this.state.imageUploadStatus.uploaded}
|
||||
max={this.state.imageUploadStatus.total}
|
||||
text={
|
||||
I18NextService.i18n.t("pictures_uploded_progess", {
|
||||
I18NextService.i18n.t("pictures_uploaded_progess", {
|
||||
uploaded: this.state.imageUploadStatus.uploaded,
|
||||
total: this.state.imageUploadStatus.total,
|
||||
}) ?? undefined
|
||||
|
@ -333,7 +328,7 @@ export class MarkdownTextArea extends Component<
|
|||
|
||||
getFormatButton(
|
||||
type: NoOptionI18nKeys,
|
||||
handleClick: (i: MarkdownTextArea, event: any) => void
|
||||
handleClick: (i: MarkdownTextArea, event: any) => void,
|
||||
) {
|
||||
let iconType: string;
|
||||
|
||||
|
@ -363,26 +358,65 @@ export class MarkdownTextArea extends Component<
|
|||
);
|
||||
}
|
||||
|
||||
handleEmoji(i: MarkdownTextArea, e: any) {
|
||||
handleEmoji(e: any) {
|
||||
let value = e.native;
|
||||
if (value == null) {
|
||||
if (!value) {
|
||||
const emoji = customEmojisLookup.get(e.id)?.custom_emoji;
|
||||
if (emoji) {
|
||||
value = `![${emoji.alt_text}](${emoji.image_url} "${emoji.shortcode}")`;
|
||||
value = `![${emoji.alt_text}](${emoji.image_url} "emoji ${emoji.shortcode}")`;
|
||||
}
|
||||
}
|
||||
i.setState({
|
||||
content: `${i.state.content ?? ""} ${value} `,
|
||||
this.setState({
|
||||
content: `${this.state.content ?? ""} ${value} `,
|
||||
});
|
||||
i.contentChange();
|
||||
const textarea: any = document.getElementById(i.id);
|
||||
this.contentChange();
|
||||
const textarea: any = document.getElementById(this.id);
|
||||
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];
|
||||
if (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),
|
||||
formattedCount: numToSI(maxUploadImages),
|
||||
}),
|
||||
"danger"
|
||||
"danger",
|
||||
);
|
||||
} else {
|
||||
i.setState({
|
||||
|
@ -429,7 +463,7 @@ export class MarkdownTextArea extends Component<
|
|||
uploaded: (imageUploadStatus?.uploaded ?? 0) + 1,
|
||||
},
|
||||
}));
|
||||
})
|
||||
}),
|
||||
);
|
||||
} catch (e) {
|
||||
errorOccurred = true;
|
||||
|
@ -481,7 +515,7 @@ export class MarkdownTextArea extends Component<
|
|||
// Keybind handler
|
||||
// Keybinds inspired by github comment area
|
||||
handleKeyBinds(i: MarkdownTextArea, event: KeyboardEvent) {
|
||||
if (event.ctrlKey || event.metaKey) {
|
||||
if (platform.isMac() ? event.metaKey : event.ctrlKey) {
|
||||
switch (event.key) {
|
||||
case "k": {
|
||||
i.handleInsertLink(i, event);
|
||||
|
@ -539,7 +573,7 @@ export class MarkdownTextArea extends Component<
|
|||
event.preventDefault();
|
||||
if (i.state.content) {
|
||||
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({
|
||||
content: `${content?.substring(
|
||||
0,
|
||||
start
|
||||
start,
|
||||
)}[${selectedText}]()${content?.substring(end)}`,
|
||||
});
|
||||
textarea.focus();
|
||||
|
@ -589,7 +623,7 @@ export class MarkdownTextArea extends Component<
|
|||
simpleSurroundBeforeAfter(
|
||||
beforeChars: string,
|
||||
afterChars: string,
|
||||
emptyChars = "___"
|
||||
emptyChars = "___",
|
||||
) {
|
||||
const content = this.state.content ?? "";
|
||||
if (!this.state.content) {
|
||||
|
@ -604,7 +638,7 @@ export class MarkdownTextArea extends Component<
|
|||
this.setState({
|
||||
content: `${content?.substring(
|
||||
0,
|
||||
start
|
||||
start,
|
||||
)}${beforeChars}${selectedText}${afterChars}${content?.substring(end)}`,
|
||||
});
|
||||
} else {
|
||||
|
@ -619,12 +653,12 @@ export class MarkdownTextArea extends Component<
|
|||
if (start !== end) {
|
||||
textarea.setSelectionRange(
|
||||
start + beforeChars.length,
|
||||
end + afterChars.length
|
||||
end + afterChars.length,
|
||||
);
|
||||
} else {
|
||||
textarea.setSelectionRange(
|
||||
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() {
|
||||
const updated = this.props.updated;
|
||||
let line = `${capitalizeFirstLetter(
|
||||
I18NextService.i18n.t("created")
|
||||
I18NextService.i18n.t("created"),
|
||||
)}: ${formatDate(this.props.published)}`;
|
||||
if (updated) {
|
||||
line += `\n\n\n${capitalizeFirstLetter(
|
||||
I18NextService.i18n.t("modified")
|
||||
I18NextService.i18n.t("modified"),
|
||||
)} ${formatDate(updated)}`;
|
||||
}
|
||||
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 {
|
||||
page: number;
|
||||
onChange(val: number): any;
|
||||
nextDisabled: boolean;
|
||||
}
|
||||
|
||||
export class Paginator extends Component<PaginatorProps, any> {
|
||||
|
@ -15,7 +16,7 @@ export class Paginator extends Component<PaginatorProps, any> {
|
|||
<div className="paginator my-2">
|
||||
<button
|
||||
className="btn btn-secondary me-2"
|
||||
disabled={this.props.page == 1}
|
||||
disabled={this.props.page === 1}
|
||||
onClick={linkEvent(this, this.handlePrev)}
|
||||
>
|
||||
{I18NextService.i18n.t("prev")}
|
||||
|
@ -23,6 +24,7 @@ export class Paginator extends Component<PaginatorProps, any> {
|
|||
<button
|
||||
className="btn btn-secondary"
|
||||
onClick={linkEvent(this, this.handleNext)}
|
||||
disabled={this.props.nextDisabled || false}
|
||||
>
|
||||
{I18NextService.i18n.t("next")}
|
||||
</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 { Component } from "inferno";
|
||||
|
||||
import { UserService } from "../../services";
|
||||
|
||||
const iconThumbnailSize = 96;
|
||||
const thumbnailSize = 256;
|
||||
|
||||
|
@ -13,6 +15,7 @@ interface PictrsImageProps {
|
|||
nsfw?: boolean;
|
||||
iconOverlay?: boolean;
|
||||
pushup?: boolean;
|
||||
cardTop?: boolean;
|
||||
}
|
||||
|
||||
export class PictrsImage extends Component<PictrsImageProps, any> {
|
||||
|
@ -21,28 +24,40 @@ export class PictrsImage extends Component<PictrsImageProps, any> {
|
|||
}
|
||||
|
||||
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 (
|
||||
<picture>
|
||||
<source srcSet={this.src("webp")} type="image/webp" />
|
||||
<source srcSet={this.props.src} />
|
||||
<source srcSet={src} />
|
||||
<source srcSet={this.src("jpg")} type="image/jpeg" />
|
||||
<img
|
||||
src={this.props.src}
|
||||
src={src}
|
||||
alt={this.alt()}
|
||||
title={this.alt()}
|
||||
loading="lazy"
|
||||
className={classNames("overflow-hidden pictrs-image", {
|
||||
"img-fluid": !this.props.icon && !this.props.iconOverlay,
|
||||
banner: this.props.banner,
|
||||
"img-fluid": !(icon || iconOverlay),
|
||||
banner,
|
||||
"thumbnail rounded object-fit-cover":
|
||||
this.props.thumbnail && !this.props.icon && !this.props.banner,
|
||||
"img-expanded slight-radius":
|
||||
!this.props.thumbnail && !this.props.icon,
|
||||
"img-blur": this.props.thumbnail && this.props.nsfw,
|
||||
"object-fit-cover img-icon me-1": this.props.icon,
|
||||
thumbnail && !(icon || banner),
|
||||
"img-expanded slight-radius": !(thumbnail || icon),
|
||||
"img-blur": thumbnail && nsfw,
|
||||
"object-fit-cover img-icon me-1": icon,
|
||||
"img-blur-icon": icon && blur_image,
|
||||
"img-blur-thumb": thumbnail && blur_image,
|
||||
"ms-2 mb-0 rounded-circle object-fit-cover avatar-overlay":
|
||||
this.props.iconOverlay,
|
||||
"avatar-pushup": this.props.pushup,
|
||||
iconOverlay,
|
||||
"avatar-pushup": pushup,
|
||||
"card-img-top": cardTop,
|
||||
})}
|
||||
/>
|
||||
</picture>
|
||||
|
@ -56,7 +71,7 @@ export class PictrsImage extends Component<PictrsImageProps, any> {
|
|||
const split = this.props.src.split("/pictrs/image/");
|
||||
|
||||
// If theres not multiple, then its not a pictrs image
|
||||
if (split.length == 1) {
|
||||
if (split.length === 1) {
|
||||
return this.props.src;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { myAuthRequired } from "@utils/app";
|
||||
import { Component, InfernoNode, linkEvent } from "inferno";
|
||||
import { T } from "inferno-i18next-dess";
|
||||
import {
|
||||
|
@ -42,9 +41,9 @@ export class RegistrationApplication extends Component<
|
|||
componentWillReceiveProps(
|
||||
nextProps: Readonly<
|
||||
{ children?: InfernoNode } & RegistrationApplicationProps
|
||||
>
|
||||
>,
|
||||
): void {
|
||||
if (this.props != nextProps) {
|
||||
if (this.props !== nextProps) {
|
||||
this.setState({
|
||||
denyExpanded: false,
|
||||
approveLoading: false,
|
||||
|
@ -149,7 +148,6 @@ export class RegistrationApplication extends Component<
|
|||
i.props.onApproveApplication({
|
||||
id: i.props.application.registration_application.id,
|
||||
approve: true,
|
||||
auth: myAuthRequired(),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -160,7 +158,6 @@ export class RegistrationApplication extends Component<
|
|||
id: i.props.application.registration_application.id,
|
||||
approve: false,
|
||||
deny_reason: i.state.denyReason,
|
||||
auth: myAuthRequired(),
|
||||
});
|
||||
} else {
|
||||
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";
|
||||
import { I18NextService } from "../../services";
|
||||
import { Icon, Spinner } from "./icon";
|
||||
import { LoadingEllipses } from "./loading-ellipses";
|
||||
|
||||
interface SearchableSelectProps {
|
||||
id: string;
|
||||
|
@ -22,7 +23,6 @@ interface SearchableSelectProps {
|
|||
interface SearchableSelectState {
|
||||
selectedIndex: number;
|
||||
searchText: string;
|
||||
loadingEllipses: string;
|
||||
}
|
||||
|
||||
function handleSearch(i: SearchableSelect, e: ChangeEvent<HTMLInputElement>) {
|
||||
|
@ -70,12 +70,10 @@ export class SearchableSelect extends Component<
|
|||
> {
|
||||
searchInputRef: RefObject<HTMLInputElement> = createRef();
|
||||
toggleButtonRef: RefObject<HTMLButtonElement> = createRef();
|
||||
private loadingEllipsesInterval?: NodeJS.Timer = undefined;
|
||||
|
||||
state: SearchableSelectState = {
|
||||
selectedIndex: 0,
|
||||
searchText: "",
|
||||
loadingEllipses: "...",
|
||||
};
|
||||
|
||||
constructor(props: SearchableSelectProps, context: any) {
|
||||
|
@ -83,7 +81,7 @@ export class SearchableSelect extends Component<
|
|||
|
||||
if (props.value) {
|
||||
let selectedIndex = props.options.findIndex(
|
||||
({ value }) => value === props.value?.toString()
|
||||
({ value }) => value === props.value?.toString(),
|
||||
);
|
||||
|
||||
if (selectedIndex < 0) {
|
||||
|
@ -99,7 +97,7 @@ export class SearchableSelect extends Component<
|
|||
|
||||
render() {
|
||||
const { id, options, onSearch, loading } = this.props;
|
||||
const { searchText, selectedIndex, loadingEllipses } = this.state;
|
||||
const { searchText, selectedIndex } = this.state;
|
||||
|
||||
return (
|
||||
<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)}
|
||||
ref={this.toggleButtonRef}
|
||||
>
|
||||
{loading
|
||||
? `${I18NextService.i18n.t("loading")}${loadingEllipses}`
|
||||
: options[selectedIndex].label}
|
||||
{loading ? (
|
||||
<>
|
||||
{I18NextService.i18n.t("loading")}
|
||||
<LoadingEllipses />
|
||||
</>
|
||||
) : (
|
||||
options[selectedIndex].label
|
||||
)}
|
||||
</button>
|
||||
<div className="modlog-choices-font-size dropdown-menu w-100 p-2">
|
||||
<div className="input-group">
|
||||
|
@ -140,7 +143,7 @@ export class SearchableSelect extends Component<
|
|||
(onSearch || searchText.length === 0
|
||||
? options
|
||||
: options.filter(({ label }) =>
|
||||
label.toLowerCase().includes(searchText.toLowerCase())
|
||||
label.toLowerCase().includes(searchText.toLowerCase()),
|
||||
)
|
||||
).map((option, index) => (
|
||||
<button
|
||||
|
@ -180,24 +183,4 @@ export class SearchableSelect extends Component<
|
|||
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"}>
|
||||
{I18NextService.i18n.t("active")}
|
||||
</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={"Old"}>{I18NextService.i18n.t("old")}</option>
|
||||
{!this.props.hideMostComments && [
|
||||
|
@ -79,6 +85,15 @@ export class SortSelect extends Component<SortSelectProps, SortSelectState> {
|
|||
<option value={"TopMonth"}>
|
||||
{I18NextService.i18n.t("top_month")}
|
||||
</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={"TopAll"}>{I18NextService.i18n.t("top_all")}</option>
|
||||
</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
|
||||
className={classNames(
|
||||
"row d-inline-flex gx-1",
|
||||
this.props.classNames
|
||||
this.props.classNames,
|
||||
)}
|
||||
>
|
||||
{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 classNames from "classnames";
|
||||
import { Component, linkEvent } from "inferno";
|
||||
|
@ -53,7 +53,6 @@ const handleUpvote = (i: VoteButtons) => {
|
|||
i.props.onVote({
|
||||
comment_id: i.props.id,
|
||||
score: newVote(VoteType.Upvote, i.props.my_vote),
|
||||
auth: myAuthRequired(),
|
||||
});
|
||||
break;
|
||||
case VoteContentType.Post:
|
||||
|
@ -61,7 +60,6 @@ const handleUpvote = (i: VoteButtons) => {
|
|||
i.props.onVote({
|
||||
post_id: i.props.id,
|
||||
score: newVote(VoteType.Upvote, i.props.my_vote),
|
||||
auth: myAuthRequired(),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
@ -73,7 +71,6 @@ const handleDownvote = (i: VoteButtons) => {
|
|||
i.props.onVote({
|
||||
comment_id: i.props.id,
|
||||
score: newVote(VoteType.Downvote, i.props.my_vote),
|
||||
auth: myAuthRequired(),
|
||||
});
|
||||
break;
|
||||
case VoteContentType.Post:
|
||||
|
@ -81,7 +78,6 @@ const handleDownvote = (i: VoteButtons) => {
|
|||
i.props.onVote({
|
||||
post_id: i.props.id,
|
||||
score: newVote(VoteType.Downvote, i.props.my_vote),
|
||||
auth: myAuthRequired(),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
@ -193,7 +189,7 @@ export class VoteButtons extends Component<VoteButtonsProps, VoteButtonsState> {
|
|||
<button
|
||||
type="button"
|
||||
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)}
|
||||
data-tippy-content={I18NextService.i18n.t("upvote")}
|
||||
|
@ -220,7 +216,7 @@ export class VoteButtons extends Component<VoteButtonsProps, VoteButtonsState> {
|
|||
<button
|
||||
type="button"
|
||||
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)}
|
||||
data-tippy-content={I18NextService.i18n.t("downvote")}
|
||||
|
|
|
@ -1,10 +1,4 @@
|
|||
import {
|
||||
editCommunity,
|
||||
myAuth,
|
||||
myAuthRequired,
|
||||
setIsoData,
|
||||
showLocal,
|
||||
} from "@utils/app";
|
||||
import { editCommunity, setIsoData, showLocal } from "@utils/app";
|
||||
import {
|
||||
getPageFromString,
|
||||
getQueryParams,
|
||||
|
@ -20,17 +14,25 @@ import {
|
|||
ListCommunities,
|
||||
ListCommunitiesResponse,
|
||||
ListingType,
|
||||
SortType,
|
||||
} from "lemmy-js-client";
|
||||
import { InitialFetchRequest } from "../../interfaces";
|
||||
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 { Spinner } from "../common/icon";
|
||||
import { ListingTypeSelect } from "../common/listing-type-select";
|
||||
import { Paginator } from "../common/paginator";
|
||||
import { SortSelect } from "../common/sort-select";
|
||||
import { CommunityLink } from "./community-link";
|
||||
|
||||
const communityLimit = 50;
|
||||
import { communityLimit } from "../../config";
|
||||
import { SubscribeButton } from "../common/subscribe-button";
|
||||
|
||||
type CommunitiesData = RouteDataResponse<{
|
||||
listCommunitiesResponse: ListCommunitiesResponse;
|
||||
|
@ -45,6 +47,7 @@ interface CommunitiesState {
|
|||
|
||||
interface CommunitiesProps {
|
||||
listingType: ListingType;
|
||||
sort: SortType;
|
||||
page: number;
|
||||
}
|
||||
|
||||
|
@ -52,10 +55,21 @@ function getListingTypeFromQuery(listingType?: string): ListingType {
|
|||
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> {
|
||||
private isoData = setIsoData<CommunitiesData>(this.context);
|
||||
state: CommunitiesState = {
|
||||
listCommunitiesResponse: { state: "empty" },
|
||||
listCommunitiesResponse: EMPTY_REQUEST,
|
||||
siteRes: this.isoData.site_res,
|
||||
searchText: "",
|
||||
isIsomorphic: false,
|
||||
|
@ -64,6 +78,7 @@ export class Communities extends Component<any, CommunitiesState> {
|
|||
constructor(props: any, context: any) {
|
||||
super(props, context);
|
||||
this.handlePageChange = this.handlePageChange.bind(this);
|
||||
this.handleSortChange = this.handleSortChange.bind(this);
|
||||
this.handleListingTypeChange = this.handleListingTypeChange.bind(this);
|
||||
|
||||
// Only fetch the data if coming from another route
|
||||
|
@ -99,13 +114,13 @@ export class Communities extends Component<any, CommunitiesState> {
|
|||
</h5>
|
||||
);
|
||||
case "success": {
|
||||
const { listingType, page } = this.getCommunitiesQueryParams();
|
||||
const { listingType, sort, page } = getCommunitiesQueryParams();
|
||||
return (
|
||||
<div>
|
||||
<h1 className="h4 mb-4">
|
||||
{I18NextService.i18n.t("list_of_communities")}
|
||||
</h1>
|
||||
<div className="row g-2 justify-content-between">
|
||||
<div className="row g-3 align-items-center mb-2">
|
||||
<div className="col-auto">
|
||||
<ListingTypeSelect
|
||||
type_={listingType}
|
||||
|
@ -114,6 +129,9 @@ export class Communities extends Component<any, CommunitiesState> {
|
|||
onChange={this.handleListingTypeChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-auto me-auto">
|
||||
<SortSelect sort={sort} onChange={this.handleSortChange} />
|
||||
</div>
|
||||
<div className="col-auto">{this.searchForm()}</div>
|
||||
</div>
|
||||
|
||||
|
@ -161,49 +179,41 @@ export class Communities extends Component<any, CommunitiesState> {
|
|||
{numToSI(cv.counts.comments)}
|
||||
</td>
|
||||
<td className="text-right">
|
||||
{cv.subscribed == "Subscribed" && (
|
||||
<button
|
||||
className="btn btn-link d-inline-block"
|
||||
onClick={linkEvent(
|
||||
{
|
||||
i: this,
|
||||
communityId: cv.community.id,
|
||||
follow: false,
|
||||
},
|
||||
this.handleFollow
|
||||
)}
|
||||
>
|
||||
{I18NextService.i18n.t("unsubscribe")}
|
||||
</button>
|
||||
)}
|
||||
{cv.subscribed === "NotSubscribed" && (
|
||||
<button
|
||||
className="btn btn-link d-inline-block"
|
||||
onClick={linkEvent(
|
||||
{
|
||||
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>
|
||||
)}
|
||||
<SubscribeButton
|
||||
communityView={cv}
|
||||
onFollow={linkEvent(
|
||||
{
|
||||
i: this,
|
||||
communityId: cv.community.id,
|
||||
follow: false,
|
||||
},
|
||||
this.handleFollow,
|
||||
)}
|
||||
onUnFollow={linkEvent(
|
||||
{
|
||||
i: this,
|
||||
communityId: cv.community.id,
|
||||
follow: true,
|
||||
},
|
||||
this.handleFollow,
|
||||
)}
|
||||
isLink
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
),
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<Paginator page={page} onChange={this.handlePageChange} />
|
||||
<Paginator
|
||||
page={page}
|
||||
onChange={this.handlePageChange}
|
||||
nextDisabled={
|
||||
communityLimit >
|
||||
this.state.listCommunitiesResponse.data.communities.length
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -224,10 +234,7 @@ export class Communities extends Component<any, CommunitiesState> {
|
|||
|
||||
searchForm() {
|
||||
return (
|
||||
<form
|
||||
className="row mb-2"
|
||||
onSubmit={linkEvent(this, this.handleSearchSubmit)}
|
||||
>
|
||||
<form className="row" onSubmit={linkEvent(this, this.handleSearchSubmit)}>
|
||||
<div className="col-auto">
|
||||
<input
|
||||
type="text"
|
||||
|
@ -252,12 +259,16 @@ export class Communities extends Component<any, CommunitiesState> {
|
|||
);
|
||||
}
|
||||
|
||||
async updateUrl({ listingType, page }: Partial<CommunitiesProps>) {
|
||||
const { listingType: urlListingType, page: urlPage } =
|
||||
this.getCommunitiesQueryParams();
|
||||
async updateUrl({ listingType, sort, page }: Partial<CommunitiesProps>) {
|
||||
const {
|
||||
listingType: urlListingType,
|
||||
sort: urlSort,
|
||||
page: urlPage,
|
||||
} = getCommunitiesQueryParams();
|
||||
|
||||
const queryParams: QueryParams<CommunitiesProps> = {
|
||||
listingType: listingType ?? urlListingType,
|
||||
sort: sort ?? urlSort,
|
||||
page: (page ?? urlPage)?.toString(),
|
||||
};
|
||||
|
||||
|
@ -270,6 +281,10 @@ export class Communities extends Component<any, CommunitiesState> {
|
|||
this.updateUrl({ page });
|
||||
}
|
||||
|
||||
handleSortChange(val: SortType) {
|
||||
this.updateUrl({ sort: val, page: 1 });
|
||||
}
|
||||
|
||||
handleListingTypeChange(val: ListingType) {
|
||||
this.updateUrl({
|
||||
listingType: val,
|
||||
|
@ -284,40 +299,32 @@ export class Communities extends Component<any, CommunitiesState> {
|
|||
handleSearchSubmit(i: Communities, event: any) {
|
||||
event.preventDefault();
|
||||
const searchParamEncoded = encodeURIComponent(i.state.searchText);
|
||||
const { listingType } = getCommunitiesQueryParams();
|
||||
i.context.router.history.push(
|
||||
`/search?q=${searchParamEncoded}&type=Communities`
|
||||
`/search?q=${searchParamEncoded}&type=Communities&listingType=${listingType}`,
|
||||
);
|
||||
}
|
||||
|
||||
static async fetchInitialData({
|
||||
query: { listingType, page },
|
||||
query: { listingType, sort, page },
|
||||
client,
|
||||
auth,
|
||||
}: InitialFetchRequest<
|
||||
QueryParams<CommunitiesProps>
|
||||
>): Promise<CommunitiesData> {
|
||||
const listCommunitiesForm: ListCommunities = {
|
||||
type_: getListingTypeFromQuery(listingType),
|
||||
sort: "TopMonth",
|
||||
sort: getSortTypeFromQuery(sort),
|
||||
limit: communityLimit,
|
||||
page: getPageFromString(page),
|
||||
auth: auth,
|
||||
};
|
||||
|
||||
return {
|
||||
listCommunitiesResponse: await client.listCommunities(
|
||||
listCommunitiesForm
|
||||
listCommunitiesForm,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
getCommunitiesQueryParams() {
|
||||
return getQueryParams<CommunitiesProps>({
|
||||
listingType: getListingTypeFromQuery,
|
||||
page: getPageFromString,
|
||||
});
|
||||
}
|
||||
|
||||
async handleFollow(data: {
|
||||
i: Communities;
|
||||
communityId: number;
|
||||
|
@ -326,23 +333,21 @@ export class Communities extends Component<any, CommunitiesState> {
|
|||
const res = await HttpService.client.followCommunity({
|
||||
community_id: data.communityId,
|
||||
follow: data.follow,
|
||||
auth: myAuthRequired(),
|
||||
});
|
||||
data.i.findAndUpdateCommunity(res);
|
||||
}
|
||||
|
||||
async refetch() {
|
||||
this.setState({ listCommunitiesResponse: { state: "loading" } });
|
||||
this.setState({ listCommunitiesResponse: LOADING_REQUEST });
|
||||
|
||||
const { listingType, page } = this.getCommunitiesQueryParams();
|
||||
const { listingType, sort, page } = getCommunitiesQueryParams();
|
||||
|
||||
this.setState({
|
||||
listCommunitiesResponse: await HttpService.client.listCommunities({
|
||||
type_: listingType,
|
||||
sort: "TopMonth",
|
||||
sort: sort,
|
||||
limit: communityLimit,
|
||||
page,
|
||||
auth: myAuth(),
|
||||
}),
|
||||
});
|
||||
|
||||
|
@ -352,12 +357,12 @@ export class Communities extends Component<any, CommunitiesState> {
|
|||
findAndUpdateCommunity(res: RequestState<CommunityResponse>) {
|
||||
this.setState(s => {
|
||||
if (
|
||||
s.listCommunitiesResponse.state == "success" &&
|
||||
res.state == "success"
|
||||
s.listCommunitiesResponse.state === "success" &&
|
||||
res.state === "success"
|
||||
) {
|
||||
s.listCommunitiesResponse.data.communities = editCommunity(
|
||||
res.data.community_view,
|
||||
s.listCommunitiesResponse.data.communities
|
||||
s.listCommunitiesResponse.data.communities,
|
||||
);
|
||||
}
|
||||
return s;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { myAuthRequired } from "@utils/app";
|
||||
import { capitalizeFirstLetter, randomStr } from "@utils/helpers";
|
||||
import { Component, linkEvent } from "inferno";
|
||||
import { Prompt } from "inferno-router";
|
||||
import {
|
||||
CommunityView,
|
||||
CreateCommunity,
|
||||
|
@ -12,7 +12,6 @@ import { Icon, Spinner } from "../common/icon";
|
|||
import { ImageUploadForm } from "../common/image-upload-form";
|
||||
import { LanguageSelect } from "../common/language-select";
|
||||
import { MarkdownTextArea } from "../common/markdown-textarea";
|
||||
import NavigationPrompt from "../common/navigation-prompt";
|
||||
|
||||
interface CommunityFormProps {
|
||||
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"
|
||||
onSubmit={linkEvent(this, this.handleCreateCommunitySubmit)}
|
||||
>
|
||||
<NavigationPrompt
|
||||
<Prompt
|
||||
message={I18NextService.i18n.t("block_leaving")}
|
||||
when={
|
||||
!this.props.loading &&
|
||||
!!(
|
||||
|
@ -230,7 +230,7 @@ export class CommunityForm extends Component<
|
|||
checked={this.state.form.posting_restricted_to_mods}
|
||||
onChange={linkEvent(
|
||||
this,
|
||||
this.handleCommunityPostingRestrictedToMods
|
||||
this.handleCommunityPostingRestrictedToMods,
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
@ -278,7 +278,6 @@ export class CommunityForm extends Component<
|
|||
event.preventDefault();
|
||||
i.setState({ submitted: true });
|
||||
const cForm = i.state.form;
|
||||
const auth = myAuthRequired();
|
||||
|
||||
const cv = i.props.community_view;
|
||||
|
||||
|
@ -292,7 +291,6 @@ export class CommunityForm extends Component<
|
|||
nsfw: cForm.nsfw,
|
||||
posting_restricted_to_mods: cForm.posting_restricted_to_mods,
|
||||
discussion_languages: cForm.discussion_languages,
|
||||
auth,
|
||||
});
|
||||
} else {
|
||||
if (cForm.title && cForm.name) {
|
||||
|
@ -305,7 +303,6 @@ export class CommunityForm extends Component<
|
|||
nsfw: cForm.nsfw,
|
||||
posting_restricted_to_mods: cForm.posting_restricted_to_mods,
|
||||
discussion_languages: cForm.discussion_languages,
|
||||
auth,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -329,7 +326,7 @@ export class CommunityForm extends Component<
|
|||
|
||||
handleCommunityPostingRestrictedToMods(i: CommunityForm, event: any) {
|
||||
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() {
|
||||
const community = this.props.community;
|
||||
let name_: string, title: string, link: string;
|
||||
const local = community.local == null ? true : community.local;
|
||||
let title: string, link: string;
|
||||
const local = community.local === null ? true : community.local;
|
||||
const domain = hostname(community.actor_id);
|
||||
if (local) {
|
||||
name_ = community.name;
|
||||
title = community.title;
|
||||
link = `/c/${community.name}`;
|
||||
} else {
|
||||
const domain = hostname(community.actor_id);
|
||||
name_ = `${community.name}@${domain}`;
|
||||
const name_ = `${community.name}@${domain}`;
|
||||
title = `${community.title}@${domain}`;
|
||||
link = !this.props.realLink ? `/c/${name_}` : community.actor_id;
|
||||
}
|
||||
|
||||
const apubName = `!${name_}`;
|
||||
const apubName = `!${community.name}@${domain}`;
|
||||
const displayName = this.props.useApubName ? apubName : title;
|
||||
return !this.props.realLink ? (
|
||||
<Link
|
||||
|
@ -58,12 +57,14 @@ export class CommunityLink extends Component<CommunityLinkProps, any> {
|
|||
|
||||
avatarAndName(displayName: string) {
|
||||
const icon = this.props.community.icon;
|
||||
const nsfw = this.props.community.nsfw;
|
||||
|
||||
return (
|
||||
<>
|
||||
{!this.props.hideAvatar &&
|
||||
!this.props.community.removed &&
|
||||
showAvatars() &&
|
||||
icon && <PictrsImage src={icon} icon />}
|
||||
icon && <PictrsImage src={icon} icon nsfw={nsfw} />}
|
||||
<span className="overflow-wrap-anywhere">{displayName}</span>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -8,18 +8,13 @@ import {
|
|||
enableNsfw,
|
||||
getCommentParentId,
|
||||
getDataTypeString,
|
||||
myAuth,
|
||||
postToCommentSortType,
|
||||
setIsoData,
|
||||
showLocal,
|
||||
updateCommunityBlock,
|
||||
updatePersonBlock,
|
||||
} from "@utils/app";
|
||||
import {
|
||||
getPageFromString,
|
||||
getQueryParams,
|
||||
getQueryString,
|
||||
} from "@utils/helpers";
|
||||
import { getQueryParams, getQueryString } from "@utils/helpers";
|
||||
import type { QueryParams } from "@utils/types";
|
||||
import { RouteDataResponse } from "@utils/types";
|
||||
import { Component, RefObject, createRef, linkEvent } from "inferno";
|
||||
|
@ -62,6 +57,8 @@ import {
|
|||
LockPost,
|
||||
MarkCommentReplyAsRead,
|
||||
MarkPersonMentionAsRead,
|
||||
MarkPostAsRead,
|
||||
PaginationCursor,
|
||||
PostResponse,
|
||||
PurgeComment,
|
||||
PurgeCommunity,
|
||||
|
@ -83,7 +80,12 @@ import {
|
|||
InitialFetchRequest,
|
||||
} from "../../interfaces";
|
||||
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 { toast } from "../../toast";
|
||||
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 { HtmlTags } from "../common/html-tags";
|
||||
import { Icon, Spinner } from "../common/icon";
|
||||
import { Paginator } from "../common/paginator";
|
||||
import { SortSelect } from "../common/sort-select";
|
||||
import { Sidebar } from "../community/sidebar";
|
||||
import { SiteSidebar } from "../home/site-sidebar";
|
||||
import { PostListings } from "../post/post-listings";
|
||||
import { CommunityLink } from "./community-link";
|
||||
import { PaginatorCursor } from "../common/paginator-cursor";
|
||||
|
||||
type CommunityData = RouteDataResponse<{
|
||||
communityRes: GetCommunityResponse;
|
||||
|
@ -117,13 +119,13 @@ interface State {
|
|||
interface CommunityProps {
|
||||
dataType: DataType;
|
||||
sort: SortType;
|
||||
page: number;
|
||||
pageCursor?: PaginationCursor;
|
||||
}
|
||||
|
||||
function getCommunityQueryParams() {
|
||||
return getQueryParams<CommunityProps>({
|
||||
dataType: getDataTypeFromQuery,
|
||||
page: getPageFromString,
|
||||
pageCursor: cursor => cursor,
|
||||
sort: getSortTypeFromQuery,
|
||||
});
|
||||
}
|
||||
|
@ -146,9 +148,9 @@ export class Community extends Component<
|
|||
> {
|
||||
private isoData = setIsoData<CommunityData>(this.context);
|
||||
state: State = {
|
||||
communityRes: { state: "empty" },
|
||||
postsRes: { state: "empty" },
|
||||
commentsRes: { state: "empty" },
|
||||
communityRes: EMPTY_REQUEST,
|
||||
postsRes: EMPTY_REQUEST,
|
||||
commentsRes: EMPTY_REQUEST,
|
||||
siteRes: this.isoData.site_res,
|
||||
showSidebarMobile: false,
|
||||
finished: new Map(),
|
||||
|
@ -160,7 +162,8 @@ export class Community extends Component<
|
|||
|
||||
this.handleSortChange = this.handleSortChange.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
|
||||
this.handleDeleteCommunity = this.handleDeleteCommunity.bind(this);
|
||||
|
@ -195,6 +198,7 @@ export class Community extends Component<
|
|||
this.handleSavePost = this.handleSavePost.bind(this);
|
||||
this.handlePurgePost = this.handlePurgePost.bind(this);
|
||||
this.handleFeaturePost = this.handleFeaturePost.bind(this);
|
||||
this.handleMarkPostAsRead = this.handleMarkPostAsRead.bind(this);
|
||||
this.mainContentRef = createRef();
|
||||
// Only fetch the data if coming from another route
|
||||
if (FirstLoadService.isFirstLoad) {
|
||||
|
@ -211,11 +215,10 @@ export class Community extends Component<
|
|||
}
|
||||
|
||||
async fetchCommunity() {
|
||||
this.setState({ communityRes: { state: "loading" } });
|
||||
this.setState({ communityRes: LOADING_REQUEST });
|
||||
this.setState({
|
||||
communityRes: await HttpService.client.getCommunity({
|
||||
name: this.props.match.params.name,
|
||||
auth: myAuth(),
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
@ -231,8 +234,7 @@ export class Community extends Component<
|
|||
static async fetchInitialData({
|
||||
client,
|
||||
path,
|
||||
query: { dataType: urlDataType, page: urlPage, sort: urlSort },
|
||||
auth,
|
||||
query: { dataType: urlDataType, pageCursor, sort: urlSort },
|
||||
}: InitialFetchRequest<QueryParams<CommunityProps>>): Promise<
|
||||
Promise<CommunityData>
|
||||
> {
|
||||
|
@ -241,41 +243,33 @@ export class Community extends Component<
|
|||
const communityName = pathSplit[2];
|
||||
const communityForm: GetCommunity = {
|
||||
name: communityName,
|
||||
auth,
|
||||
};
|
||||
|
||||
const dataType = getDataTypeFromQuery(urlDataType);
|
||||
|
||||
const sort = getSortTypeFromQuery(urlSort);
|
||||
|
||||
const page = getPageFromString(urlPage);
|
||||
|
||||
let postsResponse: RequestState<GetPostsResponse> = { state: "empty" };
|
||||
let commentsResponse: RequestState<GetCommentsResponse> = {
|
||||
state: "empty",
|
||||
};
|
||||
let postsResponse: RequestState<GetPostsResponse> = EMPTY_REQUEST;
|
||||
let commentsResponse: RequestState<GetCommentsResponse> = EMPTY_REQUEST;
|
||||
|
||||
if (dataType === DataType.Post) {
|
||||
const getPostsForm: GetPosts = {
|
||||
community_name: communityName,
|
||||
page,
|
||||
page_cursor: pageCursor,
|
||||
limit: fetchLimit,
|
||||
sort,
|
||||
type_: "All",
|
||||
saved_only: false,
|
||||
auth,
|
||||
};
|
||||
|
||||
postsResponse = await client.getPosts(getPostsForm);
|
||||
} else {
|
||||
const getCommentsForm: GetComments = {
|
||||
community_name: communityName,
|
||||
page,
|
||||
limit: fetchLimit,
|
||||
sort: postToCommentSortType(sort),
|
||||
type_: "All",
|
||||
saved_only: false,
|
||||
auth,
|
||||
};
|
||||
|
||||
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 {
|
||||
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}`
|
||||
: "";
|
||||
}
|
||||
|
||||
renderCommunity() {
|
||||
const { pageCursor } = getCommunityQueryParams();
|
||||
switch (this.state.communityRes.state) {
|
||||
case "loading":
|
||||
return (
|
||||
|
@ -305,13 +306,13 @@ export class Community extends Component<
|
|||
);
|
||||
case "success": {
|
||||
const res = this.state.communityRes.data;
|
||||
const { page } = getCommunityQueryParams();
|
||||
|
||||
return (
|
||||
<>
|
||||
<HtmlTags
|
||||
title={this.documentTitle}
|
||||
path={this.context.router.route.match.url}
|
||||
canonicalPath={res.community_view.community.actor_id}
|
||||
description={res.community_view.community.description}
|
||||
image={res.community_view.community.icon}
|
||||
/>
|
||||
|
@ -341,7 +342,12 @@ export class Community extends Component<
|
|||
</div>
|
||||
{this.selects(res)}
|
||||
{this.listings(res)}
|
||||
<Paginator page={page} onChange={this.handlePageChange} />
|
||||
<PaginatorCursor
|
||||
prevPage={pageCursor}
|
||||
nextPage={this.getNextPage}
|
||||
onNext={this.handlePageNext}
|
||||
onPrev={this.handlePagePrev}
|
||||
/>
|
||||
</main>
|
||||
<aside className="d-none d-md-block col-md-4 col-lg-3">
|
||||
{this.sidebar(res)}
|
||||
|
@ -430,6 +436,7 @@ export class Community extends Component<
|
|||
onAddAdmin={this.handleAddAdmin}
|
||||
onTransferCommunity={this.handleTransferCommunity}
|
||||
onFeaturePost={this.handleFeaturePost}
|
||||
onMarkPostAsRead={this.handleMarkPostAsRead}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -447,7 +454,7 @@ export class Community extends Component<
|
|||
nodes={commentsToFlatNodes(this.state.commentsRes.data.comments)}
|
||||
viewType={CommentViewType.Flat}
|
||||
finished={this.state.finished}
|
||||
noIndent
|
||||
isTopLevel
|
||||
showContext
|
||||
enableDownvotes={enableDownvotes(site_res)}
|
||||
moderators={communityRes.moderators}
|
||||
|
@ -534,18 +541,22 @@ export class Community extends Component<
|
|||
);
|
||||
}
|
||||
|
||||
handlePageChange(page: number) {
|
||||
this.updateUrl({ page });
|
||||
handlePagePrev() {
|
||||
this.props.history.back();
|
||||
}
|
||||
|
||||
handlePageNext(nextPage: PaginationCursor) {
|
||||
this.updateUrl({ pageCursor: nextPage });
|
||||
window.scrollTo(0, 0);
|
||||
}
|
||||
|
||||
handleSortChange(sort: SortType) {
|
||||
this.updateUrl({ sort, page: 1 });
|
||||
this.updateUrl({ sort, pageCursor: undefined });
|
||||
window.scrollTo(0, 0);
|
||||
}
|
||||
|
||||
handleDataTypeChange(dataType: DataType) {
|
||||
this.updateUrl({ dataType, page: 1 });
|
||||
this.updateUrl({ dataType, pageCursor: undefined });
|
||||
window.scrollTo(0, 0);
|
||||
}
|
||||
|
||||
|
@ -555,54 +566,47 @@ export class Community extends Component<
|
|||
}));
|
||||
}
|
||||
|
||||
async updateUrl({ dataType, page, sort }: Partial<CommunityProps>) {
|
||||
const {
|
||||
dataType: urlDataType,
|
||||
page: urlPage,
|
||||
sort: urlSort,
|
||||
} = getCommunityQueryParams();
|
||||
async updateUrl({ dataType, pageCursor, sort }: Partial<CommunityProps>) {
|
||||
const { dataType: urlDataType, sort: urlSort } = getCommunityQueryParams();
|
||||
|
||||
const queryParams: QueryParams<CommunityProps> = {
|
||||
dataType: getDataTypeString(dataType ?? urlDataType),
|
||||
page: (page ?? urlPage).toString(),
|
||||
pageCursor: pageCursor,
|
||||
sort: sort ?? urlSort,
|
||||
};
|
||||
|
||||
this.props.history.push(
|
||||
`/c/${this.props.match.params.name}${getQueryString(queryParams)}`
|
||||
`/c/${this.props.match.params.name}${getQueryString(queryParams)}`,
|
||||
);
|
||||
|
||||
await this.fetchData();
|
||||
}
|
||||
|
||||
async fetchData() {
|
||||
const { dataType, page, sort } = getCommunityQueryParams();
|
||||
const { dataType, pageCursor, sort } = getCommunityQueryParams();
|
||||
const { name } = this.props.match.params;
|
||||
|
||||
if (dataType === DataType.Post) {
|
||||
this.setState({ postsRes: { state: "loading" } });
|
||||
this.setState({ postsRes: LOADING_REQUEST });
|
||||
this.setState({
|
||||
postsRes: await HttpService.client.getPosts({
|
||||
page,
|
||||
page_cursor: pageCursor,
|
||||
limit: fetchLimit,
|
||||
sort,
|
||||
type_: "All",
|
||||
community_name: name,
|
||||
saved_only: false,
|
||||
auth: myAuth(),
|
||||
}),
|
||||
});
|
||||
} else {
|
||||
this.setState({ commentsRes: { state: "loading" } });
|
||||
this.setState({ commentsRes: LOADING_REQUEST });
|
||||
this.setState({
|
||||
commentsRes: await HttpService.client.getComments({
|
||||
page,
|
||||
limit: fetchLimit,
|
||||
sort: postToCommentSortType(sort),
|
||||
type_: "All",
|
||||
community_name: name,
|
||||
saved_only: false,
|
||||
auth: myAuth(),
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
@ -625,11 +629,11 @@ export class Community extends Component<
|
|||
this.updateCommunity(followCommunityRes);
|
||||
|
||||
// Update myUserInfo
|
||||
if (followCommunityRes.state == "success") {
|
||||
if (followCommunityRes.state === "success") {
|
||||
const communityId = followCommunityRes.data.community_view.community.id;
|
||||
const mui = UserService.Instance.myUserInfo;
|
||||
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) {
|
||||
const blockCommunityRes = await HttpService.client.blockCommunity(form);
|
||||
if (blockCommunityRes.state == "success") {
|
||||
if (blockCommunityRes.state === "success") {
|
||||
updateCommunityBlock(blockCommunityRes.data);
|
||||
this.setState(s => {
|
||||
if (s.communityRes.state == "success") {
|
||||
if (s.communityRes.state === "success") {
|
||||
s.communityRes.data.community_view.blocked =
|
||||
blockCommunityRes.data.blocked;
|
||||
}
|
||||
|
@ -669,7 +673,7 @@ export class Community extends Component<
|
|||
|
||||
async handleBlockPerson(form: BlockPerson) {
|
||||
const blockPersonRes = await HttpService.client.blockPerson(form);
|
||||
if (blockPersonRes.state == "success") {
|
||||
if (blockPersonRes.state === "success") {
|
||||
updatePersonBlock(blockPersonRes.data);
|
||||
}
|
||||
}
|
||||
|
@ -695,7 +699,7 @@ export class Community extends Component<
|
|||
|
||||
async handleEditComment(form: EditComment) {
|
||||
const editCommentRes = await HttpService.client.editComment(form);
|
||||
this.findAndUpdateComment(editCommentRes);
|
||||
this.findAndUpdateCommentEdit(editCommentRes);
|
||||
|
||||
return editCommentRes;
|
||||
}
|
||||
|
@ -752,14 +756,14 @@ export class Community extends Component<
|
|||
|
||||
async handleCommentReport(form: CreateCommentReport) {
|
||||
const reportRes = await HttpService.client.createCommentReport(form);
|
||||
if (reportRes.state == "success") {
|
||||
if (reportRes.state === "success") {
|
||||
toast(I18NextService.i18n.t("report_created"));
|
||||
}
|
||||
}
|
||||
|
||||
async handlePostReport(form: CreatePostReport) {
|
||||
const reportRes = await HttpService.client.createPostReport(form);
|
||||
if (reportRes.state == "success") {
|
||||
if (reportRes.state === "success") {
|
||||
toast(I18NextService.i18n.t("report_created"));
|
||||
}
|
||||
}
|
||||
|
@ -777,14 +781,14 @@ export class Community extends Component<
|
|||
async handleAddAdmin(form: AddAdmin) {
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
async handleTransferCommunity(form: TransferCommunity) {
|
||||
const transferCommunityRes = await HttpService.client.transferCommunity(
|
||||
form
|
||||
form,
|
||||
);
|
||||
toast(I18NextService.i18n.t("transfer_community"));
|
||||
this.updateCommunityFull(transferCommunityRes);
|
||||
|
@ -800,6 +804,11 @@ export class Community extends Component<
|
|||
await HttpService.client.markPersonMentionAsRead(form);
|
||||
}
|
||||
|
||||
async handleMarkPostAsRead(form: MarkPostAsRead) {
|
||||
const res = await HttpService.client.markPostAsRead(form);
|
||||
this.findAndUpdatePost(res);
|
||||
}
|
||||
|
||||
async handleBanFromCommunity(form: BanFromCommunity) {
|
||||
const banRes = await HttpService.client.banFromCommunity(form);
|
||||
this.updateBanFromCommunity(banRes);
|
||||
|
@ -812,20 +821,20 @@ export class Community extends Component<
|
|||
|
||||
updateBanFromCommunity(banRes: RequestState<BanFromCommunityResponse>) {
|
||||
// Maybe not necessary
|
||||
if (banRes.state == "success") {
|
||||
if (banRes.state === "success") {
|
||||
this.setState(s => {
|
||||
if (s.postsRes.state == "success") {
|
||||
if (s.postsRes.state === "success") {
|
||||
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_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
|
||||
.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_from_community = banRes.data.banned)
|
||||
c => (c.creator_banned_from_community = banRes.data.banned),
|
||||
);
|
||||
}
|
||||
return s;
|
||||
|
@ -835,16 +844,16 @@ export class Community extends Component<
|
|||
|
||||
updateBan(banRes: RequestState<BanPersonResponse>) {
|
||||
// Maybe not necessary
|
||||
if (banRes.state == "success") {
|
||||
if (banRes.state === "success") {
|
||||
this.setState(s => {
|
||||
if (s.postsRes.state == "success") {
|
||||
if (s.postsRes.state === "success") {
|
||||
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));
|
||||
}
|
||||
if (s.commentsRes.state == "success") {
|
||||
if (s.commentsRes.state === "success") {
|
||||
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));
|
||||
}
|
||||
return s;
|
||||
|
@ -854,7 +863,7 @@ export class Community extends Component<
|
|||
|
||||
updateCommunity(res: RequestState<CommunityResponse>) {
|
||||
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.discussion_languages =
|
||||
res.data.discussion_languages;
|
||||
|
@ -865,7 +874,7 @@ export class Community extends Component<
|
|||
|
||||
updateCommunityFull(res: RequestState<GetCommunityResponse>) {
|
||||
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.moderators = res.data.moderators;
|
||||
}
|
||||
|
@ -874,18 +883,18 @@ export class Community extends Component<
|
|||
}
|
||||
|
||||
purgeItem(purgeRes: RequestState<PurgeItemResponse>) {
|
||||
if (purgeRes.state == "success") {
|
||||
if (purgeRes.state === "success") {
|
||||
toast(I18NextService.i18n.t("purge_success"));
|
||||
this.context.router.history.push(`/`);
|
||||
}
|
||||
}
|
||||
|
||||
findAndUpdateComment(res: RequestState<CommentResponse>) {
|
||||
findAndUpdateCommentEdit(res: RequestState<CommentResponse>) {
|
||||
this.setState(s => {
|
||||
if (s.commentsRes.state == "success" && res.state == "success") {
|
||||
if (s.commentsRes.state === "success" && res.state === "success") {
|
||||
s.commentsRes.data.comments = editComment(
|
||||
res.data.comment_view,
|
||||
s.commentsRes.data.comments
|
||||
s.commentsRes.data.comments,
|
||||
);
|
||||
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>) {
|
||||
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);
|
||||
|
||||
// Set finished for the parent
|
||||
s.finished.set(
|
||||
getCommentParentId(res.data.comment_view.comment) ?? 0,
|
||||
true
|
||||
true,
|
||||
);
|
||||
}
|
||||
return s;
|
||||
|
@ -910,10 +931,10 @@ export class Community extends Component<
|
|||
|
||||
findAndUpdateCommentReply(res: RequestState<CommentReplyResponse>) {
|
||||
this.setState(s => {
|
||||
if (s.commentsRes.state == "success" && res.state == "success") {
|
||||
if (s.commentsRes.state === "success" && res.state === "success") {
|
||||
s.commentsRes.data.comments = editWith(
|
||||
res.data.comment_reply_view,
|
||||
s.commentsRes.data.comments
|
||||
s.commentsRes.data.comments,
|
||||
);
|
||||
}
|
||||
return s;
|
||||
|
@ -922,10 +943,10 @@ export class Community extends Component<
|
|||
|
||||
findAndUpdatePost(res: RequestState<PostResponse>) {
|
||||
this.setState(s => {
|
||||
if (s.postsRes.state == "success" && res.state == "success") {
|
||||
if (s.postsRes.state === "success" && res.state === "success") {
|
||||
s.postsRes.data.posts = editPost(
|
||||
res.data.post_view,
|
||||
s.postsRes.data.posts
|
||||
s.postsRes.data.posts,
|
||||
);
|
||||
}
|
||||
return s;
|
||||
|
@ -935,7 +956,7 @@ export class Community extends Component<
|
|||
updateModerators(res: RequestState<AddModToCommunityResponse>) {
|
||||
// Update the moderators
|
||||
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;
|
||||
}
|
||||
return s;
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { myAuthRequired } from "@utils/app";
|
||||
import { getUnixTime, hostname } from "@utils/helpers";
|
||||
import { hostname } from "@utils/helpers";
|
||||
import { amAdmin, amMod, amTopMod } from "@utils/roles";
|
||||
import { Component, InfernoNode, linkEvent } from "inferno";
|
||||
import { T } from "inferno-i18next-dess";
|
||||
|
@ -22,6 +21,7 @@ import { I18NextService, UserService } from "../../services";
|
|||
import { Badges } from "../common/badges";
|
||||
import { BannerIconHeader } from "../common/banner-icon-header";
|
||||
import { Icon, PurgeWarning, Spinner } from "../common/icon";
|
||||
import { SubscribeButton } from "../common/subscribe-button";
|
||||
import { CommunityForm } from "../community/community-form";
|
||||
import { CommunityLink } from "../community/community-link";
|
||||
import { PersonListing } from "../person/person-listing";
|
||||
|
@ -79,15 +79,15 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
|
|||
}
|
||||
|
||||
componentWillReceiveProps(
|
||||
nextProps: Readonly<{ children?: InfernoNode } & SidebarProps>
|
||||
nextProps: Readonly<{ children?: InfernoNode } & SidebarProps>,
|
||||
): void {
|
||||
if (this.props.moderators != nextProps.moderators) {
|
||||
if (this.props.moderators !== nextProps.moderators) {
|
||||
this.setState({
|
||||
showConfirmLeaveModTeam: false,
|
||||
});
|
||||
}
|
||||
|
||||
if (this.props.community_view != nextProps.community_view) {
|
||||
if (this.props.community_view !== nextProps.community_view) {
|
||||
this.setState({
|
||||
showEdit: false,
|
||||
showPurgeDialog: false,
|
||||
|
@ -123,7 +123,9 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
|
|||
|
||||
sidebar() {
|
||||
const myUSerInfo = UserService.Instance.myUserInfo;
|
||||
const { name, actor_id } = this.props.community_view.community;
|
||||
const {
|
||||
community: { name, actor_id },
|
||||
} = this.props.community_view;
|
||||
return (
|
||||
<aside className="mb-3">
|
||||
<div id="sidebarContainer">
|
||||
|
@ -131,7 +133,12 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
|
|||
<div className="card-body">
|
||||
{this.communityTitle()}
|
||||
{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()}
|
||||
{myUSerInfo && this.blockCommunity()}
|
||||
{!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() {
|
||||
const { subscribed, blocked } = this.props.community_view;
|
||||
|
||||
|
@ -292,7 +247,7 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
|
|||
onClick={linkEvent(this, this.handleBlockCommunity)}
|
||||
>
|
||||
{I18NextService.i18n.t(
|
||||
blocked ? "unblock_community" : "block_community"
|
||||
blocked ? "unblock_community" : "block_community",
|
||||
)}
|
||||
</button>
|
||||
)
|
||||
|
@ -332,7 +287,7 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
|
|||
className="btn btn-link text-muted d-inline-block"
|
||||
onClick={linkEvent(
|
||||
this,
|
||||
this.handleShowConfirmLeaveModTeamClick
|
||||
this.handleShowConfirmLeaveModTeamClick,
|
||||
)}
|
||||
>
|
||||
{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"
|
||||
onClick={linkEvent(
|
||||
this,
|
||||
this.handleCancelLeaveModTeamClick
|
||||
this.handleCancelLeaveModTeamClick,
|
||||
)}
|
||||
>
|
||||
{I18NextService.i18n.t("no")}
|
||||
|
@ -544,7 +499,6 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
|
|||
i.props.onFollowCommunity({
|
||||
community_id: i.props.community_view.community.id,
|
||||
follow: false,
|
||||
auth: myAuthRequired(),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -553,7 +507,6 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
|
|||
i.props.onFollowCommunity({
|
||||
community_id: i.props.community_view.community.id,
|
||||
follow: true,
|
||||
auth: myAuthRequired(),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -563,7 +516,6 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
|
|||
i.props.onBlockCommunity({
|
||||
community_id: community.id,
|
||||
block: !blocked,
|
||||
auth: myAuthRequired(),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -573,9 +525,8 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
|
|||
i.setState({ leaveModTeamLoading: true });
|
||||
i.props.onLeaveModTeam({
|
||||
community_id: i.props.community_view.community.id,
|
||||
person_id: 92,
|
||||
person_id: myId,
|
||||
added: false,
|
||||
auth: myAuthRequired(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -585,7 +536,6 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
|
|||
i.props.onDeleteCommunity({
|
||||
community_id: i.props.community_view.community.id,
|
||||
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,
|
||||
removed: !i.props.community_view.community.removed,
|
||||
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({
|
||||
community_id: i.props.community_view.community.id,
|
||||
reason: i.state.purgeReason,
|
||||
auth: myAuthRequired(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,4 @@
|
|||
import {
|
||||
fetchThemeList,
|
||||
myAuthRequired,
|
||||
setIsoData,
|
||||
showLocal,
|
||||
} from "@utils/app";
|
||||
import { fetchThemeList, setIsoData, showLocal } from "@utils/app";
|
||||
import { capitalizeFirstLetter } from "@utils/helpers";
|
||||
import { RouteDataResponse } from "@utils/types";
|
||||
import classNames from "classnames";
|
||||
|
@ -21,7 +16,12 @@ import {
|
|||
import { InitialFetchRequest } from "../../interfaces";
|
||||
import { removeFromEmojiDataModel, updateEmojiDataModel } from "../../markdown";
|
||||
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 { HtmlTags } from "../common/html-tags";
|
||||
import { Spinner } from "../common/icon";
|
||||
|
@ -55,9 +55,9 @@ export class AdminSettings extends Component<any, AdminSettingsState> {
|
|||
siteRes: this.isoData.site_res,
|
||||
banned: [],
|
||||
currentTab: "site",
|
||||
bannedRes: { state: "empty" },
|
||||
instancesRes: { state: "empty" },
|
||||
leaveAdminTeamRes: { state: "empty" },
|
||||
bannedRes: EMPTY_REQUEST,
|
||||
instancesRes: EMPTY_REQUEST,
|
||||
leaveAdminTeamRes: EMPTY_REQUEST,
|
||||
loading: false,
|
||||
themeList: [],
|
||||
isIsomorphic: false,
|
||||
|
@ -85,16 +85,11 @@ export class AdminSettings extends Component<any, AdminSettingsState> {
|
|||
}
|
||||
|
||||
static async fetchInitialData({
|
||||
auth,
|
||||
client,
|
||||
}: InitialFetchRequest): Promise<AdminSettingsData> {
|
||||
return {
|
||||
bannedRes: await client.getBannedPersons({
|
||||
auth: auth as string,
|
||||
}),
|
||||
instancesRes: await client.getFederatedInstances({
|
||||
auth: auth as string,
|
||||
}),
|
||||
bannedRes: await client.getBannedPersons(),
|
||||
instancesRes: await client.getFederatedInstances(),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -150,15 +145,26 @@ export class AdminSettings extends Component<any, AdminSettingsState> {
|
|||
loading={this.state.loading}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-12 col-md-6">
|
||||
{this.admins()}
|
||||
<hr />
|
||||
{this.bannedUsers()}
|
||||
</div>
|
||||
<div className="col-12 col-md-6">{this.admins()}</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",
|
||||
label: "Rate Limiting",
|
||||
|
@ -230,16 +236,14 @@ export class AdminSettings extends Component<any, AdminSettingsState> {
|
|||
|
||||
async fetchData() {
|
||||
this.setState({
|
||||
bannedRes: { state: "loading" },
|
||||
instancesRes: { state: "loading" },
|
||||
bannedRes: LOADING_REQUEST,
|
||||
instancesRes: LOADING_REQUEST,
|
||||
themeList: [],
|
||||
});
|
||||
|
||||
const auth = myAuthRequired();
|
||||
|
||||
const [bannedRes, instancesRes, themeList] = await Promise.all([
|
||||
HttpService.client.getBannedPersons({ auth }),
|
||||
HttpService.client.getFederatedInstances({ auth }),
|
||||
HttpService.client.getBannedPersons(),
|
||||
HttpService.client.getFederatedInstances(),
|
||||
fetchThemeList(),
|
||||
]);
|
||||
|
||||
|
@ -274,7 +278,7 @@ export class AdminSettings extends Component<any, AdminSettingsState> {
|
|||
onClick={linkEvent(this, this.handleLeaveAdminTeam)}
|
||||
className="btn btn-danger mb-2"
|
||||
>
|
||||
{this.state.leaveAdminTeamRes.state == "loading" ? (
|
||||
{this.state.leaveAdminTeamRes.state === "loading" ? (
|
||||
<Spinner />
|
||||
) : (
|
||||
I18NextService.i18n.t("leave_admin_team")
|
||||
|
@ -295,7 +299,7 @@ export class AdminSettings extends Component<any, AdminSettingsState> {
|
|||
const bans = this.state.bannedRes.data.banned;
|
||||
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">
|
||||
{bans.map(banned => (
|
||||
<li key={banned.person.id} className="list-inline-item">
|
||||
|
@ -334,11 +338,9 @@ export class AdminSettings extends Component<any, AdminSettingsState> {
|
|||
}
|
||||
|
||||
async handleLeaveAdminTeam(i: AdminSettings) {
|
||||
i.setState({ leaveAdminTeamRes: { state: "loading" } });
|
||||
i.setState({ leaveAdminTeamRes: LOADING_REQUEST });
|
||||
this.setState({
|
||||
leaveAdminTeamRes: await HttpService.client.leaveAdmin({
|
||||
auth: myAuthRequired(),
|
||||
}),
|
||||
leaveAdminTeamRes: await HttpService.client.leaveAdmin(),
|
||||
});
|
||||
|
||||
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 { Component, linkEvent } from "inferno";
|
||||
import {
|
||||
|
@ -11,7 +11,6 @@ import { customEmojisLookup } from "../../markdown";
|
|||
import { HttpService, I18NextService } from "../../services";
|
||||
import { pictrsDeleteToast, toast } from "../../toast";
|
||||
import { EmojiMart } from "../common/emoji-mart";
|
||||
import { HtmlTags } from "../common/html-tags";
|
||||
import { Icon, Spinner } from "../common/icon";
|
||||
import { Paginator } from "../common/paginator";
|
||||
|
||||
|
@ -66,17 +65,9 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
|
|||
this.handlePageChange = this.handlePageChange.bind(this);
|
||||
this.handleEmojiClick = this.handleEmojiClick.bind(this);
|
||||
}
|
||||
get documentTitle(): string {
|
||||
return I18NextService.i18n.t("custom_emojis");
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<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>
|
||||
{customEmojisLookup.size > 0 && (
|
||||
<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 +
|
||||
this.itemsPerPage
|
||||
)
|
||||
this.itemsPerPage,
|
||||
),
|
||||
)
|
||||
.map((cv, index) => (
|
||||
<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"
|
||||
htmlFor={`file-uploader-${index}`}
|
||||
data-tippy-content={I18NextService.i18n.t(
|
||||
"upload_image"
|
||||
"upload_image",
|
||||
)}
|
||||
>
|
||||
{capitalizeFirstLetter(
|
||||
I18NextService.i18n.t("upload")
|
||||
I18NextService.i18n.t("upload"),
|
||||
)}
|
||||
<input
|
||||
name={`file-uploader-${index}`}
|
||||
|
@ -153,7 +144,7 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
|
|||
className="d-none"
|
||||
onChange={linkEvent(
|
||||
{ form: this, index: index },
|
||||
this.handleImageUpload
|
||||
this.handleImageUpload,
|
||||
)}
|
||||
/>
|
||||
</label>
|
||||
|
@ -168,7 +159,7 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
|
|||
value={cv.shortcode}
|
||||
onInput={linkEvent(
|
||||
{ form: this, index: index },
|
||||
this.handleEmojiShortCodeChange
|
||||
this.handleEmojiShortCodeChange,
|
||||
)}
|
||||
/>
|
||||
</td>
|
||||
|
@ -180,7 +171,7 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
|
|||
value={cv.category}
|
||||
onInput={linkEvent(
|
||||
{ form: this, index: index },
|
||||
this.handleEmojiCategoryChange
|
||||
this.handleEmojiCategoryChange,
|
||||
)}
|
||||
/>
|
||||
</td>
|
||||
|
@ -192,7 +183,7 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
|
|||
value={cv.image_url}
|
||||
onInput={linkEvent(
|
||||
{ form: this, index: index, overrideValue: null },
|
||||
this.handleEmojiImageUrlChange
|
||||
this.handleEmojiImageUrlChange,
|
||||
)}
|
||||
/>
|
||||
</td>
|
||||
|
@ -204,7 +195,7 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
|
|||
value={cv.alt_text}
|
||||
onInput={linkEvent(
|
||||
{ form: this, index: index },
|
||||
this.handleEmojiAltTextChange
|
||||
this.handleEmojiAltTextChange,
|
||||
)}
|
||||
/>
|
||||
</td>
|
||||
|
@ -216,7 +207,7 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
|
|||
value={cv.keywords}
|
||||
onInput={linkEvent(
|
||||
{ form: this, index: index },
|
||||
this.handleEmojiKeywordChange
|
||||
this.handleEmojiKeywordChange,
|
||||
)}
|
||||
/>
|
||||
</td>
|
||||
|
@ -231,7 +222,7 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
|
|||
}
|
||||
onClick={linkEvent(
|
||||
{ i: this, cv: cv },
|
||||
this.handleEditEmojiClick
|
||||
this.handleEditEmojiClick,
|
||||
)}
|
||||
data-tippy-content={I18NextService.i18n.t("save")}
|
||||
aria-label={I18NextService.i18n.t("save")}
|
||||
|
@ -241,7 +232,7 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
|
|||
<Spinner />
|
||||
) : (
|
||||
capitalizeFirstLetter(
|
||||
I18NextService.i18n.t("save")
|
||||
I18NextService.i18n.t("save"),
|
||||
)
|
||||
)}
|
||||
</button>
|
||||
|
@ -250,7 +241,7 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
|
|||
className="btn btn-link btn-animate text-muted"
|
||||
onClick={linkEvent(
|
||||
{ i: this, index: index, cv: cv },
|
||||
this.handleDeleteEmojiClick
|
||||
this.handleDeleteEmojiClick,
|
||||
)}
|
||||
data-tippy-content={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")}
|
||||
</button>
|
||||
|
||||
<Paginator page={this.state.page} onChange={this.handlePageChange} />
|
||||
<Paginator
|
||||
page={this.state.page}
|
||||
onChange={this.handlePageChange}
|
||||
nextDisabled={false}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -290,8 +285,8 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
|
|||
cv.shortcode.length > 0;
|
||||
const noDuplicateShortCodes =
|
||||
this.state.customEmojis.filter(
|
||||
x => x.shortcode == cv.shortcode && x.id != cv.id
|
||||
).length == 0;
|
||||
x => x.shortcode === cv.shortcode && x.id !== cv.id,
|
||||
).length === 0;
|
||||
return noEmptyFields && noDuplicateShortCodes && !cv.loading && cv.changed;
|
||||
}
|
||||
|
||||
|
@ -308,7 +303,7 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
|
|||
const view = customEmojisLookup.get(e.id);
|
||||
if (view) {
|
||||
const page = this.state.customEmojis.find(
|
||||
x => x.id == view.custom_emoji.id
|
||||
x => x.id === view.custom_emoji.id,
|
||||
)?.page;
|
||||
if (page) {
|
||||
this.setState({ page: page });
|
||||
|
@ -319,7 +314,7 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
|
|||
|
||||
handleEmojiCategoryChange(
|
||||
props: { form: EmojiForm; index: number },
|
||||
event: any
|
||||
event: any,
|
||||
) {
|
||||
const custom_emojis = [...props.form.state.customEmojis];
|
||||
const pagedIndex =
|
||||
|
@ -335,7 +330,7 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
|
|||
|
||||
handleEmojiShortCodeChange(
|
||||
props: { form: EmojiForm; index: number },
|
||||
event: any
|
||||
event: any,
|
||||
) {
|
||||
const custom_emojis = [...props.form.state.customEmojis];
|
||||
const pagedIndex =
|
||||
|
@ -355,7 +350,7 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
|
|||
index,
|
||||
overrideValue,
|
||||
}: { form: EmojiForm; index: number; overrideValue: string | null },
|
||||
event: any
|
||||
event: any,
|
||||
) {
|
||||
form.setState(prevState => {
|
||||
const custom_emojis = [...form.state.customEmojis];
|
||||
|
@ -376,7 +371,7 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
|
|||
changed: true,
|
||||
loading: false,
|
||||
}
|
||||
: ce
|
||||
: ce,
|
||||
),
|
||||
};
|
||||
});
|
||||
|
@ -384,7 +379,7 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
|
|||
|
||||
handleEmojiAltTextChange(
|
||||
props: { form: EmojiForm; index: number },
|
||||
event: any
|
||||
event: any,
|
||||
) {
|
||||
const custom_emojis = [...props.form.state.customEmojis];
|
||||
const pagedIndex =
|
||||
|
@ -400,7 +395,7 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
|
|||
|
||||
handleEmojiKeywordChange(
|
||||
props: { form: EmojiForm; index: number },
|
||||
event: any
|
||||
event: any,
|
||||
) {
|
||||
const custom_emojis = [...props.form.state.customEmojis];
|
||||
const pagedIndex =
|
||||
|
@ -420,10 +415,9 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
|
|||
cv: CustomEmojiViewForm;
|
||||
}) {
|
||||
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({
|
||||
id: d.cv.id,
|
||||
auth: myAuthRequired(),
|
||||
});
|
||||
} else {
|
||||
const custom_emojis = [...d.i.state.customEmojis];
|
||||
|
@ -444,7 +438,6 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
|
|||
image_url: d.cv.image_url,
|
||||
alt_text: d.cv.alt_text,
|
||||
keywords: uniqueKeywords,
|
||||
auth: myAuthRequired(),
|
||||
});
|
||||
} else {
|
||||
d.i.props.onCreate({
|
||||
|
@ -453,7 +446,6 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
|
|||
image_url: d.cv.image_url,
|
||||
alt_text: d.cv.alt_text,
|
||||
keywords: uniqueKeywords,
|
||||
auth: myAuthRequired(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -485,7 +477,7 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
|
|||
|
||||
handleImageUpload(
|
||||
{ form, index }: { form: EmojiForm; index: number },
|
||||
event: any
|
||||
event: any,
|
||||
) {
|
||||
let file: any;
|
||||
if (event.target) {
|
||||
|
@ -498,7 +490,7 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
|
|||
form.setState(prevState => ({
|
||||
...prevState,
|
||||
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);
|
||||
form.handleEmojiImageUrlChange(
|
||||
{ form: form, index: index, overrideValue: res.data.url as string },
|
||||
event
|
||||
event,
|
||||
);
|
||||
} else if (res.data.msg === "too_large") {
|
||||
toast(I18NextService.i18n.t("upload_too_large"), "danger");
|
||||
|
|
|
@ -14,7 +14,6 @@ import {
|
|||
updatePersonBlock,
|
||||
} from "@utils/app";
|
||||
import {
|
||||
getPageFromString,
|
||||
getQueryParams,
|
||||
getQueryString,
|
||||
getRandomFromList,
|
||||
|
@ -59,6 +58,8 @@ import {
|
|||
LockPost,
|
||||
MarkCommentReplyAsRead,
|
||||
MarkPersonMentionAsRead,
|
||||
MarkPostAsRead,
|
||||
PaginationCursor,
|
||||
PostResponse,
|
||||
PurgeComment,
|
||||
PurgeItemResponse,
|
||||
|
@ -84,7 +85,12 @@ 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 { toast } from "../../toast";
|
||||
import { CommentNodes } from "../comment/comment-nodes";
|
||||
|
@ -92,11 +98,11 @@ import { DataTypeSelect } from "../common/data-type-select";
|
|||
import { HtmlTags } from "../common/html-tags";
|
||||
import { Icon, Spinner } from "../common/icon";
|
||||
import { ListingTypeSelect } from "../common/listing-type-select";
|
||||
import { Paginator } from "../common/paginator";
|
||||
import { SortSelect } from "../common/sort-select";
|
||||
import { CommunityLink } from "../community/community-link";
|
||||
import { PostListings } from "../post/post-listings";
|
||||
import { SiteSidebar } from "./site-sidebar";
|
||||
import { PaginatorCursor } from "../common/paginator-cursor";
|
||||
|
||||
interface HomeState {
|
||||
postsRes: RequestState<GetPostsResponse>;
|
||||
|
@ -117,7 +123,7 @@ interface HomeProps {
|
|||
listingType?: ListingType;
|
||||
dataType: DataType;
|
||||
sort: SortType;
|
||||
page: number;
|
||||
pageCursor?: PaginationCursor;
|
||||
}
|
||||
|
||||
type HomeData = RouteDataResponse<{
|
||||
|
@ -128,7 +134,6 @@ type HomeData = RouteDataResponse<{
|
|||
|
||||
function getRss(listingType: ListingType) {
|
||||
const { sort } = getHomeQueryParams();
|
||||
const auth = myAuth();
|
||||
|
||||
let rss: string | undefined = undefined;
|
||||
|
||||
|
@ -142,6 +147,7 @@ function getRss(listingType: ListingType) {
|
|||
break;
|
||||
}
|
||||
case "Subscribed": {
|
||||
const auth = myAuth();
|
||||
rss = auth ? `/feeds/front/${auth}.xml?sort=${sort}` : undefined;
|
||||
break;
|
||||
}
|
||||
|
@ -179,13 +185,14 @@ function getSortTypeFromQuery(type?: string): SortType {
|
|||
return (type ? (type as SortType) : mySortType) ?? "Active";
|
||||
}
|
||||
|
||||
const getHomeQueryParams = () =>
|
||||
getQueryParams<HomeProps>({
|
||||
function getHomeQueryParams() {
|
||||
return getQueryParams<HomeProps>({
|
||||
sort: getSortTypeFromQuery,
|
||||
listingType: getListingTypeFromQuery,
|
||||
page: getPageFromString,
|
||||
pageCursor: cursor => cursor,
|
||||
dataType: getDataTypeFromQuery,
|
||||
});
|
||||
}
|
||||
|
||||
const MobileButton = ({
|
||||
textKey,
|
||||
|
@ -220,9 +227,9 @@ const LinkButton = ({
|
|||
export class Home extends Component<any, HomeState> {
|
||||
private isoData = setIsoData<HomeData>(this.context);
|
||||
state: HomeState = {
|
||||
postsRes: { state: "empty" },
|
||||
commentsRes: { state: "empty" },
|
||||
trendingCommunitiesRes: { state: "empty" },
|
||||
postsRes: EMPTY_REQUEST,
|
||||
commentsRes: EMPTY_REQUEST,
|
||||
trendingCommunitiesRes: EMPTY_REQUEST,
|
||||
scrolled: true,
|
||||
siteRes: this.isoData.site_res,
|
||||
showSubscribedMobile: false,
|
||||
|
@ -239,7 +246,8 @@ export class Home extends Component<any, HomeState> {
|
|||
this.handleSortChange = this.handleSortChange.bind(this);
|
||||
this.handleListingTypeChange = this.handleListingTypeChange.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.handleEditComment = this.handleEditComment.bind(this);
|
||||
|
@ -268,6 +276,7 @@ export class Home extends Component<any, HomeState> {
|
|||
this.handleSavePost = this.handleSavePost.bind(this);
|
||||
this.handlePurgePost = this.handlePurgePost.bind(this);
|
||||
this.handleFeaturePost = this.handleFeaturePost.bind(this);
|
||||
this.handleMarkPostAsRead = this.handleMarkPostAsRead.bind(this);
|
||||
|
||||
// Only fetch the data if coming from another route
|
||||
if (FirstLoadService.isFirstLoad) {
|
||||
|
@ -285,9 +294,8 @@ export class Home extends Component<any, HomeState> {
|
|||
HomeCacheService.postsRes = postsRes;
|
||||
}
|
||||
|
||||
this.state.tagline = getRandomFromList(
|
||||
this.state?.siteRes?.taglines ?? []
|
||||
)?.content;
|
||||
this.state.tagline = getRandomFromList(this.state?.siteRes?.taglines ?? [])
|
||||
?.content;
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
|
@ -298,7 +306,7 @@ export class Home extends Component<any, HomeState> {
|
|||
if (
|
||||
!this.state.isIsomorphic ||
|
||||
!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()]);
|
||||
|
@ -309,8 +317,7 @@ export class Home extends Component<any, HomeState> {
|
|||
|
||||
static async fetchInitialData({
|
||||
client,
|
||||
auth,
|
||||
query: { dataType: urlDataType, listingType, page: urlPage, sort: urlSort },
|
||||
query: { dataType: urlDataType, listingType, pageCursor, sort: urlSort },
|
||||
site,
|
||||
}: InitialFetchRequest<QueryParams<HomeProps>>): Promise<HomeData> {
|
||||
const dataType = getDataTypeFromQuery(urlDataType);
|
||||
|
@ -319,32 +326,25 @@ export class Home extends Component<any, HomeState> {
|
|||
site.site_view.local_site.default_post_listing_type;
|
||||
const sort = getSortTypeFromQuery(urlSort);
|
||||
|
||||
const page = urlPage ? Number(urlPage) : 1;
|
||||
|
||||
let postsRes: RequestState<GetPostsResponse> = { state: "empty" };
|
||||
let commentsRes: RequestState<GetCommentsResponse> = {
|
||||
state: "empty",
|
||||
};
|
||||
let postsRes: RequestState<GetPostsResponse> = EMPTY_REQUEST;
|
||||
let commentsRes: RequestState<GetCommentsResponse> = EMPTY_REQUEST;
|
||||
|
||||
if (dataType === DataType.Post) {
|
||||
const getPostsForm: GetPosts = {
|
||||
type_,
|
||||
page,
|
||||
page_cursor: pageCursor,
|
||||
limit: fetchLimit,
|
||||
sort,
|
||||
saved_only: false,
|
||||
auth,
|
||||
};
|
||||
|
||||
postsRes = await client.getPosts(getPostsForm);
|
||||
} else {
|
||||
const getCommentsForm: GetComments = {
|
||||
page,
|
||||
limit: fetchLimit,
|
||||
sort: postToCommentSortType(sort),
|
||||
type_,
|
||||
saved_only: false,
|
||||
auth,
|
||||
};
|
||||
|
||||
commentsRes = await client.getComments(getCommentsForm);
|
||||
|
@ -354,12 +354,11 @@ export class Home extends Component<any, HomeState> {
|
|||
type_: "Local",
|
||||
sort: "Hot",
|
||||
limit: trendingFetchLimit,
|
||||
auth,
|
||||
};
|
||||
|
||||
return {
|
||||
trendingCommunitiesRes: await client.listCommunities(
|
||||
trendingCommunitiesForm
|
||||
trendingCommunitiesForm,
|
||||
),
|
||||
commentsRes,
|
||||
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 {
|
||||
dataType: urlDataType,
|
||||
listingType: urlListingType,
|
||||
page: urlPage,
|
||||
sort: urlSort,
|
||||
} = getHomeQueryParams();
|
||||
|
||||
const queryParams: QueryParams<HomeProps> = {
|
||||
dataType: getDataTypeString(dataType ?? urlDataType),
|
||||
listingType: listingType ?? urlListingType,
|
||||
page: (page ?? urlPage).toString(),
|
||||
pageCursor: pageCursor,
|
||||
sort: sort ?? urlSort,
|
||||
};
|
||||
|
||||
|
@ -645,19 +648,30 @@ export class Home extends Component<any, HomeState> {
|
|||
}
|
||||
|
||||
get posts() {
|
||||
const { page } = getHomeQueryParams();
|
||||
const { pageCursor } = getHomeQueryParams();
|
||||
|
||||
return (
|
||||
<div className="main-content-wrapper">
|
||||
<div>
|
||||
{this.selects}
|
||||
{this.listings}
|
||||
<Paginator page={page} onChange={this.handlePageChange} />
|
||||
<PaginatorCursor
|
||||
prevPage={pageCursor}
|
||||
nextPage={this.getNextPage}
|
||||
onNext={this.handlePageNext}
|
||||
onPrev={this.handlePagePrev}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
get getNextPage(): PaginationCursor | undefined {
|
||||
return this.state.postsRes.state === "success"
|
||||
? this.state.postsRes.data.next_page
|
||||
: undefined;
|
||||
}
|
||||
|
||||
get listings() {
|
||||
const { dataType } = getHomeQueryParams();
|
||||
const siteRes = this.state.siteRes;
|
||||
|
@ -699,6 +713,7 @@ export class Home extends Component<any, HomeState> {
|
|||
onAddAdmin={this.handleAddAdmin}
|
||||
onTransferCommunity={this.handleTransferCommunity}
|
||||
onFeaturePost={this.handleFeaturePost}
|
||||
onMarkPostAsRead={this.handleMarkPostAsRead}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -718,7 +733,7 @@ export class Home extends Component<any, HomeState> {
|
|||
nodes={commentsToFlatNodes(comments)}
|
||||
viewType={CommentViewType.Flat}
|
||||
finished={this.state.finished}
|
||||
noIndent
|
||||
isTopLevel
|
||||
showCommunity
|
||||
showContext
|
||||
enableDownvotes={enableDownvotes(siteRes)}
|
||||
|
@ -777,7 +792,7 @@ export class Home extends Component<any, HomeState> {
|
|||
<div className="col-auto ps-0">
|
||||
{getRss(
|
||||
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>
|
||||
|
@ -785,20 +800,18 @@ export class Home extends Component<any, HomeState> {
|
|||
}
|
||||
|
||||
async fetchTrendingCommunities() {
|
||||
this.setState({ trendingCommunitiesRes: { state: "loading" } });
|
||||
this.setState({ trendingCommunitiesRes: LOADING_REQUEST });
|
||||
this.setState({
|
||||
trendingCommunitiesRes: await HttpService.client.listCommunities({
|
||||
type_: "Local",
|
||||
sort: "Hot",
|
||||
limit: trendingFetchLimit,
|
||||
auth: myAuth(),
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
async fetchData() {
|
||||
const auth = myAuth();
|
||||
const { dataType, page, listingType, sort } = getHomeQueryParams();
|
||||
const { dataType, pageCursor, listingType, sort } = getHomeQueryParams();
|
||||
|
||||
if (dataType === DataType.Post) {
|
||||
if (HomeCacheService.active) {
|
||||
|
@ -808,33 +821,29 @@ export class Home extends Component<any, HomeState> {
|
|||
window.scrollTo({
|
||||
left: 0,
|
||||
top: scrollY,
|
||||
behavior: "instant",
|
||||
});
|
||||
} else {
|
||||
this.setState({ postsRes: { state: "loading" } });
|
||||
this.setState({ postsRes: LOADING_REQUEST });
|
||||
this.setState({
|
||||
postsRes: await HttpService.client.getPosts({
|
||||
page,
|
||||
page_cursor: pageCursor,
|
||||
limit: fetchLimit,
|
||||
sort,
|
||||
saved_only: false,
|
||||
type_: listingType,
|
||||
auth,
|
||||
}),
|
||||
});
|
||||
|
||||
HomeCacheService.postsRes = this.state.postsRes;
|
||||
}
|
||||
} else {
|
||||
this.setState({ commentsRes: { state: "loading" } });
|
||||
this.setState({ commentsRes: LOADING_REQUEST });
|
||||
this.setState({
|
||||
commentsRes: await HttpService.client.getComments({
|
||||
page,
|
||||
limit: fetchLimit,
|
||||
sort: postToCommentSortType(sort),
|
||||
saved_only: false,
|
||||
type_: listingType,
|
||||
auth,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
@ -858,24 +867,32 @@ export class Home extends Component<any, HomeState> {
|
|||
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.updateUrl({ page });
|
||||
this.updateUrl({ pageCursor: nextPage });
|
||||
}
|
||||
|
||||
handleSortChange(val: SortType) {
|
||||
this.setState({ scrolled: false });
|
||||
this.updateUrl({ sort: val, page: 1 });
|
||||
this.updateUrl({ sort: val, pageCursor: undefined });
|
||||
}
|
||||
|
||||
handleListingTypeChange(val: ListingType) {
|
||||
this.setState({ scrolled: false });
|
||||
this.updateUrl({ listingType: val, page: 1 });
|
||||
this.updateUrl({ listingType: val, pageCursor: undefined });
|
||||
}
|
||||
|
||||
handleDataTypeChange(val: DataType) {
|
||||
this.setState({ scrolled: false });
|
||||
this.updateUrl({ dataType: val, page: 1 });
|
||||
this.updateUrl({ dataType: val, pageCursor: undefined });
|
||||
}
|
||||
|
||||
async handleAddModToCommunity(form: AddModToCommunity) {
|
||||
|
@ -900,7 +917,7 @@ export class Home extends Component<any, HomeState> {
|
|||
|
||||
async handleBlockPerson(form: BlockPerson) {
|
||||
const blockPersonRes = await HttpService.client.blockPerson(form);
|
||||
if (blockPersonRes.state == "success") {
|
||||
if (blockPersonRes.state === "success") {
|
||||
updatePersonBlock(blockPersonRes.data);
|
||||
}
|
||||
}
|
||||
|
@ -914,7 +931,7 @@ export class Home extends Component<any, HomeState> {
|
|||
|
||||
async handleEditComment(form: EditComment) {
|
||||
const editCommentRes = await HttpService.client.editComment(form);
|
||||
this.findAndUpdateComment(editCommentRes);
|
||||
this.findAndUpdateCommentEdit(editCommentRes);
|
||||
|
||||
return editCommentRes;
|
||||
}
|
||||
|
@ -971,14 +988,14 @@ export class Home extends Component<any, HomeState> {
|
|||
|
||||
async handleCommentReport(form: CreateCommentReport) {
|
||||
const reportRes = await HttpService.client.createCommentReport(form);
|
||||
if (reportRes.state == "success") {
|
||||
if (reportRes.state === "success") {
|
||||
toast(I18NextService.i18n.t("report_created"));
|
||||
}
|
||||
}
|
||||
|
||||
async handlePostReport(form: CreatePostReport) {
|
||||
const reportRes = await HttpService.client.createPostReport(form);
|
||||
if (reportRes.state == "success") {
|
||||
if (reportRes.state === "success") {
|
||||
toast(I18NextService.i18n.t("report_created"));
|
||||
}
|
||||
}
|
||||
|
@ -996,7 +1013,7 @@ export class Home extends Component<any, HomeState> {
|
|||
async handleAddAdmin(form: AddAdmin) {
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
@ -1026,22 +1043,27 @@ export class Home extends Component<any, HomeState> {
|
|||
this.updateBan(banRes);
|
||||
}
|
||||
|
||||
async handleMarkPostAsRead(form: MarkPostAsRead) {
|
||||
const res = await HttpService.client.markPostAsRead(form);
|
||||
this.findAndUpdatePost(res);
|
||||
}
|
||||
|
||||
updateBanFromCommunity(banRes: RequestState<BanFromCommunityResponse>) {
|
||||
// Maybe not necessary
|
||||
if (banRes.state == "success") {
|
||||
if (banRes.state === "success") {
|
||||
this.setState(s => {
|
||||
if (s.postsRes.state == "success") {
|
||||
if (s.postsRes.state === "success") {
|
||||
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_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
|
||||
.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_from_community = banRes.data.banned)
|
||||
c => (c.creator_banned_from_community = banRes.data.banned),
|
||||
);
|
||||
}
|
||||
return s;
|
||||
|
@ -1051,16 +1073,16 @@ export class Home extends Component<any, HomeState> {
|
|||
|
||||
updateBan(banRes: RequestState<BanPersonResponse>) {
|
||||
// Maybe not necessary
|
||||
if (banRes.state == "success") {
|
||||
if (banRes.state === "success") {
|
||||
this.setState(s => {
|
||||
if (s.postsRes.state == "success") {
|
||||
if (s.postsRes.state === "success") {
|
||||
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));
|
||||
}
|
||||
if (s.commentsRes.state == "success") {
|
||||
if (s.commentsRes.state === "success") {
|
||||
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));
|
||||
}
|
||||
return s;
|
||||
|
@ -1069,18 +1091,18 @@ export class Home extends Component<any, HomeState> {
|
|||
}
|
||||
|
||||
purgeItem(purgeRes: RequestState<PurgeItemResponse>) {
|
||||
if (purgeRes.state == "success") {
|
||||
if (purgeRes.state === "success") {
|
||||
toast(I18NextService.i18n.t("purge_success"));
|
||||
this.context.router.history.push(`/`);
|
||||
}
|
||||
}
|
||||
|
||||
findAndUpdateComment(res: RequestState<CommentResponse>) {
|
||||
findAndUpdateCommentEdit(res: RequestState<CommentResponse>) {
|
||||
this.setState(s => {
|
||||
if (s.commentsRes.state == "success" && res.state == "success") {
|
||||
if (s.commentsRes.state === "success" && res.state === "success") {
|
||||
s.commentsRes.data.comments = editComment(
|
||||
res.data.comment_view,
|
||||
s.commentsRes.data.comments
|
||||
s.commentsRes.data.comments,
|
||||
);
|
||||
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>) {
|
||||
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);
|
||||
|
||||
// Set finished for the parent
|
||||
s.finished.set(
|
||||
getCommentParentId(res.data.comment_view.comment) ?? 0,
|
||||
true
|
||||
true,
|
||||
);
|
||||
}
|
||||
return s;
|
||||
|
@ -1105,10 +1139,10 @@ export class Home extends Component<any, HomeState> {
|
|||
|
||||
findAndUpdateCommentReply(res: RequestState<CommentReplyResponse>) {
|
||||
this.setState(s => {
|
||||
if (s.commentsRes.state == "success" && res.state == "success") {
|
||||
if (s.commentsRes.state === "success" && res.state === "success") {
|
||||
s.commentsRes.data.comments = editWith(
|
||||
res.data.comment_reply_view,
|
||||
s.commentsRes.data.comments
|
||||
s.commentsRes.data.comments,
|
||||
);
|
||||
}
|
||||
return s;
|
||||
|
@ -1117,10 +1151,10 @@ export class Home extends Component<any, HomeState> {
|
|||
|
||||
findAndUpdatePost(res: RequestState<PostResponse>) {
|
||||
this.setState(s => {
|
||||
if (s.postsRes.state == "success" && res.state == "success") {
|
||||
if (s.postsRes.state === "success" && res.state === "success") {
|
||||
s.postsRes.data.posts = editPost(
|
||||
res.data.post_view,
|
||||
s.postsRes.data.posts
|
||||
s.postsRes.data.posts,
|
||||
);
|
||||
}
|
||||
return s;
|
||||
|
|
|
@ -6,12 +6,19 @@ import {
|
|||
GetSiteResponse,
|
||||
Instance,
|
||||
} from "lemmy-js-client";
|
||||
import classNames from "classnames";
|
||||
import { relTags } from "../../config";
|
||||
import { InitialFetchRequest } from "../../interfaces";
|
||||
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 { Spinner } from "../common/icon";
|
||||
import Tabs from "../common/tabs";
|
||||
|
||||
type InstancesData = RouteDataResponse<{
|
||||
federatedInstancesResponse: GetFederatedInstancesResponse;
|
||||
|
@ -26,7 +33,7 @@ interface InstancesState {
|
|||
export class Instances extends Component<any, InstancesState> {
|
||||
private isoData = setIsoData<InstancesData>(this.context);
|
||||
state: InstancesState = {
|
||||
instancesRes: { state: "empty" },
|
||||
instancesRes: EMPTY_REQUEST,
|
||||
siteRes: this.isoData.site_res,
|
||||
isIsomorphic: false,
|
||||
};
|
||||
|
@ -52,11 +59,11 @@ export class Instances extends Component<any, InstancesState> {
|
|||
|
||||
async fetchInstances() {
|
||||
this.setState({
|
||||
instancesRes: { state: "loading" },
|
||||
instancesRes: LOADING_REQUEST,
|
||||
});
|
||||
|
||||
this.setState({
|
||||
instancesRes: await HttpService.client.getFederatedInstances({}),
|
||||
instancesRes: await HttpService.client.getFederatedInstances(),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -64,7 +71,7 @@ export class Instances extends Component<any, InstancesState> {
|
|||
client,
|
||||
}: InitialFetchRequest): Promise<InstancesData> {
|
||||
return {
|
||||
federatedInstancesResponse: await client.getFederatedInstances({}),
|
||||
federatedInstancesResponse: await client.getFederatedInstances(),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -85,37 +92,32 @@ export class Instances extends Component<any, InstancesState> {
|
|||
case "success": {
|
||||
const instances = this.state.instancesRes.data.federated_instances;
|
||||
return instances ? (
|
||||
<>
|
||||
<h1 className="h4 mb-4">{I18NextService.i18n.t("instances")}</h1>
|
||||
<div className="row">
|
||||
<div className="col-md-6">
|
||||
<h2 className="h5 mb-3">
|
||||
{I18NextService.i18n.t("linked_instances")}
|
||||
</h2>
|
||||
{this.itemList(instances.linked)}
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className="col-lg-8">
|
||||
<Tabs
|
||||
tabs={["linked", "allowed", "blocked"]
|
||||
.filter(status => instances[status].length)
|
||||
.map(status => ({
|
||||
key: status,
|
||||
label: I18NextService.i18n.t(`${status}_instances`),
|
||||
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 className="row">
|
||||
{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>
|
||||
</>
|
||||
</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 ? (
|
||||
<div className="table-responsive">
|
||||
<table id="instances_table" className="table table-sm table-hover">
|
||||
|
@ -148,9 +150,13 @@ export class Instances extends Component<any, InstancesState> {
|
|||
{items.map(i => (
|
||||
<tr key={i.domain}>
|
||||
<td>
|
||||
<a href={`https://${i.domain}`} rel={relTags}>
|
||||
{i.domain}
|
||||
</a>
|
||||
{link ? (
|
||||
<a href={`https://${i.domain}`} rel={relTags}>
|
||||
{i.domain}{" "}
|
||||
</a>
|
||||
) : (
|
||||
<span>{i.domain}</span>
|
||||
)}
|
||||
</td>
|
||||
<td>{i.software}</td>
|
||||
<td>{i.version}</td>
|
||||
|
|
|
@ -2,7 +2,7 @@ import { setIsoData } from "@utils/app";
|
|||
import { capitalizeFirstLetter, validEmail } from "@utils/helpers";
|
||||
import { Component, linkEvent } from "inferno";
|
||||
import { GetSiteResponse } from "lemmy-js-client";
|
||||
import { HttpService, I18NextService, UserService } from "../../services";
|
||||
import { HttpService, I18NextService } from "../../services";
|
||||
import { toast } from "../../toast";
|
||||
import { HtmlTags } from "../common/html-tags";
|
||||
import { Spinner } from "../common/icon";
|
||||
|
@ -30,15 +30,9 @@ export class LoginReset extends Component<any, State> {
|
|||
super(props, context);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (UserService.Instance.myUserInfo) {
|
||||
this.context.router.history.push("/");
|
||||
}
|
||||
}
|
||||
|
||||
get documentTitle(): string {
|
||||
return `${capitalizeFirstLetter(
|
||||
I18NextService.i18n.t("forgot_password")
|
||||
I18NextService.i18n.t("forgot_password"),
|
||||
)} - ${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 });
|
||||
|
||||
if (res.state == "success") {
|
||||
if (res.state === "success") {
|
||||
toast(I18NextService.i18n.t("reset_password_mail_sent"));
|
||||
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 { getQueryParams } from "@utils/helpers";
|
||||
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 { 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 { HtmlTags } from "../common/html-tags";
|
||||
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 {
|
||||
loginRes: RequestState<LoginResponse>;
|
||||
form: {
|
||||
username_or_email?: string;
|
||||
password?: string;
|
||||
totp_2fa_token?: string;
|
||||
username_or_email: string;
|
||||
password: string;
|
||||
};
|
||||
showTotp: boolean;
|
||||
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);
|
||||
|
||||
state: State = {
|
||||
loginRes: { state: "empty" },
|
||||
form: {},
|
||||
showTotp: false,
|
||||
loginRes: EMPTY_REQUEST,
|
||||
form: {
|
||||
username_or_email: "",
|
||||
password: "",
|
||||
},
|
||||
siteRes: this.isoData.site_res,
|
||||
show2faModal: false,
|
||||
};
|
||||
|
||||
constructor(props: any, context: any) {
|
||||
super(props, context);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
// Navigate to home if already logged in
|
||||
if (UserService.Instance.myUserInfo) {
|
||||
this.context.router.history.push("/");
|
||||
}
|
||||
this.handleSubmitTotp = this.handleSubmitTotp.bind(this);
|
||||
}
|
||||
|
||||
get documentTitle(): string {
|
||||
|
@ -48,7 +134,7 @@ export class Login extends Component<any, State> {
|
|||
}
|
||||
|
||||
get isLemmyMl(): boolean {
|
||||
return isBrowser() && window.location.hostname == "lemmy.ml";
|
||||
return isBrowser() && window.location.hostname === "lemmy.ml";
|
||||
}
|
||||
|
||||
render() {
|
||||
|
@ -58,6 +144,12 @@ export class Login extends Component<any, State> {
|
|||
title={this.documentTitle}
|
||||
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="col-12 col-lg-6 offset-lg-3">{this.loginForm()}</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() {
|
||||
return (
|
||||
<div>
|
||||
<form onSubmit={linkEvent(this, this.handleLoginSubmit)}>
|
||||
<form onSubmit={linkEvent(this, handleLoginSubmit)}>
|
||||
<h1 className="h4 mb-4">{I18NextService.i18n.t("login")}</h1>
|
||||
<div className="mb-3 row">
|
||||
<label
|
||||
|
@ -83,62 +193,26 @@ export class Login extends Component<any, State> {
|
|||
className="form-control"
|
||||
id="login-email-or-username"
|
||||
value={this.state.form.username_or_email}
|
||||
onInput={linkEvent(this, this.handleLoginUsernameChange)}
|
||||
onInput={linkEvent(this, handleLoginUsernameChange)}
|
||||
autoComplete="email"
|
||||
required
|
||||
minLength={3}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-3 row">
|
||||
<label className="col-sm-2 col-form-label" htmlFor="login-password">
|
||||
{I18NextService.i18n.t("password")}
|
||||
</label>
|
||||
<div className="col-sm-10">
|
||||
<input
|
||||
type="password"
|
||||
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 className="mb-3">
|
||||
<PasswordInput
|
||||
id="login-password"
|
||||
value={this.state.form.password}
|
||||
onInput={linkEvent(this, handleLoginPasswordChange)}
|
||||
label={I18NextService.i18n.t("password")}
|
||||
showForgotLink
|
||||
/>
|
||||
</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="col-sm-10">
|
||||
<button type="submit" className="btn btn-secondary">
|
||||
{this.state.loginRes.state == "loading" ? (
|
||||
{this.state.loginRes.state === "loading" ? (
|
||||
<Spinner />
|
||||
) : (
|
||||
I18NextService.i18n.t("login")
|
||||
|
@ -150,64 +224,4 @@ export class Login extends Component<any, State> {
|
|||
</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 classNames from "classnames";
|
||||
import { Component, FormEventHandler, linkEvent } from "inferno";
|
||||
|
@ -88,7 +87,7 @@ function RateLimits({
|
|||
|
||||
function handleRateLimitChange(
|
||||
{ rateLimitType, ctx }: { rateLimitType: string; ctx: RateLimitsForm },
|
||||
event: any
|
||||
event: any,
|
||||
) {
|
||||
ctx.setState(prev => ({
|
||||
...prev,
|
||||
|
@ -101,7 +100,7 @@ function handleRateLimitChange(
|
|||
|
||||
function handlePerSecondChange(
|
||||
{ rateLimitType, ctx }: { rateLimitType: string; ctx: RateLimitsForm },
|
||||
event: any
|
||||
event: any,
|
||||
) {
|
||||
ctx.setState(prev => ({
|
||||
...prev,
|
||||
|
@ -114,15 +113,12 @@ function handlePerSecondChange(
|
|||
|
||||
function submitRateLimitForm(i: RateLimitsForm, event: any) {
|
||||
event.preventDefault();
|
||||
const auth = myAuthRequired();
|
||||
const form: EditSite = Object.entries(i.state.form).reduce(
|
||||
(acc, [key, val]) => {
|
||||
acc[`rate_limit_${key}`] = val;
|
||||
return acc;
|
||||
},
|
||||
{
|
||||
auth,
|
||||
}
|
||||
{},
|
||||
);
|
||||
|
||||
i.props.onSaveSite(form);
|
||||
|
@ -159,11 +155,11 @@ export default class RateLimitsForm extends Component<
|
|||
})}
|
||||
handleRateLimit={linkEvent(
|
||||
{ rateLimitType, ctx: this },
|
||||
handleRateLimitChange
|
||||
handleRateLimitChange,
|
||||
)}
|
||||
handleRateLimitPerSecond={linkEvent(
|
||||
{ rateLimitType, ctx: this },
|
||||
handlePerSecondChange
|
||||
handlePerSecondChange,
|
||||
)}
|
||||
rateLimitValue={this.state.form[rateLimitType]}
|
||||
rateLimitPerSecondValue={
|
||||
|
|
|
@ -8,8 +8,14 @@ import {
|
|||
Register,
|
||||
} from "lemmy-js-client";
|
||||
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 PasswordInput from "../common/password-input";
|
||||
import { SiteForm } from "./site-form";
|
||||
|
||||
interface State {
|
||||
|
@ -34,7 +40,7 @@ export class Setup extends Component<any, State> {
|
|||
private isoData = setIsoData(this.context);
|
||||
|
||||
state: State = {
|
||||
registerRes: { state: "empty" },
|
||||
registerRes: EMPTY_REQUEST,
|
||||
themeList: [],
|
||||
form: {
|
||||
show_nsfw: true,
|
||||
|
@ -121,46 +127,28 @@ export class Setup extends Component<any, State> {
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-3 row">
|
||||
<label className="col-sm-2 col-form-label" htmlFor="password">
|
||||
{I18NextService.i18n.t("password")}
|
||||
</label>
|
||||
<div className="col-sm-10">
|
||||
<input
|
||||
type="password"
|
||||
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 className="mb-3">
|
||||
<PasswordInput
|
||||
id="password"
|
||||
value={this.state.form.password}
|
||||
onInput={linkEvent(this, this.handleRegisterPasswordChange)}
|
||||
label={I18NextService.i18n.t("password")}
|
||||
isNew
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-3 row">
|
||||
<label className="col-sm-2 col-form-label" htmlFor="verify-password">
|
||||
{I18NextService.i18n.t("verify_password")}
|
||||
</label>
|
||||
<div className="col-sm-10">
|
||||
<input
|
||||
type="password"
|
||||
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 className="mb-3">
|
||||
<PasswordInput
|
||||
id="verify-password"
|
||||
value={this.state.form.password_verify}
|
||||
onInput={linkEvent(this, this.handleRegisterPasswordVerifyChange)}
|
||||
label={I18NextService.i18n.t("verify_password")}
|
||||
isNew
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-3 row">
|
||||
<div className="col-sm-10">
|
||||
<button type="submit" className="btn btn-secondary">
|
||||
{this.state.registerRes.state == "loading" ? (
|
||||
{this.state.registerRes.state === "loading" ? (
|
||||
<Spinner />
|
||||
) : (
|
||||
I18NextService.i18n.t("sign_up")
|
||||
|
@ -174,7 +162,7 @@ export class Setup extends Component<any, State> {
|
|||
|
||||
async handleRegisterSubmit(i: Setup, event: any) {
|
||||
event.preventDefault();
|
||||
i.setState({ registerRes: { state: "loading" } });
|
||||
i.setState({ registerRes: LOADING_REQUEST });
|
||||
const {
|
||||
username,
|
||||
password_verify,
|
||||
|
@ -203,7 +191,7 @@ export class Setup extends Component<any, State> {
|
|||
registerRes: await HttpService.client.register(form),
|
||||
});
|
||||
|
||||
if (i.state.registerRes.state == "success") {
|
||||
if (i.state.registerRes.state === "success") {
|
||||
const data = i.state.registerRes.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 { validEmail } from "@utils/helpers";
|
||||
import { Options, passwordStrength } from "check-password-strength";
|
||||
import { NoOptionI18nKeys } from "i18next";
|
||||
import { Component, linkEvent } from "inferno";
|
||||
import { T } from "inferno-i18next-dess";
|
||||
import {
|
||||
|
@ -15,38 +13,17 @@ import {
|
|||
import { joinLemmyUrl } from "../../config";
|
||||
import { mdToHtml } from "../../markdown";
|
||||
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 { HtmlTags } from "../common/html-tags";
|
||||
import { Icon, Spinner } from "../common/icon";
|
||||
import { MarkdownTextArea } from "../common/markdown-textarea";
|
||||
|
||||
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,
|
||||
},
|
||||
];
|
||||
import PasswordInput from "../common/password-input";
|
||||
|
||||
interface State {
|
||||
registerRes: RequestState<LoginResponse>;
|
||||
|
@ -71,8 +48,8 @@ export class Signup extends Component<any, State> {
|
|||
private audio?: HTMLAudioElement;
|
||||
|
||||
state: State = {
|
||||
registerRes: { state: "empty" },
|
||||
captchaRes: { state: "empty" },
|
||||
registerRes: EMPTY_REQUEST,
|
||||
captchaRes: EMPTY_REQUEST,
|
||||
form: {
|
||||
show_nsfw: false,
|
||||
},
|
||||
|
@ -93,13 +70,13 @@ export class Signup extends Component<any, State> {
|
|||
}
|
||||
|
||||
async fetchCaptcha() {
|
||||
this.setState({ captchaRes: { state: "loading" } });
|
||||
this.setState({ captchaRes: LOADING_REQUEST });
|
||||
this.setState({
|
||||
captchaRes: await HttpService.client.getCaptcha({}),
|
||||
captchaRes: await HttpService.client.getCaptcha(),
|
||||
});
|
||||
|
||||
this.setState(s => {
|
||||
if (s.captchaRes.state == "success") {
|
||||
if (s.captchaRes.state === "success") {
|
||||
s.form.captcha_uuid = s.captchaRes.data.ok?.uuid;
|
||||
}
|
||||
return s;
|
||||
|
@ -113,12 +90,12 @@ export class Signup extends Component<any, State> {
|
|||
|
||||
titleName(siteView: SiteView): string {
|
||||
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 {
|
||||
return isBrowser() && window.location.hostname == "lemmy.ml";
|
||||
return isBrowser() && window.location.hostname === "lemmy.ml";
|
||||
}
|
||||
|
||||
render() {
|
||||
|
@ -219,57 +196,28 @@ export class Signup extends Component<any, State> {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mb-3 row">
|
||||
<label
|
||||
className="col-sm-2 col-form-label"
|
||||
htmlFor="register-password"
|
||||
>
|
||||
{I18NextService.i18n.t("password")}
|
||||
</label>
|
||||
<div className="col-sm-10">
|
||||
<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 className="mb-3">
|
||||
<PasswordInput
|
||||
id="register-password"
|
||||
value={this.state.form.password}
|
||||
onInput={linkEvent(this, this.handleRegisterPasswordChange)}
|
||||
showStrength
|
||||
label={I18NextService.i18n.t("password")}
|
||||
isNew
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mb-3 row">
|
||||
<label
|
||||
className="col-sm-2 col-form-label"
|
||||
htmlFor="register-verify-password"
|
||||
>
|
||||
{I18NextService.i18n.t("verify_password")}
|
||||
</label>
|
||||
<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 className="mb-3">
|
||||
<PasswordInput
|
||||
id="register-verify-password"
|
||||
value={this.state.form.password_verify}
|
||||
onInput={linkEvent(this, this.handleRegisterPasswordVerifyChange)}
|
||||
label={I18NextService.i18n.t("verify_password")}
|
||||
isNew
|
||||
/>
|
||||
</div>
|
||||
|
||||
{siteView.local_site.registration_mode == "RequireApplication" && (
|
||||
{siteView.local_site.registration_mode === "RequireApplication" && (
|
||||
<>
|
||||
<div className="mb-3 row">
|
||||
<div className="offset-sm-2 col-sm-10">
|
||||
|
@ -281,7 +229,7 @@ export class Signup extends Component<any, State> {
|
|||
<div
|
||||
className="md-div"
|
||||
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="col-sm-10">
|
||||
<button type="submit" className="btn btn-secondary">
|
||||
{this.state.registerRes.state == "loading" ? (
|
||||
{this.state.registerRes.state === "loading" ? (
|
||||
<Spinner />
|
||||
) : (
|
||||
this.titleName(siteView)
|
||||
|
@ -379,7 +327,7 @@ export class Signup extends Component<any, State> {
|
|||
value={this.state.form.captcha_answer}
|
||||
onInput={linkEvent(
|
||||
this,
|
||||
this.handleRegisterCaptchaAnswerChange
|
||||
this.handleRegisterCaptchaAnswerChange,
|
||||
)}
|
||||
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) {
|
||||
event.preventDefault();
|
||||
const {
|
||||
|
@ -453,7 +382,7 @@ export class Signup extends Component<any, State> {
|
|||
username,
|
||||
} = i.state.form;
|
||||
if (username && password && password_verify) {
|
||||
i.setState({ registerRes: { state: "loading" } });
|
||||
i.setState({ registerRes: LOADING_REQUEST });
|
||||
|
||||
const registerRes = await HttpService.client.register({
|
||||
username,
|
||||
|
@ -469,7 +398,7 @@ export class Signup extends Component<any, State> {
|
|||
switch (registerRes.state) {
|
||||
case "failed": {
|
||||
toast(registerRes.msg, "danger");
|
||||
i.setState({ registerRes: { state: "empty" } });
|
||||
i.setState({ registerRes: EMPTY_REQUEST });
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -482,7 +411,7 @@ export class Signup extends Component<any, State> {
|
|||
res: data,
|
||||
});
|
||||
|
||||
const site = await HttpService.client.getSite({ auth: myAuth() });
|
||||
const site = await HttpService.client.getSite();
|
||||
|
||||
if (site.state === "success") {
|
||||
UserService.Instance.myUserInfo = site.data.my_user;
|
||||
|
@ -511,7 +440,7 @@ export class Signup extends Component<any, State> {
|
|||
|
||||
handleRegisterEmailChange(i: Signup, event: any) {
|
||||
i.state.form.email = event.target.value;
|
||||
if (i.state.form.email == "") {
|
||||
if (i.state.form.email === "") {
|
||||
i.state.form.email = undefined;
|
||||
}
|
||||
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.
|
||||
// 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;
|
||||
if (!i.audio) {
|
||||
const base64 = `data:audio/wav;base64,${captchaRes.wav}`;
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { myAuthRequired } from "@utils/app";
|
||||
import { capitalizeFirstLetter, validInstanceTLD } from "@utils/helpers";
|
||||
import {
|
||||
Component,
|
||||
|
@ -7,6 +6,7 @@ import {
|
|||
InfernoNode,
|
||||
linkEvent,
|
||||
} from "inferno";
|
||||
import { Prompt } from "inferno-router";
|
||||
import {
|
||||
CreateSite,
|
||||
EditSite,
|
||||
|
@ -21,7 +21,6 @@ import { ImageUploadForm } from "../common/image-upload-form";
|
|||
import { LanguageSelect } from "../common/language-select";
|
||||
import { ListingTypeSelect } from "../common/listing-type-select";
|
||||
import { MarkdownTextArea } from "../common/markdown-textarea";
|
||||
import NavigationPrompt from "../common/navigation-prompt";
|
||||
|
||||
interface SiteFormProps {
|
||||
blockedInstances?: Instance[];
|
||||
|
@ -85,7 +84,6 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
|
|||
captcha_difficulty: ls.captcha_difficulty,
|
||||
allowed_instances: this.props.allowedInstances?.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"
|
||||
onSubmit={linkEvent(this, this.handleSaveSiteSubmit)}
|
||||
>
|
||||
<NavigationPrompt
|
||||
<Prompt
|
||||
message={I18NextService.i18n.t("block_leaving")}
|
||||
when={
|
||||
!this.props.loading &&
|
||||
!siteSetup &&
|
||||
|
@ -292,7 +291,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
|
|||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{this.state.siteForm.registration_mode == "RequireApplication" && (
|
||||
{this.state.siteForm.registration_mode === "RequireApplication" && (
|
||||
<div className="mb-3 row">
|
||||
<label className="col-12 col-form-label">
|
||||
{I18NextService.i18n.t("application_questionnaire")}
|
||||
|
@ -318,7 +317,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
|
|||
checked={this.state.siteForm.community_creation_admin_only}
|
||||
onChange={linkEvent(
|
||||
this,
|
||||
this.handleSiteCommunityCreationAdminOnly
|
||||
this.handleSiteCommunityCreationAdminOnly,
|
||||
)}
|
||||
/>
|
||||
<label
|
||||
|
@ -340,7 +339,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
|
|||
checked={this.state.siteForm.require_email_verification}
|
||||
onChange={linkEvent(
|
||||
this,
|
||||
this.handleSiteRequireEmailVerification
|
||||
this.handleSiteRequireEmailVerification,
|
||||
)}
|
||||
/>
|
||||
<label
|
||||
|
@ -362,7 +361,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
|
|||
checked={this.state.siteForm.application_email_admins}
|
||||
onChange={linkEvent(
|
||||
this,
|
||||
this.handleSiteApplicationEmailAdmins
|
||||
this.handleSiteApplicationEmailAdmins,
|
||||
)}
|
||||
/>
|
||||
<label
|
||||
|
@ -410,6 +409,9 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
|
|||
<option value="browser">
|
||||
{I18NextService.i18n.t("browser_default")}
|
||||
</option>
|
||||
<option value="browser-compact">
|
||||
{I18NextService.i18n.t("browser_default_compact")}
|
||||
</option>
|
||||
{this.props.themeList?.map(theme => (
|
||||
<option key={theme} value={theme}>
|
||||
{theme}
|
||||
|
@ -627,7 +629,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
|
|||
}
|
||||
|
||||
componentDidUpdate(
|
||||
prevProps: Readonly<{ children?: InfernoNode } & SiteFormProps>
|
||||
prevProps: Readonly<{ children?: InfernoNode } & SiteFormProps>,
|
||||
) {
|
||||
if (
|
||||
!(
|
||||
|
@ -690,7 +692,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
|
|||
className="btn btn-sm bg-danger"
|
||||
onClick={linkEvent(
|
||||
{ key, instance },
|
||||
this.handleRemoveInstance
|
||||
this.handleRemoveInstance,
|
||||
)}
|
||||
>
|
||||
<Icon
|
||||
|
@ -718,7 +720,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
|
|||
|
||||
handleInstanceEnterPress(
|
||||
key: InstanceKey,
|
||||
event: InfernoKeyboardEvent<HTMLInputElement>
|
||||
event: InfernoKeyboardEvent<HTMLInputElement>,
|
||||
) {
|
||||
if (event.code.toLowerCase() === "enter") {
|
||||
event.preventDefault();
|
||||
|
@ -729,8 +731,6 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
|
|||
|
||||
handleSaveSiteSubmit(i: SiteForm, event: any) {
|
||||
event.preventDefault();
|
||||
const auth = myAuthRequired();
|
||||
i.setState(s => ((s.siteForm.auth = auth), s));
|
||||
i.setState({ submitted: true });
|
||||
|
||||
const stateSiteForm = i.state.siteForm;
|
||||
|
@ -784,7 +784,6 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
|
|||
allowed_instances: stateSiteForm.allowed_instances,
|
||||
blocked_instances: stateSiteForm.blocked_instances,
|
||||
discussion_languages: stateSiteForm.discussion_languages,
|
||||
auth,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -859,7 +858,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
|
|||
handleDeleteTaglineClick(
|
||||
i: SiteForm,
|
||||
index: number,
|
||||
event: InfernoMouseEvent<HTMLButtonElement>
|
||||
event: InfernoMouseEvent<HTMLButtonElement>,
|
||||
) {
|
||||
event.preventDefault();
|
||||
const taglines = i.state.siteForm.taglines;
|
||||
|
@ -874,7 +873,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
|
|||
|
||||
handleAddTaglineClick(
|
||||
i: SiteForm,
|
||||
event: InfernoMouseEvent<HTMLButtonElement>
|
||||
event: InfernoMouseEvent<HTMLButtonElement>,
|
||||
) {
|
||||
event.preventDefault();
|
||||
if (!i.state.siteForm.taglines) {
|
||||
|
@ -965,7 +964,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
|
|||
|
||||
handleSiteActorNameMaxLength(i: SiteForm, event: any) {
|
||||
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 { PersonView, Site, SiteAggregates } from "lemmy-js-client";
|
||||
import { mdToHtml } from "../../markdown";
|
||||
|
@ -32,10 +33,7 @@ export class SiteSidebar extends Component<SiteSidebarProps, SiteSidebarState> {
|
|||
return (
|
||||
<div className="site-sidebar accordion">
|
||||
<section id="sidebarInfo" className="card border-secondary mb-3">
|
||||
<header
|
||||
className="card-header d-flex align-items-center"
|
||||
id="sidebarInfoHeader"
|
||||
>
|
||||
<header className="card-header" id="sidebarInfoHeader">
|
||||
{this.siteName()}
|
||||
{!this.state.collapsed && (
|
||||
<BannerIconHeader banner={this.props.site.banner} />
|
||||
|
@ -54,7 +52,7 @@ export class SiteSidebar extends Component<SiteSidebarProps, SiteSidebarState> {
|
|||
|
||||
siteName() {
|
||||
return (
|
||||
<>
|
||||
<div className={classNames({ "mb-2": !this.state.collapsed })}>
|
||||
<h5 className="mb-0 d-inline">{this.props.site.name}</h5>
|
||||
{!this.props.isMobile && (
|
||||
<button
|
||||
|
@ -83,7 +81,7 @@ export class SiteSidebar extends Component<SiteSidebarProps, SiteSidebarState> {
|
|||
)}
|
||||
</button>
|
||||
)}
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
import { myAuthRequired } from "@utils/app";
|
||||
import { capitalizeFirstLetter } from "@utils/helpers";
|
||||
import { Component, InfernoMouseEvent, linkEvent } from "inferno";
|
||||
import { EditSite, Tagline } from "lemmy-js-client";
|
||||
import { I18NextService } from "../../services";
|
||||
import { HtmlTags } from "../common/html-tags";
|
||||
import { Icon, Spinner } from "../common/icon";
|
||||
import { MarkdownTextArea } from "../common/markdown-textarea";
|
||||
|
||||
|
@ -26,17 +24,10 @@ export class TaglineForm extends Component<TaglineFormProps, TaglineFormState> {
|
|||
constructor(props: any, context: any) {
|
||||
super(props, context);
|
||||
}
|
||||
get documentTitle(): string {
|
||||
return I18NextService.i18n.t("taglines");
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<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>
|
||||
<div className="table-responsive col-12">
|
||||
<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) => (
|
||||
<tr key={index}>
|
||||
<td>
|
||||
{this.state.editingRow == index && (
|
||||
{this.state.editingRow === index && (
|
||||
<MarkdownTextArea
|
||||
initialContent={cv}
|
||||
onContentChange={s =>
|
||||
|
@ -59,14 +50,14 @@ export class TaglineForm extends Component<TaglineFormProps, TaglineFormState> {
|
|||
siteLanguages={[]}
|
||||
/>
|
||||
)}
|
||||
{this.state.editingRow != index && <div>{cv}</div>}
|
||||
{this.state.editingRow !== index && <div>{cv}</div>}
|
||||
</td>
|
||||
<td className="text-right">
|
||||
<button
|
||||
className="btn btn-link btn-animate text-muted"
|
||||
onClick={linkEvent(
|
||||
{ i: this, index: index },
|
||||
this.handleEditTaglineClick
|
||||
this.handleEditTaglineClick,
|
||||
)}
|
||||
data-tippy-content={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"
|
||||
onClick={linkEvent(
|
||||
{ i: this, index: index },
|
||||
this.handleDeleteTaglineClick
|
||||
this.handleDeleteTaglineClick,
|
||||
)}
|
||||
data-tippy-content={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) {
|
||||
event.preventDefault();
|
||||
if (d.i.state.editingRow == d.index) {
|
||||
if (d.i.state.editingRow === d.index) {
|
||||
d.i.setState({ editingRow: undefined });
|
||||
} else {
|
||||
d.i.setState({ editingRow: d.index });
|
||||
|
@ -151,13 +142,12 @@ export class TaglineForm extends Component<TaglineFormProps, TaglineFormState> {
|
|||
async handleSaveClick(i: TaglineForm) {
|
||||
i.props.onSaveSite({
|
||||
taglines: i.state.taglines,
|
||||
auth: myAuthRequired(),
|
||||
});
|
||||
}
|
||||
|
||||
handleAddTaglineClick(
|
||||
i: TaglineForm,
|
||||
event: InfernoMouseEvent<HTMLButtonElement>
|
||||
event: InfernoMouseEvent<HTMLButtonElement>,
|
||||
) {
|
||||
event.preventDefault();
|
||||
const newTaglines = [...i.state.taglines];
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import {
|
||||
fetchUsers,
|
||||
getUpdatedSearchId,
|
||||
myAuth,
|
||||
personToChoice,
|
||||
setIsoData,
|
||||
} from "@utils/app";
|
||||
|
@ -48,7 +47,12 @@ import {
|
|||
import { fetchLimit } from "../config";
|
||||
import { InitialFetchRequest } from "../interfaces";
|
||||
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 { Icon, Spinner } from "./common/icon";
|
||||
import { MomentTime } from "./common/moment-time";
|
||||
|
@ -121,7 +125,7 @@ function getActionFromString(action?: string): ModlogActionType {
|
|||
const getModlogActionMapper =
|
||||
(
|
||||
actionType: ModlogActionType,
|
||||
getAction: (view: View) => { id: number; when_: string }
|
||||
getAction: (view: View) => { id: number; when_: string },
|
||||
) =>
|
||||
(view: View & { moderator?: Person; admin?: Person }): ModlogType => {
|
||||
const { id, when_ } = getAction(view);
|
||||
|
@ -155,111 +159,111 @@ function buildCombined({
|
|||
.map(
|
||||
getModlogActionMapper(
|
||||
"ModRemovePost",
|
||||
({ mod_remove_post }: ModRemovePostView) => mod_remove_post
|
||||
)
|
||||
({ mod_remove_post }: ModRemovePostView) => mod_remove_post,
|
||||
),
|
||||
)
|
||||
.concat(
|
||||
locked_posts.map(
|
||||
getModlogActionMapper(
|
||||
"ModLockPost",
|
||||
({ mod_lock_post }: ModLockPostView) => mod_lock_post
|
||||
)
|
||||
)
|
||||
({ mod_lock_post }: ModLockPostView) => mod_lock_post,
|
||||
),
|
||||
),
|
||||
)
|
||||
.concat(
|
||||
featured_posts.map(
|
||||
getModlogActionMapper(
|
||||
"ModFeaturePost",
|
||||
({ mod_feature_post }: ModFeaturePostView) => mod_feature_post
|
||||
)
|
||||
)
|
||||
({ mod_feature_post }: ModFeaturePostView) => mod_feature_post,
|
||||
),
|
||||
),
|
||||
)
|
||||
.concat(
|
||||
removed_comments.map(
|
||||
getModlogActionMapper(
|
||||
"ModRemoveComment",
|
||||
({ mod_remove_comment }: ModRemoveCommentView) => mod_remove_comment
|
||||
)
|
||||
)
|
||||
({ mod_remove_comment }: ModRemoveCommentView) => mod_remove_comment,
|
||||
),
|
||||
),
|
||||
)
|
||||
.concat(
|
||||
removed_communities.map(
|
||||
getModlogActionMapper(
|
||||
"ModRemoveCommunity",
|
||||
({ mod_remove_community }: ModRemoveCommunityView) =>
|
||||
mod_remove_community
|
||||
)
|
||||
)
|
||||
mod_remove_community,
|
||||
),
|
||||
),
|
||||
)
|
||||
.concat(
|
||||
banned_from_community.map(
|
||||
getModlogActionMapper(
|
||||
"ModBanFromCommunity",
|
||||
({ mod_ban_from_community }: ModBanFromCommunityView) =>
|
||||
mod_ban_from_community
|
||||
)
|
||||
)
|
||||
mod_ban_from_community,
|
||||
),
|
||||
),
|
||||
)
|
||||
.concat(
|
||||
added_to_community.map(
|
||||
getModlogActionMapper(
|
||||
"ModAddCommunity",
|
||||
({ mod_add_community }: ModAddCommunityView) => mod_add_community
|
||||
)
|
||||
)
|
||||
({ mod_add_community }: ModAddCommunityView) => mod_add_community,
|
||||
),
|
||||
),
|
||||
)
|
||||
.concat(
|
||||
transferred_to_community.map(
|
||||
getModlogActionMapper(
|
||||
"ModTransferCommunity",
|
||||
({ mod_transfer_community }: ModTransferCommunityView) =>
|
||||
mod_transfer_community
|
||||
)
|
||||
)
|
||||
mod_transfer_community,
|
||||
),
|
||||
),
|
||||
)
|
||||
.concat(
|
||||
added.map(
|
||||
getModlogActionMapper("ModAdd", ({ mod_add }: ModAddView) => mod_add)
|
||||
)
|
||||
getModlogActionMapper("ModAdd", ({ mod_add }: ModAddView) => mod_add),
|
||||
),
|
||||
)
|
||||
.concat(
|
||||
banned.map(
|
||||
getModlogActionMapper("ModBan", ({ mod_ban }: ModBanView) => mod_ban)
|
||||
)
|
||||
getModlogActionMapper("ModBan", ({ mod_ban }: ModBanView) => mod_ban),
|
||||
),
|
||||
)
|
||||
.concat(
|
||||
admin_purged_persons.map(
|
||||
getModlogActionMapper(
|
||||
"AdminPurgePerson",
|
||||
({ admin_purge_person }: AdminPurgePersonView) => admin_purge_person
|
||||
)
|
||||
)
|
||||
({ admin_purge_person }: AdminPurgePersonView) => admin_purge_person,
|
||||
),
|
||||
),
|
||||
)
|
||||
.concat(
|
||||
admin_purged_communities.map(
|
||||
getModlogActionMapper(
|
||||
"AdminPurgeCommunity",
|
||||
({ admin_purge_community }: AdminPurgeCommunityView) =>
|
||||
admin_purge_community
|
||||
)
|
||||
)
|
||||
admin_purge_community,
|
||||
),
|
||||
),
|
||||
)
|
||||
.concat(
|
||||
admin_purged_posts.map(
|
||||
getModlogActionMapper(
|
||||
"AdminPurgePost",
|
||||
({ admin_purge_post }: AdminPurgePostView) => admin_purge_post
|
||||
)
|
||||
)
|
||||
({ admin_purge_post }: AdminPurgePostView) => admin_purge_post,
|
||||
),
|
||||
),
|
||||
)
|
||||
.concat(
|
||||
admin_purged_comments.map(
|
||||
getModlogActionMapper(
|
||||
"AdminPurgeComment",
|
||||
({ admin_purge_comment }: AdminPurgeCommentView) =>
|
||||
admin_purge_comment
|
||||
)
|
||||
)
|
||||
admin_purge_comment,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Sort them by time
|
||||
|
@ -312,6 +316,7 @@ function renderModlogType({ type_, view }: ModlogType) {
|
|||
const {
|
||||
mod_feature_post: { featured, is_featured_community },
|
||||
post: { id, name },
|
||||
community,
|
||||
} = view as ModFeaturePostView;
|
||||
|
||||
return (
|
||||
|
@ -320,7 +325,12 @@ function renderModlogType({ type_, view }: ModlogType) {
|
|||
<span>
|
||||
Post <Link to={`/post/${id}`}>{name}</Link>
|
||||
</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": {
|
||||
const mrco = view as ModRemoveCommunityView;
|
||||
const {
|
||||
mod_remove_community: { reason, expires, removed },
|
||||
mod_remove_community: { reason, removed },
|
||||
community,
|
||||
} = mrco;
|
||||
|
||||
|
@ -369,11 +379,6 @@ function renderModlogType({ type_, view }: ModlogType) {
|
|||
<div>reason: {reason}</div>
|
||||
</span>
|
||||
)}
|
||||
{expires && (
|
||||
<span>
|
||||
<div>expires: {formatPastDate(expires)}</div>
|
||||
</span>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -532,7 +537,7 @@ function renderModlogType({ type_, view }: ModlogType) {
|
|||
|
||||
return (
|
||||
<>
|
||||
<span>Purged a Post from from </span>
|
||||
<span>Purged a Post from </span>
|
||||
<CommunityLink community={community} />
|
||||
{reason && (
|
||||
<span>
|
||||
|
@ -616,7 +621,7 @@ async function createNewOptions({
|
|||
|
||||
if (id) {
|
||||
const selectedUser = oldOptions.find(
|
||||
({ value }) => value === id.toString()
|
||||
({ value }) => value === id.toString(),
|
||||
);
|
||||
|
||||
if (selectedUser) {
|
||||
|
@ -628,7 +633,7 @@ async function createNewOptions({
|
|||
newOptions.push(
|
||||
...(await fetchUsers(text))
|
||||
.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);
|
||||
|
||||
state: ModlogState = {
|
||||
res: { state: "empty" },
|
||||
communityRes: { state: "empty" },
|
||||
res: EMPTY_REQUEST,
|
||||
communityRes: EMPTY_REQUEST,
|
||||
loadingModSearch: false,
|
||||
loadingUserSearch: false,
|
||||
userSearchOptions: [],
|
||||
|
@ -652,7 +657,7 @@ export class Modlog extends Component<
|
|||
|
||||
constructor(
|
||||
props: RouteComponentProps<{ communityId?: string }>,
|
||||
context: any
|
||||
context: any,
|
||||
) {
|
||||
super(props, context);
|
||||
this.handlePageChange = this.handlePageChange.bind(this);
|
||||
|
@ -692,7 +697,7 @@ export class Modlog extends Component<
|
|||
|
||||
get combined() {
|
||||
const res = this.state.res;
|
||||
const combined = res.state == "success" ? buildCombined(res.data) : [];
|
||||
const combined = res.state === "success" ? buildCombined(res.data) : [];
|
||||
|
||||
return (
|
||||
<tbody>
|
||||
|
@ -717,7 +722,7 @@ export class Modlog extends Component<
|
|||
|
||||
get amAdminOrMod(): boolean {
|
||||
const amMod_ =
|
||||
this.state.communityRes.state == "success" &&
|
||||
this.state.communityRes.state === "success" &&
|
||||
amMod(this.state.communityRes.data.moderators);
|
||||
return amAdmin() || amMod_;
|
||||
}
|
||||
|
@ -725,7 +730,7 @@ export class Modlog extends Component<
|
|||
modOrAdminText(person?: Person): string {
|
||||
return person &&
|
||||
this.isoData.site_res.admins.some(
|
||||
({ person: { id } }) => id === person.id
|
||||
({ person: { id } }) => id === person.id,
|
||||
)
|
||||
? I18NextService.i18n.t("admin")
|
||||
: I18NextService.i18n.t("mod");
|
||||
|
@ -854,7 +859,11 @@ export class Modlog extends Component<
|
|||
</thead>
|
||||
{this.combined}
|
||||
</table>
|
||||
<Paginator page={page} onChange={this.handlePageChange} />
|
||||
<Paginator
|
||||
page={page}
|
||||
onChange={this.handlePageChange}
|
||||
nextDisabled={false}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -933,20 +942,19 @@ export class Modlog extends Component<
|
|||
|
||||
this.props.history.push(
|
||||
`/modlog${communityId ? `/${communityId}` : ""}${getQueryString(
|
||||
queryParams
|
||||
)}`
|
||||
queryParams,
|
||||
)}`,
|
||||
);
|
||||
|
||||
await this.refetch();
|
||||
}
|
||||
|
||||
async refetch() {
|
||||
const auth = myAuth();
|
||||
const { actionType, page, modId, userId } = getModlogQueryParams();
|
||||
const { communityId: urlCommunityId } = this.props.match.params;
|
||||
const communityId = getIdFromString(urlCommunityId);
|
||||
|
||||
this.setState({ res: { state: "loading" } });
|
||||
this.setState({ res: LOADING_REQUEST });
|
||||
this.setState({
|
||||
res: await HttpService.client.getModlog({
|
||||
community_id: communityId,
|
||||
|
@ -958,16 +966,14 @@ export class Modlog extends Component<
|
|||
.hide_modlog_mod_names
|
||||
? modId ?? undefined
|
||||
: undefined,
|
||||
auth,
|
||||
}),
|
||||
});
|
||||
|
||||
if (communityId) {
|
||||
this.setState({ communityRes: { state: "loading" } });
|
||||
this.setState({ communityRes: LOADING_REQUEST });
|
||||
this.setState({
|
||||
communityRes: await HttpService.client.getCommunity({
|
||||
id: communityId,
|
||||
auth,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
@ -977,7 +983,6 @@ export class Modlog extends Component<
|
|||
client,
|
||||
path,
|
||||
query: { modId: urlModId, page, userId: urlUserId, actionType },
|
||||
auth,
|
||||
site,
|
||||
}: InitialFetchRequest<QueryParams<ModlogProps>>): Promise<ModlogData> {
|
||||
const pathSplit = path.split("/");
|
||||
|
@ -994,43 +999,33 @@ export class Modlog extends Component<
|
|||
type_: getActionFromString(actionType),
|
||||
mod_person_id: modId,
|
||||
other_person_id: userId,
|
||||
auth,
|
||||
};
|
||||
|
||||
let communityResponse: RequestState<GetCommunityResponse> = {
|
||||
state: "empty",
|
||||
};
|
||||
let communityResponse: RequestState<GetCommunityResponse> = EMPTY_REQUEST;
|
||||
|
||||
if (communityId) {
|
||||
const communityForm: GetCommunity = {
|
||||
id: communityId,
|
||||
auth,
|
||||
};
|
||||
|
||||
communityResponse = await client.getCommunity(communityForm);
|
||||
}
|
||||
|
||||
let modUserResponse: RequestState<GetPersonDetailsResponse> = {
|
||||
state: "empty",
|
||||
};
|
||||
let modUserResponse: RequestState<GetPersonDetailsResponse> = EMPTY_REQUEST;
|
||||
|
||||
if (modId) {
|
||||
const getPersonForm: GetPersonDetails = {
|
||||
person_id: modId,
|
||||
auth,
|
||||
};
|
||||
|
||||
modUserResponse = await client.getPersonDetails(getPersonForm);
|
||||
}
|
||||
|
||||
let userResponse: RequestState<GetPersonDetailsResponse> = {
|
||||
state: "empty",
|
||||
};
|
||||
let userResponse: RequestState<GetPersonDetailsResponse> = EMPTY_REQUEST;
|
||||
|
||||
if (userId) {
|
||||
const getPersonForm: GetPersonDetails = {
|
||||
person_id: userId,
|
||||
auth,
|
||||
};
|
||||
|
||||
userResponse = await client.getPersonDetails(getPersonForm);
|
||||
|
|
|
@ -7,7 +7,6 @@ import {
|
|||
enableDownvotes,
|
||||
getCommentParentId,
|
||||
myAuth,
|
||||
myAuthRequired,
|
||||
setIsoData,
|
||||
updatePersonBlock,
|
||||
} from "@utils/app";
|
||||
|
@ -63,7 +62,14 @@ import {
|
|||
import { fetchLimit, relTags } from "../../config";
|
||||
import { CommentViewType, InitialFetchRequest } from "../../interfaces";
|
||||
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 { CommentNodes } from "../comment/comment-nodes";
|
||||
import { CommentSortSelect } from "../common/comment-sort-select";
|
||||
|
@ -125,10 +131,10 @@ export class Inbox extends Component<any, InboxState> {
|
|||
sort: "New",
|
||||
page: 1,
|
||||
siteRes: this.isoData.site_res,
|
||||
repliesRes: { state: "empty" },
|
||||
mentionsRes: { state: "empty" },
|
||||
messagesRes: { state: "empty" },
|
||||
markAllAsReadRes: { state: "empty" },
|
||||
repliesRes: EMPTY_REQUEST,
|
||||
mentionsRes: EMPTY_REQUEST,
|
||||
messagesRes: EMPTY_REQUEST,
|
||||
markAllAsReadRes: EMPTY_REQUEST,
|
||||
finished: new Map(),
|
||||
isIsomorphic: false,
|
||||
};
|
||||
|
@ -188,20 +194,20 @@ export class Inbox extends Component<any, InboxState> {
|
|||
const mui = UserService.Instance.myUserInfo;
|
||||
return mui
|
||||
? `@${mui.local_user_view.person.name} ${I18NextService.i18n.t(
|
||||
"inbox"
|
||||
"inbox",
|
||||
)} - ${this.state.siteRes.site_view.site.name}`
|
||||
: "";
|
||||
}
|
||||
|
||||
get hasUnreads(): boolean {
|
||||
if (this.state.unreadOrAll == UnreadOrAll.Unread) {
|
||||
if (this.state.unreadOrAll === UnreadOrAll.Unread) {
|
||||
const { repliesRes, mentionsRes, messagesRes } = this.state;
|
||||
const replyCount =
|
||||
repliesRes.state == "success" ? repliesRes.data.replies.length : 0;
|
||||
repliesRes.state === "success" ? repliesRes.data.replies.length : 0;
|
||||
const mentionCount =
|
||||
mentionsRes.state == "success" ? mentionsRes.data.mentions.length : 0;
|
||||
mentionsRes.state === "success" ? mentionsRes.data.mentions.length : 0;
|
||||
const messageCount =
|
||||
messagesRes.state == "success"
|
||||
messagesRes.state === "success"
|
||||
? messagesRes.data.private_messages.length
|
||||
: 0;
|
||||
|
||||
|
@ -242,11 +248,11 @@ export class Inbox extends Component<any, InboxState> {
|
|||
className="btn btn-secondary mb-2 mb-sm-3"
|
||||
onClick={linkEvent(this, this.handleMarkAllAsRead)}
|
||||
>
|
||||
{this.state.markAllAsReadRes.state == "loading" ? (
|
||||
{this.state.markAllAsReadRes.state === "loading" ? (
|
||||
<Spinner />
|
||||
) : (
|
||||
capitalizeFirstLetter(
|
||||
I18NextService.i18n.t("mark_all_as_read")
|
||||
I18NextService.i18n.t("mark_all_as_read"),
|
||||
)
|
||||
)}
|
||||
</button>
|
||||
|
@ -256,6 +262,7 @@ export class Inbox extends Component<any, InboxState> {
|
|||
<Paginator
|
||||
page={this.state.page}
|
||||
onChange={this.handlePageChange}
|
||||
nextDisabled={false}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -445,22 +452,22 @@ export class Inbox extends Component<any, InboxState> {
|
|||
|
||||
buildCombined(): ReplyType[] {
|
||||
const replies: ReplyType[] =
|
||||
this.state.repliesRes.state == "success"
|
||||
this.state.repliesRes.state === "success"
|
||||
? this.state.repliesRes.data.replies.map(this.replyToReplyType)
|
||||
: [];
|
||||
const mentions: ReplyType[] =
|
||||
this.state.mentionsRes.state == "success"
|
||||
this.state.mentionsRes.state === "success"
|
||||
? this.state.mentionsRes.data.mentions.map(this.mentionToReplyType)
|
||||
: [];
|
||||
const messages: ReplyType[] =
|
||||
this.state.messagesRes.state == "success"
|
||||
this.state.messagesRes.state === "success"
|
||||
? this.state.messagesRes.data.private_messages.map(
|
||||
this.messageToReplyType
|
||||
this.messageToReplyType,
|
||||
)
|
||||
: [];
|
||||
|
||||
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() {
|
||||
if (
|
||||
this.state.repliesRes.state == "loading" ||
|
||||
this.state.mentionsRes.state == "loading" ||
|
||||
this.state.messagesRes.state == "loading"
|
||||
this.state.repliesRes.state === "loading" ||
|
||||
this.state.mentionsRes.state === "loading" ||
|
||||
this.state.messagesRes.state === "loading"
|
||||
) {
|
||||
return (
|
||||
<h1 className="h4">
|
||||
|
@ -718,78 +725,77 @@ export class Inbox extends Component<any, InboxState> {
|
|||
|
||||
static async fetchInitialData({
|
||||
client,
|
||||
auth,
|
||||
}: InitialFetchRequest): Promise<InboxData> {
|
||||
const sort: CommentSortType = "New";
|
||||
|
||||
return {
|
||||
mentionsRes: auth
|
||||
? await client.getPersonMentions({
|
||||
sort,
|
||||
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" },
|
||||
const empty: EmptyRequestState = EMPTY_REQUEST;
|
||||
let inboxData: InboxData = {
|
||||
mentionsRes: empty,
|
||||
messagesRes: empty,
|
||||
repliesRes: 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() {
|
||||
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 limit = fetchLimit;
|
||||
const auth = myAuthRequired();
|
||||
|
||||
this.setState({ repliesRes: { state: "loading" } });
|
||||
this.setState({ repliesRes: LOADING_REQUEST });
|
||||
this.setState({
|
||||
repliesRes: await HttpService.client.getReplies({
|
||||
sort,
|
||||
unread_only,
|
||||
page,
|
||||
limit,
|
||||
auth,
|
||||
}),
|
||||
});
|
||||
|
||||
this.setState({ mentionsRes: { state: "loading" } });
|
||||
this.setState({ mentionsRes: LOADING_REQUEST });
|
||||
this.setState({
|
||||
mentionsRes: await HttpService.client.getPersonMentions({
|
||||
sort,
|
||||
unread_only,
|
||||
page,
|
||||
limit,
|
||||
auth,
|
||||
}),
|
||||
});
|
||||
|
||||
this.setState({ messagesRes: { state: "loading" } });
|
||||
this.setState({ messagesRes: LOADING_REQUEST });
|
||||
this.setState({
|
||||
messagesRes: await HttpService.client.getPrivateMessages({
|
||||
unread_only,
|
||||
page,
|
||||
limit,
|
||||
auth,
|
||||
}),
|
||||
});
|
||||
UnreadCounterService.Instance.updateInboxCounts();
|
||||
}
|
||||
|
||||
async handleSortChange(val: CommentSortType) {
|
||||
|
@ -798,19 +804,17 @@ export class Inbox extends Component<any, InboxState> {
|
|||
}
|
||||
|
||||
async handleMarkAllAsRead(i: Inbox) {
|
||||
i.setState({ markAllAsReadRes: { state: "loading" } });
|
||||
i.setState({ markAllAsReadRes: LOADING_REQUEST });
|
||||
|
||||
i.setState({
|
||||
markAllAsReadRes: await HttpService.client.markAllAsRead({
|
||||
auth: myAuthRequired(),
|
||||
}),
|
||||
markAllAsReadRes: await HttpService.client.markAllAsRead(),
|
||||
});
|
||||
|
||||
if (i.state.markAllAsReadRes.state == "success") {
|
||||
if (i.state.markAllAsReadRes.state === "success") {
|
||||
i.setState({
|
||||
repliesRes: { state: "empty" },
|
||||
mentionsRes: { state: "empty" },
|
||||
messagesRes: { state: "empty" },
|
||||
repliesRes: EMPTY_REQUEST,
|
||||
mentionsRes: EMPTY_REQUEST,
|
||||
messagesRes: EMPTY_REQUEST,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -837,7 +841,7 @@ export class Inbox extends Component<any, InboxState> {
|
|||
|
||||
async handleBlockPerson(form: BlockPerson) {
|
||||
const blockPersonRes = await HttpService.client.blockPerson(form);
|
||||
if (blockPersonRes.state == "success") {
|
||||
if (blockPersonRes.state === "success") {
|
||||
updatePersonBlock(blockPersonRes.data);
|
||||
}
|
||||
}
|
||||
|
@ -868,7 +872,7 @@ export class Inbox extends Component<any, InboxState> {
|
|||
|
||||
async handleDeleteComment(form: DeleteComment) {
|
||||
const res = await HttpService.client.deleteComment(form);
|
||||
if (res.state == "success") {
|
||||
if (res.state === "success") {
|
||||
toast(I18NextService.i18n.t("deleted"));
|
||||
this.findAndUpdateComment(res);
|
||||
}
|
||||
|
@ -876,7 +880,7 @@ export class Inbox extends Component<any, InboxState> {
|
|||
|
||||
async handleRemoveComment(form: RemoveComment) {
|
||||
const res = await HttpService.client.removeComment(form);
|
||||
if (res.state == "success") {
|
||||
if (res.state === "success") {
|
||||
toast(I18NextService.i18n.t("remove_comment"));
|
||||
this.findAndUpdateComment(res);
|
||||
}
|
||||
|
@ -917,12 +921,20 @@ export class Inbox extends Component<any, InboxState> {
|
|||
|
||||
async handleCommentReplyRead(form: MarkCommentReplyAsRead) {
|
||||
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) {
|
||||
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) {
|
||||
|
@ -947,7 +959,11 @@ export class Inbox extends Component<any, InboxState> {
|
|||
|
||||
async handleMarkMessageAsRead(form: MarkPrivateMessageAsRead) {
|
||||
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) {
|
||||
|
@ -958,9 +974,9 @@ export class Inbox extends Component<any, InboxState> {
|
|||
async handleCreateMessage(form: CreatePrivateMessage) {
|
||||
const res = await HttpService.client.createPrivateMessage(form);
|
||||
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(
|
||||
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") {
|
||||
s.messagesRes.data.private_messages = editPrivateMessage(
|
||||
res.data.private_message_view,
|
||||
s.messagesRes.data.private_messages
|
||||
s.messagesRes.data.private_messages,
|
||||
);
|
||||
}
|
||||
return s;
|
||||
|
@ -982,20 +998,20 @@ export class Inbox extends Component<any, InboxState> {
|
|||
|
||||
updateBanFromCommunity(banRes: RequestState<BanFromCommunityResponse>) {
|
||||
// Maybe not necessary
|
||||
if (banRes.state == "success") {
|
||||
if (banRes.state === "success") {
|
||||
this.setState(s => {
|
||||
if (s.repliesRes.state == "success") {
|
||||
if (s.repliesRes.state === "success") {
|
||||
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_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
|
||||
.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_from_community = banRes.data.banned)
|
||||
c => (c.creator_banned_from_community = banRes.data.banned),
|
||||
);
|
||||
}
|
||||
return s;
|
||||
|
@ -1005,16 +1021,16 @@ export class Inbox extends Component<any, InboxState> {
|
|||
|
||||
updateBan(banRes: RequestState<BanPersonResponse>) {
|
||||
// Maybe not necessary
|
||||
if (banRes.state == "success") {
|
||||
if (banRes.state === "success") {
|
||||
this.setState(s => {
|
||||
if (s.repliesRes.state == "success") {
|
||||
if (s.repliesRes.state === "success") {
|
||||
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));
|
||||
}
|
||||
if (s.mentionsRes.state == "success") {
|
||||
if (s.mentionsRes.state === "success") {
|
||||
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));
|
||||
}
|
||||
return s;
|
||||
|
@ -1023,40 +1039,40 @@ export class Inbox extends Component<any, InboxState> {
|
|||
}
|
||||
|
||||
purgeItem(purgeRes: RequestState<PurgeItemResponse>) {
|
||||
if (purgeRes.state == "success") {
|
||||
if (purgeRes.state === "success") {
|
||||
toast(I18NextService.i18n.t("purge_success"));
|
||||
this.context.router.history.push(`/`);
|
||||
}
|
||||
}
|
||||
|
||||
reportToast(
|
||||
res: RequestState<PrivateMessageReportResponse | CommentReportResponse>
|
||||
res: RequestState<PrivateMessageReportResponse | CommentReportResponse>,
|
||||
) {
|
||||
if (res.state == "success") {
|
||||
if (res.state === "success") {
|
||||
toast(I18NextService.i18n.t("report_created"));
|
||||
}
|
||||
}
|
||||
|
||||
// A weird case, since you have only replies and mentions, not comment responses
|
||||
findAndUpdateComment(res: RequestState<CommentResponse>) {
|
||||
if (res.state == "success") {
|
||||
if (res.state === "success") {
|
||||
this.setState(s => {
|
||||
if (s.repliesRes.state == "success") {
|
||||
if (s.repliesRes.state === "success") {
|
||||
s.repliesRes.data.replies = editWith(
|
||||
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(
|
||||
res.data.comment_view,
|
||||
s.mentionsRes.data.mentions
|
||||
s.mentionsRes.data.mentions,
|
||||
);
|
||||
}
|
||||
// Set finished for the parent
|
||||
s.finished.set(
|
||||
getCommentParentId(res.data.comment_view.comment) ?? 0,
|
||||
true
|
||||
true,
|
||||
);
|
||||
return s;
|
||||
});
|
||||
|
@ -1065,10 +1081,10 @@ export class Inbox extends Component<any, InboxState> {
|
|||
|
||||
findAndUpdateCommentReply(res: RequestState<CommentReplyResponse>) {
|
||||
this.setState(s => {
|
||||
if (s.repliesRes.state == "success" && res.state == "success") {
|
||||
if (s.repliesRes.state === "success" && res.state === "success") {
|
||||
s.repliesRes.data.replies = editCommentReply(
|
||||
res.data.comment_reply_view,
|
||||
s.repliesRes.data.replies
|
||||
s.repliesRes.data.replies,
|
||||
);
|
||||
}
|
||||
return s;
|
||||
|
@ -1077,10 +1093,10 @@ export class Inbox extends Component<any, InboxState> {
|
|||
|
||||
findAndUpdateMention(res: RequestState<PersonMentionResponse>) {
|
||||
this.setState(s => {
|
||||
if (s.mentionsRes.state == "success" && res.state == "success") {
|
||||
if (s.mentionsRes.state === "success" && res.state === "success") {
|
||||
s.mentionsRes.data.mentions = editMention(
|
||||
res.data.person_mention_view,
|
||||
s.mentionsRes.data.mentions
|
||||
s.mentionsRes.data.mentions,
|
||||
);
|
||||
}
|
||||
return s;
|
||||
|
|
|
@ -1,11 +1,16 @@
|
|||
import { myAuth, setIsoData } from "@utils/app";
|
||||
import { setIsoData } from "@utils/app";
|
||||
import { capitalizeFirstLetter } from "@utils/helpers";
|
||||
import { Component, linkEvent } from "inferno";
|
||||
import { GetSiteResponse, LoginResponse } from "lemmy-js-client";
|
||||
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 { Spinner } from "../common/icon";
|
||||
import PasswordInput from "../common/password-input";
|
||||
|
||||
interface State {
|
||||
passwordChangeRes: RequestState<LoginResponse>;
|
||||
|
@ -21,7 +26,7 @@ export class PasswordChange extends Component<any, State> {
|
|||
private isoData = setIsoData(this.context);
|
||||
|
||||
state: State = {
|
||||
passwordChangeRes: { state: "empty" },
|
||||
passwordChangeRes: EMPTY_REQUEST,
|
||||
siteRes: this.isoData.site_res,
|
||||
form: {
|
||||
token: this.props.match.params.token,
|
||||
|
@ -60,42 +65,28 @@ export class PasswordChange extends Component<any, State> {
|
|||
passwordChangeForm() {
|
||||
return (
|
||||
<form onSubmit={linkEvent(this, this.handlePasswordChangeSubmit)}>
|
||||
<div className="mb-3 row">
|
||||
<label className="col-sm-2 col-form-label" htmlFor="new-password">
|
||||
{I18NextService.i18n.t("new_password")}
|
||||
</label>
|
||||
<div className="col-sm-10">
|
||||
<input
|
||||
id="new-password"
|
||||
type="password"
|
||||
value={this.state.form.password}
|
||||
onInput={linkEvent(this, this.handlePasswordChange)}
|
||||
className="form-control"
|
||||
required
|
||||
maxLength={60}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<PasswordInput
|
||||
id="new-password"
|
||||
value={this.state.form.password}
|
||||
onInput={linkEvent(this, this.handlePasswordChange)}
|
||||
showStrength
|
||||
label={I18NextService.i18n.t("new_password")}
|
||||
isNew
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-3 row">
|
||||
<label className="col-sm-2 col-form-label" htmlFor="verify-password">
|
||||
{I18NextService.i18n.t("verify_password")}
|
||||
</label>
|
||||
<div className="col-sm-10">
|
||||
<input
|
||||
id="verify-password"
|
||||
type="password"
|
||||
value={this.state.form.password_verify}
|
||||
onInput={linkEvent(this, this.handleVerifyPasswordChange)}
|
||||
className="form-control"
|
||||
required
|
||||
maxLength={60}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<PasswordInput
|
||||
id="password"
|
||||
value={this.state.form.password_verify}
|
||||
onInput={linkEvent(this, this.handleVerifyPasswordChange)}
|
||||
label={I18NextService.i18n.t("verify_password")}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-3 row">
|
||||
<div className="col-sm-10">
|
||||
<button type="submit" className="btn btn-secondary">
|
||||
{this.state.passwordChangeRes.state == "loading" ? (
|
||||
{this.state.passwordChangeRes.state === "loading" ? (
|
||||
<Spinner />
|
||||
) : (
|
||||
capitalizeFirstLetter(I18NextService.i18n.t("save"))
|
||||
|
@ -119,7 +110,7 @@ export class PasswordChange extends Component<any, State> {
|
|||
|
||||
async handlePasswordChangeSubmit(i: PasswordChange, event: any) {
|
||||
event.preventDefault();
|
||||
i.setState({ passwordChangeRes: { state: "loading" } });
|
||||
i.setState({ passwordChangeRes: LOADING_REQUEST });
|
||||
|
||||
const password = i.state.form.password;
|
||||
const password_verify = i.state.form.password_verify;
|
||||
|
@ -139,7 +130,7 @@ export class PasswordChange extends Component<any, State> {
|
|||
res: data,
|
||||
});
|
||||
|
||||
const site = await HttpService.client.getSite({ auth: myAuth() });
|
||||
const site = await HttpService.client.getSite();
|
||||
if (site.state === "success") {
|
||||
UserService.Instance.myUserInfo = site.data.my_user;
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ import {
|
|||
LockPost,
|
||||
MarkCommentReplyAsRead,
|
||||
MarkPersonMentionAsRead,
|
||||
MarkPostAsRead,
|
||||
PersonView,
|
||||
PostView,
|
||||
PurgeComment,
|
||||
|
@ -84,6 +85,7 @@ interface PersonDetailsProps {
|
|||
onSavePost(form: SavePost): void;
|
||||
onFeaturePost(form: FeaturePost): void;
|
||||
onPurgePost(form: PurgePost): void;
|
||||
onMarkPostAsRead(form: MarkPostAsRead): void;
|
||||
}
|
||||
|
||||
enum ItemEnum {
|
||||
|
@ -113,7 +115,16 @@ export class PersonDetails extends Component<PersonDetailsProps, any> {
|
|||
<div className="person-details">
|
||||
{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>
|
||||
);
|
||||
}
|
||||
|
@ -200,6 +211,7 @@ export class PersonDetails extends Component<PersonDetailsProps, any> {
|
|||
onAddModToCommunity={this.props.onAddModToCommunity}
|
||||
onAddAdmin={this.props.onAddAdmin}
|
||||
onTransferCommunity={this.props.onTransferCommunity}
|
||||
onMarkPostAsRead={this.props.onMarkPostAsRead}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -252,7 +264,7 @@ export class PersonDetails extends Component<PersonDetailsProps, any> {
|
|||
viewType={CommentViewType.Flat}
|
||||
admins={this.props.admins}
|
||||
finished={this.props.finished}
|
||||
noIndent
|
||||
isTopLevel
|
||||
showCommunity
|
||||
showContext
|
||||
enableDownvotes={this.props.enableDownvotes}
|
||||
|
@ -311,6 +323,7 @@ export class PersonDetails extends Component<PersonDetailsProps, any> {
|
|||
onAddModToCommunity={this.props.onAddModToCommunity}
|
||||
onAddAdmin={this.props.onAddAdmin}
|
||||
onTransferCommunity={this.props.onTransferCommunity}
|
||||
onMarkPostAsRead={this.props.onMarkPostAsRead}
|
||||
/>
|
||||
<hr className="my-3" />
|
||||
</>
|
||||
|
|
|
@ -57,7 +57,7 @@ export class PersonListing extends Component<PersonListingProps, any> {
|
|||
{
|
||||
"text-muted": this.props.muted,
|
||||
"text-info": !this.props.muted,
|
||||
}
|
||||
},
|
||||
)}
|
||||
to={link}
|
||||
>
|
||||
|
|
|
@ -5,8 +5,6 @@ import {
|
|||
enableDownvotes,
|
||||
enableNsfw,
|
||||
getCommentParentId,
|
||||
myAuth,
|
||||
myAuthRequired,
|
||||
setIsoData,
|
||||
updatePersonBlock,
|
||||
} from "@utils/app";
|
||||
|
@ -60,6 +58,7 @@ import {
|
|||
LockPost,
|
||||
MarkCommentReplyAsRead,
|
||||
MarkPersonMentionAsRead,
|
||||
MarkPostAsRead,
|
||||
PersonView,
|
||||
PostResponse,
|
||||
PurgeComment,
|
||||
|
@ -77,7 +76,12 @@ import { fetchLimit, relTags } from "../../config";
|
|||
import { InitialFetchRequest, PersonDetailsView } from "../../interfaces";
|
||||
import { mdToHtml } from "../../markdown";
|
||||
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 { toast } from "../../toast";
|
||||
import { BannerIconHeader } from "../common/banner-icon-header";
|
||||
|
@ -132,7 +136,7 @@ function getViewFromProps(view?: string): PersonDetailsView {
|
|||
|
||||
const getCommunitiesListing = (
|
||||
translationKey: NoOptionI18nKeys,
|
||||
communityViews?: { community: Community }[]
|
||||
communityViews?: { community: Community }[],
|
||||
) =>
|
||||
communityViews &&
|
||||
communityViews.length > 0 && (
|
||||
|
@ -156,13 +160,23 @@ const Moderates = ({ moderates }: { moderates?: CommunityModeratorView[] }) =>
|
|||
const 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<
|
||||
RouteComponentProps<{ username: string }>,
|
||||
ProfileState
|
||||
> {
|
||||
private isoData = setIsoData<ProfileData>(this.context);
|
||||
state: ProfileState = {
|
||||
personRes: { state: "empty" },
|
||||
personRes: EMPTY_REQUEST,
|
||||
personBlocked: false,
|
||||
siteRes: this.isoData.site_res,
|
||||
showBanDialog: false,
|
||||
|
@ -208,13 +222,16 @@ export class Profile extends Component<
|
|||
this.handlePurgePost = this.handlePurgePost.bind(this);
|
||||
this.handleFeaturePost = this.handleFeaturePost.bind(this);
|
||||
this.handleModBanSubmit = this.handleModBanSubmit.bind(this);
|
||||
this.handleMarkPostAsRead = this.handleMarkPostAsRead.bind(this);
|
||||
|
||||
// Only fetch the data if coming from another route
|
||||
if (FirstLoadService.isFirstLoad) {
|
||||
const personRes = this.isoData.routeData.personResponse;
|
||||
this.state = {
|
||||
...this.state,
|
||||
personRes: this.isoData.routeData.personResponse,
|
||||
personRes,
|
||||
isIsomorphic: true,
|
||||
personBlocked: isPersonBlocked(personRes),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -233,19 +250,19 @@ export class Profile extends Component<
|
|||
async fetchUserData() {
|
||||
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({
|
||||
personRes: await HttpService.client.getPersonDetails({
|
||||
username: this.props.match.params.username,
|
||||
sort,
|
||||
saved_only: view === PersonDetailsView.Saved,
|
||||
page,
|
||||
limit: fetchLimit,
|
||||
auth: myAuth(),
|
||||
}),
|
||||
personRes,
|
||||
personBlocked: isPersonBlocked(personRes),
|
||||
});
|
||||
restoreScrollPosition(this.context);
|
||||
this.setPersonBlock();
|
||||
}
|
||||
|
||||
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({
|
||||
client,
|
||||
path,
|
||||
query: { page, sort, view: urlView },
|
||||
auth,
|
||||
}: InitialFetchRequest<QueryParams<ProfileProps>>): Promise<ProfileData> {
|
||||
const pathSplit = path.split("/");
|
||||
|
||||
|
@ -289,7 +292,6 @@ export class Profile extends Component<
|
|||
saved_only: view === PersonDetailsView.Saved,
|
||||
page: getPageFromString(page),
|
||||
limit: fetchLimit,
|
||||
auth,
|
||||
};
|
||||
|
||||
return {
|
||||
|
@ -300,7 +302,7 @@ export class Profile extends Component<
|
|||
get documentTitle(): string {
|
||||
const siteName = this.state.siteRes.site_view.site.name;
|
||||
const res = this.state.personRes;
|
||||
return res.state == "success"
|
||||
return res.state === "success"
|
||||
? `@${res.data.person_view.person.name} - ${siteName}`
|
||||
: siteName;
|
||||
}
|
||||
|
@ -324,6 +326,7 @@ export class Profile extends Component<
|
|||
<HtmlTags
|
||||
title={this.documentTitle}
|
||||
path={this.context.router.route.match.url}
|
||||
canonicalPath={personRes.person_view.person.actor_id}
|
||||
description={personRes.person_view.person.bio}
|
||||
image={personRes.person_view.person.avatar}
|
||||
/>
|
||||
|
@ -375,6 +378,7 @@ export class Profile extends Component<
|
|||
onSavePost={this.handleSavePost}
|
||||
onPurgePost={this.handlePurgePost}
|
||||
onFeaturePost={this.handleFeaturePost}
|
||||
onMarkPostAsRead={this.handleMarkPostAsRead}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
@ -495,7 +499,7 @@ export class Profile extends Component<
|
|||
classNames="ms-1"
|
||||
isBanned={isBanned(pv.person)}
|
||||
isDeleted={pv.person.deleted}
|
||||
isAdmin={pv.person.admin}
|
||||
isAdmin={isAdmin(pv.person.id, admins)}
|
||||
isBot={pv.person.bot_account}
|
||||
/>
|
||||
</li>
|
||||
|
@ -529,7 +533,7 @@ export class Profile extends Component<
|
|||
}
|
||||
onClick={linkEvent(
|
||||
pv.person.id,
|
||||
this.handleUnblockPerson
|
||||
this.handleUnblockPerson,
|
||||
)}
|
||||
>
|
||||
{I18NextService.i18n.t("unblock_user")}
|
||||
|
@ -541,7 +545,7 @@ export class Profile extends Component<
|
|||
}
|
||||
onClick={linkEvent(
|
||||
pv.person.id,
|
||||
this.handleBlockPerson
|
||||
this.handleBlockPerson,
|
||||
)}
|
||||
>
|
||||
{I18NextService.i18n.t("block_user")}
|
||||
|
@ -763,7 +767,7 @@ export class Profile extends Component<
|
|||
|
||||
const personRes = i.state.personRes;
|
||||
|
||||
if (personRes.state == "success") {
|
||||
if (personRes.state === "success") {
|
||||
const person = personRes.data.person_view.person;
|
||||
const ban = !person.banned;
|
||||
|
||||
|
@ -778,7 +782,6 @@ export class Profile extends Component<
|
|||
remove_data: removeData,
|
||||
reason: banReason,
|
||||
expires: futureDaysToUnixTime(banExpireDays),
|
||||
auth: myAuthRequired(),
|
||||
});
|
||||
// TODO
|
||||
this.updateBan(res);
|
||||
|
@ -790,10 +793,10 @@ export class Profile extends Component<
|
|||
const res = await HttpService.client.blockPerson({
|
||||
person_id: recipientId,
|
||||
block,
|
||||
auth: myAuthRequired(),
|
||||
});
|
||||
if (res.state == "success") {
|
||||
if (res.state === "success") {
|
||||
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);
|
||||
if (blockPersonRes.state === "success") {
|
||||
updatePersonBlock(blockPersonRes.data);
|
||||
this.setState({ personBlocked: blockPersonRes.data.blocked });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -841,7 +845,7 @@ export class Profile extends Component<
|
|||
|
||||
async handleEditComment(form: EditComment) {
|
||||
const editCommentRes = await HttpService.client.editComment(form);
|
||||
this.findAndUpdateComment(editCommentRes);
|
||||
this.findAndUpdateCommentEdit(editCommentRes);
|
||||
|
||||
return editCommentRes;
|
||||
}
|
||||
|
@ -923,7 +927,7 @@ export class Profile extends Component<
|
|||
async handleAddAdmin(form: AddAdmin) {
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
@ -943,6 +947,11 @@ export class Profile extends Component<
|
|||
await HttpService.client.markPersonMentionAsRead(form);
|
||||
}
|
||||
|
||||
async handleMarkPostAsRead(form: MarkPostAsRead) {
|
||||
const res = await HttpService.client.markPostAsRead(form);
|
||||
this.findAndUpdatePost(res);
|
||||
}
|
||||
|
||||
async handleBanFromCommunity(form: BanFromCommunity) {
|
||||
const banRes = await HttpService.client.banFromCommunity(form);
|
||||
this.updateBanFromCommunity(banRes);
|
||||
|
@ -957,17 +966,17 @@ export class Profile extends Component<
|
|||
// Maybe not necessary
|
||||
if (banRes.state === "success") {
|
||||
this.setState(s => {
|
||||
if (s.personRes.state == "success") {
|
||||
if (s.personRes.state === "success") {
|
||||
s.personRes.data.posts
|
||||
.filter(c => c.creator.id === banRes.data.person_view.person.id)
|
||||
.forEach(
|
||||
c => (c.creator_banned_from_community = banRes.data.banned)
|
||||
c => (c.creator_banned_from_community = banRes.data.banned),
|
||||
);
|
||||
|
||||
s.personRes.data.comments
|
||||
.filter(c => c.creator.id === banRes.data.person_view.person.id)
|
||||
.forEach(
|
||||
c => (c.creator_banned_from_community = banRes.data.banned)
|
||||
c => (c.creator_banned_from_community = banRes.data.banned),
|
||||
);
|
||||
}
|
||||
return s;
|
||||
|
@ -977,14 +986,14 @@ export class Profile extends Component<
|
|||
|
||||
updateBan(banRes: RequestState<BanPersonResponse>) {
|
||||
// Maybe not necessary
|
||||
if (banRes.state == "success") {
|
||||
if (banRes.state === "success") {
|
||||
this.setState(s => {
|
||||
if (s.personRes.state == "success") {
|
||||
if (s.personRes.state === "success") {
|
||||
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));
|
||||
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));
|
||||
s.personRes.data.person_view.person.banned = banRes.data.banned;
|
||||
}
|
||||
|
@ -994,18 +1003,18 @@ export class Profile extends Component<
|
|||
}
|
||||
|
||||
purgeItem(purgeRes: RequestState<PurgeItemResponse>) {
|
||||
if (purgeRes.state == "success") {
|
||||
if (purgeRes.state === "success") {
|
||||
toast(I18NextService.i18n.t("purge_success"));
|
||||
this.context.router.history.push(`/`);
|
||||
}
|
||||
}
|
||||
|
||||
findAndUpdateComment(res: RequestState<CommentResponse>) {
|
||||
findAndUpdateCommentEdit(res: RequestState<CommentResponse>) {
|
||||
this.setState(s => {
|
||||
if (s.personRes.state == "success" && res.state == "success") {
|
||||
if (s.personRes.state === "success" && res.state === "success") {
|
||||
s.personRes.data.comments = editComment(
|
||||
res.data.comment_view,
|
||||
s.personRes.data.comments
|
||||
s.personRes.data.comments,
|
||||
);
|
||||
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>) {
|
||||
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);
|
||||
// Set finished for the parent
|
||||
s.finished.set(
|
||||
getCommentParentId(res.data.comment_view.comment) ?? 0,
|
||||
true
|
||||
true,
|
||||
);
|
||||
}
|
||||
return s;
|
||||
|
@ -1029,10 +1050,10 @@ export class Profile extends Component<
|
|||
|
||||
findAndUpdateCommentReply(res: RequestState<CommentReplyResponse>) {
|
||||
this.setState(s => {
|
||||
if (s.personRes.state == "success" && res.state == "success") {
|
||||
if (s.personRes.state === "success" && res.state === "success") {
|
||||
s.personRes.data.comments = editWith(
|
||||
res.data.comment_reply_view,
|
||||
s.personRes.data.comments
|
||||
s.personRes.data.comments,
|
||||
);
|
||||
}
|
||||
return s;
|
||||
|
@ -1041,10 +1062,10 @@ export class Profile extends Component<
|
|||
|
||||
findAndUpdatePost(res: RequestState<PostResponse>) {
|
||||
this.setState(s => {
|
||||
if (s.personRes.state == "success" && res.state == "success") {
|
||||
if (s.personRes.state === "success" && res.state === "success") {
|
||||
s.personRes.data.posts = editPost(
|
||||
res.data.post_view,
|
||||
s.personRes.data.posts
|
||||
s.personRes.data.posts,
|
||||
);
|
||||
}
|
||||
return s;
|
||||
|
|
|
@ -1,8 +1,4 @@
|
|||
import {
|
||||
editRegistrationApplication,
|
||||
myAuthRequired,
|
||||
setIsoData,
|
||||
} from "@utils/app";
|
||||
import { editRegistrationApplication, myAuth, setIsoData } from "@utils/app";
|
||||
import { randomStr } from "@utils/helpers";
|
||||
import { RouteDataResponse } from "@utils/types";
|
||||
import classNames from "classnames";
|
||||
|
@ -16,12 +12,18 @@ import {
|
|||
import { fetchLimit } from "../../config";
|
||||
import { InitialFetchRequest } from "../../interfaces";
|
||||
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 { HtmlTags } from "../common/html-tags";
|
||||
import { Spinner } from "../common/icon";
|
||||
import { Paginator } from "../common/paginator";
|
||||
import { RegistrationApplication } from "../common/registration-application";
|
||||
import { UnreadCounterService } from "../../services";
|
||||
|
||||
enum UnreadOrAll {
|
||||
Unread,
|
||||
|
@ -46,7 +48,7 @@ export class RegistrationApplications extends Component<
|
|||
> {
|
||||
private isoData = setIsoData<RegistrationApplicationsData>(this.context);
|
||||
state: RegistrationApplicationsState = {
|
||||
appsRes: { state: "empty" },
|
||||
appsRes: EMPTY_REQUEST,
|
||||
siteRes: this.isoData.site_res,
|
||||
unreadOrAll: UnreadOrAll.Unread,
|
||||
page: 1,
|
||||
|
@ -80,7 +82,7 @@ export class RegistrationApplications extends Component<
|
|||
const mui = UserService.Instance.myUserInfo;
|
||||
return mui
|
||||
? `@${mui.local_user_view.person.name} ${I18NextService.i18n.t(
|
||||
"registration_applications"
|
||||
"registration_applications",
|
||||
)} - ${this.state.siteRes.site_view.site.name}`
|
||||
: "";
|
||||
}
|
||||
|
@ -110,6 +112,7 @@ export class RegistrationApplications extends Component<
|
|||
<Paginator
|
||||
page={this.state.page}
|
||||
onChange={this.handlePageChange}
|
||||
nextDisabled={fetchLimit > apps.length}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -204,46 +207,47 @@ export class RegistrationApplications extends Component<
|
|||
}
|
||||
|
||||
static async fetchInitialData({
|
||||
auth,
|
||||
client,
|
||||
}: InitialFetchRequest): Promise<RegistrationApplicationsData> {
|
||||
return {
|
||||
listRegistrationApplicationsResponse: auth
|
||||
listRegistrationApplicationsResponse: myAuth()
|
||||
? await client.listRegistrationApplications({
|
||||
unread_only: true,
|
||||
page: 1,
|
||||
limit: fetchLimit,
|
||||
auth: auth as string,
|
||||
})
|
||||
: { state: "empty" },
|
||||
: EMPTY_REQUEST,
|
||||
};
|
||||
}
|
||||
|
||||
async refetch() {
|
||||
const unread_only = this.state.unreadOrAll == UnreadOrAll.Unread;
|
||||
const unread_only = this.state.unreadOrAll === UnreadOrAll.Unread;
|
||||
this.setState({
|
||||
appsRes: { state: "loading" },
|
||||
appsRes: LOADING_REQUEST,
|
||||
});
|
||||
this.setState({
|
||||
appsRes: await HttpService.client.listRegistrationApplications({
|
||||
unread_only: unread_only,
|
||||
page: this.state.page,
|
||||
limit: fetchLimit,
|
||||
auth: myAuthRequired(),
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
async handleApproveApplication(form: ApproveRegistrationApplication) {
|
||||
const approveRes = await HttpService.client.approveRegistrationApplication(
|
||||
form
|
||||
form,
|
||||
);
|
||||
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(
|
||||
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;
|
||||
});
|
||||
|
|
|
@ -2,7 +2,6 @@ import {
|
|||
editCommentReport,
|
||||
editPostReport,
|
||||
editPrivateMessageReport,
|
||||
myAuthRequired,
|
||||
setIsoData,
|
||||
} from "@utils/app";
|
||||
import { randomStr } from "@utils/helpers";
|
||||
|
@ -36,13 +35,18 @@ import {
|
|||
I18NextService,
|
||||
UserService,
|
||||
} from "../../services";
|
||||
import { RequestState } from "../../services/HttpService";
|
||||
import {
|
||||
EMPTY_REQUEST,
|
||||
LOADING_REQUEST,
|
||||
RequestState,
|
||||
} from "../../services/HttpService";
|
||||
import { CommentReport } from "../comment/comment-report";
|
||||
import { HtmlTags } from "../common/html-tags";
|
||||
import { Spinner } from "../common/icon";
|
||||
import { Paginator } from "../common/paginator";
|
||||
import { PostReport } from "../post/post-report";
|
||||
import { PrivateMessageReport } from "../private_message/private-message-report";
|
||||
import { UnreadCounterService } from "../../services";
|
||||
|
||||
enum UnreadOrAll {
|
||||
Unread,
|
||||
|
@ -89,9 +93,9 @@ interface ReportsState {
|
|||
export class Reports extends Component<any, ReportsState> {
|
||||
private isoData = setIsoData<ReportsData>(this.context);
|
||||
state: ReportsState = {
|
||||
commentReportsRes: { state: "empty" },
|
||||
postReportsRes: { state: "empty" },
|
||||
messageReportsRes: { state: "empty" },
|
||||
commentReportsRes: EMPTY_REQUEST,
|
||||
postReportsRes: EMPTY_REQUEST,
|
||||
messageReportsRes: EMPTY_REQUEST,
|
||||
unreadOrAll: UnreadOrAll.Unread,
|
||||
messageType: MessageType.All,
|
||||
page: 1,
|
||||
|
@ -140,7 +144,7 @@ export class Reports extends Component<any, ReportsState> {
|
|||
const mui = UserService.Instance.myUserInfo;
|
||||
return mui
|
||||
? `@${mui.local_user_view.person.name} ${I18NextService.i18n.t(
|
||||
"reports"
|
||||
"reports",
|
||||
)} - ${this.state.siteRes.site_view.site.name}`
|
||||
: "";
|
||||
}
|
||||
|
@ -160,6 +164,16 @@ export class Reports extends Component<any, ReportsState> {
|
|||
<Paginator
|
||||
page={this.state.page}
|
||||
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>
|
||||
|
@ -352,25 +366,25 @@ export class Reports extends Component<any, ReportsState> {
|
|||
get buildCombined(): ItemType[] {
|
||||
const commentRes = this.state.commentReportsRes;
|
||||
const comments =
|
||||
commentRes.state == "success"
|
||||
commentRes.state === "success"
|
||||
? commentRes.data.comment_reports.map(this.commentReportToItemType)
|
||||
: [];
|
||||
|
||||
const postRes = this.state.postReportsRes;
|
||||
const posts =
|
||||
postRes.state == "success"
|
||||
postRes.state === "success"
|
||||
? postRes.data.post_reports.map(this.postReportToItemType)
|
||||
: [];
|
||||
const pmRes = this.state.messageReportsRes;
|
||||
const privateMessages =
|
||||
pmRes.state == "success"
|
||||
pmRes.state === "success"
|
||||
? pmRes.data.private_message_reports.map(
|
||||
this.privateMessageReportToItemType
|
||||
this.privateMessageReportToItemType,
|
||||
)
|
||||
: [];
|
||||
|
||||
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({
|
||||
auth,
|
||||
client,
|
||||
}: InitialFetchRequest): Promise<ReportsData> {
|
||||
const unresolved_only = true;
|
||||
|
@ -532,20 +545,18 @@ export class Reports extends Component<any, ReportsState> {
|
|||
unresolved_only,
|
||||
page,
|
||||
limit,
|
||||
auth: auth as string,
|
||||
};
|
||||
|
||||
const postReportsForm: ListPostReports = {
|
||||
unresolved_only,
|
||||
page,
|
||||
limit,
|
||||
auth: auth as string,
|
||||
};
|
||||
|
||||
const data: ReportsData = {
|
||||
commentReportsRes: await client.listCommentReports(commentReportsForm),
|
||||
postReportsRes: await client.listPostReports(postReportsForm),
|
||||
messageReportsRes: { state: "empty" },
|
||||
messageReportsRes: EMPTY_REQUEST,
|
||||
};
|
||||
|
||||
if (amAdmin()) {
|
||||
|
@ -553,11 +564,10 @@ export class Reports extends Component<any, ReportsState> {
|
|||
unresolved_only,
|
||||
page,
|
||||
limit,
|
||||
auth: auth as string,
|
||||
};
|
||||
|
||||
data.messageReportsRes = await client.listPrivateMessageReports(
|
||||
privateMessageReportsForm
|
||||
privateMessageReportsForm,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -565,15 +575,14 @@ export class Reports extends Component<any, ReportsState> {
|
|||
}
|
||||
|
||||
async refetch() {
|
||||
const unresolved_only = this.state.unreadOrAll == UnreadOrAll.Unread;
|
||||
const unresolved_only = this.state.unreadOrAll === UnreadOrAll.Unread;
|
||||
const page = this.state.page;
|
||||
const limit = fetchLimit;
|
||||
const auth = myAuthRequired();
|
||||
|
||||
this.setState({
|
||||
commentReportsRes: { state: "loading" },
|
||||
postReportsRes: { state: "loading" },
|
||||
messageReportsRes: { state: "loading" },
|
||||
commentReportsRes: LOADING_REQUEST,
|
||||
postReportsRes: LOADING_REQUEST,
|
||||
messageReportsRes: LOADING_REQUEST,
|
||||
});
|
||||
|
||||
const form:
|
||||
|
@ -583,7 +592,6 @@ export class Reports extends Component<any, ReportsState> {
|
|||
unresolved_only,
|
||||
page,
|
||||
limit,
|
||||
auth,
|
||||
};
|
||||
|
||||
this.setState({
|
||||
|
@ -594,7 +602,7 @@ export class Reports extends Component<any, ReportsState> {
|
|||
if (amAdmin()) {
|
||||
this.setState({
|
||||
messageReportsRes: await HttpService.client.listPrivateMessageReports(
|
||||
form
|
||||
form,
|
||||
),
|
||||
});
|
||||
}
|
||||
|
@ -603,24 +611,36 @@ export class Reports extends Component<any, ReportsState> {
|
|||
async handleResolveCommentReport(form: ResolveCommentReport) {
|
||||
const res = await HttpService.client.resolveCommentReport(form);
|
||||
this.findAndUpdateCommentReport(res);
|
||||
if (this.state.unreadOrAll === UnreadOrAll.Unread) {
|
||||
this.refetch();
|
||||
UnreadCounterService.Instance.updateReports();
|
||||
}
|
||||
}
|
||||
|
||||
async handleResolvePostReport(form: ResolvePostReport) {
|
||||
const res = await HttpService.client.resolvePostReport(form);
|
||||
this.findAndUpdatePostReport(res);
|
||||
if (this.state.unreadOrAll === UnreadOrAll.Unread) {
|
||||
this.refetch();
|
||||
UnreadCounterService.Instance.updateReports();
|
||||
}
|
||||
}
|
||||
|
||||
async handleResolvePrivateMessageReport(form: ResolvePrivateMessageReport) {
|
||||
const res = await HttpService.client.resolvePrivateMessageReport(form);
|
||||
this.findAndUpdatePrivateMessageReport(res);
|
||||
if (this.state.unreadOrAll === UnreadOrAll.Unread) {
|
||||
this.refetch();
|
||||
UnreadCounterService.Instance.updateReports();
|
||||
}
|
||||
}
|
||||
|
||||
findAndUpdateCommentReport(res: RequestState<CommentReportResponse>) {
|
||||
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(
|
||||
res.data.comment_report_view,
|
||||
s.commentReportsRes.data.comment_reports
|
||||
s.commentReportsRes.data.comment_reports,
|
||||
);
|
||||
}
|
||||
return s;
|
||||
|
@ -629,10 +649,10 @@ export class Reports extends Component<any, ReportsState> {
|
|||
|
||||
findAndUpdatePostReport(res: RequestState<PostReportResponse>) {
|
||||
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(
|
||||
res.data.post_report_view,
|
||||
s.postReportsRes.data.post_reports
|
||||
s.postReportsRes.data.post_reports,
|
||||
);
|
||||
}
|
||||
return s;
|
||||
|
@ -640,14 +660,14 @@ export class Reports extends Component<any, ReportsState> {
|
|||
}
|
||||
|
||||
findAndUpdatePrivateMessageReport(
|
||||
res: RequestState<PrivateMessageReportResponse>
|
||||
res: RequestState<PrivateMessageReportResponse>,
|
||||
) {
|
||||
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 =
|
||||
editPrivateMessageReport(
|
||||
res.data.private_message_report_view,
|
||||
s.messageReportsRes.data.private_message_reports
|
||||
s.messageReportsRes.data.private_message_reports,
|
||||
);
|
||||
}
|
||||
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 { GetSiteResponse, VerifyEmailResponse } from "lemmy-js-client";
|
||||
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 { HtmlTags } from "../common/html-tags";
|
||||
import { Spinner } from "../common/icon";
|
||||
|
@ -16,7 +21,7 @@ export class VerifyEmail extends Component<any, State> {
|
|||
private isoData = setIsoData(this.context);
|
||||
|
||||
state: State = {
|
||||
verifyRes: { state: "empty" },
|
||||
verifyRes: EMPTY_REQUEST,
|
||||
siteRes: this.isoData.site_res,
|
||||
};
|
||||
|
||||
|
@ -26,7 +31,7 @@ export class VerifyEmail extends Component<any, State> {
|
|||
|
||||
async verify() {
|
||||
this.setState({
|
||||
verifyRes: { state: "loading" },
|
||||
verifyRes: LOADING_REQUEST,
|
||||
});
|
||||
|
||||
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"));
|
||||
this.props.history.push("/login");
|
||||
}
|
||||
|
@ -61,7 +66,7 @@ export class VerifyEmail extends Component<any, State> {
|
|||
<div className="row">
|
||||
<div className="col-12 col-lg-6 offset-lg-3 mb-4">
|
||||
<h1 className="h4 mb-4">{I18NextService.i18n.t("verify_email")}</h1>
|
||||
{this.state.verifyRes.state == "loading" && (
|
||||
{this.state.verifyRes.state === "loading" && (
|
||||
<h5>
|
||||
<Spinner large />
|
||||
</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 type { QueryParams } from "@utils/types";
|
||||
import { Choice, RouteDataResponse } from "@utils/types";
|
||||
|
@ -14,6 +14,7 @@ import {
|
|||
import { InitialFetchRequest, PostFormParams } from "../../interfaces";
|
||||
import { FirstLoadService, I18NextService } from "../../services";
|
||||
import {
|
||||
EMPTY_REQUEST,
|
||||
HttpService,
|
||||
RequestState,
|
||||
WrappedLemmyHttp,
|
||||
|
@ -57,7 +58,7 @@ export class CreatePost extends Component<
|
|||
state: CreatePostState = {
|
||||
siteRes: this.isoData.site_res,
|
||||
loading: true,
|
||||
initialCommunitiesRes: { state: "empty" },
|
||||
initialCommunitiesRes: EMPTY_REQUEST,
|
||||
isIsomorphic: false,
|
||||
};
|
||||
|
||||
|
@ -96,12 +97,10 @@ export class CreatePost extends Component<
|
|||
|
||||
async fetchCommunity() {
|
||||
const { communityId } = getCreatePostQueryParams();
|
||||
const auth = myAuth();
|
||||
|
||||
if (communityId) {
|
||||
const res = await HttpService.client.getCommunity({
|
||||
id: communityId,
|
||||
auth,
|
||||
});
|
||||
if (res.state === "success") {
|
||||
this.setState({
|
||||
|
@ -121,7 +120,7 @@ export class CreatePost extends Component<
|
|||
const { communityId } = getCreatePostQueryParams();
|
||||
|
||||
const initialCommunitiesRes = await fetchCommunitiesForOptions(
|
||||
HttpService.client
|
||||
HttpService.client,
|
||||
);
|
||||
|
||||
this.setState({
|
||||
|
@ -239,18 +238,16 @@ export class CreatePost extends Component<
|
|||
static async fetchInitialData({
|
||||
client,
|
||||
query: { communityId },
|
||||
auth,
|
||||
}: InitialFetchRequest<
|
||||
QueryParams<CreatePostProps>
|
||||
>): Promise<CreatePostData> {
|
||||
const data: CreatePostData = {
|
||||
initialCommunitiesRes: await fetchCommunitiesForOptions(client),
|
||||
communityResponse: { state: "empty" },
|
||||
communityResponse: EMPTY_REQUEST,
|
||||
};
|
||||
|
||||
if (communityId) {
|
||||
const form: GetCommunity = {
|
||||
auth,
|
||||
id: getIdFromString(communityId),
|
||||
};
|
||||
|
||||
|
|
|
@ -1,9 +1,4 @@
|
|||
import {
|
||||
communityToChoice,
|
||||
fetchCommunities,
|
||||
myAuth,
|
||||
myAuthRequired,
|
||||
} from "@utils/app";
|
||||
import { communityToChoice, fetchCommunities } from "@utils/app";
|
||||
import {
|
||||
capitalizeFirstLetter,
|
||||
debounce,
|
||||
|
@ -15,6 +10,7 @@ import { isImage } from "@utils/media";
|
|||
import { Choice } from "@utils/types";
|
||||
import autosize from "autosize";
|
||||
import { Component, InfernoNode, linkEvent } from "inferno";
|
||||
import { Prompt } from "inferno-router";
|
||||
import {
|
||||
CommunityView,
|
||||
CreatePost,
|
||||
|
@ -33,13 +29,17 @@ import {
|
|||
} from "../../config";
|
||||
import { PostFormParams } from "../../interfaces";
|
||||
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 { toast } from "../../toast";
|
||||
import { Icon, Spinner } from "../common/icon";
|
||||
import { LanguageSelect } from "../common/language-select";
|
||||
import { MarkdownTextArea } from "../common/markdown-textarea";
|
||||
import NavigationPrompt from "../common/navigation-prompt";
|
||||
import { SearchableSelect } from "../common/searchable-select";
|
||||
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({ loading: true, submitted: true });
|
||||
const auth = myAuthRequired();
|
||||
|
||||
const pForm = i.state.form;
|
||||
const pv = i.props.post_view;
|
||||
|
@ -102,7 +101,6 @@ function handlePostSubmit(i: PostForm, event: any) {
|
|||
nsfw: pForm.nsfw,
|
||||
post_id: pv.post.id,
|
||||
language_id: pForm.language_id,
|
||||
auth,
|
||||
});
|
||||
} else if (pForm.name && pForm.community_id) {
|
||||
i.props.onCreate?.({
|
||||
|
@ -113,7 +111,6 @@ function handlePostSubmit(i: PostForm, event: any) {
|
|||
nsfw: pForm.nsfw,
|
||||
language_id: pForm.language_id,
|
||||
honeypot: pForm.honeypot,
|
||||
auth,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -122,9 +119,9 @@ function copySuggestedTitle(d: { i: PostForm; suggestedTitle?: string }) {
|
|||
const sTitle = d.suggestedTitle;
|
||||
if (sTitle) {
|
||||
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(() => {
|
||||
const textarea: any = document.getElementById("post-title");
|
||||
autosize.update(textarea);
|
||||
|
@ -223,8 +220,8 @@ function handleImageDelete(i: PostForm) {
|
|||
|
||||
export class PostForm extends Component<PostFormProps, PostFormState> {
|
||||
state: PostFormState = {
|
||||
suggestedPostsRes: { state: "empty" },
|
||||
metadataRes: { state: "empty" },
|
||||
suggestedPostsRes: EMPTY_REQUEST,
|
||||
metadataRes: EMPTY_REQUEST,
|
||||
form: {},
|
||||
loading: false,
|
||||
imageLoading: false,
|
||||
|
@ -271,9 +268,9 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
|||
({ community: { id, title } }) => ({
|
||||
label: title,
|
||||
value: id.toString(),
|
||||
})
|
||||
}),
|
||||
) ?? []
|
||||
).filter(option => option.value !== selectedCommunityChoice.value)
|
||||
).filter(option => option.value !== selectedCommunityChoice.value),
|
||||
),
|
||||
};
|
||||
} else {
|
||||
|
@ -284,7 +281,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
|||
({ community: { id, title } }) => ({
|
||||
label: title,
|
||||
value: id.toString(),
|
||||
})
|
||||
}),
|
||||
) ?? [],
|
||||
};
|
||||
}
|
||||
|
@ -310,16 +307,16 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
|||
}
|
||||
|
||||
componentWillReceiveProps(
|
||||
nextProps: Readonly<{ children?: InfernoNode } & PostFormProps>
|
||||
nextProps: Readonly<{ children?: InfernoNode } & PostFormProps>,
|
||||
): void {
|
||||
if (this.props != nextProps) {
|
||||
if (this.props !== nextProps) {
|
||||
this.setState(
|
||||
s => (
|
||||
(s.form.community_id = getIdFromString(
|
||||
nextProps.selectedCommunityChoice?.value
|
||||
nextProps.selectedCommunityChoice?.value,
|
||||
)),
|
||||
s
|
||||
)
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -332,7 +329,8 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
|||
|
||||
return (
|
||||
<form className="post-form" onSubmit={linkEvent(this, handlePostSubmit)}>
|
||||
<NavigationPrompt
|
||||
<Prompt
|
||||
message={I18NextService.i18n.t("block_leaving")}
|
||||
when={
|
||||
!!(
|
||||
this.state.form.name ||
|
||||
|
@ -341,6 +339,32 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
|||
) && !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">
|
||||
<label className="col-sm-2 col-form-label" htmlFor="post-url">
|
||||
{I18NextService.i18n.t("url")}
|
||||
|
@ -366,7 +390,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
|||
</a>
|
||||
<a
|
||||
href={`${ghostArchiveUrl}/search?term=${encodeURIComponent(
|
||||
url
|
||||
url,
|
||||
)}`}
|
||||
className="me-2 d-inline-block float-right text-muted small fw-bold"
|
||||
rel={relTags}
|
||||
|
@ -375,7 +399,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
|||
</a>
|
||||
<a
|
||||
href={`${archiveTodayUrl}/?run=1&url=${encodeURIComponent(
|
||||
url
|
||||
url,
|
||||
)}`}
|
||||
className="me-2 d-inline-block float-right text-muted small fw-bold"
|
||||
rel={relTags}
|
||||
|
@ -446,37 +470,12 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
|||
onAddModToCommunity={() => {}}
|
||||
onAddAdmin={() => {}}
|
||||
onTransferCommunity={() => {}}
|
||||
onMarkPostAsRead={() => {}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</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">
|
||||
<label className="col-sm-2 col-form-label">
|
||||
{I18NextService.i18n.t("body")}
|
||||
|
@ -580,8 +579,10 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
|||
case "loading":
|
||||
return <Spinner />;
|
||||
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 (
|
||||
suggestedTitle && (
|
||||
<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"
|
||||
onClick={linkEvent(
|
||||
{ i: this, suggestedTitle },
|
||||
copySuggestedTitle
|
||||
copySuggestedTitle,
|
||||
)}
|
||||
>
|
||||
{I18NextService.i18n.t("copy_suggested_title", { title: "" })}{" "}
|
||||
|
@ -640,6 +641,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
|||
onAddModToCommunity={() => {}}
|
||||
onAddAdmin={() => {}}
|
||||
onTransferCommunity={() => {}}
|
||||
onMarkPostAsRead={() => {}}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
|
@ -651,7 +653,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
|||
async fetchPageTitle() {
|
||||
const url = this.state.form.url;
|
||||
if (url && validURL(url)) {
|
||||
this.setState({ metadataRes: { state: "loading" } });
|
||||
this.setState({ metadataRes: LOADING_REQUEST });
|
||||
this.setState({
|
||||
metadataRes: await HttpService.client.getSiteMetadata({ url }),
|
||||
});
|
||||
|
@ -661,7 +663,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
|||
async fetchSimilarPosts() {
|
||||
const q = this.state.form.name;
|
||||
if (q && q !== "") {
|
||||
this.setState({ suggestedPostsRes: { state: "loading" } });
|
||||
this.setState({ suggestedPostsRes: LOADING_REQUEST });
|
||||
this.setState({
|
||||
suggestedPostsRes: await HttpService.client.search({
|
||||
q,
|
||||
|
@ -671,7 +673,6 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
|||
community_id: this.state.form.community_id,
|
||||
page: 1,
|
||||
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