diff --git a/.eslintrc.json b/.eslintrc.json index ad777401..ea26ba3b 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -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, diff --git a/.prettierignore b/.prettierignore index c6145fd8..1640ee12 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,5 +1,6 @@ src/shared/translations lemmy-translations src/assets/css/themes/*.css +src/assets/css/code-themes/*.css stats.json dist diff --git a/.woodpecker.yml b/.woodpecker.yml index a55d39ab..53949585 100644 --- a/.woodpecker.yml +++ b/.woodpecker.yml @@ -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 diff --git a/Dockerfile b/Dockerfile index 92b3f7e6..671ab1e3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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 diff --git a/dev.dockerfile b/dev.dockerfile index 881d9bc3..42d12400 100644 --- a/dev.dockerfile +++ b/dev.dockerfile @@ -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 diff --git a/generate_translations.js b/generate_translations.js index ac3e9875..f9cde87f 100644 --- a/generate_translations.js +++ b/generate_translations.js @@ -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 =${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); export interface TFunctionTyped { diff --git a/lemmy-translations b/lemmy-translations index a241fe12..6fbc8693 160000 --- a/lemmy-translations +++ b/lemmy-translations @@ -1 +1 @@ -Subproject commit a241fe1255a6363c7ae1ec5a09520c066745e6ce +Subproject commit 6fbc86932a03c4d40829ee4a3395259b2a7660e5 diff --git a/package.json b/package.json index 1222f5ba..0ec4c20a 100644 --- a/package.json +++ b/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": { diff --git a/src/assets/css/code-themes/atom-one-dark.css b/src/assets/css/code-themes/atom-one-dark.css new file mode 100644 index 00000000..5344ee38 --- /dev/null +++ b/src/assets/css/code-themes/atom-one-dark.css @@ -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} \ No newline at end of file diff --git a/src/assets/css/code-themes/atom-one-light.css b/src/assets/css/code-themes/atom-one-light.css new file mode 100644 index 00000000..df0268a9 --- /dev/null +++ b/src/assets/css/code-themes/atom-one-light.css @@ -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} \ No newline at end of file diff --git a/src/assets/css/main.css b/src/assets/css/main.css index 36566b9b..bad10ec5 100644 --- a/src/assets/css/main.css +++ b/src/assets/css/main.css @@ -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; +} diff --git a/src/assets/css/themes/_variables.darkly-pureblack.scss b/src/assets/css/themes/_variables.darkly-pureblack.scss index 816118ef..1028706c 100644 --- a/src/assets/css/themes/_variables.darkly-pureblack.scss +++ b/src/assets/css/themes/_variables.darkly-pureblack.scss @@ -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; diff --git a/src/assets/css/themes/_variables.darkly-red.scss b/src/assets/css/themes/_variables.darkly-red.scss index 691678a7..29c959fa 100644 --- a/src/assets/css/themes/_variables.darkly-red.scss +++ b/src/assets/css/themes/_variables.darkly-red.scss @@ -1,7 +1,6 @@ @import "variables.darkly"; $primary: $blue; -$secondary: #444; $light: $gray-800; $link-color: $red; diff --git a/src/assets/css/themes/_variables.darkly.scss b/src/assets/css/themes/_variables.darkly.scss index e0762a95..1fec2297 100644 --- a/src/assets/css/themes/_variables.darkly.scss +++ b/src/assets/css/themes/_variables.darkly.scss @@ -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; diff --git a/src/assets/css/themes/_variables.i386.scss b/src/assets/css/themes/_variables.i386.scss index 3273a63b..750429e2 100644 --- a/src/assets/css/themes/_variables.i386.scss +++ b/src/assets/css/themes/_variables.i386.scss @@ -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; diff --git a/src/assets/css/themes/_variables.vaporwave-dark.scss b/src/assets/css/themes/_variables.vaporwave-dark.scss index 09c8eb35..ca737262 100644 --- a/src/assets/css/themes/_variables.vaporwave-dark.scss +++ b/src/assets/css/themes/_variables.vaporwave-dark.scss @@ -9,6 +9,7 @@ $gray-800: #303030; $gray-900: #222; $light: $gray-700; +$dark: $gray-200; $body-bg: $gray-900; $body-color: $gray-200; diff --git a/src/assets/css/themes/darkly-compact.css b/src/assets/css/themes/darkly-compact.css index 5061c106..02f7ee7a 100644 --- a/src/assets/css/themes/darkly-compact.css +++ b/src/assets/css/themes/darkly-compact.css @@ -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 { diff --git a/src/assets/css/themes/darkly-pureblack.css b/src/assets/css/themes/darkly-pureblack.css index 3fe1e912..f532a285 100644 --- a/src/assets/css/themes/darkly-pureblack.css +++ b/src/assets/css/themes/darkly-pureblack.css @@ -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 { diff --git a/src/assets/css/themes/darkly-red.css b/src/assets/css/themes/darkly-red.css index 0527b429..20349d12 100644 --- a/src/assets/css/themes/darkly-red.css +++ b/src/assets/css/themes/darkly-red.css @@ -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 { diff --git a/src/assets/css/themes/darkly.css b/src/assets/css/themes/darkly.css index 8baaf71c..9450e54a 100644 --- a/src/assets/css/themes/darkly.css +++ b/src/assets/css/themes/darkly.css @@ -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 { diff --git a/src/assets/css/themes/i386.css b/src/assets/css/themes/i386.css index 51de7d67..6c0084a2 100644 --- a/src/assets/css/themes/i386.css +++ b/src/assets/css/themes/i386.css @@ -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 { diff --git a/src/assets/css/themes/vaporwave-dark.css b/src/assets/css/themes/vaporwave-dark.css index c7f13e19..61a75914 100644 --- a/src/assets/css/themes/vaporwave-dark.css +++ b/src/assets/css/themes/vaporwave-dark.css @@ -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 { diff --git a/src/assets/css/themes/vaporwave-dark.scss b/src/assets/css/themes/vaporwave-dark.scss index c18e8a0c..cd947ad0 100644 --- a/src/assets/css/themes/vaporwave-dark.scss +++ b/src/assets/css/themes/vaporwave-dark.scss @@ -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 { diff --git a/src/assets/css/themes/vaporwave-light.css b/src/assets/css/themes/vaporwave-light.css index 14a19d72..cb03d8ef 100644 --- a/src/assets/css/themes/vaporwave-light.css +++ b/src/assets/css/themes/vaporwave-light.css @@ -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 { diff --git a/src/assets/icons/favicon.svg b/src/assets/icons/favicon.svg index 82545965..791a54ab 100644 --- a/src/assets/icons/favicon.svg +++ b/src/assets/icons/favicon.svg @@ -1,118 +1,27 @@ - - - - - - - - - image/svg+xml - - - - - - + version="1.1"> + - + d="m 167.03908,270.78735 c -0.94784,-0.002 -1.8939,0.004 -2.83789,0.0215 -4.31538,0.0778 -8.58934,0.3593 -12.8125,0.8457 -33.78522,3.89116 -64.215716,21.86394 -82.871086,53.27344 -18.27982,30.77718 -22.77749,64.66635 -13.46094,96.06837 9.31655,31.40203 31.88488,59.93174 65.296886,82.5332 0.20163,0.13618 0.40678,0.26709 0.61523,0.39258 28.65434,17.27768 57.18167,28.93179 87.74218,34.95508 -0.74566,12.61339 -0.72532,25.5717 0.082,38.84375 2.43989,40.10943 16.60718,77.03742 38.0957,109.67187 l -77.00781,31.4375 c -8.30605,3.25932 -12.34178,12.68234 -8.96967,20.94324 3.37211,8.2609 12.84919,12.16798 21.06342,8.68371 l 84.69727,-34.57617 c 15.70675,18.72702 33.75346,35.68305 53.12109,50.57032 0.74013,0.56891 1.4904,1.12236 2.23437,1.68554 l -49.61132,65.69141 c -5.45446,7.0474 -4.10058,17.19288 3.01098,22.5634 7.11156,5.37052 17.24028,3.89649 22.52612,-3.27824 l 50.38672,-66.71876 c 27.68572,17.53469 57.07524,31.20388 86.07227,40.25196 14.88153,27.28008 43.96965,44.64648 77.58789,44.64648 33.93762,0 63.04252,-18.68693 77.80082,-45.4375 28.7072,-9.21295 57.7527,-22.93196 85.1484,-40.40234 l 51.0977,67.66016 c 5.2858,7.17473 15.4145,8.64876 22.5261,3.27824 7.1115,-5.37052 8.4654,-15.516 3.011,-22.5634 l -50.3614,-66.68555 c 0.334,-0.25394 0.6727,-0.50077 1.0059,-0.75586 19.1376,-14.64919 37.0259,-31.28581 52.7031,-49.63476 l 82.5625,33.70507 c 8.2143,3.48427 17.6913,-0.42281 21.0634,-8.68371 3.3722,-8.2609 -0.6636,-17.68392 -8.9696,-20.94324 l -74.5391,-30.42773 c 22.1722,-32.82971 37.0383,-70.03397 40.1426,-110.46094 1.0253,-13.35251 1.2292,-26.42535 0.6387,-39.17578 30.3557,-6.05408 58.7164,-17.66833 87.2011,-34.84375 0.2085,-0.12549 0.4136,-0.2564 0.6153,-0.39258 33.412,-22.60147 55.9803,-51.13117 65.2968,-82.5332 9.3166,-31.40202 4.8189,-65.29118 -13.4609,-96.06837 -18.6553,-31.40951 -49.0859,-49.38228 -82.8711,-53.27344 -4.2231,-0.4864 -8.4971,-0.76791 -12.8125,-0.8457 -30.2077,-0.54448 -62.4407,8.82427 -93.4316,26.71484 -22.7976,13.16063 -43.3521,33.31423 -59.4375,55.30469 -44.9968,-25.75094 -103.5444,-40.25065 -175.4785,-41.43945 -6.4522,-0.10663 -13.0125,-0.10696 -19.67974,0.002 -80.18875,1.30929 -144.38284,16.5086 -192.87109,43.9922 -0.11914,-0.19111 -0.24287,-0.37932 -0.37109,-0.56446 -16.29,-22.764 -37.41085,-43.73706 -60.89649,-57.29493 -30.02247,-17.33149 -61.21051,-26.66489 -90.59375,-26.73633 z" /> + d="m 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" /> + + d="m 610.4991,644.28932 c 0,23.11198 18.70595,41.84795 41.78091,41.84795 23.07495,0 41.7809,-18.73597 41.7809,-41.84795 0,-23.112 -18.70594,-41.84796 -41.7809,-41.84796 -23.07496,0 -41.78091,18.73596 -41.78091,41.84796 z m -280.56002,0 c 0,23.32492 18.87829,42.23352 42.16586,42.23352 23.28755,0 42.16585,-18.9086 42.16585,-42.23352 0,-23.32494 -18.87829,-42.23353 -42.16585,-42.23353 -23.28757,0 -42.16586,18.90859 -42.16586,42.23353 z" /> + d="m 339.72919,769.2467 -54.54422,72.22481 m 399.08582,-72.22481 54.54423,72.22481 M 263.68341,697.82002 175.92752,733.64353 m 579.85765,-35.82351 87.7559,35.82351" /> + d="m 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" /> diff --git a/src/assets/symbols.svg b/src/assets/symbols.svg index 72214eaf..dba5dbe6 100644 --- a/src/assets/symbols.svg +++ b/src/assets/symbols.svg @@ -142,8 +142,8 @@ - - + + @@ -258,5 +258,12 @@ + + + + + + + diff --git a/src/client/index.tsx b/src/client/index.tsx index 2bdd948f..ffe52ce8 100644 --- a/src/client/index.tsx +++ b/src/client/index.tsx @@ -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 = ( - + - + ); const root = document.getElementById("root"); diff --git a/src/server/handlers/catch-all-handler.tsx b/src/server/handlers/catch-all-handler.tsx index 4b011045..6e93a1ae 100644 --- a/src/server/handlers/catch-all-handler.tsx +++ b/src/server/handlers/catch-all-handler.tsx @@ -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", ); } }; diff --git a/src/server/handlers/code-theme-handler.ts b/src/server/handlers/code-theme-handler.ts new file mode 100644 index 00000000..37c9eda6 --- /dev/null +++ b/src/server/handlers/code-theme-handler.ts @@ -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"), + ); + } + } +}; diff --git a/src/server/handlers/manifest-handler.ts b/src/server/handlers/manifest-handler.ts index c1756f75..dfc1ad8e 100644 --- a/src/server/handlers/manifest-handler.ts +++ b/src/server/handlers/manifest-handler.ts @@ -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); diff --git a/src/server/handlers/robots-handler.ts b/src/server/handlers/robots-handler.ts index 80678aa0..f26ddc92 100644 --- a/src/server/handlers/robots-handler.ts +++ b/src/server/handlers/robots-handler.ts @@ -15,5 +15,6 @@ export default async ({ res }: { res: Response }) => { Disallow: /admin Disallow: /password_change Disallow: /search/ + Disallow: /modlog `); }; diff --git a/src/server/handlers/security-handler.ts b/src/server/handlers/security-handler.ts index 0aed0cdc..af0ebc59 100644 --- a/src/server/handlers/security-handler.ts +++ b/src/server/handlers/security-handler.ts @@ -12,6 +12,6 @@ export default async ({ res }: { res: Response }) => { process.env.LEMMY_UI_LEMMY_EXTERNAL_HOST + ` Expires: 2024-01-01T04:59:00.000Z - ` + `, ); }; diff --git a/src/server/handlers/service-worker-handler.ts b/src/server/handlers/service-worker-handler.ts index 15c6b3fb..d801e22c 100644 --- a/src/server/handlers/service-worker-handler.ts +++ b/src/server/handlers/service-worker-handler.ts @@ -8,7 +8,7 @@ export default async ({ res }: { res: Response }) => { path.resolve( `./dist/service-worker${ process.env.NODE_ENV === "development" ? "-development" : "" - }.js` - ) + }.js`, + ), ); }; diff --git a/src/server/index.tsx b/src/server/index.tsx index 458d7f03..f4216970 100644 --- a/src/server/index.tsx +++ b/src/server/index.tsx @@ -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); diff --git a/src/server/middleware.ts b/src/server/middleware.ts index 0420e47e..e10f5c41 100644 --- a/src/server/middleware.ts +++ b/src/server/middleware.ts @@ -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"; } } diff --git a/src/server/utils/create-ssr-html.tsx b/src/server/utils/create-ssr-html.tsx index ba85228f..0958588d 100644 --- a/src/server/utils/create-ssr-html.tsx +++ b/src/server/utils/create-ssr-html.tsx @@ -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(" - - - + + + , ) : ""; const helmet = Helmet.renderStatic(); - const config: ILemmyConfig = { wsHost: process.env.LEMMY_UI_LEMMY_WS_HOST }; - return ` - - + ${erudaStr} - ${customHtmlHeader} + ${customHtmlHeaderWithNonce} ${helmet.title.toString()} ${helmet.meta.toString()} diff --git a/src/server/utils/generate-manifest-json.ts b/src/server/utils/generate-manifest-json.ts index 2f9d8b80..dd56bc3c 100644 --- a/src/server/utils/generate-manifest-json.ts +++ b/src/server/utils/generate-manifest-json.ts @@ -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: [ { diff --git a/src/shared/components/app/app.tsx b/src/shared/components/app/app.tsx index 2000bec2..c573ea41 100644 --- a/src/shared/components/app/app.tsx +++ b/src/shared/components/app/app.tsx @@ -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 { {I18NextService.i18n.t("jump_to_content", "Jump to content")} {siteView && ( - + <> + + + )}
@@ -75,9 +80,13 @@ export class App extends Component {
{RouteComponent && (isAuthPath(path ?? "") ? ( - + + ) : isAnonymousPath(path ?? "") ? ( + + + ) : ( ))} @@ -86,7 +95,7 @@ export class App extends Component { ); }} /> - ) + ), )} diff --git a/src/shared/components/app/code-theme.tsx b/src/shared/components/app/code-theme.tsx new file mode 100644 index 00000000..492fbefc --- /dev/null +++ b/src/shared/components/app/code-theme.tsx @@ -0,0 +1,22 @@ +import { Component } from "inferno"; + +export class CodeTheme extends Component { + render() { + return ( + <> + + + + ); + } +} diff --git a/src/shared/components/app/error-page.tsx b/src/shared/components/app/error-page.tsx index e083276e..a0b8d535 100644 --- a/src/shared/components/app/error-page.tsx +++ b/src/shared/components/app/error-page.tsx @@ -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 { )} {errorPageData?.error && ( - + ### )} diff --git a/src/shared/components/app/navbar.tsx b/src/shared/components/app/navbar.tsx index 5c017189..900a12ec 100644 --- a/src/shared/components/app/navbar.tsx +++ b/src/shared/components/app/navbar.tsx @@ -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; - unreadReportCountRes: RequestState; - unreadApplicationCountRes: RequestState; 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 { - state: NavbarState = { - unreadInboxCountRes: { state: "empty" }, - unreadReportCountRes: { state: "empty" }, - unreadApplicationCountRes: { state: "empty" }, - }; collapseButtonRef = createRef(); mobileMenuRef = createRef(); + 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 { 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 { 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 { 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)} > - {this.unreadInboxCount > 0 && ( + {this.state.unreadInboxCount > 0 && ( - {numToSI(this.unreadInboxCount)} + {numToSI(this.state.unreadInboxCount)} )} - {this.moderatesSomething && ( + {UserService.Instance.moderatesSomething && (
  • - {this.unreadReportCount > 0 && ( + {this.state.unreadReportCount > 0 && ( - {numToSI(this.unreadReportCount)} + {numToSI(this.state.unreadReportCount)} )} @@ -144,16 +161,18 @@ export class Navbar extends Component { 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)} > - {this.unreadApplicationCount > 0 && ( + {this.state.unreadApplicationCount > 0 && ( - {numToSI(this.unreadApplicationCount)} + {numToSI(this.state.unreadApplicationCount)} )} @@ -268,46 +287,48 @@ export class Navbar extends Component { 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)} > {I18NextService.i18n.t("unread_messages", { - count: Number(this.unreadInboxCount), - formattedCount: numToSI(this.unreadInboxCount), + count: Number(this.state.unreadInboxCount), + formattedCount: numToSI(this.state.unreadInboxCount), })} - {this.unreadInboxCount > 0 && ( + {this.state.unreadInboxCount > 0 && ( - {numToSI(this.unreadInboxCount)} + {numToSI(this.state.unreadInboxCount)} )}
  • - {this.moderatesSomething && ( + {UserService.Instance.moderatesSomething && (
  • +
  • { mark: this.isCommentNew || this.commentView.comment.distinguished, })} > -
    +
    ) : ( @@ -669,7 +658,7 @@ export class CommentNode extends Component { 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 { 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 { ) : ( @@ -889,7 +878,7 @@ export class CommentNode extends Component { 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 { 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 { )}
    {/* end of button group */} -
    + )}
    @@ -941,7 +930,7 @@ export class CommentNode extends Component { {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 { )} {this.state.showReportDialog && ( -
    - - - -
    + )} {this.state.showBanDialog && (
    @@ -1116,7 +1079,7 @@ export class CommentNode extends Component { 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 { 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 { 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 { 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 { } 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 { i.props.onSaveComment({ comment_id: i.commentView.comment.id, save: !i.commentView.saved, - auth: myAuthRequired(), }); } @@ -1423,7 +1381,6 @@ export class CommentNode extends Component { i.props.onBlockPerson({ person_id: i.commentView.creator.id, block: true, - auth: myAuthRequired(), }); } @@ -1434,13 +1391,11 @@ export class CommentNode extends Component { 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 { i.props.onDeleteComment({ comment_id: i.commentId, deleted: !i.commentView.comment.deleted, - auth: myAuthRequired(), }); } @@ -1460,7 +1414,6 @@ export class CommentNode extends Component { 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 { i.props.onDistinguishComment({ comment_id: i.commentId, distinguished: !i.commentView.comment.distinguished, - auth: myAuthRequired(), }); } @@ -1483,7 +1435,6 @@ export class CommentNode extends Component { 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 { 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 { community_id: i.commentView.community.id, person_id: i.commentView.creator.id, added, - auth: myAuthRequired(), }); } @@ -1527,7 +1476,6 @@ export class CommentNode extends Component { i.props.onAddAdmin({ person_id: i.commentView.creator.id, added, - auth: myAuthRequired(), }); } @@ -1536,17 +1484,17 @@ export class CommentNode extends Component { 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 { 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 { limit: 999, // TODO type_: "All", saved_only: false, - auth: myAuth(), }); } } diff --git a/src/shared/components/comment/comment-nodes.tsx b/src/shared/components/comment/comment-nodes.tsx index 495b6bbc..2e60309e 100644 --- a/src/shared/components/comment/comment-nodes.tsx +++ b/src/shared/components/comment/comment-nodes.tsx @@ -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 { this.props.nodes.length > 0 && (
      1, "border-top border-light": !this.props.noBorder, })} style={ @@ -100,7 +100,7 @@ export class CommentNodes extends Component { 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} diff --git a/src/shared/components/comment/comment-report.tsx b/src/shared/components/comment/comment-report.tsx index 3b328f67..d5d0bba7 100644 --- a/src/shared/components/comment/comment-report.tsx +++ b/src/shared/components/comment/comment-report.tsx @@ -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)} />
      {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(), }); } } diff --git a/src/shared/components/common/anonymous-guard.tsx b/src/shared/components/common/anonymous-guard.tsx new file mode 100644 index 00000000..70a1f7aa --- /dev/null +++ b/src/shared/components/common/anonymous-guard.tsx @@ -0,0 +1,31 @@ +import { Component } from "inferno"; +import { UserService } from "../../services"; +import { Spinner } from "./icon"; + +interface AnonymousGuardState { + hasRedirected: boolean; +} + +class AnonymousGuard extends Component { + 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 : ; + } +} + +export default AnonymousGuard; diff --git a/src/shared/components/common/auth-guard.tsx b/src/shared/components/common/auth-guard.tsx index e79a541e..03352901 100644 --- a/src/shared/components/common/auth-guard.tsx +++ b/src/shared/components/common/auth-guard.tsx @@ -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 ; - } else { - return props.children; +interface AuthGuardState { + hasRedirected: boolean; +} + +class AuthGuard extends Component< + RouteComponentProps>, + AuthGuardState +> { + state = { + hasRedirected: false, + } as AuthGuardState; + + constructor( + props: RouteComponentProps>, + 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 : ; } } diff --git a/src/shared/components/common/badges.tsx b/src/shared/components/common/badges.tsx index c1eeed46..e3b2172a 100644 --- a/src/shared/components/common/badges.tsx +++ b/src/shared/components/common/badges.tsx @@ -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", { diff --git a/src/shared/components/common/comment-sort-select.tsx b/src/shared/components/common/comment-sort-select.tsx index ad4eebfe..f9115583 100644 --- a/src/shared/components/common/comment-sort-select.tsx +++ b/src/shared/components/common/comment-sort-select.tsx @@ -48,6 +48,9 @@ export class CommentSortSelect extends Component< {I18NextService.i18n.t("sort_type")} , + , diff --git a/src/shared/components/common/emoji-mart.tsx b/src/shared/components/common/emoji-mart.tsx index 6ee3aa83..fad871c1 100644 --- a/src/shared/components/common/emoji-mart.tsx +++ b/src/shared/components/common/emoji-mart.tsx @@ -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 { + div: RefObject; + 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
      ; + return
      ; } handleEmojiClick(e: any) { diff --git a/src/shared/components/common/emoji-picker.tsx b/src/shared/components/common/emoji-picker.tsx index 7eac4cf4..0d131be4 100644 --- a/src/shared/components/common/emoji-picker.tsx +++ b/src/shared/components/common/emoji-picker.tsx @@ -77,5 +77,6 @@ export class EmojiPicker extends Component { handleEmojiClick(e: any) { this.props.onEmojiClick?.(e); + this.setState({ showPicker: false }); } } diff --git a/src/shared/components/common/html-tags.tsx b/src/shared/components/common/html-tags.tsx index 5d532589..cd24a666 100644 --- a/src/shared/components/common/html-tags.tsx +++ b/src/shared/components/common/html-tags.tsx @@ -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 { 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 { ))} + + {/* Open Graph / Facebook */} @@ -45,10 +50,10 @@ export class HtmlTags extends Component { name={n} content={htmlToText(md.renderInline(desc))} /> - ) + ), )} {["og:image", "twitter:image"].map( - p => image && + p => image && , )} ); diff --git a/src/shared/components/common/language-select.tsx b/src/shared/components/common/language-select.tsx index c74f24cd..fbc84d6f 100644 --- a/src/shared/components/common/language-select.tsx +++ b/src/shared/components/common/language-select.tsx @@ -53,12 +53,12 @@ export class LanguageSelect extends Component { {this.props.multiple && this.props.showLanguageWarning && ( @@ -97,7 +97,7 @@ export class LanguageSelect extends Component { this.props.siteLanguages, this.props.showAll, this.props.showSite, - UserService.Instance.myUserInfo + UserService.Instance.myUserInfo, ); return ( diff --git a/src/shared/components/common/listing-type-select.tsx b/src/shared/components/common/listing-type-select.tsx index 1d917dcd..3439fac4 100644 --- a/src/shared/components/common/listing-type-select.tsx +++ b/src/shared/components/common/listing-type-select.tsx @@ -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")} + {(UserService.Instance.myUserInfo?.moderates.length ?? 0) > 0 && ( + <> + + + + )}
      ); } diff --git a/src/shared/components/common/loading-ellipses.tsx b/src/shared/components/common/loading-ellipses.tsx new file mode 100644 index 00000000..ba96101f --- /dev/null +++ b/src/shared/components/common/loading-ellipses.tsx @@ -0,0 +1,34 @@ +import { Component } from "inferno"; + +interface LoadingEllipsesState { + ellipses: string; +} + +export class LoadingEllipses extends Component { + 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 + ".", + })); + }; +} diff --git a/src/shared/components/common/markdown-textarea.tsx b/src/shared/components/common/markdown-textarea.tsx index cd4b5e4b..d032b5a5 100644 --- a/src/shared/components/common/markdown-textarea.tsx +++ b/src/shared/components/common/markdown-textarea.tsx @@ -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 - // return ( - this.handleEmoji(this, e)} - > +
      @@ -785,20 +800,18 @@ export class Home extends Component { } 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 { 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 { 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 { 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 { 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 { 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 { 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 { this.updateBan(banRes); } + async handleMarkPostAsRead(form: MarkPostAsRead) { + const res = await HttpService.client.markPostAsRead(form); + this.findAndUpdatePost(res); + } + updateBanFromCommunity(banRes: RequestState) { // 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 { updateBan(banRes: RequestState) { // 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 { } purgeItem(purgeRes: RequestState) { - if (purgeRes.state == "success") { + if (purgeRes.state === "success") { toast(I18NextService.i18n.t("purge_success")); this.context.router.history.push(`/`); } } - findAndUpdateComment(res: RequestState) { + findAndUpdateCommentEdit(res: RequestState) { 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 { }); } + findAndUpdateComment(res: RequestState) { + 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) { 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 { findAndUpdateCommentReply(res: RequestState) { 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 { findAndUpdatePost(res: RequestState) { 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; diff --git a/src/shared/components/home/instances.tsx b/src/shared/components/home/instances.tsx index 0d6748a7..40ab420a 100644 --- a/src/shared/components/home/instances.tsx +++ b/src/shared/components/home/instances.tsx @@ -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 { private isoData = setIsoData(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 { 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 { client, }: InitialFetchRequest): Promise { return { - federatedInstancesResponse: await client.getFederatedInstances({}), + federatedInstancesResponse: await client.getFederatedInstances(), }; } @@ -85,37 +92,32 @@ export class Instances extends Component { case "success": { const instances = this.state.instancesRes.data.federated_instances; return instances ? ( - <> -

      {I18NextService.i18n.t("instances")}

      -
      -
      -

      - {I18NextService.i18n.t("linked_instances")} -

      - {this.itemList(instances.linked)} -
      +
      +
      + instances[status].length) + .map(status => ({ + key: status, + label: I18NextService.i18n.t(`${status}_instances`), + getNode: isSelected => ( +
      + {status === "blocked" + ? this.itemList(instances[status], false) + : this.itemList(instances[status])} +
      + ), + }))} + />
      -
      - {instances.allowed && instances.allowed.length > 0 && ( -
      -

      - {I18NextService.i18n.t("allowed_instances")} -

      - {this.itemList(instances.allowed)} -
      - )} - {instances.blocked && instances.blocked.length > 0 && ( -
      -

      - {I18NextService.i18n.t("blocked_instances")} -

      - {this.itemList(instances.blocked)} -
      - )} -
      - +
      ) : ( - <> +
      No linked instance
      ); } } @@ -133,7 +135,7 @@ export class Instances extends Component { ); } - itemList(items: Instance[]) { + itemList(items: Instance[], link = true) { return items.length > 0 ? (
      @@ -148,9 +150,13 @@ export class Instances extends Component { {items.map(i => ( diff --git a/src/shared/components/home/login-reset.tsx b/src/shared/components/home/login-reset.tsx index 7d1d0023..131011ae 100644 --- a/src/shared/components/home/login-reset.tsx +++ b/src/shared/components/home/login-reset.tsx @@ -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 { 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 { 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"); } diff --git a/src/shared/components/home/login.tsx b/src/shared/components/home/login.tsx index e2986b57..475eeeaf 100644 --- a/src/shared/components/home/login.tsx +++ b/src/shared/components/home/login.tsx @@ -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({ + prev(param) { + return param ? decodeURIComponent(param) : undefined; + }, + }); interface State { loginRes: RequestState; 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 { +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>, + 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 { } 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 { title={this.documentTitle} path={this.context.router.route.match.url} /> +
      {this.loginForm()}
      @@ -65,10 +157,28 @@ export class Login extends Component { ); } + 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 (
      - +

      {I18NextService.i18n.t("login")}

      -
      - -
      - - - {I18NextService.i18n.t("forgot_password")} - -
      +
      +
      - {this.state.showTotp && ( -
      - -
      - -
      -
      - )}
      ); } - - 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); - } } diff --git a/src/shared/components/home/rate-limit-form.tsx b/src/shared/components/home/rate-limit-form.tsx index 78229da0..21b3364b 100644 --- a/src/shared/components/home/rate-limit-form.tsx +++ b/src/shared/components/home/rate-limit-form.tsx @@ -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={ diff --git a/src/shared/components/home/setup.tsx b/src/shared/components/home/setup.tsx index f4bdb555..5dbad944 100644 --- a/src/shared/components/home/setup.tsx +++ b/src/shared/components/home/setup.tsx @@ -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 { 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 { />
      -
      - -
      - -
      +
      +
      -
      - -
      - -
      +
      +
      -
      - -
      - - {this.state.form.password && ( -
      - {I18NextService.i18n.t( - this.passwordStrength as NoOptionI18nKeys - )} -
      - )} -
      +
      +
      -
      - -
      - -
      +
      +
      - {siteView.local_site.registration_mode == "RequireApplication" && ( + {siteView.local_site.registration_mode === "RequireApplication" && ( <>
      @@ -281,7 +229,7 @@ export class Signup extends Component {
      )} @@ -337,7 +285,7 @@ export class Signup extends Component {
      - {this.state.siteForm.registration_mode == "RequireApplication" && ( + {this.state.siteForm.registration_mode === "RequireApplication" && (
      - - {i.domain} - + {link ? ( + + {i.domain}{" "} + + ) : ( + {i.domain} + )} {i.software} {i.version}
      @@ -48,7 +39,7 @@ export class TaglineForm extends Component { {this.state.taglines.map((cv, index) => ( @@ -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< {this.combined}
      - {this.state.editingRow == index && ( + {this.state.editingRow === index && ( @@ -59,14 +50,14 @@ export class TaglineForm extends Component { siteLanguages={[]} /> )} - {this.state.editingRow != index &&
      {cv}
      } + {this.state.editingRow !== index &&
      {cv}
      }
      - +
      ); } @@ -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>): Promise { 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 = { - state: "empty", - }; + let communityResponse: RequestState = EMPTY_REQUEST; if (communityId) { const communityForm: GetCommunity = { id: communityId, - auth, }; communityResponse = await client.getCommunity(communityForm); } - let modUserResponse: RequestState = { - state: "empty", - }; + let modUserResponse: RequestState = EMPTY_REQUEST; if (modId) { const getPersonForm: GetPersonDetails = { person_id: modId, - auth, }; modUserResponse = await client.getPersonDetails(getPersonForm); } - let userResponse: RequestState = { - state: "empty", - }; + let userResponse: RequestState = EMPTY_REQUEST; if (userId) { const getPersonForm: GetPersonDetails = { person_id: userId, - auth, }; userResponse = await client.getPersonDetails(getPersonForm); diff --git a/src/shared/components/person/inbox.tsx b/src/shared/components/person/inbox.tsx index bf246f64..d1237e7b 100644 --- a/src/shared/components/person/inbox.tsx +++ b/src/shared/components/person/inbox.tsx @@ -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 { 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 { 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 { className="btn btn-secondary mb-2 mb-sm-3" onClick={linkEvent(this, this.handleMarkAllAsRead)} > - {this.state.markAllAsReadRes.state == "loading" ? ( + {this.state.markAllAsReadRes.state === "loading" ? ( ) : ( capitalizeFirstLetter( - I18NextService.i18n.t("mark_all_as_read") + I18NextService.i18n.t("mark_all_as_read"), ) )} @@ -256,6 +262,7 @@ export class Inbox extends Component {
  • @@ -445,22 +452,22 @@ export class Inbox extends Component { 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 { 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 (

    @@ -718,78 +725,77 @@ export class Inbox extends Component { static async fetchInitialData({ client, - auth, }: InitialFetchRequest): Promise { 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 { } 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { updateBanFromCommunity(banRes: RequestState) { // 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 { updateBan(banRes: RequestState) { // 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 { } purgeItem(purgeRes: RequestState) { - if (purgeRes.state == "success") { + if (purgeRes.state === "success") { toast(I18NextService.i18n.t("purge_success")); this.context.router.history.push(`/`); } } reportToast( - res: RequestState + res: RequestState, ) { - 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) { - 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 { findAndUpdateCommentReply(res: RequestState) { 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 { findAndUpdateMention(res: RequestState) { 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; diff --git a/src/shared/components/person/password-change.tsx b/src/shared/components/person/password-change.tsx index 565f55e6..8fc8078d 100644 --- a/src/shared/components/person/password-change.tsx +++ b/src/shared/components/person/password-change.tsx @@ -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; @@ -21,7 +26,7 @@ export class PasswordChange extends Component { 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 { passwordChangeForm() { return ( -
    - -
    - -
    +
    +
    -
    - -
    - -
    +
    +
    @@ -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} /> @@ -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) { // 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) { - if (purgeRes.state == "success") { + if (purgeRes.state === "success") { toast(I18NextService.i18n.t("purge_success")); this.context.router.history.push(`/`); } } - findAndUpdateComment(res: RequestState) { + findAndUpdateCommentEdit(res: RequestState) { 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) { + 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) { 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) { 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) { 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; diff --git a/src/shared/components/person/registration-applications.tsx b/src/shared/components/person/registration-applications.tsx index 757170f8..2aca3cad 100644 --- a/src/shared/components/person/registration-applications.tsx +++ b/src/shared/components/person/registration-applications.tsx @@ -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(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< apps.length} />
    @@ -204,46 +207,47 @@ export class RegistrationApplications extends Component< } static async fetchInitialData({ - auth, client, }: InitialFetchRequest): Promise { 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; }); diff --git a/src/shared/components/person/reports.tsx b/src/shared/components/person/reports.tsx index d298930e..915107c1 100644 --- a/src/shared/components/person/reports.tsx +++ b/src/shared/components/person/reports.tsx @@ -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 { private isoData = setIsoData(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 { 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 { 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) + } />

    @@ -352,25 +366,25 @@ export class Reports extends Component { 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 { } static async fetchInitialData({ - auth, client, }: InitialFetchRequest): Promise { const unresolved_only = true; @@ -532,20 +545,18 @@ export class Reports extends Component { 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 { unresolved_only, page, limit, - auth: auth as string, }; data.messageReportsRes = await client.listPrivateMessageReports( - privateMessageReportsForm + privateMessageReportsForm, ); } @@ -565,15 +575,14 @@ export class Reports extends Component { } 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 { unresolved_only, page, limit, - auth, }; this.setState({ @@ -594,7 +602,7 @@ export class Reports extends Component { if (amAdmin()) { this.setState({ messageReportsRes: await HttpService.client.listPrivateMessageReports( - form + form, ), }); } @@ -603,24 +611,36 @@ export class Reports extends Component { 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) { 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 { findAndUpdatePostReport(res: RequestState) { 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 { } findAndUpdatePrivateMessageReport( - res: RequestState + res: RequestState, ) { 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; diff --git a/src/shared/components/person/settings.tsx b/src/shared/components/person/settings.tsx index 4caef458..2e74aa09 100644 --- a/src/shared/components/person/settings.tsx +++ b/src/shared/components/person/settings.tsx @@ -3,34 +3,46 @@ import { fetchCommunities, fetchThemeList, fetchUsers, + instanceToChoice, myAuth, - myAuthRequired, personToChoice, setIsoData, setTheme, showLocal, updateCommunityBlock, + updateInstanceBlock, updatePersonBlock, } from "@utils/app"; import { capitalizeFirstLetter, debounce } from "@utils/helpers"; -import { Choice } from "@utils/types"; +import { Choice, RouteDataResponse } from "@utils/types"; import classNames from "classnames"; import { NoOptionI18nKeys } from "i18next"; import { Component, linkEvent } from "inferno"; import { BlockCommunityResponse, + BlockInstanceResponse, BlockPersonResponse, CommunityBlockView, DeleteAccountResponse, + GenerateTotpSecretResponse, + GetFederatedInstancesResponse, GetSiteResponse, + Instance, + InstanceBlockView, ListingType, LoginResponse, PersonBlockView, SortType, + UpdateTotpResponse, } from "lemmy-js-client"; import { elementUrl, emDash, relTags } from "../../config"; -import { UserService } from "../../services"; -import { HttpService, RequestState } from "../../services/HttpService"; +import { FirstLoadService, UserService } from "../../services"; +import { + EMPTY_REQUEST, + HttpService, + LOADING_REQUEST, + RequestState, +} from "../../services/HttpService"; import { I18NextService, languages } from "../../services/I18NextService"; import { setupTippy } from "../../tippy"; import { toast } from "../../toast"; @@ -40,19 +52,31 @@ 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 PasswordInput from "../common/password-input"; import { SearchableSelect } from "../common/searchable-select"; import { SortSelect } from "../common/sort-select"; import Tabs from "../common/tabs"; import { CommunityLink } from "../community/community-link"; import { PersonListing } from "./person-listing"; +import { InitialFetchRequest } from "../../interfaces"; +import TotpModal from "../common/totp-modal"; + +type SettingsData = RouteDataResponse<{ + instancesRes: GetFederatedInstancesResponse; +}>; interface SettingsState { saveRes: RequestState; changePasswordRes: RequestState; deleteAccountRes: RequestState; + instancesRes: RequestState; + generateTotpRes: RequestState; + updateTotpRes: RequestState; // TODO redo these forms saveUserSettingsForm: { show_nsfw?: boolean; + blur_nsfw?: boolean; + auto_expand?: boolean; theme?: string; default_sort_type?: SortType; default_listing_type?: ListingType; @@ -71,7 +95,7 @@ interface SettingsState { show_read_posts?: boolean; show_new_post_notifs?: boolean; discussion_languages?: number[]; - generate_totp_2fa?: boolean; + open_links_in_new_tab?: boolean; }; changePasswordForm: { new_password?: string; @@ -83,6 +107,7 @@ interface SettingsState { }; personBlocks: PersonBlockView[]; communityBlocks: CommunityBlockView[]; + instanceBlocks: InstanceBlockView[]; currentTab: string; themeList: string[]; deleteAccountShowConfirm: boolean; @@ -91,22 +116,25 @@ interface SettingsState { searchCommunityOptions: Choice[]; searchPersonLoading: boolean; searchPersonOptions: Choice[]; + searchInstanceOptions: Choice[]; + isIsomorphic: boolean; + show2faModal: boolean; } -type FilterType = "user" | "community"; +type FilterType = "user" | "community" | "instance"; const Filter = ({ filterType, options, onChange, onSearch, - loading, + loading = false, }: { filterType: FilterType; options: Choice[]; onSearch: (text: string) => void; onChange: (choice: Choice) => void; - loading: boolean; + loading?: boolean; }) => (
    ); @@ -318,59 +418,32 @@ export class Settings extends Component { <>

    {I18NextService.i18n.t("change_password")}

    -
    - -
    - -
    +
    +
    -
    - -
    - -
    +
    +
    -
    - -
    - -
    +
    +
    + + + ))} + + + ); + } + saveUserSettingsHtmlForm() { const selectedLangs = this.state.saveUserSettingsForm.discussion_languages; @@ -615,6 +731,7 @@ export class Settings extends Component { selectedLanguageIds={selectedLangs} multiple={true} showLanguageWarning={true} + showAll={true} showSite onChange={this.handleDiscussionLanguageChange} /> @@ -635,6 +752,9 @@ export class Settings extends Component { + {this.state.themeList.map(theme => (
    +
    +
    + + +
    +
    +
    +
    + + +
    +
    { } onChange={linkEvent( this, - this.handleSendNotificationsToEmailChange + this.handleSendNotificationsToEmailChange, )} />
    +
    +
    + + +
    +
    {this.totpSection()}

    -
    + {this.state.deleteAccountShowConfirm && ( <> -
    +
    - + )} -
    + ); } totpSection() { - const totpUrl = - UserService.Instance.myUserInfo?.local_user_view.local_user.totp_2fa_url; + const totpEnabled = + !!UserService.Instance.myUserInfo?.local_user_view.local_user + .totp_2fa_enabled; + const { generateTotpRes } = this.state; return ( <> - {!totpUrl && ( -
    -
    - - -
    -
    - )} - - {totpUrl && ( - <> - -
    -
    - - -
    -
    - + + {totpEnabled ? ( + + ) : ( + )} ); } + async handleToggle2fa(totp: string, enabled: boolean) { + this.setState({ updateTotpRes: LOADING_REQUEST }); + + const updateTotpRes = await HttpService.client.updateTotp({ + enabled, + totp_token: totp, + }); + + this.setState({ updateTotpRes }); + + const successful = updateTotpRes.state === "success"; + if (successful) { + this.setState({ show2faModal: false }); + + const siteRes = await HttpService.client.getSite(); + UserService.Instance.myUserInfo!.local_user_view.local_user.totp_2fa_enabled = + enabled; + + if (siteRes.state === "success") { + this.setState({ siteRes: siteRes.data }); + } + + toast( + I18NextService.i18n.t( + enabled ? "enable_totp_success" : "disable_totp_success", + ), + ); + } else { + toast(I18NextService.i18n.t("incorrect_totp_code"), "danger"); + } + + return successful; + } + + handleEnable2fa(totp: string) { + return this.handleToggle2fa(totp, true); + } + + handleDisable2fa(totp: string) { + return this.handleToggle2fa(totp, false); + } + handlePersonSearch = debounce(async (text: string) => { this.setState({ searchPersonLoading: true }); @@ -941,7 +1145,7 @@ export class Settings extends Component { if (text.length > 0) { searchCommunityOptions.push( - ...(await fetchCommunities(text)).map(communityToChoice) + ...(await fetchCommunities(text)).map(communityToChoice), ); } @@ -951,12 +1155,32 @@ export class Settings extends Component { }); }); + handleInstanceSearch = debounce(async (text: string) => { + let searchInstanceOptions: Instance[] = []; + + if (this.state.instancesRes.state === "success") { + searchInstanceOptions = + this.state.instancesRes.data.federated_instances?.linked.filter( + instance => + instance.domain.toLowerCase().includes(text.toLowerCase()) || + !this.state.instanceBlocks.some( + blockedIntance => blockedIntance.instance.id === instance.id, + ), + ) ?? []; + } + + this.setState({ + searchInstanceOptions: searchInstanceOptions + .slice(0, 30) + .map(instanceToChoice), + }); + }); + async handleBlockPerson({ value }: Choice) { if (value !== "0") { const res = await HttpService.client.blockPerson({ person_id: Number(value), block: true, - auth: myAuthRequired(), }); this.personBlock(res); } @@ -972,7 +1196,6 @@ export class Settings extends Component { const res = await HttpService.client.blockPerson({ person_id: recipientId, block: false, - auth: myAuthRequired(), }); ctx.personBlock(res); } @@ -982,27 +1205,61 @@ export class Settings extends Component { const res = await HttpService.client.blockCommunity({ community_id: Number(value), block: true, - auth: myAuthRequired(), }); this.communityBlock(res); } } async handleUnblockCommunity(i: { ctx: Settings; communityId: number }) { - const auth = myAuth(); - if (auth) { + if (myAuth()) { const res = await HttpService.client.blockCommunity({ community_id: i.communityId, block: false, - auth: myAuthRequired(), }); i.ctx.communityBlock(res); } } + async handleBlockInstance({ value }: Choice) { + if (value !== "0") { + const id = Number(value); + const res = await HttpService.client.blockInstance({ + block: true, + instance_id: id, + }); + this.instanceBlock(id, res); + } + } + + async handleUnblockInstance({ + ctx, + instanceId, + }: { + ctx: Settings; + instanceId: number; + }) { + const res = await HttpService.client.blockInstance({ + block: false, + instance_id: instanceId, + }); + ctx.instanceBlock(instanceId, res); + } + handleShowNsfwChange(i: Settings, event: any) { i.setState( - s => ((s.saveUserSettingsForm.show_nsfw = event.target.checked), s) + s => ((s.saveUserSettingsForm.show_nsfw = event.target.checked), s), + ); + } + + handleBlurNsfwChange(i: Settings, event: any) { + i.setState( + s => ((s.saveUserSettingsForm.blur_nsfw = event.target.checked), s), + ); + } + + handleAutoExpandChange(i: Settings, event: any) { + i.setState( + s => ((s.saveUserSettingsForm.auto_expand = event.target.checked), s), ); } @@ -1012,13 +1269,13 @@ export class Settings extends Component { mui.local_user_view.local_user.show_avatars = event.target.checked; } i.setState( - s => ((s.saveUserSettingsForm.show_avatars = event.target.checked), s) + s => ((s.saveUserSettingsForm.show_avatars = event.target.checked), s), ); } handleBotAccount(i: Settings, event: any) { i.setState( - s => ((s.saveUserSettingsForm.bot_account = event.target.checked), s) + s => ((s.saveUserSettingsForm.bot_account = event.target.checked), s), ); } @@ -1026,13 +1283,13 @@ export class Settings extends Component { i.setState( s => ( (s.saveUserSettingsForm.show_bot_accounts = event.target.checked), s - ) + ), ); } handleReadPosts(i: Settings, event: any) { i.setState( - s => ((s.saveUserSettingsForm.show_read_posts = event.target.checked), s) + s => ((s.saveUserSettingsForm.show_read_posts = event.target.checked), s), ); } @@ -1040,7 +1297,15 @@ export class Settings extends Component { i.setState( s => ( (s.saveUserSettingsForm.show_new_post_notifs = event.target.checked), s - ) + ), + ); + } + + handleOpenInNewTab(i: Settings, event: any) { + i.setState( + s => ( + (s.saveUserSettingsForm.open_links_in_new_tab = event.target.checked), s + ), ); } @@ -1050,23 +1315,16 @@ export class Settings extends Component { mui.local_user_view.local_user.show_scores = event.target.checked; } i.setState( - s => ((s.saveUserSettingsForm.show_scores = event.target.checked), s) + s => ((s.saveUserSettingsForm.show_scores = event.target.checked), s), ); } - handleGenerateTotp(i: Settings, event: any) { - // Coerce false to undefined here, so it won't generate it. - const checked: boolean | undefined = event.target.checked || undefined; - if (checked) { - toast(I18NextService.i18n.t("two_factor_setup_instructions")); - } - i.setState(s => ((s.saveUserSettingsForm.generate_totp_2fa = checked), s)); - } + async handleGenerateTotp(i: Settings) { + i.setState({ generateTotpRes: LOADING_REQUEST }); - handleRemoveTotp(i: Settings, event: any) { - // Coerce true to undefined here, so it won't generate it. - const checked: boolean | undefined = !event.target.checked && undefined; - i.setState(s => ((s.saveUserSettingsForm.generate_totp_2fa = checked), s)); + i.setState({ + generateTotpRes: await HttpService.client.generateTotpSecret(), + }); } handleSendNotificationsToEmailChange(i: Settings, event: any) { @@ -1075,7 +1333,7 @@ export class Settings extends Component { (s.saveUserSettingsForm.send_notifications_to_email = event.target.checked), s - ) + ), ); } @@ -1087,17 +1345,19 @@ export class Settings extends Component { handleInterfaceLangChange(i: Settings, event: any) { const newLang = event.target.value ?? "browser"; I18NextService.i18n.changeLanguage( - newLang === "browser" ? navigator.languages : newLang + newLang === "browser" ? navigator.languages : newLang, ); i.setState( - s => ((s.saveUserSettingsForm.interface_language = event.target.value), s) + s => ( + (s.saveUserSettingsForm.interface_language = event.target.value), s + ), ); } handleDiscussionLanguageChange(val: number[]) { this.setState( - s => ((s.saveUserSettingsForm.discussion_languages = val), s) + s => ((s.saveUserSettingsForm.discussion_languages = val), s), ); } @@ -1107,7 +1367,7 @@ export class Settings extends Component { handleListingTypeChange(val: ListingType) { this.setState( - s => ((s.saveUserSettingsForm.default_listing_type = val), s) + s => ((s.saveUserSettingsForm.default_listing_type = val), s), ); } @@ -1137,43 +1397,42 @@ export class Settings extends Component { handleDisplayNameChange(i: Settings, event: any) { i.setState( - s => ((s.saveUserSettingsForm.display_name = event.target.value), s) + s => ((s.saveUserSettingsForm.display_name = event.target.value), s), ); } handleMatrixUserIdChange(i: Settings, event: any) { i.setState( - s => ((s.saveUserSettingsForm.matrix_user_id = event.target.value), s) + s => ((s.saveUserSettingsForm.matrix_user_id = event.target.value), s), ); } handleNewPasswordChange(i: Settings, event: any) { const newPass: string | undefined = - event.target.value == "" ? undefined : event.target.value; + event.target.value === "" ? undefined : event.target.value; i.setState(s => ((s.changePasswordForm.new_password = newPass), s)); } handleNewPasswordVerifyChange(i: Settings, event: any) { const newPassVerify: string | undefined = - event.target.value == "" ? undefined : event.target.value; + event.target.value === "" ? undefined : event.target.value; i.setState( - s => ((s.changePasswordForm.new_password_verify = newPassVerify), s) + s => ((s.changePasswordForm.new_password_verify = newPassVerify), s), ); } handleOldPasswordChange(i: Settings, event: any) { const oldPass: string | undefined = - event.target.value == "" ? undefined : event.target.value; + event.target.value === "" ? undefined : event.target.value; i.setState(s => ((s.changePasswordForm.old_password = oldPass), s)); } async handleSaveSettingsSubmit(i: Settings, event: any) { event.preventDefault(); - i.setState({ saveRes: { state: "loading" } }); + i.setState({ saveRes: LOADING_REQUEST }); const saveRes = await HttpService.client.saveUserSettings({ ...i.state.saveUserSettingsForm, - auth: myAuthRequired(), }); if (saveRes.state === "success") { @@ -1181,6 +1440,17 @@ export class Settings extends Component { res: saveRes.data, showToast: false, }); + + const siteRes = await HttpService.client.getSite(); + + if (siteRes.state === "success") { + i.setState({ + siteRes: siteRes.data, + }); + + UserService.Instance.myUserInfo = siteRes.data.my_user; + } + toast(I18NextService.i18n.t("saved")); window.scrollTo(0, 0); } @@ -1194,12 +1464,11 @@ export class Settings extends Component { i.state.changePasswordForm; if (new_password && old_password && new_password_verify) { - i.setState({ changePasswordRes: { state: "loading" } }); + i.setState({ changePasswordRes: LOADING_REQUEST }); const changePasswordRes = await HttpService.client.changePassword({ new_password, new_password_verify, old_password, - auth: myAuthRequired(), }); if (changePasswordRes.state === "success") { UserService.Instance.login({ @@ -1222,13 +1491,15 @@ export class Settings extends Component { i.setState(s => ((s.deleteAccountForm.password = event.target.value), s)); } - async handleDeleteAccount(i: Settings) { + async handleDeleteAccount(i: Settings, event: Event) { + event.preventDefault(); const password = i.state.deleteAccountForm.password; if (password) { - i.setState({ deleteAccountRes: { state: "loading" } }); + i.setState({ deleteAccountRes: LOADING_REQUEST }); const deleteAccountRes = await HttpService.client.deleteAccount({ password, - auth: myAuthRequired(), + // TODO: promt user weather he wants the content to be deleted + delete_content: false, }); if (deleteAccountRes.state === "success") { UserService.Instance.logout(); @@ -1262,4 +1533,19 @@ export class Settings extends Component { } } } + + instanceBlock(id: number, res: RequestState) { + if ( + res.state === "success" && + this.state.instancesRes.state === "success" + ) { + const linkedInstances = + this.state.instancesRes.data.federated_instances?.linked ?? []; + updateInstanceBlock(res.data, id, linkedInstances); + const mui = UserService.Instance.myUserInfo; + if (mui) { + this.setState({ instanceBlocks: mui.instance_blocks }); + } + } + } } diff --git a/src/shared/components/person/verify-email.tsx b/src/shared/components/person/verify-email.tsx index 66e59596..35bece5f 100644 --- a/src/shared/components/person/verify-email.tsx +++ b/src/shared/components/person/verify-email.tsx @@ -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 { 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 { async verify() { this.setState({ - verifyRes: { state: "loading" }, + verifyRes: LOADING_REQUEST, }); this.setState({ @@ -35,7 +40,7 @@ export class VerifyEmail extends Component { }), }); - 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 {

    {I18NextService.i18n.t("verify_email")}

    - {this.state.verifyRes.state == "loading" && ( + {this.state.verifyRes.state === "loading" && (
    diff --git a/src/shared/components/post/create-post.tsx b/src/shared/components/post/create-post.tsx index 36bc99d2..bd5b3edf 100644 --- a/src/shared/components/post/create-post.tsx +++ b/src/shared/components/post/create-post.tsx @@ -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 >): Promise { const data: CreatePostData = { initialCommunitiesRes: await fetchCommunitiesForOptions(client), - communityResponse: { state: "empty" }, + communityResponse: EMPTY_REQUEST, }; if (communityId) { const form: GetCommunity = { - auth, id: getIdFromString(communityId), }; diff --git a/src/shared/components/post/post-form.tsx b/src/shared/components/post/post-form.tsx index 8ba423ab..970bf93c 100644 --- a/src/shared/components/post/post-form.tsx +++ b/src/shared/components/post/post-form.tsx @@ -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 { 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 { ({ 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 { ({ community: { id, title } }) => ({ label: title, value: id.toString(), - }) + }), ) ?? [], }; } @@ -310,16 +307,16 @@ export class PostForm extends Component { } 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 { return (
    - { ) && !this.state.submitted } /> +
    + +
    +