diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000..c5b9ea10e1 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +/build/webpack.prod.conf.js export-subst diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 305155d84b..2b4452d53e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -4,11 +4,36 @@ image: node:16 stages: + - check-changelog - lint - build - test - deploy +# https://git.pleroma.social/help/ci/yaml/workflow.md#switch-between-branch-pipelines-and-merge-request-pipelines +workflow: + rules: + - if: $CI_PIPELINE_SOURCE == "merge_request_event" + - if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS + when: never + - if: $CI_COMMIT_BRANCH + +check-changelog: + stage: check-changelog + image: alpine + rules: + - if: $CI_MERGE_REQUEST_SOURCE_PROJECT_PATH == 'pleroma/pleroma-fe' && $CI_MERGE_REQUEST_SOURCE_BRANCH_NAME =~ /^renovate/ + when: never + - if: $CI_MERGE_REQUEST_SOURCE_PROJECT_PATH == 'pleroma/pleroma-fe' && $CI_MERGE_REQUEST_SOURCE_BRANCH_NAME == 'weblate' + when: never + - if: $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "develop" + before_script: '' + after_script: '' + cache: {} + script: + - apk add git + - sh ./tools/check-changelog + lint: stage: lint script: diff --git a/.stylelintrc.json b/.stylelintrc.json index fbf3a245cb..d6689cc015 100644 --- a/.stylelintrc.json +++ b/.stylelintrc.json @@ -1,19 +1,41 @@ { "extends": [ "stylelint-rscss/config", - "stylelint-config-recommended", - "stylelint-config-standard" + "stylelint-config-standard", + "stylelint-config-recommended-scss", + "stylelint-config-html", + "stylelint-config-recommended-vue/scss" ], "rules": { "declaration-no-important": true, "rscss/no-descendant-combinator": false, "rscss/class-format": [ - true, + false, { "component": "pascal-case", "variant": "^-[a-z]\\w+", "element": "^[a-z]\\w+" } + ], + "selector-class-pattern": null, + "import-notation": null, + "custom-property-pattern": null, + "keyframes-name-pattern": null, + "scss/operator-no-newline-after": null, + "declaration-block-no-redundant-longhand-properties": [ + true, + { + "ignoreShorthands": [ + "grid-template", + "margin", + "padding", + "border", + "border-width", + "border-style", + "border-color", + "border-radius" + ] + } ] } } diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ed1c1860b..3fb7293154 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,34 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). +## 2.5.1 +### Fixed +- Checkboxes in settings can now work with screenreaders +- Autocomplete in edit boxes can now work with screenreaders +- Status interact buttons now have focus indicator for anonymous users +- Top bar buttons now correctly have text labels +- It is now possible to register if the site admin requires birthday to register +- User cards from search results will correctly popup +- Fix notification attachment icon overflow +- Editing mute words is less laggy +- Repeater's name will no longer mess up with the directionality of the text sitting on the same line +- Unauthenticated access will give better error messages +- It is now easier to close the media viewer with a mouse when there is only one image +- Deleting profile fields can work properly +- Clicking the react button will correctly focus the search box +- Clicking buttons on the top-bar will no longer bring you to the top of the page +- Emoji picker is much faster to load +- `blockquote`s have a better display style +- Announcements posting and editing are now available to everyone with such a privilege, not just admins +- Adding or removing list members will actually work +- Emojis without a pack are now correctly displayed in emoji picker +- Changing notification settings will actually work + +### Added +- You can now set and see birthdays +- Optional confirmation dialogs when performing various actions +- You can now set fallback languages + ## 2.5.0 - 23.12.2022 ### Fixed - UI no longer lags when switching between mobile and desktop mode diff --git a/build/webpack.base.conf.js b/build/webpack.base.conf.js index bf94692220..7e69352a6a 100644 --- a/build/webpack.base.conf.js +++ b/build/webpack.base.conf.js @@ -6,7 +6,7 @@ var ServiceWorkerWebpackPlugin = require('serviceworker-webpack5-plugin') var CopyPlugin = require('copy-webpack-plugin'); var { VueLoaderPlugin } = require('vue-loader') var ESLintPlugin = require('eslint-webpack-plugin'); - +var StylelintPlugin = require('stylelint-webpack-plugin'); var env = process.env.NODE_ENV // check env & config/index.js to decide weither to enable CSS Sourcemaps for the @@ -111,6 +111,7 @@ module.exports = { extensions: ['js', 'vue'], formatter: require('eslint-formatter-friendly') }), + new StylelintPlugin({}), new VueLoaderPlugin(), // This copies Ruffle's WASM to a directory so that JS side can access it new CopyPlugin({ diff --git a/build/webpack.prod.conf.js b/build/webpack.prod.conf.js index 7de9372132..7a108f68d8 100644 --- a/build/webpack.prod.conf.js +++ b/build/webpack.prod.conf.js @@ -11,9 +11,16 @@ var env = process.env.NODE_ENV === 'testing' ? require('../config/test.env') : config.build.env -let commitHash = require('child_process') - .execSync('git rev-parse --short HEAD') - .toString(); +let commitHash = (() => { + const subst = "$Format:%h$"; + if(!subst.match(/Format:/)) { + return subst; + } else { + return require('child_process') + .execSync('git rev-parse --short HEAD') + .toString(); + } +})(); var webpackConfig = merge(baseWebpackConfig, { mode: 'production', diff --git a/changelog.d/add-taiwanese-aka-hokkien-i18n-support.add b/changelog.d/add-taiwanese-aka-hokkien-i18n-support.add new file mode 100644 index 0000000000..53d8980553 --- /dev/null +++ b/changelog.d/add-taiwanese-aka-hokkien-i18n-support.add @@ -0,0 +1 @@ +add the initial i18n translation file for Taiwanese (Hokkien), and modify some related files. \ No newline at end of file diff --git a/changelog.d/adminfe.add b/changelog.d/adminfe.add new file mode 100644 index 0000000000..188c45550a --- /dev/null +++ b/changelog.d/adminfe.add @@ -0,0 +1 @@ +Implemented a very basic instance administration screen diff --git a/changelog.d/check-changelog.skip b/changelog.d/check-changelog.skip new file mode 100644 index 0000000000..e69de29bb2 diff --git a/changelog.d/custom-emoji-notif-width.fix b/changelog.d/custom-emoji-notif-width.fix new file mode 100644 index 0000000000..da118f6ba7 --- /dev/null +++ b/changelog.d/custom-emoji-notif-width.fix @@ -0,0 +1 @@ +Keep aspect ratio of custom emoji reaction in notification diff --git a/changelog.d/edit-profile-button.fix b/changelog.d/edit-profile-button.fix new file mode 100644 index 0000000000..5a92765cd9 --- /dev/null +++ b/changelog.d/edit-profile-button.fix @@ -0,0 +1 @@ +Fix openSettingsModalTab so that it correctly opens Settings modal instead of Admin modal diff --git a/changelog.d/emoji-picker-button-accessible.fix b/changelog.d/emoji-picker-button-accessible.fix new file mode 100644 index 0000000000..12898a1ac5 --- /dev/null +++ b/changelog.d/emoji-picker-button-accessible.fix @@ -0,0 +1 @@ +Add alt text to emoji picker buttons diff --git a/changelog.d/export-subst-hash.fix b/changelog.d/export-subst-hash.fix new file mode 100644 index 0000000000..fb0d85cbb4 --- /dev/null +++ b/changelog.d/export-subst-hash.fix @@ -0,0 +1 @@ +Use export-subst gitattribute to allow tarball builds diff --git a/changelog.d/fix-reports.fix b/changelog.d/fix-reports.fix new file mode 100644 index 0000000000..961b80336f --- /dev/null +++ b/changelog.d/fix-reports.fix @@ -0,0 +1 @@ +fix reports now showing reason/content:w diff --git a/changelog.d/html-attribute-parsing.fix b/changelog.d/html-attribute-parsing.fix new file mode 100644 index 0000000000..0952f773ed --- /dev/null +++ b/changelog.d/html-attribute-parsing.fix @@ -0,0 +1 @@ +Fix HTML attribute parsing, discard attributes not strating with a letter diff --git a/changelog.d/mention-twice.fix b/changelog.d/mention-twice.fix new file mode 100644 index 0000000000..0e4b71dfd4 --- /dev/null +++ b/changelog.d/mention-twice.fix @@ -0,0 +1 @@ +Fix a bug where mentioning a user twice will not fill the mention into the textarea diff --git a/changelog.d/mentionsline-shouldbreak.fix b/changelog.d/mentionsline-shouldbreak.fix new file mode 100644 index 0000000000..33ee8d2c99 --- /dev/null +++ b/changelog.d/mentionsline-shouldbreak.fix @@ -0,0 +1 @@ +Make MentionsLine aware of line breaking by non-br elements diff --git a/changelog.d/nonascii-tags.fix b/changelog.d/nonascii-tags.fix new file mode 100644 index 0000000000..e4c6dc82d3 --- /dev/null +++ b/changelog.d/nonascii-tags.fix @@ -0,0 +1 @@ +Fix parsing non-ascii tags diff --git a/changelog.d/oauth2-token-linger.fix b/changelog.d/oauth2-token-linger.fix new file mode 100644 index 0000000000..da4e46316b --- /dev/null +++ b/changelog.d/oauth2-token-linger.fix @@ -0,0 +1 @@ +Fix OAuth2 token lingering after revocation diff --git a/changelog.d/quote-hide-oops.fix b/changelog.d/quote-hide-oops.fix new file mode 100644 index 0000000000..d93c0d29ee --- /dev/null +++ b/changelog.d/quote-hide-oops.fix @@ -0,0 +1 @@ +fix typo in code that prevented cards from showing at all diff --git a/changelog.d/quote-hide.fix b/changelog.d/quote-hide.fix new file mode 100644 index 0000000000..678fc3bc65 --- /dev/null +++ b/changelog.d/quote-hide.fix @@ -0,0 +1 @@ +don't display quoted status twice diff --git a/changelog.d/quote.add b/changelog.d/quote.add new file mode 100644 index 0000000000..b43b6abad3 --- /dev/null +++ b/changelog.d/quote.add @@ -0,0 +1 @@ +Implement quoting diff --git a/changelog.d/react-button-safari.fix b/changelog.d/react-button-safari.fix new file mode 100644 index 0000000000..9846d50d07 --- /dev/null +++ b/changelog.d/react-button-safari.fix @@ -0,0 +1 @@ +Fix react button misalignment on safari ios diff --git a/changelog.d/react-button.fix b/changelog.d/react-button.fix new file mode 100644 index 0000000000..c2222fb605 --- /dev/null +++ b/changelog.d/react-button.fix @@ -0,0 +1 @@ +Fix react button not working if reaction accounts are not loaded diff --git a/changelog.d/reload-user-pinned.fix b/changelog.d/reload-user-pinned.fix new file mode 100644 index 0000000000..db241c205a --- /dev/null +++ b/changelog.d/reload-user-pinned.fix @@ -0,0 +1 @@ +Fix pinned statuses gone when reloading user timeline diff --git a/changelog.d/scroll-emoji-selector-safari.fix b/changelog.d/scroll-emoji-selector-safari.fix new file mode 100644 index 0000000000..3f5dda7d80 --- /dev/null +++ b/changelog.d/scroll-emoji-selector-safari.fix @@ -0,0 +1 @@ +Fix scrolling emoji selector in modal in safari ios diff --git a/docs/HACKING.md b/docs/HACKING.md index 7f2964b4e6..a5c491136d 100644 --- a/docs/HACKING.md +++ b/docs/HACKING.md @@ -25,7 +25,17 @@ This could be a bit trickier, you basically need steps 1-4 from *develop build* ### Replacing your instance's frontend with custom FE build -This is the most easiest way to use and test FE build: you just need to copy or symlink contents of `dist` folder into backend's [static directory](../backend/configuration/static_dir.md), by default it is located in `instance/static`, or in `/var/lib/pleroma/static` for OTP release installations, create it if it doesn't exist already. Be aware that running `yarn build` wipes the contents of `dist` folder. +#### New way (via AdminFE, a bit janky but works) + +In backend's [static directory](../backend/configuration/static_dir.md) there should be a folder called `frontends` if you installed any frontends from AdminFE before, otherwise you can create it yourself (ensuring correct permissions). Backend will serve given frontend from path `frontends/{frontend}/{reference}`, where `{frontend}` is name of frontend (`pleroma-fe`) and `{reference}` is version. You could make a production build, move `dist` folder into `frontends/pleroma-fe` and rename it into something like `myCustomVersion`. To actually make backend serve this frontend by default, in AdminFE you'll need to set name/reference in Settings -> Frontend -> Frontends -> Primary. + +You could also install from a zip file (i.e. CI build) but AdminFE UI is a bit buggy and lacking, so this approach is not recommended. + +Take note that frontend management is in early development and currently there's no way for user to change frontend or version for themselves, primary frontend becomes default frontend for all users and visitors. + +#### Old way (replaces everything, hard to maintain, not recommended) + +Copy or symlink contents of `dist` folder into backend's [static directory](../backend/configuration/static_dir.md), by default it is located in `instance/static`, or in `/var/lib/pleroma/static` for OTP release installations, create it if it doesn't exist already. Be aware that running `yarn build` wipes the contents of `dist` folder, and this could remove emojis, other frontends etc. and therefore this approach is not recommended. ### Running production build locally or on a separate server diff --git a/index.html b/index.html index 4af84a5940..a02939f7ea 100644 --- a/index.html +++ b/index.html @@ -9,6 +9,7 @@
+
diff --git a/package.json b/package.json index 507521c617..873f04ff1e 100644 --- a/package.json +++ b/package.json @@ -11,115 +11,121 @@ "unit:watch": "karma start test/unit/karma.conf.js --single-run=false", "e2e": "node test/e2e/runner.js", "test": "npm run unit && npm run e2e", - "stylelint": "npx stylelint src/components/status/status.scss", + "stylelint": "npx stylelint '**/*.scss' '**/*.vue'", "lint": "eslint --ext .js,.vue src test/unit/specs test/e2e/specs", "lint-fix": "eslint --fix --ext .js,.vue src test/unit/specs test/e2e/specs" }, "dependencies": { - "@babel/runtime": "7.20.0", + "@babel/runtime": "7.21.5", "@chenfengyuan/vue-qrcode": "2.0.0", - "@fortawesome/fontawesome-svg-core": "6.2.0", - "@fortawesome/free-regular-svg-icons": "6.2.0", - "@fortawesome/free-solid-svg-icons": "6.2.0", - "@fortawesome/vue-fontawesome": "3.0.1", + "@fortawesome/fontawesome-svg-core": "6.4.0", + "@fortawesome/free-regular-svg-icons": "6.4.0", + "@fortawesome/free-solid-svg-icons": "6.4.0", + "@fortawesome/vue-fontawesome": "3.0.3", "@kazvmoe-infra/pinch-zoom-element": "1.2.0", "@kazvmoe-infra/unicode-emoji-json": "0.4.0", "@ruffle-rs/ruffle": "0.1.0-nightly.2022.7.12", - "@vuelidate/core": "2.0.0", + "@vuelidate/core": "2.0.2", "@vuelidate/validators": "2.0.0", "body-scroll-lock": "3.1.5", "chromatism": "3.0.0", "click-outside-vue3": "4.0.1", - "cropperjs": "1.5.12", + "cropperjs": "1.5.13", "escape-html": "1.0.3", "js-cookie": "3.0.1", "localforage": "1.10.0", - "lozad": "1.16.0", "parse-link-header": "2.0.0", - "phoenix": "1.6.2", - "punycode.js": "2.1.0", - "qrcode": "1.5.0", + "phoenix": "1.7.7", + "punycode.js": "2.3.0", + "qrcode": "1.5.3", "querystring-es3": "0.2.1", "url": "0.11.0", "utf8": "3.0.0", - "vue": "3.2.41", + "vue": "3.2.45", "vue-i18n": "9.2.2", "vue-router": "4.1.6", - "vue-template-compiler": "2.7.13", + "vue-template-compiler": "2.7.14", + "vue-virtual-scroller": "^2.0.0-beta.7", "vuex": "4.1.0" }, "devDependencies": { - "@babel/core": "7.19.6", - "@babel/eslint-parser": "7.19.1", - "@babel/plugin-transform-runtime": "7.19.6", - "@babel/preset-env": "7.19.4", - "@babel/register": "7.18.9", - "@intlify/vue-i18n-loader": "5.0.0", + "@babel/core": "7.21.8", + "@babel/eslint-parser": "7.21.8", + "@babel/plugin-transform-runtime": "7.21.4", + "@babel/preset-env": "7.21.5", + "@babel/register": "7.21.0", + "@intlify/vue-i18n-loader": "5.0.1", "@ungap/event-target": "0.2.3", "@vue/babel-helper-vue-jsx-merge-props": "1.4.0", "@vue/babel-plugin-jsx": "1.1.1", - "@vue/compiler-sfc": "3.2.41", - "@vue/test-utils": "2.2.6", - "autoprefixer": "10.4.12", - "babel-loader": "8.2.5", + "@vue/compiler-sfc": "3.2.45", + "@vue/test-utils": "2.2.8", + "autoprefixer": "10.4.14", + "babel-loader": "9.1.2", "babel-plugin-lodash": "3.3.4", "chai": "4.3.7", "chalk": "1.1.3", - "chromedriver": "104.0.0", + "chromedriver": "108.0.0", "connect-history-api-fallback": "2.0.0", "copy-webpack-plugin": "11.0.0", "cross-spawn": "7.0.3", - "css-loader": "6.7.1", + "css-loader": "6.7.3", "css-minimizer-webpack-plugin": "4.2.2", "custom-event-polyfill": "1.0.7", - "eslint": "8.29.0", + "eslint": "8.33.0", "eslint-config-standard": "17.0.0", "eslint-formatter-friendly": "7.0.0", - "eslint-plugin-import": "2.26.0", - "eslint-plugin-n": "15.6.0", + "eslint-plugin-import": "2.27.5", + "eslint-plugin-n": "15.6.1", "eslint-plugin-promise": "6.1.1", - "eslint-plugin-vue": "9.7.0", + "eslint-plugin-vue": "9.9.0", "eslint-webpack-plugin": "3.2.0", "eventsource-polyfill": "0.9.6", "express": "4.18.2", "function-bind": "1.1.1", - "html-webpack-plugin": "5.5.0", + "html-webpack-plugin": "5.5.1", "http-proxy-middleware": "2.0.6", "iso-639-1": "2.1.15", "json-loader": "0.5.7", - "karma": "6.4.1", + "karma": "6.4.2", "karma-coverage": "2.2.0", "karma-firefox-launcher": "2.1.2", "karma-mocha": "2.0.1", "karma-mocha-reporter": "2.2.5", "karma-sinon-chai": "2.0.2", "karma-sourcemap-loader": "0.3.8", - "karma-spec-reporter": "0.0.34", + "karma-spec-reporter": "0.0.36", "karma-webpack": "5.0.0", "lodash": "4.17.21", - "mini-css-extract-plugin": "2.6.1", - "mocha": "10.0.0", - "nightwatch": "2.3.3", + "mini-css-extract-plugin": "2.7.6", + "mocha": "10.2.0", + "nightwatch": "2.6.20", "opn": "5.5.0", "ora": "0.4.1", - "postcss": "8.4.16", - "postcss-loader": "7.0.1", - "sass": "1.55.0", - "sass-loader": "13.0.2", + "postcss": "8.4.23", + "postcss-html": "^1.5.0", + "postcss-loader": "7.0.2", + "postcss-scss": "^4.0.6", + "sass": "1.60.0", + "sass-loader": "13.2.2", "selenium-server": "2.53.1", "semver": "7.3.8", "serviceworker-webpack5-plugin": "2.0.0", "shelljs": "0.8.5", - "sinon": "14.0.2", + "sinon": "15.0.4", "sinon-chai": "3.7.0", - "stylelint": "13.13.1", - "stylelint-config-standard": "20.0.0", + "stylelint": "14.16.1", + "stylelint-config-html": "^1.1.0", + "stylelint-config-recommended-scss": "^8.0.0", + "stylelint-config-recommended-vue": "^1.4.0", + "stylelint-config-standard": "29.0.0", "stylelint-rscss": "0.4.0", + "stylelint-webpack-plugin": "^3.3.0", "vue-loader": "17.0.1", "vue-style-loader": "4.1.3", - "webpack": "5.74.0", + "webpack": "5.75.0", "webpack-dev-middleware": "3.7.3", - "webpack-hot-middleware": "2.25.2", + "webpack-hot-middleware": "2.25.3", "webpack-merge": "0.20.0" }, "engines": { diff --git a/src/App.scss b/src/App.scss index 75b2667c13..ef68ac50b5 100644 --- a/src/App.scss +++ b/src/App.scss @@ -1,5 +1,7 @@ // stylelint-disable rscss/class-format -@import './_variables.scss'; +/* stylelint-disable no-descending-specificity */ +@import "./variables"; +@import "./panel"; :root { --navbar-height: 3.5rem; @@ -123,7 +125,7 @@ h4 { font-weight: 1000; } -i[class*=icon-], +i[class*="icon-"], .svg-inline--fa, .iconLetter { color: $fallback--icon; @@ -132,7 +134,7 @@ i[class*=icon-], .button-unstyled:hover, a:hover { - > i[class*=icon-], + > i[class*="icon-"], > .svg-inline--fa, > .iconLetter { color: var(--text); @@ -141,12 +143,11 @@ a:hover { nav { z-index: var(--ZI_navbar); - color: var(--topBarText); background-color: $fallback--fg; background-color: var(--topBar, $fallback--fg); color: $fallback--faint; color: var(--faint, $fallback--faint); - box-shadow: 0 0 4px rgba(0, 0, 0, 0.6); + box-shadow: 0 0 4px rgb(0 0 0 / 60%); box-shadow: var(--topBarShadow); box-sizing: border-box; height: var(--navbar-height); @@ -191,13 +192,11 @@ nav { } .underlay { - grid-column-start: 1; - grid-column-end: span 3; - grid-row-start: 1; - grid-row-end: 1; + grid-column: 1 / span 3; + grid-row: 1 / 1; pointer-events: none; - background-color: rgba(0, 0, 0, 0.15); - background-color: var(--underlay, rgba(0, 0, 0, 0.15)); + background-color: rgb(0 0 0 / 15%); + background-color: var(--underlay, rgb(0 0 0 / 15%)); z-index: -1000; } @@ -231,8 +230,7 @@ nav { display: grid; grid-template-columns: 100%; box-sizing: border-box; - grid-row-start: 1; - grid-row-end: 1; + grid-row: 1 / 1; margin: 0 calc(var(--___columnMargin) / 2); padding: calc(var(--___columnMargin)) 0; row-gap: var(--___columnMargin); @@ -307,7 +305,7 @@ nav { align-content: start; } - &.-reverse:not(.-wide):not(.-mobile) { + &.-reverse:not(.-wide, .-mobile) { grid-template-columns: var(--effectiveContentColumnWidth) var(--effectiveSidebarColumnWidth); @@ -336,11 +334,8 @@ nav { padding: 0; .column { - margin-left: 0; - margin-right: 0; padding-top: 0; - margin-top: var(--navbar-height); - margin-bottom: 0; + margin: var(--navbar-height) 0 0 0; } .panel-heading, @@ -389,7 +384,7 @@ nav { background: transparent; } - i[class*=icon-], + i[class*="icon-"], .svg-inline--fa { color: $fallback--text; color: var(--btnText, $fallback--text); @@ -400,12 +395,15 @@ nav { } &:hover { - box-shadow: 0 0 4px rgba(255, 255, 255, 0.3); + box-shadow: 0 0 4px rgb(255 255 255 / 30%); box-shadow: var(--buttonHoverShadow); } &:active { - box-shadow: 0 0 4px 0 rgba(255, 255, 255, 0.3), 0 1px 0 0 rgba(0, 0, 0, 0.2) inset, 0 -1px 0 0 rgba(255, 255, 255, 0.2) inset; + box-shadow: + 0 0 4px 0 rgb(255 255 255 / 30%), + 0 1px 0 0 rgb(0 0 0 / 20%) inset, + 0 -1px 0 0 rgb(255 255 255 / 20%) inset; box-shadow: var(--buttonPressedShadow); color: $fallback--text; color: var(--btnPressedText, $fallback--text); @@ -438,7 +436,10 @@ nav { color: var(--btnToggledText, $fallback--text); background-color: $fallback--fg; background-color: var(--btnToggled, $fallback--fg); - box-shadow: 0 0 4px 0 rgba(255, 255, 255, 0.3), 0 1px 0 0 rgba(0, 0, 0, 0.2) inset, 0 -1px 0 0 rgba(255, 255, 255, 0.2) inset; + box-shadow: + 0 0 4px 0 rgb(255 255 255 / 30%), + 0 1px 0 0 rgb(0 0 0 / 20%) inset, + 0 -1px 0 0 rgb(255 255 255 / 20%) inset; box-shadow: var(--buttonPressedShadow); svg, @@ -503,7 +504,10 @@ textarea, border: none; border-radius: $fallback--inputRadius; border-radius: var(--inputRadius, $fallback--inputRadius); - box-shadow: 0 1px 0 0 rgba(0, 0, 0, 0.2) inset, 0 -1px 0 0 rgba(255, 255, 255, 0.2) inset, 0 0 2px 0 rgba(0, 0, 0, 1) inset; + box-shadow: + 0 1px 0 0 rgb(0 0 0 / 20%) inset, + 0 -1px 0 0 rgb(255 255 255 / 20%) inset, + 0 0 2px 0 rgb(0 0 0 / 100%) inset; box-shadow: var(--inputShadow); background-color: $fallback--fg; background-color: var(--input, $fallback--fg); @@ -521,13 +525,13 @@ textarea, padding: 0 var(--_padding); &:disabled, - &[disabled=disabled], + &[disabled="disabled"], &.disabled { cursor: not-allowed; opacity: 0.5; } - &[type=range] { + &[type="range"] { background: none; border: none; margin: 0; @@ -535,7 +539,7 @@ textarea, flex: 1; } - &[type=radio] { + &[type="radio"] { display: none; &:checked + label::before { @@ -555,7 +559,7 @@ textarea, + label::before { flex-shrink: 0; display: inline-block; - content: ''; + content: ""; transition: box-shadow 200ms; width: 1.1em; height: 1.1em; @@ -575,9 +579,7 @@ textarea, } } - &[type=checkbox] { - display: none; - + &[type="checkbox"] { &:checked + label::before { color: $fallback--text; color: var(--inputText, $fallback--text); @@ -594,7 +596,7 @@ textarea, + label::before { flex-shrink: 0; display: inline-block; - content: '✓'; + content: "✓"; transition: color 200ms; width: 1.1em; height: 1.1em; @@ -634,15 +636,29 @@ option { } .hide-number-spinner { - -moz-appearance: textfield; + appearance: textfield; - &[type=number]::-webkit-inner-spin-button, - &[type=number]::-webkit-outer-spin-button { + &[type="number"]::-webkit-inner-spin-button, + &[type="number"]::-webkit-outer-spin-button { opacity: 0; display: none; } } +.cards-list { + list-style: none; + display: grid; + grid-auto-flow: row dense; + grid-template-columns: 1fr 1fr; + + li { + border: 1px solid var(--border); + border-radius: var(--inputRadius); + padding: 0.5em; + margin: 0.25em; + } +} + .btn-block { display: block; width: 100%; @@ -653,24 +669,25 @@ option { display: inline-flex; vertical-align: middle; - button { + button, + .button-dropdown { position: relative; flex: 1 1 auto; - &:not(:last-child) { + &:not(:last-child), + &:not(:last-child) .button-default { border-top-right-radius: 0; border-bottom-right-radius: 0; } - &:not(:first-child) { + &:not(:first-child), + &:not(:first-child) .button-default { border-top-left-radius: 0; border-bottom-left-radius: 0; } } } -@import './panel.scss'; - .fa { color: grey; } @@ -686,7 +703,7 @@ option { max-width: 10em; min-width: 1.7em; height: 1.3em; - padding: 0.15em 0.15em; + padding: 0.15em; vertical-align: middle; font-weight: normal; font-style: normal; @@ -789,7 +806,8 @@ option { .fa-old-padding { &.iconLetter, - &.svg-inline--fa, &-layer { + &.svg-inline--fa, + &-layer { padding: 0 0.3em; } } @@ -883,3 +901,16 @@ option { .fade-leave-active { opacity: 0; } +/* stylelint-enable no-descending-specificity */ + +.visible-for-screenreader-only { + display: block; + width: 1px; + height: 1px; + margin: -1px; + overflow: hidden; + visibility: visible; + clip: rect(0 0 0 0); + padding: 0; + position: absolute; +} diff --git a/src/App.vue b/src/App.vue index 23a388a629..fe214ce718 100644 --- a/src/App.vue +++ b/src/App.vue @@ -71,7 +71,6 @@ - diff --git a/src/_mixins.scss b/src/_mixins.scss index 1fed16c3d1..e99fe26f86 100644 --- a/src/_mixins.scss +++ b/src/_mixins.scss @@ -1,13 +1,14 @@ @mixin unfocused-style { @content; - &:focus:not(:focus-visible):not(:hover) { + &:focus:not(:focus-visible, :hover) { @content; } } @mixin focused-style { - &:hover, &:focus { + &:hover, + &:focus { @content; } diff --git a/src/_variables.scss b/src/_variables.scss index 099d36064a..751fc9a467 100644 --- a/src/_variables.scss +++ b/src/_variables.scss @@ -4,20 +4,20 @@ $darkened-background: whitesmoke; $fallback--bg: #121a24; $fallback--fg: #182230; -$fallback--faint: rgba(185, 185, 186, .5); +$fallback--faint: rgb(185 185 186 / 50%); $fallback--text: #b9b9ba; $fallback--link: #d8a070; $fallback--icon: #666; -$fallback--lightBg: rgb(21, 30, 42); +$fallback--lightBg: rgb(21 30 42); $fallback--lightText: #b9b9ba; $fallback--border: #222; -$fallback--cRed: #ff0000; +$fallback--cRed: #f00; $fallback--cBlue: #0095ff; $fallback--cGreen: #0fa00f; $fallback--cOrange: orange; -$fallback--alertError: rgba(211,16,20,.5); -$fallback--alertWarning: rgba(111,111,20,.5); +$fallback--alertError: rgb(211 16 20 / 50%); +$fallback--alertWarning: rgb(111 111 20 / 50%); $fallback--panelRadius: 10px; $fallback--checkboxRadius: 2px; @@ -29,6 +29,8 @@ $fallback--avatarAltRadius: 10px; $fallback--attachmentRadius: 10px; $fallback--chatMessageRadius: 10px; -$fallback--buttonShadow: 0px 0px 2px 0px rgba(0, 0, 0, 1), 0px 1px 0px 0px rgba(255, 255, 255, 0.2) inset, 0px -1px 0px 0px rgba(0, 0, 0, 0.2) inset; +$fallback--buttonShadow: 0 0 2px 0 rgb(0 0 0 / 100%), + 0 1px 0 0 rgb(255 255 255 / 20%) inset, + 0 -1px 0 0 rgb(0 0 0 / 20%) inset; $status-margin: 0.75em; diff --git a/src/boot/after_store.js b/src/boot/after_store.js index 7a4672b654..395d483449 100644 --- a/src/boot/after_store.js +++ b/src/boot/after_store.js @@ -1,6 +1,8 @@ import { createApp } from 'vue' import { createRouter, createWebHistory } from 'vue-router' import vClickOutside from 'click-outside-vue3' +import VueVirtualScroller from 'vue-virtual-scroller' +import 'vue-virtual-scroller/dist/vue-virtual-scroller.css' import { FontAwesomeIcon, FontAwesomeLayers } from '@fortawesome/vue-fontawesome' @@ -58,6 +60,8 @@ const getInstanceConfig = async ({ store }) => { store.dispatch('setInstanceOption', { name: 'textlimit', value: textlimit }) store.dispatch('setInstanceOption', { name: 'accountApprovalRequired', value: data.approval_required }) + store.dispatch('setInstanceOption', { name: 'birthdayRequired', value: !!data.pleroma.metadata.birthday_required }) + store.dispatch('setInstanceOption', { name: 'birthdayMinAge', value: data.pleroma.metadata.birthday_min_age || 0 }) if (vapidPublicKey) { store.dispatch('setInstanceOption', { name: 'vapidPublicKey', value: vapidPublicKey }) @@ -249,11 +253,13 @@ const getNodeInfo = async ({ store }) => { store.dispatch('setInstanceOption', { name: 'safeDM', value: features.includes('safe_dm_mentions') }) store.dispatch('setInstanceOption', { name: 'shoutAvailable', value: features.includes('chat') }) store.dispatch('setInstanceOption', { name: 'pleromaChatMessagesAvailable', value: features.includes('pleroma_chat_messages') }) + store.dispatch('setInstanceOption', { name: 'pleromaCustomEmojiReactionsAvailable', value: features.includes('pleroma_custom_emoji_reactions') }) store.dispatch('setInstanceOption', { name: 'gopherAvailable', value: features.includes('gopher') }) store.dispatch('setInstanceOption', { name: 'pollsAvailable', value: features.includes('polls') }) store.dispatch('setInstanceOption', { name: 'editingAvailable', value: features.includes('editing') }) store.dispatch('setInstanceOption', { name: 'pollLimits', value: metadata.pollLimits }) store.dispatch('setInstanceOption', { name: 'mailerEnabled', value: metadata.mailerEnabled }) + store.dispatch('setInstanceOption', { name: 'quotingAvailable', value: features.includes('quote_posting') }) const uploadLimits = metadata.uploadLimits store.dispatch('setInstanceOption', { name: 'uploadlimit', value: parseInt(uploadLimits.general) }) @@ -397,6 +403,7 @@ const afterStoreSetup = async ({ store, i18n }) => { app.use(vClickOutside) app.use(VBodyScrollLock) + app.use(VueVirtualScroller) app.component('FAIcon', FontAwesomeIcon) app.component('FALayers', FontAwesomeLayers) diff --git a/src/components/about/about.vue b/src/components/about/about.vue index 33586c9708..8a551485fa 100644 --- a/src/components/about/about.vue +++ b/src/components/about/about.vue @@ -9,6 +9,3 @@ - - diff --git a/src/components/account_actions/account_actions.js b/src/components/account_actions/account_actions.js index c23407f90e..acd93e0650 100644 --- a/src/components/account_actions/account_actions.js +++ b/src/components/account_actions/account_actions.js @@ -2,6 +2,7 @@ import { mapState } from 'vuex' import ProgressButton from '../progress_button/progress_button.vue' import Popover from '../popover/popover.vue' import UserListMenu from 'src/components/user_list_menu/user_list_menu.vue' +import ConfirmModal from '../confirm_modal/confirm_modal.vue' import { library } from '@fortawesome/fontawesome-svg-core' import { faEllipsisV @@ -16,14 +17,30 @@ const AccountActions = { 'user', 'relationship' ], data () { - return { } + return { + showingConfirmBlock: false, + showingConfirmRemoveFollower: false + } }, components: { ProgressButton, Popover, - UserListMenu + UserListMenu, + ConfirmModal }, methods: { + showConfirmBlock () { + this.showingConfirmBlock = true + }, + hideConfirmBlock () { + this.showingConfirmBlock = false + }, + showConfirmRemoveUserFromFollowers () { + this.showingConfirmRemoveFollower = true + }, + hideConfirmRemoveUserFromFollowers () { + this.showingConfirmRemoveFollower = false + }, showRepeats () { this.$store.dispatch('showReblogs', this.user.id) }, @@ -31,13 +48,29 @@ const AccountActions = { this.$store.dispatch('hideReblogs', this.user.id) }, blockUser () { + if (!this.shouldConfirmBlock) { + this.doBlockUser() + } else { + this.showConfirmBlock() + } + }, + doBlockUser () { this.$store.dispatch('blockUser', this.user.id) + this.hideConfirmBlock() }, unblockUser () { this.$store.dispatch('unblockUser', this.user.id) }, removeUserFromFollowers () { + if (!this.shouldConfirmRemoveUserFromFollowers) { + this.doRemoveUserFromFollowers() + } else { + this.showConfirmRemoveUserFromFollowers() + } + }, + doRemoveUserFromFollowers () { this.$store.dispatch('removeUserFromFollowers', this.user.id) + this.hideConfirmRemoveUserFromFollowers() }, reportUser () { this.$store.dispatch('openUserReportingModal', { userId: this.user.id }) @@ -50,6 +83,12 @@ const AccountActions = { } }, computed: { + shouldConfirmBlock () { + return this.$store.getters.mergedConfig.modalOnBlock + }, + shouldConfirmRemoveUserFromFollowers () { + return this.$store.getters.mergedConfig.modalOnRemoveUserFromFollowers + }, ...mapState({ pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable }) diff --git a/src/components/account_actions/account_actions.vue b/src/components/account_actions/account_actions.vue index 218aa6b357..ce19291a1d 100644 --- a/src/components/account_actions/account_actions.vue +++ b/src/components/account_actions/account_actions.vue @@ -74,13 +74,56 @@ + + + + + + + + + + + + + +
diff --git a/src/components/attachment/attachment.js b/src/components/attachment/attachment.js index 5dc5047556..6e14b24d74 100644 --- a/src/components/attachment/attachment.js +++ b/src/components/attachment/attachment.js @@ -36,6 +36,7 @@ library.add( const Attachment = { props: [ 'attachment', + 'compact', 'description', 'hideDescription', 'nsfw', @@ -71,7 +72,8 @@ const Attachment = { { '-loading': this.loading, '-nsfw-placeholder': this.hidden, - '-editable': this.edit !== undefined + '-editable': this.edit !== undefined, + '-compact': this.compact }, '-type-' + this.type, this.size && '-size-' + this.size, diff --git a/src/components/attachment/attachment.scss b/src/components/attachment/attachment.scss index b2dea98d50..681bab291a 100644 --- a/src/components/attachment/attachment.scss +++ b/src/components/attachment/attachment.scss @@ -1,4 +1,4 @@ -@import '../../_variables.scss'; +@import "../../variables"; .Attachment { display: inline-flex; @@ -102,14 +102,13 @@ padding-top: 0.5em; } - .play-icon { position: absolute; font-size: 64px; top: calc(50% - 32px); left: calc(50% - 32px); - color: rgba(255, 255, 255, 0.75); - text-shadow: 0 0 2px rgba(0, 0, 0, 0.4); + color: rgb(255 255 255 / 75%); + text-shadow: 0 0 2px rgb(0 0 0 / 40%); &::before { margin: 0; @@ -135,18 +134,32 @@ margin-left: 0.5em; font-size: 1.25em; // TODO: theming? hard to theme with unknown background image color - background: rgba(230, 230, 230, 0.7); + background: rgb(230 230 230 / 70%); .svg-inline--fa { - color: rgba(0, 0, 0, 0.6); + color: rgb(0 0 0 / 60%); } &:hover .svg-inline--fa { - color: rgba(0, 0, 0, 0.9); + color: rgb(0 0 0 / 90%); } } } + &.-contain-fit { + img, + canvas { + object-fit: contain; + } + } + + &.-cover-fit { + img, + canvas { + object-fit: cover; + } + } + .oembed-container { line-height: 1.2em; flex: 1 0 100%; @@ -160,8 +173,9 @@ .image { flex: 1; + img { - border: 0px; + border: 0; border-radius: 5px; height: 100%; object-fit: cover; @@ -172,9 +186,10 @@ flex: 2; margin: 8px; word-break: break-all; + h1 { font-size: 1rem; - margin: 0px; + margin: 0; } } } @@ -252,17 +267,9 @@ cursor: progress; } - &.-contain-fit { - img, - canvas { - object-fit: contain; - } - } - - &.-cover-fit { - img, - canvas { - object-fit: cover; + &.-compact { + .placeholder-container { + padding-bottom: 0.5em; } } } diff --git a/src/components/attachment/attachment.vue b/src/components/attachment/attachment.vue index 2a89886d7d..79f628061c 100644 --- a/src/components/attachment/attachment.vue +++ b/src/components/attachment/attachment.vue @@ -162,10 +162,11 @@ target="_blank" > -

+

{{ localDescription }}

diff --git a/src/components/autosuggest/autosuggest.vue b/src/components/autosuggest/autosuggest.vue index f283ab82a8..7b7102fd2e 100644 --- a/src/components/autosuggest/autosuggest.vue +++ b/src/components/autosuggest/autosuggest.vue @@ -24,7 +24,7 @@ diff --git a/src/components/chat_list/chat_list.vue b/src/components/chat_list/chat_list.vue index 1248c4c8cc..27a475edff 100644 --- a/src/components/chat_list/chat_list.vue +++ b/src/components/chat_list/chat_list.vue @@ -45,7 +45,7 @@ diff --git a/src/components/chat_message/chat_message.scss b/src/components/chat_message/chat_message.scss index 1913479f28..fd5b7aa45f 100644 --- a/src/components/chat_message/chat_message.scss +++ b/src/components/chat_message/chat_message.scss @@ -1,12 +1,12 @@ -@import '../../_variables.scss'; +@import "../../variables"; .chat-message-wrapper { - &.hovered-message-chain { .animated.Avatar { canvas { display: none; } + img { visibility: visible; } @@ -28,7 +28,8 @@ .menu-icon { cursor: pointer; - &:hover, .extra-button-popover.open & { + &:hover, + .extra-button-popover.open & { color: $fallback--text; color: var(--text, $fallback--text); } @@ -54,27 +55,11 @@ width: 32px; } - .link-preview, .attachments { + .link-preview, + .attachments { margin-bottom: 1em; } - .chat-message-inner { - display: flex; - flex-direction: column; - align-items: flex-start; - max-width: 80%; - min-width: 10em; - width: 100%; - - &.with-media { - width: 100%; - - .status { - width: 100%; - } - } - } - .status { border-radius: $fallback--chatMessageRadius; border-radius: var(--chatMessageRadius, $fallback--chatMessageRadius); @@ -86,7 +71,7 @@ position: relative; float: right; font-size: 0.8em; - margin: -1em 0 -0.5em 0; + margin: -1em 0 -0.5em; font-style: italic; opacity: 0.8; } @@ -103,18 +88,54 @@ } .pending { - .status-content.media-body, .created-at { + .status-content.media-body, + .created-at { color: var(--faint); } } .error { - .status-content.media-body, .created-at { + .status-content.media-body, + .created-at { color: $fallback--cRed; color: var(--badgeNotification, $fallback--cRed); } } + .chat-message-inner { + display: flex; + flex-direction: column; + align-items: flex-start; + max-width: 80%; + min-width: 10em; + width: 100%; + } + + .outgoing { + display: flex; + flex-flow: row wrap; + align-content: end; + justify-content: flex-end; + + a { + color: var(--chatMessageOutgoingLink, $fallback--link); + } + + .status { + color: var(--chatMessageOutgoingText, $fallback--text); + background-color: var(--chatMessageOutgoingBg, $fallback--lightBg); + border: 1px solid var(--chatMessageOutgoingBorder, --lightBg); + } + + .chat-message-inner { + align-items: flex-end; + } + + .chat-message-menu { + right: 0.4rem; + } + } + .incoming { a { color: var(--chatMessageIncomingLink, $fallback--link); @@ -137,36 +158,17 @@ } } - .outgoing { - display: flex; - flex-direction: row; - flex-wrap: wrap; - align-content: end; - justify-content: flex-end; - - a { - color: var(--chatMessageOutgoingLink, $fallback--link); - } + .chat-message-inner.with-media { + width: 100%; .status { - color: var(--chatMessageOutgoingText, $fallback--text); - background-color: var(--chatMessageOutgoingBg, $fallback--lightBg); - border: 1px solid var(--chatMessageOutgoingBorder, --lightBg); - } - - .chat-message-inner { - align-items: flex-end; - } - - .chat-message-menu { - right: 0.4rem; + width: 100%; } } .visible { opacity: 1; } - } .chat-message-date-separator { diff --git a/src/components/chat_message/chat_message.vue b/src/components/chat_message/chat_message.vue index d635c47e4f..381574c32e 100644 --- a/src/components/chat_message/chat_message.vue +++ b/src/components/chat_message/chat_message.vue @@ -33,7 +33,7 @@
@@ -98,6 +98,6 @@ diff --git a/src/components/chat_new/chat_new.scss b/src/components/chat_new/chat_new.scss index 240e1a3802..b145ecf9ad 100644 --- a/src/components/chat_new/chat_new.scss +++ b/src/components/chat_new/chat_new.scss @@ -1,7 +1,7 @@ .chat-new { .input-wrap { display: flex; - margin: 0.7em 0.5em 0.7em 0.5em; + margin: 0.7em 0.5em; input { width: 100%; diff --git a/src/components/chat_new/chat_new.vue b/src/components/chat_new/chat_new.vue index bf09a37965..52306c1da5 100644 --- a/src/components/chat_new/chat_new.vue +++ b/src/components/chat_new/chat_new.vue @@ -46,6 +46,6 @@ diff --git a/src/components/chat_title/chat_title.vue b/src/components/chat_title/chat_title.vue index ab7491faed..93db4fa7f1 100644 --- a/src/components/chat_title/chat_title.vue +++ b/src/components/chat_title/chat_title.vue @@ -26,7 +26,7 @@ diff --git a/src/components/color_input/color_input.scss b/src/components/color_input/color_input.scss index 3de31fded8..ca46199a52 100644 --- a/src/components/color_input/color_input.scss +++ b/src/components/color_input/color_input.scss @@ -1,4 +1,4 @@ -@import '../../_variables.scss'; +@import "../../variables"; .color-input { display: inline-flex; @@ -8,7 +8,7 @@ flex: 0 0 0; max-width: 9em; align-items: stretch; - padding: .2em 8px; + padding: 0.2em 8px; input { background: none; @@ -31,6 +31,7 @@ min-height: 100%; } } + .computedIndicator, .transparentIndicator { flex: 0 0 2em; @@ -38,22 +39,27 @@ align-self: stretch; min-height: 100%; } + .transparentIndicator { // forgot to install counter-strike source, ooops - background-color: #FF00FF; + background-color: #f0f; position: relative; - &::before, &::after { + + &::before, + &::after { display: block; - content: ''; - background-color: #000000; + content: ""; + background-color: #000; position: absolute; height: 50%; width: 50%; } + &::after { top: 0; left: 0; } + &::before { bottom: 0; right: 0; @@ -64,5 +70,4 @@ .label { flex: 1 1 auto; } - } diff --git a/src/components/confirm_modal/confirm_modal.js b/src/components/confirm_modal/confirm_modal.js new file mode 100644 index 0000000000..96ddc118fd --- /dev/null +++ b/src/components/confirm_modal/confirm_modal.js @@ -0,0 +1,37 @@ +import DialogModal from '../dialog_modal/dialog_modal.vue' + +/** + * This component emits the following events: + * cancelled, emitted when the action should not be performed; + * accepted, emitted when the action should be performed; + * + * The caller should close this dialog after receiving any of the two events. + */ +const ConfirmModal = { + components: { + DialogModal + }, + props: { + title: { + type: String + }, + cancelText: { + type: String + }, + confirmText: { + type: String + } + }, + computed: { + }, + methods: { + onCancel () { + this.$emit('cancelled') + }, + onAccept () { + this.$emit('accepted') + } + } +} + +export default ConfirmModal diff --git a/src/components/confirm_modal/confirm_modal.vue b/src/components/confirm_modal/confirm_modal.vue new file mode 100644 index 0000000000..3b98174aa9 --- /dev/null +++ b/src/components/confirm_modal/confirm_modal.vue @@ -0,0 +1,29 @@ + + + diff --git a/src/components/contrast_ratio/contrast_ratio.vue b/src/components/contrast_ratio/contrast_ratio.vue index 374cb9ba22..bbd6fd4a52 100644 --- a/src/components/contrast_ratio/contrast_ratio.vue +++ b/src/components/contrast_ratio/contrast_ratio.vue @@ -87,7 +87,6 @@ export default { .contrast-ratio { display: flex; justify-content: flex-end; - margin-top: -4px; margin-bottom: 5px; diff --git a/src/components/conversation/conversation.vue b/src/components/conversation/conversation.vue index afa04db06d..7577129e24 100644 --- a/src/components/conversation/conversation.vue +++ b/src/components/conversation/conversation.vue @@ -210,17 +210,16 @@ diff --git a/src/components/extra_buttons/extra_buttons.js b/src/components/extra_buttons/extra_buttons.js index 3dc968c981..48b960b2b7 100644 --- a/src/components/extra_buttons/extra_buttons.js +++ b/src/components/extra_buttons/extra_buttons.js @@ -1,4 +1,5 @@ import Popover from '../popover/popover.vue' +import ConfirmModal from '../confirm_modal/confirm_modal.vue' import { library } from '@fortawesome/fontawesome-svg-core' import { faEllipsisH, @@ -32,10 +33,14 @@ library.add( const ExtraButtons = { props: ['status'], - components: { Popover }, + components: { + Popover, + ConfirmModal + }, data () { return { - expanded: false + expanded: false, + showingDeleteDialog: false } }, methods: { @@ -46,11 +51,22 @@ const ExtraButtons = { this.expanded = false }, deleteStatus () { - const confirmed = window.confirm(this.$t('status.delete_confirm')) - if (confirmed) { - this.$store.dispatch('deleteStatus', { id: this.status.id }) + if (this.shouldConfirmDelete) { + this.showDeleteStatusConfirmDialog() + } else { + this.doDeleteStatus() } }, + doDeleteStatus () { + this.$store.dispatch('deleteStatus', { id: this.status.id }) + this.hideDeleteStatusConfirmDialog() + }, + showDeleteStatusConfirmDialog () { + this.showingDeleteDialog = true + }, + hideDeleteStatusConfirmDialog () { + this.showingDeleteDialog = false + }, pinStatus () { this.$store.dispatch('pinStatus', this.status.id) .then(() => this.$emit('onSuccess')) @@ -133,7 +149,10 @@ const ExtraButtons = { isEdited () { return this.status.edited_at !== null }, - editingAvailable () { return this.$store.state.instance.editingAvailable } + editingAvailable () { return this.$store.state.instance.editingAvailable }, + shouldConfirmDelete () { + return this.$store.getters.mergedConfig.modalOnDelete + } } } diff --git a/src/components/extra_buttons/extra_buttons.vue b/src/components/extra_buttons/extra_buttons.vue index b2fad1c952..c1c15c0fb5 100644 --- a/src/components/extra_buttons/extra_buttons.vue +++ b/src/components/extra_buttons/extra_buttons.vue @@ -165,6 +165,18 @@ /> + + + {{ $t('status.delete_confirm') }} + + @@ -172,15 +184,10 @@ diff --git a/src/components/follow_button/follow_button.js b/src/components/follow_button/follow_button.js index 3edbcb86fb..443aa9bccc 100644 --- a/src/components/follow_button/follow_button.js +++ b/src/components/follow_button/follow_button.js @@ -1,12 +1,20 @@ +import ConfirmModal from '../confirm_modal/confirm_modal.vue' import { requestFollow, requestUnfollow } from '../../services/follow_manipulate/follow_manipulate' export default { props: ['relationship', 'user', 'labelFollowing', 'buttonClass'], + components: { + ConfirmModal + }, data () { return { - inProgress: false + inProgress: false, + showingConfirmUnfollow: false } }, computed: { + shouldConfirmUnfollow () { + return this.$store.getters.mergedConfig.modalOnUnfollow + }, isPressed () { return this.inProgress || this.relationship.following }, @@ -35,6 +43,12 @@ export default { } }, methods: { + showConfirmUnfollow () { + this.showingConfirmUnfollow = true + }, + hideConfirmUnfollow () { + this.showingConfirmUnfollow = false + }, onClick () { this.relationship.following || this.relationship.requested ? this.unfollow() : this.follow() }, @@ -45,12 +59,21 @@ export default { }) }, unfollow () { + if (this.shouldConfirmUnfollow) { + this.showConfirmUnfollow() + } else { + this.doUnfollow() + } + }, + doUnfollow () { const store = this.$store this.inProgress = true requestUnfollow(this.relationship.id, store).then(() => { this.inProgress = false store.commit('removeStatus', { timeline: 'friends', userId: this.relationship.id }) }) + + this.hideConfirmUnfollow() } } } diff --git a/src/components/follow_button/follow_button.vue b/src/components/follow_button/follow_button.vue index 965d5256a6..e421c15b3a 100644 --- a/src/components/follow_button/follow_button.vue +++ b/src/components/follow_button/follow_button.vue @@ -7,6 +7,27 @@ @click="onClick" > {{ label }} + + + + + + + diff --git a/src/components/follow_card/follow_card.vue b/src/components/follow_card/follow_card.vue index c919b11aa4..bdb6b80927 100644 --- a/src/components/follow_card/follow_card.vue +++ b/src/components/follow_card/follow_card.vue @@ -24,6 +24,7 @@ />
+ + + {{ $t('user_card.approve_confirm', { user: user.screen_name_ui }) }} + + + {{ $t('user_card.deny_confirm', { user: user.screen_name_ui }) }} + + @@ -22,8 +44,8 @@ diff --git a/src/components/global_notice_list/global_notice_list.vue b/src/components/global_notice_list/global_notice_list.vue index d828b81910..0e58476f1c 100644 --- a/src/components/global_notice_list/global_notice_list.vue +++ b/src/components/global_notice_list/global_notice_list.vue @@ -25,7 +25,7 @@ diff --git a/src/components/link-preview/link-preview.vue b/src/components/link-preview/link-preview.vue index 220527f29f..09f341ac4c 100644 --- a/src/components/link-preview/link-preview.vue +++ b/src/components/link-preview/link-preview.vue @@ -33,7 +33,7 @@ diff --git a/src/components/lists_edit/lists_edit.js b/src/components/lists_edit/lists_edit.js index c22d1323d7..c33659dfc6 100644 --- a/src/components/lists_edit/lists_edit.js +++ b/src/components/lists_edit/lists_edit.js @@ -95,10 +95,10 @@ const ListsNew = { return this.addedUserIds.has(user.id) }, addUser (user) { - this.$store.dispatch('addListAccount', { accountId: this.user.id, listId: this.id }) + this.$store.dispatch('addListAccount', { accountId: user.id, listId: this.id }) }, removeUser (userId) { - this.$store.dispatch('removeListAccount', { accountId: this.user.id, listId: this.id }) + this.$store.dispatch('removeListAccount', { accountId: userId, listId: this.id }) }, onSearchLoading (results) { this.searchLoading = true diff --git a/src/components/lists_edit/lists_edit.vue b/src/components/lists_edit/lists_edit.vue index 6521aba62b..eec0f9786e 100644 --- a/src/components/lists_edit/lists_edit.vue +++ b/src/components/lists_edit/lists_edit.vue @@ -164,7 +164,7 @@ diff --git a/src/components/media_upload/media_upload.js b/src/components/media_upload/media_upload.js index cfd42d4ccd..8c9e5f71d1 100644 --- a/src/components/media_upload/media_upload.js +++ b/src/components/media_upload/media_upload.js @@ -23,6 +23,11 @@ const mediaUpload = { } }, methods: { + onClick () { + if (this.uploadReady) { + this.$refs.input.click() + } + }, uploadFile (file) { const self = this const store = this.$store @@ -69,10 +74,15 @@ const mediaUpload = { this.multiUpload(target.files) } }, - props: [ - 'dropFiles', - 'disabled' - ], + props: { + dropFiles: Object, + disabled: Boolean, + normalButton: Boolean, + acceptTypes: { + type: String, + default: '*/*' + } + }, watch: { dropFiles: function (fileInfos) { if (!this.uploading) { diff --git a/src/components/media_upload/media_upload.vue b/src/components/media_upload/media_upload.vue index a538a5ede6..2ea5567a25 100644 --- a/src/components/media_upload/media_upload.vue +++ b/src/components/media_upload/media_upload.vue @@ -1,8 +1,9 @@ + +label.media-upload { + cursor: pointer; // We use diff --git a/src/components/settings_modal/helpers/choice_setting.js b/src/components/settings_modal/helpers/choice_setting.js index 3da559fe22..bdeece7603 100644 --- a/src/components/settings_modal/helpers/choice_setting.js +++ b/src/components/settings_modal/helpers/choice_setting.js @@ -1,51 +1,41 @@ -import { get, set } from 'lodash' import Select from 'src/components/select/select.vue' -import ModifiedIndicator from './modified_indicator.vue' -import ServerSideIndicator from './server_side_indicator.vue' +import Setting from './setting.js' + export default { + ...Setting, components: { - Select, - ModifiedIndicator, - ServerSideIndicator + ...Setting.components, + Select }, - props: [ - 'path', - 'disabled', - 'options', - 'expert' - ], - computed: { - pathDefault () { - const [firstSegment, ...rest] = this.path.split('.') - return [firstSegment + 'DefaultValue', ...rest].join('.') + props: { + ...Setting.props, + options: { + type: Array, + required: false }, - state () { - const value = get(this.$parent, this.path) - if (value === undefined) { - return this.defaultState - } else { - return value + optionLabelMap: { + type: Object, + required: false, + default: {} + } + }, + computed: { + ...Setting.computed, + realOptions () { + if (this.realSource === 'admin') { + return this.backendDescriptionSuggestions.map(x => ({ + key: x, + value: x, + label: this.optionLabelMap[x] || x + })) } - }, - defaultState () { - return get(this.$parent, this.pathDefault) - }, - isServerSide () { - return this.path.startsWith('serverSide_') - }, - isChanged () { - return !this.path.startsWith('serverSide_') && this.state !== this.defaultState - }, - matchesExpertLevel () { - return (this.expert || 0) <= this.$parent.expertLevel + return this.options } }, methods: { - update (e) { - set(this.$parent, this.path, e) - }, - reset () { - set(this.$parent, this.path, this.defaultState) + ...Setting.methods, + getValue (e) { + return e } } } diff --git a/src/components/settings_modal/helpers/choice_setting.vue b/src/components/settings_modal/helpers/choice_setting.vue index d141a0d61a..114e9b7dc8 100644 --- a/src/components/settings_modal/helpers/choice_setting.vue +++ b/src/components/settings_modal/helpers/choice_setting.vue @@ -3,15 +3,20 @@ v-if="matchesExpertLevel" class="ChoiceSetting" > - + + {{ ' ' }} +

{{ $t('settings.bio') }}

-