diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..c5b9ea10 --- /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 305155d8..2b4452d5 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 fbf3a245..d6689cc0 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 8ed1c186..3fb72931 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 bf946922..7e69352a 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 7de93721..7a108f68 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 00000000..53d89805 --- /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 00000000..188c4555 --- /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 00000000..e69de29b diff --git a/changelog.d/custom-emoji-notif-width.fix b/changelog.d/custom-emoji-notif-width.fix new file mode 100644 index 00000000..da118f6b --- /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 00000000..5a92765c --- /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 00000000..12898a1a --- /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 00000000..fb0d85cb --- /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 00000000..961b8033 --- /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 00000000..0952f773 --- /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 00000000..0e4b71df --- /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 00000000..33ee8d2c --- /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 00000000..e4c6dc82 --- /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 00000000..da4e4631 --- /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 00000000..d93c0d29 --- /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 00000000..678fc3bc --- /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 00000000..b43b6aba --- /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 00000000..9846d50d --- /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 00000000..c2222fb6 --- /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 00000000..db241c20 --- /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 00000000..3f5dda7d --- /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 7f2964b4..a5c49113 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 4af84a59..a02939f7 100644 --- a/index.html +++ b/index.html @@ -9,6 +9,7 @@
+
diff --git a/package.json b/package.json index 507521c6..873f04ff 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 75b2667c..ef68ac50 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 23a388a6..fe214ce7 100644 --- a/src/App.vue +++ b/src/App.vue @@ -71,7 +71,6 @@ - diff --git a/src/_mixins.scss b/src/_mixins.scss index 1fed16c3..e99fe26f 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 099d3606..751fc9a4 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 7a4672b6..395d4834 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 33586c97..8a551485 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 c23407f9..acd93e06 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 218aa6b3..ce19291a 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 5dc50475..6e14b24d 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 b2dea98d..681bab29 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 2a89886d..79f62806 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 f283ab82..7b7102fd 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 1248c4c8..27a475ed 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 1913479f..fd5b7aa4 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 d635c47e..381574c3 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 240e1a38..b145ecf9 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 bf09a379..52306c1d 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 ab7491fa..93db4fa7 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 3de31fde..ca46199a 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 00000000..96ddc118 --- /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 00000000..3b98174a --- /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 374cb9ba..bbd6fd4a 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 afa04db0..7577129e 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 3dc968c9..48b960b2 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 b2fad1c9..c1c15c0f 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 3edbcb86..443aa9bc 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 965d5256..e421c15b 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 c919b11a..bdb6b809 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 d828b819..0e58476f 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 220527f2..09f341ac 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 c22d1323..c33659df 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 6521aba6..eec0f978 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 cfd42d4c..8c9e5f71 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 a538a5ed..2ea5567a 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 3da559fe..bdeece76 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: { + ...Setting.props, + options: { + type: Array, + required: false + }, + optionLabelMap: { + type: Object, + required: false, + default: {} + } }, - props: [ - 'path', - 'disabled', - 'options', - 'expert' - ], computed: { - pathDefault () { - const [firstSegment, ...rest] = this.path.split('.') - return [firstSegment + 'DefaultValue', ...rest].join('.') - }, - state () { - const value = get(this.$parent, this.path) - if (value === undefined) { - return this.defaultState - } else { - return value + ...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 d141a0d6..114e9b7d 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') }}

-